]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Merge pull request #11431 from jroessler-ox/docs-kskzskroll-update
authorPeter van Dijk <peter.van.dijk@powerdns.com>
Thu, 15 Feb 2024 13:59:25 +0000 (14:59 +0100)
committerGitHub <noreply@github.com>
Thu, 15 Feb 2024 13:59:25 +0000 (14:59 +0100)
updated KSK and ZSK Rollover procedures, small fixes in Algorithm Rol…

1768 files changed:
.circleci/config.yml
.clang-tidy.bugs
.clang-tidy.full
.gitattributes [new file with mode: 0644]
.github/ISSUE_TEMPLATE/bug_report.md
.github/actions/spell-check/README.md [new file with mode: 0644]
.github/actions/spell-check/advice.md
.github/actions/spell-check/allow.txt
.github/actions/spell-check/candidate.patterns [new file with mode: 0644]
.github/actions/spell-check/excludes.txt
.github/actions/spell-check/expect.txt
.github/actions/spell-check/line_forbidden.patterns [new file with mode: 0644]
.github/actions/spell-check/only.txt
.github/actions/spell-check/patterns.txt
.github/actions/spell-check/reject.txt
.github/dependabot.yml [new file with mode: 0644]
.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 [new file with mode: 0644]
.github/workflows/formatting.yml
.github/workflows/fuzz.yml
.github/workflows/misc-dailies.yml [new file with mode: 0644]
.github/workflows/secpoll.yml
.github/workflows/spelling.yml
.github/workflows/spelling2.yml
.github/workflows/spelling3.yml [new file with mode: 0644]
.gitignore
.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
Makefile.am
README.md
SECURITY.md
auth-tsan.supp [new file with mode: 0644]
build-scripts/check-debian-autoremovals.py [new file with mode: 0755]
build-scripts/docker/repo-test/generate-repo-files.py
build-scripts/docker/repo-test/templates/Dockerfile-el.jinja2
build-scripts/gh-actions-setup-inv
build-scripts/gh-actions-setup-inv-no-dist-upgrade [new file with mode: 0755]
build-scripts/test-auth [deleted file]
build-scripts/test-recursor
build-scripts/test-recursor-bulk
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/rules
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/README.source [deleted file]
builder-support/debian/recursor/debian-buster/configure-helpers/net-snmp-config [new file with mode: 0755]
builder-support/debian/recursor/debian-buster/control
builder-support/debian/recursor/debian-buster/copyright
builder-support/debian/recursor/debian-buster/pdns-recursor.default [deleted file]
builder-support/debian/recursor/debian-buster/pdns-recursor.init [deleted file]
builder-support/debian/recursor/debian-buster/pdns-recursor.lintian-overrides
builder-support/debian/recursor/debian-buster/pdns-recursor.postinst
builder-support/debian/recursor/debian-buster/pdns-recursor.preinst [new file with mode: 0644]
builder-support/debian/recursor/debian-buster/pdns-recursor.prerm [deleted file]
builder-support/debian/recursor/debian-buster/rules
builder-support/debian/recursor/debian-buster/source.lintian-overrides [deleted file]
builder-support/debian/recursor/debian-buster/tests/control
builder-support/debian/recursor/debian-buster/tests/smoke
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.amazon-2023 [new file with mode: 0644]
builder-support/dockerfiles/Dockerfile.target.centos-7
builder-support/dockerfiles/Dockerfile.target.centos-9-stream [new file with mode: 0644]
builder-support/dockerfiles/Dockerfile.target.debian-bookworm [new file with mode: 0644]
builder-support/dockerfiles/Dockerfile.target.debian-bookworm-amd64 [new symlink]
builder-support/dockerfiles/Dockerfile.target.debian-bookworm-arm64 [new symlink]
builder-support/dockerfiles/Dockerfile.target.debian-trixie [new file with mode: 0644]
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.el-9 [new file with mode: 0644]
builder-support/dockerfiles/Dockerfile.target.el-9-amd64 [new symlink]
builder-support/dockerfiles/Dockerfile.target.el-9-arm64 [new symlink]
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-lunar [moved from builder-support/dockerfiles/Dockerfile.target.ubuntu-bionic with 75% similarity]
builder-support/dockerfiles/Dockerfile.target.ubuntu-lunar-amd64 [new symlink]
builder-support/dockerfiles/Dockerfile.target.ubuntu-lunar-arm64 [new 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 [new file with mode: 0644]
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]
builder-support/specs/pdns.spec
configure.ac
contrib/ProtobufLogger.py
contrib/_pdnsutil.zsh_completion [new file with mode: 0644]
contrib/assert-equal-DNSMessage/eqdnsmessage.py
contrib/pdnsutil.bash_completion.d
contrib/xdp-filter.ebpf.src
contrib/xdp-logging-middleware.ebpf.src [new file with mode: 0644]
contrib/xdp-logging.py [new file with mode: 0644]
contrib/xdp.h [new file with mode: 0644]
contrib/xdp.py
dockerdata/startup.py
docs/.gitignore
docs/Makefile.am
docs/Makefile.sphinx
docs/appendices/EOL.rst
docs/appendices/types.rst
docs/backends/bind.rst
docs/backends/generic-mysql.rst
docs/backends/generic-postgresql.rst
docs/backends/generic-sql.rst
docs/backends/generic-sqlite3.rst
docs/backends/geoip.rst
docs/backends/index.rst
docs/backends/ldap.rst
docs/backends/lmdb.rst
docs/backends/remote.rst
docs/catalog.rst [new file with mode: 0644]
docs/changelog/4.1.rst
docs/changelog/4.2.rst
docs/changelog/4.4.rst
docs/changelog/4.5.rst
docs/changelog/4.6.rst
docs/changelog/4.7.rst
docs/changelog/4.8.rst [new file with mode: 0644]
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/dnssec/pkcs11.rst
docs/dnsupdate.rst
docs/domainmetadata.rst
docs/guides/alias.rst
docs/guides/recursion.rst
docs/guides/svcb.rst
docs/http-api/server.rst
docs/http-api/swagger/authoritative-api-swagger.yaml
docs/http-api/tsigkey.rst
docs/http-api/zone.rst
docs/indexTOC.rst
docs/installation.rst
docs/lua-records/functions.rst
docs/lua-records/index.rst
docs/lua-records/reference/dnsresourcerecord.rst
docs/manpages/dnsreplay.1.rst
docs/manpages/dnsscope.1.rst
docs/manpages/ixfrdist.yml.5.rst
docs/manpages/pdnsutil.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/running.rst
docs/secpoll.zone
docs/security-advisories/powerdns-advisory-2022-01.rst [new file with mode: 0644]
docs/settings.rst
docs/tsig.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.cc
ext/lmdb-safe/lmdb-typed.hh
ext/luawrapper/include/LuaContext.hpp
ext/yahttp/yahttp/cookie.hpp
ext/yahttp/yahttp/reqresp.cpp
ext/yahttp/yahttp/reqresp.hpp
ext/yahttp/yahttp/router.cpp
ext/yahttp/yahttp/router.hpp
ext/yahttp/yahttp/utility.hpp
fuzzing/README.md
fuzzing/corpus/http-raw-payloads/http0_get.raw [new file with mode: 0644]
fuzzing/corpus/http-raw-payloads/http10_nohost_get.raw [new file with mode: 0644]
fuzzing/corpus/http-raw-payloads/http11_get.raw [new file with mode: 0644]
fuzzing/corpus/http-raw-payloads/http11_put.raw [new file with mode: 0644]
fuzzing/corpus/raw-xsk-frames/v4-udp.raw [new file with mode: 0644]
m4/ax_check_sign.m4
m4/ax_compare_version.m4 [new file with mode: 0644]
m4/ax_cxx_compile_stdcxx.m4
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_d_fortify_source.m4
m4/pdns_enable_coverage.m4
m4/pdns_enable_gss_tsig.m4 [new file with mode: 0644]
m4/pdns_enable_lto.m4 [new file with mode: 0644]
m4/pdns_init_auto_vars.m4 [new file with mode: 0644]
m4/pdns_with_libdecaf.m4
m4/pdns_with_net_snmp.m4
m4/systemd.m4
modules/bindbackend/Makefile.am
modules/bindbackend/bindbackend2.cc
modules/bindbackend/bindbackend2.hh
modules/bindbackend/binddnssec.cc
modules/geoipbackend/Makefile.am
modules/geoipbackend/geoipbackend.cc
modules/geoipbackend/geoipbackend.hh
modules/geoipbackend/geoipinterface-dat.cc
modules/geoipbackend/geoipinterface-mmdb.cc
modules/geoipbackend/geoipinterface.hh
modules/geoipbackend/regression-tests/GeoLiteCity.mmdb
modules/geoipbackend/regression-tests/apex-record/expected_result
modules/geoipbackend/regression-tests/basic-a-dnssec/expected_result
modules/geoipbackend/regression-tests/basic-a-resolution/expected_result
modules/geoipbackend/regression-tests/city-resolution/expected_result
modules/geoipbackend/regression-tests/custom-mapping-txt-resolution/expected_result
modules/geoipbackend/regression-tests/empty-record-resolution/expected_result
modules/geoipbackend/regression-tests/ent-resolution/expected_result
modules/geoipbackend/regression-tests/loc-resolution/expected_result
modules/geoipbackend/regression-tests/mixed-weight-resolution/expected_result
modules/geoipbackend/regression-tests/static-any-resolution/expected_result
modules/geoipbackend/regression-tests/write-mmdb.pl
modules/gmysqlbackend/4.3.0_to_4.7.0_schema.mysql.sql [new file with mode: 0644]
modules/gmysqlbackend/Makefile.am
modules/gmysqlbackend/gmysqlbackend.cc
modules/gmysqlbackend/schema.mysql.sql
modules/gmysqlbackend/smysql.cc
modules/gmysqlbackend/smysql.hh
modules/godbcbackend/4.3.0_to_4.7.0_schema.mssql.sql [new file with mode: 0644]
modules/godbcbackend/Makefile.am
modules/godbcbackend/godbcbackend.cc
modules/godbcbackend/schema.mssql.sql
modules/godbcbackend/sodbc.cc
modules/godbcbackend/sodbc.hh
modules/gpgsqlbackend/4.3.0_to_4.7.0_schema.pgsql.sql [new file with mode: 0644]
modules/gpgsqlbackend/Makefile.am
modules/gpgsqlbackend/gpgsqlbackend.cc
modules/gpgsqlbackend/schema.pgsql.sql
modules/gpgsqlbackend/spgsql.cc
modules/gpgsqlbackend/spgsql.hh
modules/gsqlite3backend/3.4.0_to_4.0.0_schema.sqlite3.sql
modules/gsqlite3backend/4.0.0_to_4.2.0_schema.sqlite3.sql
modules/gsqlite3backend/4.2.0_to_4.3.0_schema.sqlite3.sql
modules/gsqlite3backend/4.3.0_to_4.3.1_schema.sqlite3.sql
modules/gsqlite3backend/4.3.1_to_4.7.0_schema.sqlite3.sql [new file with mode: 0644]
modules/gsqlite3backend/Makefile.am
modules/gsqlite3backend/dnssec-3.x_to_3.4.0_schema.sqlite3.sql
modules/gsqlite3backend/gsqlite3backend.cc
modules/gsqlite3backend/schema.sqlite3.sql
modules/ldapbackend/Makefile.am
modules/ldapbackend/OBJECTFILES
modules/ldapbackend/ldapauthenticator.cc
modules/ldapbackend/ldapauthenticator.hh
modules/ldapbackend/ldapauthenticator_p.hh
modules/ldapbackend/ldapbackend.cc
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/lmdbbackend/old-schema-versions
modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb-0 [deleted file]
modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb-1 [deleted file]
modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb-0 [deleted file]
modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb-1 [deleted file]
modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb [moved from modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb with 95% similarity]
modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb-0 [new file with mode: 0644]
modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb-0-lock [moved from modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb-0-lock with 97% similarity]
modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb-1 [new file with mode: 0644]
modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb-1-lock [moved from modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb-1-lock with 97% similarity]
modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb-lock [moved from modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb-lock with 97% similarity]
modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb [moved from modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb with 95% similarity]
modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb-0 [new file with mode: 0644]
modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb-0-lock [moved from modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb-0-lock with 97% similarity]
modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb-1 [new file with mode: 0644]
modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb-1-lock [moved from modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb-1-lock with 97% similarity]
modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb-lock [moved from modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb-lock with 97% similarity]
modules/lua2backend/Makefile.am
modules/lua2backend/lua2api2.hh
modules/lua2backend/regression-tests/basic-a-dnssec/expected_result
modules/lua2backend/regression-tests/basic-a-resolution/expected_result
modules/lua2backend/regression-tests/basic-aaaa-resolution/expected_result
modules/lua2backend/regression-tests/nsec-2-dnssec/expected_result
modules/lua2backend/regression-tests/nsec-dnssec/expected_result
modules/pipebackend/Makefile.am
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/regression-tests/apex-test/expected_result
modules/remotebackend/regression-tests/basic-a-dnssec/expected_result
modules/remotebackend/regression-tests/basic-a-resolution/expected_result
modules/remotebackend/regression-tests/basic-aaaa-resolution/expected_result
modules/remotebackend/regression-tests/dnssec-keys/expected_result
modules/remotebackend/regression-tests/long-txt-resolution/expected_result
modules/remotebackend/regression-tests/ns-at-delegation/expected_result
modules/remotebackend/regression-tests/nsec-middle/expected_result
modules/remotebackend/regression-tests/nsec-middle/expected_result.narrow
modules/remotebackend/regression-tests/nsec-middle/expected_result.nsec3
modules/remotebackend/regression-tests/nsec-sibling-test/expected_result
modules/remotebackend/regression-tests/nsec-sibling-test/expected_result.narrow
modules/remotebackend/regression-tests/nsec-sibling-test/expected_result.nsec3
modules/remotebackend/regression-tests/nsec-wraparound-begin/expected_result
modules/remotebackend/regression-tests/nsec-wraparound-begin/expected_result.nsec3
modules/remotebackend/regression-tests/nsec-wraparound-end/expected_result
modules/remotebackend/regression-tests/nsec-wraparound-end/expected_result.nsec3
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/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-carbon.cc
pdns/auth-catalogzone.cc [new file with mode: 0644]
pdns/auth-catalogzone.hh [new file with mode: 0644]
pdns/auth-main.cc [new file with mode: 0644]
pdns/auth-main.hh [moved from pdns/common_startup.hh with 80% similarity]
pdns/auth-packetcache.hh
pdns/auth-primarycommunicator.cc [new file with mode: 0644]
pdns/auth-querycache.cc
pdns/auth-querycache.hh
pdns/auth-secondarycommunicator.cc [new file with mode: 0644]
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/bpf-filter.hh
pdns/burtle.hh [new file with mode: 0644]
pdns/cachecleaner.hh
pdns/calidns.cc
pdns/capabilities.cc
pdns/capabilities.hh
pdns/cdb.cc
pdns/channel.cc [new file with mode: 0644]
pdns/channel.hh [new file with mode: 0644]
pdns/comment.hh
pdns/common_startup.cc [deleted file]
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/mtasker_context.cc with 86% similarity]
pdns/credentials.cc
pdns/credentials.hh
pdns/dbdnsseckeeper.cc
pdns/decafsigners.cc
pdns/delaypipe.cc
pdns/delaypipe.hh
pdns/devpollmplexer.cc
pdns/digests.hh
pdns/distributor.hh
pdns/dns.cc
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
pdns/dnsdist-cache.hh
pdns/dnsdist-carbon.cc
pdns/dnsdist-console.cc
pdns/dnsdist-console.hh
pdns/dnsdist-dnscrypt.cc
pdns/dnsdist-doh-common.hh [new file with mode: 0644]
pdns/dnsdist-dynblocks.hh
pdns/dnsdist-dynbpf.cc
pdns/dnsdist-ecs.cc
pdns/dnsdist-ecs.hh
pdns/dnsdist-idstate.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.cc
pdns/dnsdist-lua.hh
pdns/dnsdist-protobuf.cc
pdns/dnsdist-protobuf.hh
pdns/dnsdist-protocols.cc
pdns/dnsdist-protocols.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.cc
pdns/dnsdist.hh
pdns/dnsdistconf.lua
pdns/dnsdistdist/.gitignore
pdns/dnsdistdist/DNSDIST-MIB.txt
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/ascii.hh [deleted symlink]
pdns/dnsdistdist/burtle.hh [new symlink]
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 [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-async.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-backend.cc
pdns/dnsdistdist/dnsdist-backoff.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-carbon.hh
pdns/dnsdistdist/dnsdist-concurrent-connections.hh [new file with 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-discovery.hh
pdns/dnsdistdist/dnsdist-dnsparser.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-dnsparser.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-doh-common.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-doh-common.hh [new symlink]
pdns/dnsdistdist/dnsdist-downstream-connection.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-dynblocks.cc
pdns/dnsdistdist/dnsdist-edns.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-edns.hh [moved from pdns/rec-snmp.hh with 73% similarity]
pdns/dnsdistdist/dnsdist-healthchecks.cc
pdns/dnsdistdist/dnsdist-healthchecks.hh
pdns/dnsdistdist/dnsdist-idstate.cc [deleted file]
pdns/dnsdistdist/dnsdist-internal-queries.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-internal-queries.hh [moved from pdns/secpoll-recursor.hh with 85% similarity]
pdns/dnsdistdist/dnsdist-kvs.cc
pdns/dnsdistdist/dnsdist-kvs.hh
pdns/dnsdistdist/dnsdist-lbpolicies.cc
pdns/dnsdistdist/dnsdist-lua-bindings-dnscrypt.cc
pdns/dnsdistdist/dnsdist-lua-bindings-dnsparser.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-lua-bindings-kvs.cc
pdns/dnsdistdist/dnsdist-lua-bindings-network.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-lua-bindings-packetcache.cc
pdns/dnsdistdist/dnsdist-lua-bindings-protobuf.cc
pdns/dnsdistdist/dnsdist-lua-bindings-rings.cc [new file with 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 [moved from pdns/ascii.hh with 76% similarity]
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-network.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-lua-network.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-mac-address.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-mac-address.hh [new file with 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-proxy-protocol.cc
pdns/dnsdistdist/dnsdist-random.cc
pdns/dnsdistdist/dnsdist-resolver.cc [moved from pdns/validate-recursor.hh with 51% similarity]
pdns/dnsdistdist/dnsdist-resolver.hh [moved from pdns/pubsuffix.hh with 76% similarity]
pdns/dnsdistdist/dnsdist-rules.hh
pdns/dnsdistdist/dnsdist-secpoll.cc
pdns/dnsdistdist/dnsdist-secpoll.hh
pdns/dnsdistdist/dnsdist-tcp-downstream.cc
pdns/dnsdistdist/dnsdist-tcp-downstream.hh
pdns/dnsdistdist/dnsdist-tcp-upstream.hh
pdns/dnsdistdist/dnsdist-tcp.hh
pdns/dnsdistdist/dnsdist-tsan.supp
pdns/dnsdistdist/dnsdist-web.hh
pdns/dnsdistdist/dnsdist-xsk.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-xsk.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist.conf-dist [moved from pdns/dnsdistdist/dnsdistconf.lua with 100% similarity]
pdns/dnsdistdist/dnsdist.service.in
pdns/dnsdistdist/docs/_static/dnsdist-keyblock.asc
pdns/dnsdistdist/docs/advanced/asynchronous-processing.rst [new file with mode: 0644]
pdns/dnsdistdist/docs/advanced/axfr.rst
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/downstreams.rst
pdns/dnsdistdist/docs/guides/dynblocks.rst
pdns/dnsdistdist/docs/guides/index.rst
pdns/dnsdistdist/docs/guides/webserver.rst
pdns/dnsdistdist/docs/imgs/AsyncQuery.png [new file with mode: 0644]
pdns/dnsdistdist/docs/imgs/DNSDistFlow.v2.png [new file with mode: 0644]
pdns/dnsdistdist/docs/imgs/DNSDistLazyHealthChecks.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 [new file with mode: 0644]
pdns/dnsdistdist/docs/reference/dnscrypt.rst
pdns/dnsdistdist/docs/reference/dnsname.rst
pdns/dnsdistdist/docs/reference/dnsparser.rst [new file with mode: 0644]
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/logging.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/svc.rst
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/js/moment.min.js
pdns/dnsdistdist/html/local.js
pdns/dnsdistdist/logging.hh [new symlink]
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/dnsdist_enable_tls_providers.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_enable_lto.m4 [new symlink]
pdns/dnsdistdist/m4/pdns_init_auto_vars.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/src_js/moment.js
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 [new file with mode: 0644]
pdns/dnsdistdist/test-dnsdist-lua-ffi.cc [new file with mode: 0644]
pdns/dnsdistdist/test-dnsdistasync.cc [new file with mode: 0644]
pdns/dnsdistdist/test-dnsdistbackend_cc.cc [new file with mode: 0644]
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 [new file with mode: 0644]
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-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/xsk.cc [new symlink]
pdns/dnsdistdist/xsk.hh [new symlink]
pdns/dnsgram.cc
pdns/dnslabeltext.rl
pdns/dnsmessage.proto
pdns/dnsname.cc
pdns/dnsname.hh
pdns/dnspacket.cc
pdns/dnspacket.hh
pdns/dnsparser.cc
pdns/dnsparser.hh
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.cc
pdns/dnswriter.hh
pdns/doh.hh
pdns/dolog.hh
pdns/dynhandler.cc
pdns/dynlistener.cc
pdns/dynlistener.hh
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/ednsoptions.hh
pdns/ednssubnet.cc
pdns/ednssubnet.hh
pdns/epollmplexer.cc
pdns/filterpo.cc [deleted file]
pdns/filterpo.hh [deleted file]
pdns/fstrm_logger.cc
pdns/fstrm_logger.hh
pdns/fuzz_moadnsparser.cc
pdns/fuzz_packetcache.cc
pdns/fuzz_proxyprotocol.cc
pdns/fuzz_yahttp.cc [new file with mode: 0644]
pdns/fuzz_zoneparsertng.cc
pdns/gettime.cc
pdns/gss_context.cc [new file with mode: 0644]
pdns/gss_context.hh [new file with mode: 0644]
pdns/histog.hh
pdns/histogram.hh
pdns/ipcipher.cc
pdns/ipcipher.hh
pdns/iputils.cc
pdns/iputils.hh
pdns/ixfr.cc
pdns/ixfr.hh
pdns/ixfrdist-stats.cc
pdns/ixfrdist-stats.hh
pdns/ixfrdist.cc
pdns/ixfrdist.example.yml
pdns/ixfrdist.service.in
pdns/ixfrutils.cc
pdns/ixfrutils.hh
pdns/ixplore.cc
pdns/json.cc
pdns/json.hh
pdns/keyroller/Pipfile [new file with mode: 0644]
pdns/keyroller/Pipfile.lock [new file with mode: 0644]
pdns/keyroller/README.md [new file with mode: 0644]
pdns/keyroller/pdns-keyroller-ctl.py [new file with mode: 0755]
pdns/keyroller/pdns-keyroller.conf.example [new file with mode: 0644]
pdns/keyroller/pdns-keyroller.py [new file with mode: 0755]
pdns/keyroller/pdnsapi/__init__.py [new file with mode: 0644]
pdns/keyroller/pdnsapi/api.py [new file with mode: 0644]
pdns/keyroller/pdnsapi/cryptokey.py [new file with mode: 0644]
pdns/keyroller/pdnsapi/metadata.py [new file with mode: 0644]
pdns/keyroller/pdnsapi/zone.py [new file with mode: 0644]
pdns/keyroller/pdnskeyroller/__init__.py [new file with mode: 0644]
pdns/keyroller/pdnskeyroller/config.py [new file with mode: 0644]
pdns/keyroller/pdnskeyroller/daemon.py [new file with mode: 0644]
pdns/keyroller/pdnskeyroller/domainconfig.py [new file with mode: 0644]
pdns/keyroller/pdnskeyroller/domainstate.py [new file with mode: 0644]
pdns/keyroller/pdnskeyroller/keyroll.py [new file with mode: 0644]
pdns/keyroller/pdnskeyroller/keyrollerdomain.py [new file with mode: 0644]
pdns/keyroller/pdnskeyroller/prepublishkeyroll.py [new file with mode: 0644]
pdns/keyroller/pdnskeyroller/util.py [new file with mode: 0644]
pdns/keyroller/requirements-test.txt [new file with mode: 0644]
pdns/keyroller/requirements.txt [new file with mode: 0644]
pdns/keyroller/setup.py [new file with mode: 0644]
pdns/kqueuemplexer.cc
pdns/lazy_allocator.hh [deleted file]
pdns/libssl.cc
pdns/libssl.hh
pdns/lock.hh
pdns/logger.cc
pdns/logger.hh
pdns/logging.hh [new file with mode: 0644]
pdns/lua-auth4.cc
pdns/lua-auth4.hh
pdns/lua-base4.cc
pdns/lua-base4.hh
pdns/lua-record.cc
pdns/lua-recursor4-ffi.hh [deleted file]
pdns/lua-recursor4.cc [deleted file]
pdns/lua-recursor4.hh [deleted file]
pdns/lwres.cc [deleted file]
pdns/lwres.hh [deleted file]
pdns/mastercommunicator.cc [deleted file]
pdns/minicurl.cc
pdns/minicurl.hh
pdns/misc.cc
pdns/misc.hh
pdns/mkpubsuffixcc [deleted file]
pdns/mplexer.hh
pdns/mtasker.cc [deleted file]
pdns/mtasker.hh [deleted file]
pdns/mtasker_context.hh [deleted file]
pdns/mtasker_fcontext.cc [deleted file]
pdns/mtasker_ucontext.cc [deleted file]
pdns/named.conf.parsertest
pdns/nameserver.cc
pdns/nameserver.hh
pdns/namespaces.hh
pdns/nod.cc [deleted file]
pdns/nod.hh [deleted file]
pdns/nsec3dig.cc
pdns/nsecrecords.cc
pdns/opensslsigners.cc
pdns/packetcache.hh
pdns/packethandler.cc
pdns/packethandler.hh
pdns/pdns.init.in [deleted file]
pdns/pdns.service.in
pdns/pdns_recursor.cc [deleted file]
pdns/pdnsutil.cc
pdns/pkcs11signers.cc
pdns/pkcs11signers.hh
pdns/pollmplexer.cc
pdns/portsmplexer.cc
pdns/protozero.cc
pdns/protozero.hh
pdns/proxy-protocol.cc
pdns/proxy-protocol.hh
pdns/qtype.cc
pdns/qtype.hh
pdns/rcpgenerator.cc
pdns/rec-carbon.cc [deleted file]
pdns/rec-lua-conf.cc [deleted file]
pdns/rec-lua-conf.hh [deleted file]
pdns/rec-snmp.cc [deleted file]
pdns/rec_channel.cc [deleted file]
pdns/rec_channel.hh [deleted file]
pdns/rec_channel_rec.cc [deleted file]
pdns/rec_control.cc [deleted file]
pdns/receiver.cc [deleted file]
pdns/recpacketcache.cc [deleted file]
pdns/recpacketcache.hh [deleted file]
pdns/recursor_cache.cc [deleted file]
pdns/recursor_cache.hh [deleted file]
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/ascii.hh [deleted symlink]
pdns/recursordist/auth-catalogzone.hh [new symlink]
pdns/recursordist/burtle.hh [new symlink]
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/compiling.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/internals.rst
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.4.rst
pdns/recursordist/docs/changelog/4.5.rst
pdns/recursordist/docs/changelog/4.6.rst
pdns/recursordist/docs/changelog/4.7.rst
pdns/recursordist/docs/changelog/4.8.rst [new file with mode: 0644]
pdns/recursordist/docs/changelog/4.9.rst [new file with mode: 0644]
pdns/recursordist/docs/changelog/5.0.rst [new file with mode: 0644]
pdns/recursordist/docs/changelog/index.rst
pdns/recursordist/docs/changelog/pre-4.0.rst
pdns/recursordist/docs/conf.py
pdns/recursordist/docs/dns64.rst
pdns/recursordist/docs/dnssec.rst
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/index.rst
pdns/recursordist/docs/lua-config/protobuf.rst
pdns/recursordist/docs/lua-config/proxymapping.rst [new file with mode: 0644]
pdns/recursordist/docs/lua-config/rpz.rst
pdns/recursordist/docs/lua-config/ztc.rst
pdns/recursordist/docs/lua-scripting/dnsname.rst
pdns/recursordist/docs/lua-scripting/dnsrecord.rst
pdns/recursordist/docs/lua-scripting/dq.rst
pdns/recursordist/docs/lua-scripting/ffi.rst
pdns/recursordist/docs/lua-scripting/hooks.rst
pdns/recursordist/docs/manpages/pdns_recursor.1.rst
pdns/recursordist/docs/manpages/rec_control.1.rst
pdns/recursordist/docs/metrics.rst
pdns/recursordist/docs/nod_udr.rst
pdns/recursordist/docs/performance.rst
pdns/recursordist/docs/requirements.txt
pdns/recursordist/docs/running.rst
pdns/recursordist/docs/security-advisories/powerdns-advisory-2017-05.rst
pdns/recursordist/docs/security-advisories/powerdns-advisory-2022-01.rst [new file with mode: 0644]
pdns/recursordist/docs/security-advisories/powerdns-advisory-2022-02.rst [new file with mode: 0644]
pdns/recursordist/docs/security-advisories/powerdns-advisory-2023-01.rst [new file with mode: 0644]
pdns/recursordist/docs/security-advisories/powerdns-advisory-2023-02.rst [new file with mode: 0644]
pdns/recursordist/docs/security-advisories/powerdns-advisory-2024-01.rst [new file with mode: 0644]
pdns/recursordist/docs/security.rst
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 [changed from symlink to file mode: 0644]
pdns/recursordist/filterpo.hh [changed from symlink to file mode: 0644]
pdns/recursordist/gss_context.cc [new symlink]
pdns/recursordist/gss_context.hh [new symlink]
pdns/recursordist/html/index.html
pdns/recursordist/html/js/d3.v3-min.js [moved from pdns/recursordist/html/js/d3.js with 100% similarity]
pdns/recursordist/html/js/handlebars-v4.0.11-min.js [new file with mode: 0644]
pdns/recursordist/html/js/jquery-1.8.3.js [deleted file]
pdns/recursordist/html/js/jquery-1.8.3.min.js [deleted file]
pdns/recursordist/html/js/jsrender.js [deleted file]
pdns/recursordist/html/js/moment.js
pdns/recursordist/html/js/moment.min.js
pdns/recursordist/html/js/underscore-min.js [deleted file]
pdns/recursordist/html/js/underscore.js [deleted file]
pdns/recursordist/html/local-2022.js [moved from pdns/recursordist/html/local.js with 64% similarity]
pdns/recursordist/incfiles
pdns/recursordist/lazy_allocator.hh [changed from symlink to file mode: 0644]
pdns/recursordist/logging.cc
pdns/recursordist/logging.hh [changed from file to symlink]
pdns/recursordist/logr.hh
pdns/recursordist/lua-recursor4-ffi.hh [changed from symlink to file mode: 0644]
pdns/recursordist/lua-recursor4.cc [changed from symlink to file mode: 0644]
pdns/recursordist/lua-recursor4.hh [changed from symlink to file mode: 0644]
pdns/recursordist/lwres.cc [changed from symlink to file mode: 0644]
pdns/recursordist/lwres.hh [changed from symlink to file mode: 0644]
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/m4/pdns_enable_lto.m4 [new symlink]
pdns/recursordist/m4/pdns_init_auto_vars.m4 [new symlink]
pdns/recursordist/mkpubsuffixcc [changed from symlink to file mode: 0755]
pdns/recursordist/mtasker.cc [deleted symlink]
pdns/recursordist/mtasker.hh [changed from symlink to file mode: 0644]
pdns/recursordist/mtasker_context.cc [changed from symlink to file mode: 0644]
pdns/recursordist/mtasker_context.hh [changed from symlink to file mode: 0644]
pdns/recursordist/mtasker_fcontext.cc [changed from symlink to file mode: 0644]
pdns/recursordist/mtasker_ucontext.cc [changed from symlink to file mode: 0644]
pdns/recursordist/negcache.cc
pdns/recursordist/negcache.hh
pdns/recursordist/nod.cc [changed from symlink to file mode: 0644]
pdns/recursordist/nod.hh [changed from symlink to file mode: 0644]
pdns/recursordist/pdns-recursor.init.d [deleted file]
pdns/recursordist/pdns-recursor.service.in
pdns/recursordist/pdns_recursor.cc [changed from symlink to file mode: 0644]
pdns/recursordist/pubsuffix.hh [changed from symlink to file mode: 0644]
pdns/recursordist/pubsuffixloader.cc
pdns/recursordist/rec-carbon.cc [changed from symlink to file mode: 0644]
pdns/recursordist/rec-lua-conf.cc [changed from symlink to file mode: 0644]
pdns/recursordist/rec-lua-conf.hh [changed from symlink to file mode: 0644]
pdns/recursordist/rec-main.cc
pdns/recursordist/rec-main.hh
pdns/recursordist/rec-protozero.cc
pdns/recursordist/rec-protozero.hh
pdns/recursordist/rec-responsestats.cc [new file with mode: 0644]
pdns/recursordist/rec-responsestats.hh [new file with mode: 0644]
pdns/recursordist/rec-snmp.cc [changed from symlink to file mode: 0644]
pdns/recursordist/rec-snmp.hh [changed from symlink to file mode: 0644]
pdns/recursordist/rec-taskqueue.cc
pdns/recursordist/rec-taskqueue.hh
pdns/recursordist/rec-tcounters.cc [new file with mode: 0644]
pdns/recursordist/rec-tcounters.hh [new file with mode: 0644]
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 [changed from symlink to file mode: 0644]
pdns/recursordist/rec_channel.hh [changed from symlink to file mode: 0644]
pdns/recursordist/rec_channel_rec.cc [changed from symlink to file mode: 0644]
pdns/recursordist/rec_control.cc [changed from symlink to file mode: 0644]
pdns/recursordist/recpacketcache.cc [changed from symlink to file mode: 0644]
pdns/recursordist/recpacketcache.hh [changed from symlink to file mode: 0644]
pdns/recursordist/recursor_cache.cc [changed from symlink to file mode: 0644]
pdns/recursordist/recursor_cache.hh [changed from symlink to file mode: 0644]
pdns/recursordist/reczones-helpers.cc [new file with mode: 0644]
pdns/recursordist/reczones-helpers.hh [new file with mode: 0644]
pdns/recursordist/reczones.cc [changed from symlink to file mode: 0644]
pdns/recursordist/resolve-context.hh [changed from symlink to file mode: 0644]
pdns/recursordist/responsestats.cc [deleted symlink]
pdns/recursordist/responsestats.hh [deleted symlink]
pdns/recursordist/root-addresses.hh [changed from symlink to file mode: 0644]
pdns/recursordist/rpzloader.cc [changed from symlink to file mode: 0644]
pdns/recursordist/rpzloader.hh [changed from symlink to file mode: 0644]
pdns/recursordist/secpoll-recursor.cc [changed from symlink to file mode: 0644]
pdns/recursordist/secpoll-recursor.hh [changed from symlink to file mode: 0644]
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 [changed from symlink to file mode: 0644]
pdns/recursordist/syncres.hh [changed from symlink to file mode: 0644]
pdns/recursordist/taskqueue.cc
pdns/recursordist/taskqueue.hh
pdns/recursordist/tcounters.hh [new symlink]
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 [new file with mode: 0644]
pdns/recursordist/test-rec-zonetocache.cc
pdns/recursordist/test-recpacketcache_cc.cc [changed from symlink to file mode: 0644]
pdns/recursordist/test-recursorcache_cc.cc
pdns/recursordist/test-reczones-helpers.cc [new file with mode: 0644]
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/test-xpf_cc.cc [deleted file]
pdns/recursordist/test_libcrypto
pdns/recursordist/testrunner.cc
pdns/recursordist/validate-recursor.cc [changed from symlink to file mode: 0644]
pdns/recursordist/validate-recursor.hh [changed from symlink to file mode: 0644]
pdns/recursordist/ws-recursor.cc [changed from symlink to file mode: 0644]
pdns/recursordist/ws-recursor.hh [changed from symlink to file mode: 0644]
pdns/recursordist/xpf.cc [deleted symlink]
pdns/recursordist/xpf.hh [deleted symlink]
pdns/reczones.cc [deleted file]
pdns/remote_logger.cc
pdns/remote_logger.hh
pdns/resolve-context.hh [deleted file]
pdns/resolver.cc
pdns/responsestats.cc
pdns/rfc2136handler.cc
pdns/root-addresses.hh [deleted file]
pdns/rpzloader.cc [deleted file]
pdns/rpzloader.hh [deleted file]
pdns/saxfr.cc
pdns/sdig.cc
pdns/secpoll-recursor.cc [deleted file]
pdns/secpoll.cc
pdns/serialtweaker.cc
pdns/sha.hh
pdns/shuffle.cc
pdns/signingpipe.cc
pdns/signingpipe.hh
pdns/sillyrecords.cc
pdns/slavecommunicator.cc [deleted file]
pdns/snmp-agent.cc
pdns/snmp-agent.hh
pdns/sodcrypto.cc [deleted file]
pdns/sodcrypto.hh [deleted file]
pdns/sodiumsigners.cc
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/stubquery.cc
pdns/stubresolver.cc
pdns/stubresolver.hh
pdns/syncres.cc [deleted file]
pdns/syncres.hh [deleted file]
pdns/tcounters.hh [new file with mode: 0644]
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
pdns/test-dnsdistpacketcache_cc.cc
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-recpacketcache_cc.cc [deleted file]
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/tools/rrd/makegraphs
pdns/toysdig.cc [deleted file]
pdns/tsigutils.cc
pdns/tsigverifier.cc
pdns/ueberbackend.cc
pdns/ueberbackend.hh
pdns/unix_utility.cc
pdns/utility.hh
pdns/validate-recursor.cc [deleted file]
pdns/validate.cc
pdns/validate.hh
pdns/version.cc
pdns/webserver.cc
pdns/webserver.hh
pdns/ws-api.cc
pdns/ws-api.hh
pdns/ws-auth.cc
pdns/ws-auth.hh
pdns/ws-recursor.cc [deleted file]
pdns/ws-recursor.hh [deleted file]
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/requirements.txt
regression-tests.api/runtests.py
regression-tests.api/test_Basics.py
regression-tests.api/test_Cache.py
regression-tests.api/test_RecursorConfig.py
regression-tests.api/test_TSIG.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/init-keytab.sh [new file with mode: 0755]
regression-tests.auth-py/kerberos-client/krb5.conf [new file with mode: 0755]
regression-tests.auth-py/kerberos-client/kt.keytab [new file with mode: 0644]
regression-tests.auth-py/kerberos-client/update-policy.lua [new file with mode: 0644]
regression-tests.auth-py/kerberos-server/Dockerfile [new file with mode: 0644]
regression-tests.auth-py/kerberos-server/docker-compose.yml [new file with mode: 0644]
regression-tests.auth-py/kerberos-server/kerberos-init.sh [new file with mode: 0755]
regression-tests.auth-py/requirements.txt
regression-tests.auth-py/runtests
regression-tests.auth-py/test_ALIAS.py
regression-tests.auth-py/test_AnyBind.py [new file with mode: 0644]
regression-tests.auth-py/test_GSSTSIG.py [new file with mode: 0644]
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 [new file with mode: 0644]
regression-tests.dnsdist/.gitignore
regression-tests.dnsdist/Makefile [new file with mode: 0644]
regression-tests.dnsdist/dnsdistDynBlockTests.py [new file with mode: 0644]
regression-tests.dnsdist/dnsdistdohtests.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/resolv.conf.sample [new file with mode: 0644]
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 [new file with mode: 0644]
regression-tests.dnsdist/test_BackendDiscovery.py
regression-tests.dnsdist/test_Basics.py
regression-tests.dnsdist/test_BrokenAnswer.py
regression-tests.dnsdist/test_CDB.py
regression-tests.dnsdist/test_CacheHitResponses.py
regression-tests.dnsdist/test_CacheInsertedResponses.py [new file with mode: 0644]
regression-tests.dnsdist/test_Caching.py
regression-tests.dnsdist/test_Carbon.py
regression-tests.dnsdist/test_CheckConfig.py
regression-tests.dnsdist/test_DNSCrypt.py
regression-tests.dnsdist/test_DNSParser.py [new file with mode: 0644]
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 [new file with mode: 0644]
regression-tests.dnsdist/test_NetworkBindings.py [new file with mode: 0644]
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_Responses.py
regression-tests.dnsdist/test_RestartQuery.py [new file with mode: 0644]
regression-tests.dnsdist/test_Routing.py
regression-tests.dnsdist/test_RulesActions.py
regression-tests.dnsdist/test_SNMP.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/expected_result
regression-tests.nobackend/default-publish-cds/named.conf
regression-tests.nobackend/distributor/command
regression-tests.nobackend/distributor/expected_result
regression-tests.nobackend/edns-packet-cache/expected_result
regression-tests.nobackend/edns-packet-cache/named.conf
regression-tests.nobackend/lmdb-metadata-leak/command [new file with mode: 0755]
regression-tests.nobackend/lmdb-metadata-leak/description [new file with mode: 0644]
regression-tests.nobackend/lmdb-metadata-leak/expected_result [new file with mode: 0644]
regression-tests.nobackend/negcache-tests-dotted-cname/expected_result
regression-tests.nobackend/negcache-tests-dotted-cname/named.conf
regression-tests.nobackend/rectify-axfr/command
regression-tests.nobackend/soa-edit/expected_result
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/command
regression-tests.nobackend/tinydns-data-check/expected_result
regression-tests.nobackend/zonemd-test-cases/zones/20-generic-zonemd/example.zone
regression-tests.nobackend/zonemd-test-cases/zones/45-root-zone/root.zone.hashed
regression-tests.nobackend/zonemd-test-cases/zones/50-uppercase-nsec-rdata-names/arpa.zone.hashed
regression-tests.nobackend/zonemd-test-cases/zones/51-uppercase-nsec3-rdata-names/arpa.zone.hashed
regression-tests.nobackend/zonemd-test-cases/zones/52-uppercase-rrsig-rdata-names/arpa.zone.hashed
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_AnyBind.py [new file with mode: 0644]
regression-tests.recursor-dnssec/test_DNS64.py
regression-tests.recursor-dnssec/test_ECS.py
regression-tests.recursor-dnssec/test_EDNS.py
regression-tests.recursor-dnssec/test_ExtendedErrors.py
regression-tests.recursor-dnssec/test_LockedCache.py [new file with mode: 0644]
regression-tests.recursor-dnssec/test_Lua.py
regression-tests.recursor-dnssec/test_Notify.py
regression-tests.recursor-dnssec/test_PacketCache.py
regression-tests.recursor-dnssec/test_Protobuf.py
regression-tests.recursor-dnssec/test_ProxyByTable.py [new file with mode: 0644]
regression-tests.recursor-dnssec/test_RDFlag.py [new file with mode: 0644]
regression-tests.recursor-dnssec/test_RPZ.py
regression-tests.recursor-dnssec/test_RPZIncomplete.py [new file with mode: 0644]
regression-tests.recursor-dnssec/test_RecDnstap.py
regression-tests.recursor-dnssec/test_RootNXTrust.py
regression-tests.recursor-dnssec/test_RoutingTag.py
regression-tests.recursor-dnssec/test_SNMP.py
regression-tests.recursor-dnssec/test_SimpleDoT.py
regression-tests.recursor-dnssec/test_SimpleForwardOverDoT.py
regression-tests.recursor-dnssec/test_SimpleYAML.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.rootzone/tests/direct-ds/expected_result
regression-tests.rootzone/tests/direct-ds/expected_result.dnssec
regression-tests.rootzone/tests/direct-ns/expected_result
regression-tests.rootzone/tests/direct-ns/expected_result.dnssec
regression-tests.rootzone/tests/direct-root/expected_result
regression-tests.rootzone/tests/ds-at-ent-from-glue/expected_result
regression-tests.rootzone/tests/ds-at-ent-from-glue/expected_result.dnssec
regression-tests.rootzone/tests/ds-at-ent/expected_result
regression-tests.rootzone/tests/ds-at-ent/expected_result.dnssec
regression-tests.rootzone/tests/ds-at-glue/expected_result
regression-tests.rootzone/tests/ds-at-glue/expected_result.dnssec
regression-tests.rootzone/tests/nx-2ld/expected_result
regression-tests.rootzone/tests/ref-3ld/expected_result
regression-tests.rootzone/tests/ref-3ld/expected_result.dnssec
regression-tests/.gitignore
regression-tests/README.md
regression-tests/backends/bind-master
regression-tests/backends/bind-slave
regression-tests/backends/geoip-master
regression-tests/backends/gmysql-master
regression-tests/backends/gmysql-slave
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/backends/lmdb-slave
regression-tests/ext/bind-master
regression-tests/ext/bind-slave
regression-tests/named.conf
regression-tests/recursor-test
regression-tests/runtests
regression-tests/tests/.gitignore
regression-tests/tests/0dyndns-prereq-all/expected_result
regression-tests/tests/0dyndns-prereq-nxrrset-full/expected_result
regression-tests/tests/1dyndns-cname-and-other-data/expected_result
regression-tests/tests/1dyndns-content-casemix/expected_result
regression-tests/tests/1dyndns-correct-zone/expected_result
regression-tests/tests/1dyndns-update-add-delete-casesensative/expected_result
regression-tests/tests/1dyndns-update-add-delete-cname/expected_result
regression-tests/tests/1dyndns-update-add-delete-mx/expected_result
regression-tests/tests/1dyndns-update-add-delete-txt/command [new file with mode: 0755]
regression-tests/tests/1dyndns-update-add-delete-txt/description [new file with mode: 0644]
regression-tests/tests/1dyndns-update-add-delete-txt/expected_result [new file with mode: 0644]
regression-tests/tests/1dyndns-update-add-delete-txt/skip.nodyndns [new file with mode: 0644]
regression-tests/tests/1dyndns-update-add-delete-wildcard/expected_result
regression-tests/tests/1dyndns-update-add-invalid-record/expected_result
regression-tests/tests/1dyndns-update-delegate-in-between/expected_result
regression-tests/tests/1dyndns-update-delegate-in-between/expected_result.dnssec
regression-tests/tests/1dyndns-update-delegate-in-between/expected_result.narrow
regression-tests/tests/1dyndns-update-delegate-in-between/expected_result.nsec3
regression-tests/tests/1dyndns-update-delegate-in-between/expected_result.nsec3-optout
regression-tests/tests/1dyndns-update-delete-add-host/expected_result
regression-tests/tests/1dyndns-update-delete-multi-add-host/expected_result
regression-tests/tests/1dyndns-update-delete-mx-prio/expected_result
regression-tests/tests/1dyndns-update-delete-ns/expected_result
regression-tests/tests/1dyndns-update-delete-soa/expected_result
regression-tests/tests/1dyndns-update-in-between/expected_result
regression-tests/tests/1dyndns-update-in-between/expected_result.dnssec
regression-tests/tests/1dyndns-update-in-between/expected_result.narrow
regression-tests/tests/1dyndns-update-in-between/expected_result.nsec3
regression-tests/tests/1dyndns-update-nsec3params-with-others/expected_result
regression-tests/tests/1dyndns-update-nsec3params-with-others/expected_result.nsec3
regression-tests/tests/1dyndns-update-nsec3params-with-others/expected_result.nsec3-optout
regression-tests/tests/1dyndns-update-nsec3params/expected_result
regression-tests/tests/1dyndns-update-nsec3params/expected_result.nsec3
regression-tests/tests/1dyndns-update-nsec3params/expected_result.nsec3-optout
regression-tests/tests/1dyndns-update-replace-a-host/expected_result
regression-tests/tests/1dyndns-update-replace-cname/expected_result
regression-tests/tests/1dyndns-update-replace-mx/expected_result
regression-tests/tests/1dyndns-update-srv/expected_result
regression-tests/tests/1dyndns-update-update-ttl/expected_result
regression-tests/tests/2dyndns-update-replace-soa/expected_result
regression-tests/tests/8bit-txt-unescaped/expected_result
regression-tests/tests/8bit-txt/expected_result
regression-tests/tests/alias-address/expected_result
regression-tests/tests/alias-address/expected_result.dnssec
regression-tests/tests/alias-mx/expected_result
regression-tests/tests/any-nxdomain/expected_result
regression-tests/tests/any-nxdomain/expected_result.dnssec
regression-tests/tests/any-nxdomain/expected_result.narrow
regression-tests/tests/any-nxdomain/expected_result.nsec3
regression-tests/tests/any-query/expected_result
regression-tests/tests/any-query/expected_result.dnssec
regression-tests/tests/any-query/expected_result.narrow
regression-tests/tests/any-query/expected_result.nsec3
regression-tests/tests/any-to-tcp-query/expected_result
regression-tests/tests/any-wildcard-dnssec/expected_result
regression-tests/tests/any-wildcard-dnssec/expected_result.narrow
regression-tests/tests/any-wildcard-dnssec/expected_result.nsec3
regression-tests/tests/any-wildcard/expected_result
regression-tests/tests/apex-level-a-but-no-a/expected_result
regression-tests/tests/apex-level-a/expected_result
regression-tests/tests/apex-level-ns/expected_result
regression-tests/tests/autoptr/expected_result
regression-tests/tests/autoptr/expected_result.ldap-strict
regression-tests/tests/axfr/expected_result
regression-tests/tests/axfr/expected_result.dnssec
regression-tests/tests/axfr/expected_result.nsec3
regression-tests/tests/axfr/expected_result.nsec3-optout
regression-tests/tests/basic-a-resolution/expected_result
regression-tests/tests/basic-aaaa-resolution/expected_result
regression-tests/tests/basic-eui48/expected_result
regression-tests/tests/basic-eui64/expected_result
regression-tests/tests/basic-hinfo/expected_result
regression-tests/tests/basic-loc/expected_result
regression-tests/tests/basic-mb-resolution/expected_result
regression-tests/tests/basic-mg-resolution/expected_result
regression-tests/tests/basic-mr-resolution/expected_result
regression-tests/tests/basic-nonzone/expected_result
regression-tests/tests/basic-ns-resolution/expected_result
regression-tests/tests/basic-soa-resolution/expected_result
regression-tests/tests/basic-srv/expected_result
regression-tests/tests/basic-txt-resolution/expected_result
regression-tests/tests/bind-add-zone/expected_result.bind
regression-tests/tests/cname-and-wildcard-at-root/expected_result
regression-tests/tests/cname-and-wildcard-but-no-correct-type/expected_result
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/cname-and-wildcard/expected_result
regression-tests/tests/cname-but-no-correct-type/expected_result
regression-tests/tests/cname-to-apex/expected_result
regression-tests/tests/cname-to-nxdomain-any/expected_result
regression-tests/tests/cname-to-nxdomain-any/expected_result.dnssec
regression-tests/tests/cname-to-nxdomain-any/expected_result.narrow
regression-tests/tests/cname-to-nxdomain-any/expected_result.nsec3
regression-tests/tests/cname-to-nxdomain/expected_result
regression-tests/tests/cname-to-nxdomain/expected_result.dnssec
regression-tests/tests/cname-to-nxdomain/expected_result.narrow
regression-tests/tests/cname-to-nxdomain/expected_result.nsec3
regression-tests/tests/cname-to-referral/expected_result
regression-tests/tests/cname-to-unauth-any/expected_result
regression-tests/tests/cname-to-unauth-any/expected_result.dnssec
regression-tests/tests/cname-to-unauth/expected_result
regression-tests/tests/cname-to-unauth/expected_result.dnssec
regression-tests/tests/cname-wildcard-chain/expected_result
regression-tests/tests/cname-wildcard-chain/expected_result.dnssec
regression-tests/tests/cname-wildcard-chain/expected_result.narrow
regression-tests/tests/cname-wildcard-chain/expected_result.nsec3
regression-tests/tests/cross-domain-cname-to-wildcard/expected_result
regression-tests/tests/cryptokeys/expected_result.dnssec
regression-tests/tests/cryptokeys/expected_result.narrow
regression-tests/tests/cryptokeys/expected_result.nsec3
regression-tests/tests/direct-dnskey/expected_result
regression-tests/tests/direct-dnskey/expected_result.dnssec
regression-tests/tests/direct-nsec-nxdomain/expected_result
regression-tests/tests/direct-nsec-nxdomain/expected_result.dnssec
regression-tests/tests/direct-nsec-nxdomain/expected_result.narrow
regression-tests/tests/direct-nsec-nxdomain/expected_result.nsec3
regression-tests/tests/direct-nsec3param/expected_result
regression-tests/tests/direct-nsec3param/expected_result.dnssec
regression-tests/tests/direct-nsec3param/expected_result.narrow
regression-tests/tests/direct-nsec3param/expected_result.nsec3
regression-tests/tests/direct-rrsig/expected_result
regression-tests/tests/direct-wildcard/expected_result
regression-tests/tests/dname-self/expected_result
regression-tests/tests/dname-too-long-synth/expected_result
regression-tests/tests/dname-too-long-synth/expected_result.dnssec
regression-tests/tests/dname/expected_result
regression-tests/tests/dname/expected_result.dnssec
regression-tests/tests/double-srv/expected_result
regression-tests/tests/double/expected_result
regression-tests/tests/double/expected_result.dnssec
regression-tests/tests/ds-at-apex-noerror/description
regression-tests/tests/ds-at-apex-noerror/expected_result
regression-tests/tests/ds-at-apex-noerror/expected_result.dnssec
regression-tests/tests/ds-at-apex-noerror/expected_result.narrow
regression-tests/tests/ds-at-apex-noerror/expected_result.nsec3
regression-tests/tests/ds-at-both-sides/expected_result
regression-tests/tests/ds-at-parent/expected_result
regression-tests/tests/ds-at-secure-delegation/expected_result
regression-tests/tests/ds-at-secure-delegation/expected_result.dnssec
regression-tests/tests/ds-at-unsecure-delegation/expected_result
regression-tests/tests/ds-at-unsecure-delegation/expected_result.dnssec
regression-tests/tests/ds-at-unsecure-delegation/expected_result.narrow
regression-tests/tests/ds-at-unsecure-delegation/expected_result.nsec3
regression-tests/tests/ds-at-unsecure-delegation/expected_result.nsec3-optout
regression-tests/tests/ds-at-unsecure-zone-cut/expected_result
regression-tests/tests/ds-at-unsecure-zone-cut/expected_result.dnssec
regression-tests/tests/ds-at-unsecure-zone-cut/expected_result.narrow
regression-tests/tests/ds-at-unsecure-zone-cut/expected_result.nsec3
regression-tests/tests/ds-at-unsecure-zone-cut/expected_result.nsec3-optout
regression-tests/tests/ds-inside-delegation/expected_result
regression-tests/tests/ds-inside-delegation/expected_result.dnssec
regression-tests/tests/ds-inside-delegation/expected_result.narrow
regression-tests/tests/ds-inside-delegation/expected_result.nsec3
regression-tests/tests/ds-inside-delegation/expected_result.nsec3-optout
regression-tests/tests/ent-any/expected_result
regression-tests/tests/ent-any/expected_result.dnssec
regression-tests/tests/ent-any/expected_result.narrow
regression-tests/tests/ent-any/expected_result.nsec3
regression-tests/tests/ent-any/expected_result.nsec3-optout
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/tests/ent-axfr/expected_result
regression-tests/tests/ent-axfr/expected_result.nsec3
regression-tests/tests/ent-axfr/expected_result.nsec3-optout
regression-tests/tests/ent-rr-enclosed-in-ent/expected_result.dnssec
regression-tests/tests/ent-rr-enclosed-in-ent/expected_result.narrow
regression-tests/tests/ent-rr-enclosed-in-ent/expected_result.nsec3
regression-tests/tests/ent-soa/expected_result
regression-tests/tests/ent-soa/expected_result.dnssec
regression-tests/tests/ent-soa/expected_result.narrow
regression-tests/tests/ent-soa/expected_result.nsec3
regression-tests/tests/ent-soa/expected_result.nsec3-optout
regression-tests/tests/ent-unsigned-delegation/expected_result
regression-tests/tests/ent-unsigned-delegation/expected_result.dnssec
regression-tests/tests/ent-unsigned-delegation/expected_result.narrow
regression-tests/tests/ent-unsigned-delegation/expected_result.nsec3
regression-tests/tests/ent-unsigned-delegation/expected_result.nsec3-optout
regression-tests/tests/ent-wildcard-below-ent/expected_result
regression-tests/tests/ent-wildcard-below-ent/expected_result.dnssec
regression-tests/tests/ent-wildcard-below-ent/expected_result.narrow
regression-tests/tests/ent-wildcard-below-ent/expected_result.nsec3
regression-tests/tests/ent/expected_result
regression-tests/tests/ent/expected_result.dnssec
regression-tests/tests/ent/expected_result.narrow
regression-tests/tests/ent/expected_result.nsec3
regression-tests/tests/ent/expected_result.nsec3-optout
regression-tests/tests/escaped-txt/expected_result
regression-tests/tests/external-cname-pointer/expected_result
regression-tests/tests/five-levels-wildcard-one-below-apex/expected_result
regression-tests/tests/five-levels-wildcard-one-below-apex/expected_result.narrow
regression-tests/tests/five-levels-wildcard-one-below-apex/expected_result.nsec3
regression-tests/tests/five-levels-wildcard/expected_result
regression-tests/tests/five-levels-wildcard/expected_result.narrow
regression-tests/tests/five-levels-wildcard/expected_result.nsec3
regression-tests/tests/glue-record/expected_result
regression-tests/tests/glue-referral/expected_result
regression-tests/tests/internal-referral/expected_result
regression-tests/tests/largettl/expected_result
regression-tests/tests/long-name/expected_result
regression-tests/tests/minimal-noerror/expected_result
regression-tests/tests/minimal-noerror/expected_result.narrow
regression-tests/tests/minimal-noerror/expected_result.nsec3
regression-tests/tests/minimal-nxdomain/description
regression-tests/tests/minimal-nxdomain/expected_result
regression-tests/tests/minimal-nxdomain/expected_result.narrow
regression-tests/tests/minimal-nxdomain/expected_result.nsec3
regression-tests/tests/multi-step-cname-resolution/expected_result
regression-tests/tests/multi-txt-escape-resolution/expected_result
regression-tests/tests/multi-txt-resolution/expected_result
regression-tests/tests/mx-case-sensitivy-with-ap/expected_result
regression-tests/tests/mx-to-cname/expected_result
regression-tests/tests/mx-with-simple-additional-processing/expected_result
regression-tests/tests/naptr/expected_result
regression-tests/tests/no-out-of-zone-data/expected_result
regression-tests/tests/non-existing-record-other-types-exist-ns/expected_result
regression-tests/tests/non-existing-record-other-types-exist/expected_result
regression-tests/tests/ns-at-delegation/expected_result
regression-tests/tests/ns-with-identical-glue/expected_result
regression-tests/tests/nsec-at-delegation/command [new file with mode: 0755]
regression-tests/tests/nsec-at-delegation/description [new file with mode: 0644]
regression-tests/tests/nsec-at-delegation/expected_result [new file with mode: 0644]
regression-tests/tests/nsec-at-delegation/expected_result.narrow [new file with mode: 0644]
regression-tests/tests/nsec-at-delegation/expected_result.nsec3 [new file with mode: 0644]
regression-tests/tests/nsec-at-delegation/expected_result.nsec3-optout [new file with mode: 0644]
regression-tests/tests/nsec-at-delegation/skip.nodnssec [new file with mode: 0644]
regression-tests/tests/nsec-bitmap/expected_result
regression-tests/tests/nsec-bitmap/expected_result.narrow
regression-tests/tests/nsec-bitmap/expected_result.nsec3
regression-tests/tests/nsec-glue-at-delegation/expected_result
regression-tests/tests/nsec-glue-at-delegation/expected_result.narrow
regression-tests/tests/nsec-glue-at-delegation/expected_result.nsec3
regression-tests/tests/nsec-glue-at-delegation/expected_result.nsec3-optout
regression-tests/tests/nsec-glue/expected_result
regression-tests/tests/nsec-glue/expected_result.narrow
regression-tests/tests/nsec-glue/expected_result.nsec3
regression-tests/tests/nsec-middle/expected_result
regression-tests/tests/nsec-middle/expected_result.narrow
regression-tests/tests/nsec-middle/expected_result.nsec3
regression-tests/tests/nsec-upcase/expected_result
regression-tests/tests/nsec-upcase/expected_result.narrow
regression-tests/tests/nsec-upcase/expected_result.nsec3
regression-tests/tests/nsec-wildcard/expected_result
regression-tests/tests/nsec-wildcard/expected_result.narrow
regression-tests/tests/nsec-wildcard/expected_result.nsec3
regression-tests/tests/nsec-wraparound/expected_result
regression-tests/tests/nsec-wraparound/expected_result.nsec3
regression-tests/tests/nsec-wrong-type-at-apex/expected_result
regression-tests/tests/nsec-wrong-type-at-apex/expected_result.narrow
regression-tests/tests/nsec-wrong-type-at-apex/expected_result.nsec3
regression-tests/tests/nsec-wrong-type/expected_result
regression-tests/tests/nsec-wrong-type/expected_result.narrow
regression-tests/tests/nsec-wrong-type/expected_result.nsec3
regression-tests/tests/nsec3-hash-query/expected_result
regression-tests/tests/nsec3-hash-query/expected_result.dnssec
regression-tests/tests/nsec3-hash-query/expected_result.narrow
regression-tests/tests/nsec3-hash-query/expected_result.nsec3
regression-tests/tests/nsecx-mode2-wildcard-nodata/expected_result
regression-tests/tests/nsecx-mode2-wildcard-nodata/expected_result.narrow
regression-tests/tests/nsecx-mode2-wildcard-nodata/expected_result.nsec3
regression-tests/tests/nsecx-mode3-wildcard/expected_result
regression-tests/tests/nsecx-mode3-wildcard/expected_result.narrow
regression-tests/tests/nsecx-mode3-wildcard/expected_result.nsec3
regression-tests/tests/nsecx-upcase/expected_result
regression-tests/tests/nsecx-upcase/expected_result.narrow
regression-tests/tests/nsecx-upcase/expected_result.nsec3
regression-tests/tests/nxdomain-below-nonempty-terminal/expected_result
regression-tests/tests/nxdomain-below-nonempty-terminal/expected_result.narrow
regression-tests/tests/nxdomain-below-nonempty-terminal/expected_result.nsec3
regression-tests/tests/nxdomain-for-unknown-record/expected_result
regression-tests/tests/obscured-wildcard/expected_result
regression-tests/tests/one-step-cname-resolution/expected_result
regression-tests/tests/out-of-bailiwick-referral/expected_result
regression-tests/tests/pretty-big-packet/expected_result
regression-tests/tests/publishing-cds-cdnskey/expected_result
regression-tests/tests/root-cname/expected_result
regression-tests/tests/root-mx/expected_result
regression-tests/tests/root-ns/expected_result
regression-tests/tests/root-srv/expected_result
regression-tests/tests/rp/expected_result
regression-tests/tests/same-level-referral-soa/expected_result
regression-tests/tests/same-level-referral/expected_result
regression-tests/tests/second-level-nxdomain/expected_result
regression-tests/tests/second-level-nxdomain/expected_result.narrow
regression-tests/tests/second-level-nxdomain/expected_result.nsec3
regression-tests/tests/secure-cname-to-insecure-child/expected_result
regression-tests/tests/secure-cname-to-insecure-child/expected_result.dnssec
regression-tests/tests/secure-cname-to-insecure/expected_result
regression-tests/tests/secure-cname-to-insecure/expected_result.dnssec
regression-tests/tests/secure-delegation-ds-ns/expected_result
regression-tests/tests/secure-delegation-ds-ns/expected_result.dnssec
regression-tests/tests/secure-delegation/expected_result
regression-tests/tests/secure-delegation/expected_result.dnssec
regression-tests/tests/space-name/expected_result
regression-tests/tests/space-name/expected_result.narrow
regression-tests/tests/space-name/expected_result.nsec3
regression-tests/tests/svcb-aliasmode/expected_result
regression-tests/tests/svcb-aliasmode/expected_result.dnssec
regression-tests/tests/svcb-servicemode/expected_result
regression-tests/tests/svcb-servicemode/expected_result.dnssec
regression-tests/tests/test-urc/expected_result
regression-tests/tests/two-level-nxdomain/expected_result
regression-tests/tests/two-level-nxdomain/expected_result.narrow
regression-tests/tests/two-level-nxdomain/expected_result.nsec3
regression-tests/tests/type65535-query/expected_result
regression-tests/tests/underscore-sorting/expected_result
regression-tests/tests/underscore-sorting/expected_result.narrow
regression-tests/tests/underscore-sorting/expected_result.nsec3
regression-tests/tests/uppercase-nsec/expected_result
regression-tests/tests/uppercase-nsec/expected_result.narrow
regression-tests/tests/uppercase-nsec/expected_result.nsec3
regression-tests/tests/very-long-txt/expected_result
regression-tests/tests/wildcard-overlaps-delegation/expected_result
regression-tests/tests/wrong-type-wildcard/expected_result
regression-tests/timestamp [deleted file]
regression-tests/tkey.py
regression-tests/zones/catalog.invalid [new file with mode: 0644]
regression-tests/zones/dnssec-parent.com
regression-tests/zones/example.com
regression-tests/zones/test.com
regression-tests/zones/test.dyndns.orig
tasks.py

index 55f387ea77c30cf6e177af9d6c07b9dd7aaeb823..f457898068783c4152266ec096315f7e930e1541 100644 (file)
@@ -53,22 +53,6 @@ commands:
             apt-get update
             apt-get -qq -t buster-backports --no-install-recommends install clang-8 llvm-8
 
-  install-coverity-tools:
-    description: Install the coverity tools to /usr/local
-    steps:
-      - run:
-          name: Install Coverity tools
-          command: curl -s https://scan.coverity.com/download/linux64 --data "token=${COVERITY_TOKEN}&project=${COVERITY_PROJECT}" |  gunzip | tar xvf /dev/stdin --strip-components=1 --no-same-owner -C /usr/local
-
-  add-docs-upload-ssh:
-    description: Add ssh known_hosts fingerprints
-    steps:
-      - run:
-          command: mkdir -p $HOME/.ssh && echo "${DOCS_HOST} ${DOCS_FINGERPRINT}" > $HOME/.ssh/known_hosts
-      - add_ssh_keys:
-          fingerprints:
-            - "3e:0a:aa:2c:30:69:89:f3:eb:17:c1:3f:3b:78:40:7a"
-
   # FIXME: the build-essential wart below is misformatted intentionally to remind us to replace the remotebackend testing deps with Debian packages
   auth-regress-setup:
     description: Prepare the environment for auth regression tests
@@ -356,142 +340,6 @@ commands:
             - ccache-cache-{{ arch }}-<< parameters.product >>-{{ .Branch }}
             - ccache-cache-{{ arch }}-<< parameters.product >>-
 
-  install-doc-deps:
-    description: Install dependencies needed to build the documentation
-    steps:
-      - run:
-          name: Install dependencies
-          command: |
-            apt-get update && apt-get -qq -y install \
-            autoconf \
-            automake \
-            bison \
-            curl \
-            flex \
-            g++ \
-            git \
-            latexmk \
-            libboost-all-dev \
-            libedit-dev \
-            libluajit-5.1-dev \
-            libssl-dev \
-            make \
-            pkg-config \
-            ragel \
-            rsync \
-            python3-venv
-            if [ "${CIRCLE_PROJECT_USERNAME}" = "PowerDNS" -a "${CIRCLE_PROJECT_REPONAME}" = "pdns" -a "${CIRCLE_BRANCH}" = "master" ]; then
-              apt-get update && apt-get -qq -y install \
-                texlive-full
-            fi
-
-  build-auth-docs:
-    description: Build documentation
-    steps:
-      - run:
-          name: autoconf
-          command: |
-            BUILDER_VERSION=0.0.0-git1 autoreconf -vfi
-      - run:
-          name: configure
-          command: |
-            ./configure \
-            --enable-option-checking=fatal \
-            --disable-lua-records \
-            --disable-unit-tests \
-            --without-dynmodules \
-            --without-modules
-      - run:
-          name: build docs
-          command: |
-              make -C docs html-docs
-              if [ "${CIRCLE_PROJECT_USERNAME}" = "PowerDNS" -a "${CIRCLE_PROJECT_REPONAME}" = "pdns" -a "${CIRCLE_BRANCH}" = "master" ]; then
-                make -C docs all-docs
-              fi
-
-  upload-auth-docs:
-    steps:
-      - run:
-          name: Upload documents
-          command: |
-            if [ "${CIRCLE_PROJECT_USERNAME}" = "PowerDNS" -a "${CIRCLE_PROJECT_REPONAME}" = "pdns" -a "${CIRCLE_BRANCH}" = "master" ]; then
-              rsync -crv --delete --no-p --chmod=g=rwX --exclude '*~' ./docs/html-docs/ docs_powerdns_com@${DOCS_HOST}:/authoritative/
-              rsync -crv --no-p --chmod=g=rwX --exclude '*~' ./docs/html-docs.tar.bz2 docs_powerdns_com@${DOCS_HOST}:/authoritative/
-              rsync -crv --no-p --chmod=g=rwX --exclude '*~' ./docs/PowerDNS-Authoritative.pdf docs_powerdns_com@${DOCS_HOST}:/authoritative/
-            fi
-
-  build-recursor-docs:
-    description: Build Recursor documentation
-    steps:
-      - run:
-          name: autoconf
-          command: |
-            BUILDER_VERSION=0.0.0-git1 autoreconf -vfi
-          working_directory: ~/project/pdns/recursordist
-      - run:
-          name: configure
-          command: |
-            ./configure \
-              --enable-option-checking=fatal \
-              --disable-unit-tests
-          working_directory: ~/project/pdns/recursordist
-      - run:
-          name: build docs
-          command: |
-            make html-docs
-            if [ "${CIRCLE_PROJECT_USERNAME}" = "PowerDNS" -a "${CIRCLE_PROJECT_REPONAME}" = "pdns" -a "${CIRCLE_BRANCH}" = "master" ]; then
-              make all-docs
-            fi
-          working_directory: ~/project/pdns/recursordist
-
-  upload-recursor-docs:
-    steps:
-      - run:
-          name: Upload documents
-          working_directory: ~/project/pdns/recursordist
-          command: |
-            if [ "${CIRCLE_PROJECT_USERNAME}" = "PowerDNS" -a "${CIRCLE_PROJECT_REPONAME}" = "pdns" -a "${CIRCLE_BRANCH}" = "master" ]; then
-              rsync -crv --delete --no-p --chmod=g=rwX --exclude '*~' html-docs/ docs_powerdns_com@${DOCS_HOST}:/recursor/
-              rsync -crv --no-p --chmod=g=rwX --exclude '*~' html-docs.tar.bz2 docs_powerdns_com@${DOCS_HOST}:/recursor/
-              rsync -crv --no-p --chmod=g=rwX --exclude '*~' PowerDNS-Recursor.pdf docs_powerdns_com@${DOCS_HOST}:/recursor/
-            fi
-
-  build-dnsdist-docs:
-    description: Build dnsdist documentation
-    steps:
-      - run:
-          name: autoconf
-          command: |
-            BUILDER_VERSION=0.0.0-git1 autoreconf -vfi
-          working_directory: ~/project/pdns/dnsdistdist
-      - run:
-          name: configure
-          command: |
-            ./configure \
-              --enable-option-checking=fatal \
-              --disable-unit-tests
-          working_directory: ~/project/pdns/dnsdistdist
-      - run:
-          name: build docs
-          command: |
-            make html-docs
-            if [ "${CIRCLE_PROJECT_USERNAME}" = "PowerDNS" -a "${CIRCLE_PROJECT_REPONAME}" = "pdns" -a "${CIRCLE_BRANCH}" = "master" ]; then
-              make all-docs
-            fi
-          working_directory: ~/project/pdns/dnsdistdist
-
-  upload-dnsdist-docs:
-    steps:
-      - run:
-          name: Upload documents
-          working_directory: ~/project/pdns/dnsdistdist
-          command: |
-            if [ "${CIRCLE_PROJECT_USERNAME}" = "PowerDNS" -a "${CIRCLE_PROJECT_REPONAME}" = "pdns" -a "${CIRCLE_BRANCH}" = "master" ]; then
-              rsync -crv --delete --no-p --chmod=g=rwX --exclude '*~' html-docs/ dnsdist_org@${DOCS_HOST}:
-              rsync -crv --no-p --chmod=g=rwX --exclude '*~' html-docs.tar.bz2 dnsdist_org@${DOCS_HOST}:
-              rsync -crv --no-p --chmod=g=rwX --exclude '*~' dnsdist.pdf dnsdist_org@${DOCS_HOST}:
-            fi
-
 jobs:
   checkout:
     resource_class: small
@@ -563,109 +411,6 @@ jobs:
           paths:
             - pdns-auth
 
-  test-auth-regress-odbc-sqlite3:
-    resource_class: small
-
-    docker:
-      - image: debian:buster
-        auth:
-          username: powerdnsreadonly
-          password: $DOCKERHUB_PASSWORD
-        environment:
-          UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1'
-          ASAN_OPTIONS: detect_leaks=0
-    steps:
-      - auth-regress-setup
-      - run:
-          name: Configure ODBC for sqlite
-          command: |
-            cat >> ~/.odbc.ini \<<- __EOF__
-            [pdns-sqlite3-1]
-            Driver = SQLite3
-            Database = ${PWD}/regression-tests/pdns.sqlite3
-            [pdns-sqlite3-2]
-            Driver = SQLite3
-            Database = ${PWD}/regression-tests/pdns.sqlite32
-            __EOF__
-      - run:
-          name: Install ODBC deps
-          command: |
-            apt-get install -qq -y \
-            unixodbc \
-            libsqliteodbc
-      - run:
-          name: Set up sqlite3 odbc testing
-          command: echo 'export GODBC_SQLITE3_DSN=pdns-sqlite3-1' > ./vars
-          workdir: ~/project/regression-tests
-      - auth-regress:
-          context: godbc_sqlite3-nsec3
-          doroot: false # Broken at the moment
-
-  test-auth-regress-odbc-mssql:
-    docker:
-      - image: debian:buster
-        auth:
-          username: powerdnsreadonly
-          password: $DOCKERHUB_PASSWORD
-        environment:
-          UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1'
-          ASAN_OPTIONS: detect_leaks=0
-      - image: mcr.microsoft.com/mssql/server:2017-GA-ubuntu
-        environment:
-          - ACCEPT_EULA: Y
-          - SA_PASSWORD: 'SAsa12%%'
-    steps:
-      - auth-regress-setup
-      - run:
-          name: Install ODBC deps
-          command: |
-            apt-get install -qq -y \
-            freetds-bin \
-            tdsodbc \
-            unixodbc
-      - run:
-          name: set up mssql odbc
-          command: |
-            cat >> ~/.odbc.ini \<<- __EOF__
-            [pdns-mssql-docker]
-            Driver=FreeTDS
-            Trace=No
-            Server=127.0.0.1
-            Port=1433
-            Database=pdns
-            TDS_Version=7.1
-            [pdns-mssql-docker-nodb]
-            Driver=FreeTDS
-            Trace=No
-            Server=127.0.0.1
-            Port=1433
-            TDS_Version=7.1
-            __EOF__
-      - run:
-          command: cat /usr/share/tdsodbc/odbcinst.ini <(echo Threading=1) >> /etc/odbcinst.ini
-      - run:
-          name: create database
-          command: echo 'create database pdns' | isql -v pdns-mssql-docker-nodb sa SAsa12%%
-      - run:
-          name: Set up mssql odbc testing
-          command: echo 'export GODBC_MSSQL_PASSWORD=SAsa12%% GODBC_MSSQL_USERNAME=sa GODBC_MSSQL_DSN=pdns-mssql-docker' > ./vars
-          workdir: ~/project/regression-tests
-      - auth-regress:
-          context: godbc_mssql-nodnssec
-          skip: 8bit-txt-unescaped
-      - auth-regress:
-          context: godbc_mssql
-          skip: 8bit-txt-unescaped
-      - auth-regress:
-          context: godbc_mssql-nsec3
-          skip: 8bit-txt-unescaped
-      - auth-regress:
-          context: godbc_mssql-nsec3-optout
-          skip: 8bit-txt-unescaped
-      - auth-regress:
-          context: godbc_mssql-nsec3-narrow
-          skip: 8bit-txt-unescaped
-
   test-auth-regress-bind:
     resource_class: small
 
@@ -704,361 +449,8 @@ jobs:
       - auth-regress:
           context: bind-hybrid-nsec3
 
-  test-auth-regress-ldap:
-    resource_class: small
-
-    docker:
-      - image: debian:buster
-        auth:
-          username: powerdnsreadonly
-          password: $DOCKERHUB_PASSWORD
-        environment:
-          LDAPHOST: ldap://ldapserver/
-          UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1'
-          ASAN_OPTIONS: detect_leaks=0
-      - image: powerdns/ldap-regress:1.2.4-1 # OpenLDAP 2.4.47
-        auth:
-          username: powerdnsreadonly
-          password: $DOCKERHUB_PASSWORD
-        name: ldapserver
-        command: '--loglevel debug'
-        environment:
-          LDAP_LOG_LEVEL: 0
-    steps:
-      - auth-regress-setup
-      - run: DEBIAN_FRONTEND=noninteractive apt-get install -qq -y ldap-utils
-      - auth-regress:
-          context: ldap-tree
-          doroot: false
-      - auth-regress:
-          context: ldap-simple
-          doroot: false
-      - auth-regress:
-          context: ldap-strict
-          doroot: false
-
-  test-auth-regress-geoip:
-    resource_class: small
-
-    docker:
-      - image: debian:buster
-        auth:
-          username: powerdnsreadonly
-          password: $DOCKERHUB_PASSWORD
-        environment:
-          UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1'
-          ASAN_OPTIONS: detect_leaks=0
-    steps:
-      - auth-regress-setup
-      - run: export geoipdatabase=../modules/geoipbackend/regression-tests/GeoLiteCity.mmdb
-      - auth-regress:
-          context: geoip
-          doroot: false
-
-  build-auth-docs:
-    resource_class: small
-
-    docker:
-      - image: texlive/texlive
-        auth:
-          username: powerdnsreadonly
-          password: $DOCKERHUB_PASSWORD
-    steps:
-      - get-workspace
-      - install-doc-deps
-      - build-auth-docs
-
-  deploy-auth-docs:
-    resource_class: small
-
-    docker:
-      - image: texlive/texlive
-        auth:
-          username: powerdnsreadonly
-          password: $DOCKERHUB_PASSWORD
-    steps:
-      - get-workspace
-      - install-doc-deps
-      - build-auth-docs
-      - add-docs-upload-ssh
-      - upload-auth-docs
-
-  build-recursor-docs:
-    resource_class: small
-
-    docker:
-      - image: texlive/texlive
-        auth:
-          username: powerdnsreadonly
-          password: $DOCKERHUB_PASSWORD
-    steps:
-      - get-workspace
-      - install-doc-deps
-      - build-recursor-docs
-
-  deploy-recursor-docs:
-    resource_class: small
-
-    docker:
-      - image: texlive/texlive
-        auth:
-          username: powerdnsreadonly
-          password: $DOCKERHUB_PASSWORD
-    steps:
-      - get-workspace
-      - install-doc-deps
-      - build-recursor-docs
-      - add-docs-upload-ssh
-      - upload-recursor-docs
-
-  build-dnsdist-docs:
-    resource_class: small
-
-    docker:
-      - image: texlive/texlive
-        auth:
-          username: powerdnsreadonly
-          password: $DOCKERHUB_PASSWORD
-    steps:
-      - get-workspace
-      - install-doc-deps
-      - build-dnsdist-docs
-
-  deploy-dnsdist-docs:
-    resource_class: small
-
-    docker:
-      - image: texlive/texlive
-        auth:
-          username: powerdnsreadonly
-          password: $DOCKERHUB_PASSWORD
-    steps:
-      - get-workspace
-      - install-doc-deps
-      - build-dnsdist-docs
-      - add-docs-upload-ssh
-      - upload-dnsdist-docs
-
-  coverity-auth:
-    docker:
-      - image: debian:buster
-        auth:
-          username: powerdnsreadonly
-          password: $DOCKERHUB_PASSWORD
-    steps:
-      - install-auth-dev-deps
-      - install-coverity-tools
-      - checkout-shallow
-      - run:
-          name: autoconf
-          working_directory: /opt/project/
-          command: BUILDER_VERSION=0.0.0-git1 autoreconf -vfi
-      - run:
-          name: configure
-          working_directory: /opt/project/
-          command: |
-            CFLAGS="-O1 -Werror=vla -Wformat=2 -Werror=format-security" \
-            CXXFLAGS="-O1 -Werror=vla -Wformat=2 -Werror=format-security -Wp,-D_GLIBCXX_ASSERTIONS" \
-            ./configure \
-              --enable-option-checking=fatal \
-              --disable-systemd \
-              --with-modules='bind lmdb ldap gmysql gsqlite3 gpgsql godbc tinydns' \
-              --enable-tools \
-              --with-lmdb=/usr \
-              --with-libsodium \
-              --prefix=/opt/pdns-auth
-      - run:
-          name: build
-          working_directory: /opt/project/
-          command: /usr/local/bin/cov-build --dir cov-int make -j2 -k
-      - run:
-          name: Create Coverity tarball
-          working_directory: /opt/project/
-          command: tar caf auth.tar.bz2 cov-int
-      - run:
-          name: Upload tarball to coverity
-          working_directory: /opt/project/
-          command: |
-            curl --form token=${COVERITY_TOKEN} \
-            --form email="${COVERITY_EMAIL}" \
-            --form file=@auth.tar.bz2 \
-            --form version="$(./builder-support/gen-version)" \
-            --form description="master build" \
-            https://scan.coverity.com/builds?project=${COVERITY_PROJECT}
-
-  coverity-dnsdist:
-    docker:
-      - image: debian:buster
-        auth:
-          username: powerdnsreadonly
-          password: $DOCKERHUB_PASSWORD
-    steps:
-      - run:
-          name: Install dependencies
-          command: |
-            apt-get update && apt-get -qq --no-install-recommends install \
-            autoconf \
-            automake \
-            bison \
-            bzip2 \
-            ca-certificates \
-            curl \
-            flex \
-            g++ \
-            git \
-            libboost-all-dev \
-            libcap-dev \
-            libcdb-dev \
-            libedit-dev \
-            libfstrm-dev \
-            libgnutls28-dev \
-            liblmdb-dev \
-            libluajit-5.1-dev \
-            libnghttp2-dev \
-            libre2-dev \
-            libsnmp-dev \
-            libsodium-dev \
-            libssl-dev \
-            libsystemd-dev \
-            libtool \
-            make \
-            pkg-config \
-            ragel \
-            python3-venv
-      - install-coverity-tools
-      - checkout-shallow
-      - run:
-          name: autoconf
-          command: BUILDER_VERSION=0.0.0-git1 autoreconf -vfi
-          working_directory: /opt/project/pdns/dnsdistdist
-      - run:
-          name: configure
-          command: |
-            CFLAGS="-O1 -Werror=vla -Werror=shadow -Wformat=2 -Werror=format-security" \
-            CXXFLAGS="-O1 -Werror=vla -Werror=shadow -Wformat=2 -Werror=format-security -Wp,-D_GLIBCXX_ASSERTIONS" \
-            ./configure \
-            --enable-option-checking=fatal \
-            --disable-systemd \
-            --disable-unit-tests \
-            --enable-dnstap \
-            --enable-dnscrypt \
-            --enable-dns-over-tls \
-            --prefix=/opt/dnsdist \
-            --with-gnutls \
-            --with-libsodium \
-            --with-lua=luajit \
-            --with-libcap \
-            --with-nghttp2 \
-            --with-re2
-          working_directory: /opt/project/pdns/dnsdistdist
-      - run:
-          name: build
-          command: /usr/local/bin/cov-build --dir cov-int make -j2 -k
-          working_directory: /opt/project/pdns/dnsdistdist
-      - run:
-          name: Create Coverity tarball
-          command: tar caf dnsdist.tar.bz2 cov-int
-          working_directory: /opt/project/pdns/dnsdistdist
-      - run:
-          name: Upload tarball to coverity
-          working_directory: /opt/project/
-          command: |
-            curl --form token=${COVERITY_TOKEN} \
-            --form email="${COVERITY_EMAIL}" \
-            --form file=@pdns/dnsdistdist/dnsdist.tar.bz2 \
-            --form version="$(./builder-support/gen-version)" \
-            --form description="master build" \
-            https://scan.coverity.com/builds?project=${COVERITY_PROJECT}
-
-  coverity-recursor:
-    docker:
-      - image: debian:buster
-        auth:
-          username: powerdnsreadonly
-          password: $DOCKERHUB_PASSWORD
-    steps:
-      - run:
-          name: Install dependencies
-          command: |
-            apt-get update && apt-get -qq --no-install-recommends install \
-            autoconf \
-            automake \
-            ca-certificates \
-            curl \
-            bison \
-            bzip2 \
-            flex \
-            g++ \
-            git \
-            libboost-all-dev \
-            libcap-dev \
-            libluajit-5.1-dev \
-            libfstrm-dev \
-            libsnmp-dev \
-            libsodium-dev \
-            libssl-dev \
-            libsystemd-dev \
-            libtool \
-            make \
-            pkg-config \
-            ragel \
-            python3-venv
-      - install-coverity-tools
-      - checkout-shallow
-      - run:
-          name: autoconf
-          command: BUILDER_VERSION=0.0.0-git1 autoreconf -vfi
-          working_directory: /opt/project/pdns/recursordist
-      - run:
-          name: configure
-          command: |
-            CFLAGS="-O1 -Werror=vla -Wformat=2 -Werror=format-security" \
-            CXXFLAGS="-O1 -Werror=vla -Wformat=2 -Werror=format-security -Wp,-D_GLIBCXX_ASSERTIONS" \
-            ./configure \
-            --enable-option-checking=fatal \
-            --disable-systemd \
-            --disable-unit-tests \
-            --prefix=/opt/pdns-recursor \
-            --with-libsodium \
-            --with-lua=luajit \
-            --with-libcap \
-            --with-net-snmp
-          working_directory: /opt/project/pdns/recursordist
-      - run:
-          name: build
-          command: /usr/local/bin/cov-build --dir cov-int make -j2 -k
-          working_directory: /opt/project/pdns/recursordist
-      - run:
-          name: Create Coverity tarball
-          command: tar caf recursor.tar.bz2 cov-int
-          working_directory: /opt/project/pdns/recursordist
-      - run:
-          name: Upload tarball to coverity
-          working_directory: /opt/project/
-          command: |
-            curl --form token=${COVERITY_TOKEN} \
-            --form email="${COVERITY_EMAIL}" \
-            --form file=@pdns/recursordist/recursor.tar.bz2 \
-            --form version="$(./builder-support/gen-version)" \
-            --form description="master build" \
-            https://scan.coverity.com/builds?project=${COVERITY_PROJECT}
-
 workflows:
   version: 2
-  coverity:
-    triggers:
-      - schedule:
-          cron: "0 0 * * *"
-          filters:
-            branches:
-              only: master
-    jobs:
-      - coverity-auth:
-          context: auth-coverity
-      - coverity-dnsdist:
-          context: dnsdist-coverity
-      - coverity-recursor:
-          context: recursor-coverity
 
   build-and-test-all:
     jobs:
@@ -1066,60 +458,3 @@ workflows:
       - build-auth:
           requires:
             - checkout
-      - test-auth-regress-odbc-sqlite3:
-          requires:
-            - build-auth
-      - test-auth-regress-odbc-mssql:
-          requires:
-            - build-auth
-      - test-auth-regress-geoip:
-          requires:
-            - build-auth
-      - test-auth-regress-ldap:
-          requires:
-            - build-auth
-
-  build-docs:
-    jobs:
-      - checkout
-      - build-auth-docs:
-          filters:
-            branches:
-              ignore: master
-          requires:
-            - checkout
-      - build-recursor-docs:
-          filters:
-            branches:
-              ignore: master
-          requires:
-            - checkout
-      - build-dnsdist-docs:
-          filters:
-            branches:
-              ignore: master
-          requires:
-            - checkout
-
-      # These actually deploy
-      - deploy-auth-docs:
-          context: docs
-          filters:
-            branches:
-              only: master
-          requires:
-            - checkout
-      - deploy-recursor-docs:
-          context: docs
-          filters:
-            branches:
-              only: master
-          requires:
-            - checkout
-      - deploy-dnsdist-docs:
-          context: docs
-          filters:
-            branches:
-              only: master
-          requires:
-            - checkout
index 4c3c2d9021cd351583152ecf0b2b907085111032..1b5ef2a04f198489aca7f0d3e5234e140cc81bc6 100644 (file)
@@ -1,5 +1,5 @@
 ---
-Checks:          'clang-diagnostic-*,clang-analyzer-*,bugprone-*,concurrency-*,-modernize-use-trailing-return-type'
+Checks:          'clang-diagnostic-*,clang-analyzer-*,bugprone-*,concurrency-*,-modernize-use-trailing-return-type,-readability-magic-numbers,-cppcoreguidelines-avoid-magic-numbers'
 WarningsAsErrors: ''
 HeaderFilterRegex: ''
 AnalyzeTemporaryDtors: false
index b98299dce691846ef89270b08beb1d1824a330f5..2b208a6996e64a3e5ec7fefd300b581f1f1eb433 100644 (file)
@@ -1,5 +1,5 @@
 ---
-Checks:          'clang-diagnostic-*,clang-analyzer-*,cppcoreguidelines-*,bugprone-*,concurrency-*,modernize-*,performance-*,portability-*,readability-*,-modernize-use-trailing-return-type'
+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'
 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
diff --git a/.gitattributes b/.gitattributes
new file mode 100644 (file)
index 0000000..367d91c
--- /dev/null
@@ -0,0 +1,2 @@
+# https://github.com/github/linguist/blob/master/docs/overrides.md
+*/zones/* linguist-language=DNS-Zone
index 4d29e08a932f8c84507f186749261e7f38d21e10..f6c0503588765d7e127b2336afae7e4277b46e6c 100644 (file)
@@ -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 -->
diff --git a/.github/actions/spell-check/README.md b/.github/actions/spell-check/README.md
new file mode 100644 (file)
index 0000000..8dd5e9f
--- /dev/null
@@ -0,0 +1,16 @@
+# check-spelling/check-spelling configuration
+
+File | Purpose | Format | Info
+-|-|-|-
+[allow.txt](allow.txt) | Add words to the dictionary | one word per line (only letters and `'`s allowed) | [allow](https://github.com/check-spelling/check-spelling/wiki/Configuration#allow)
+[reject.txt](reject.txt) | Remove words from the dictionary (after allow) | grep pattern matching whole dictionary words | [reject](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-reject)
+[excludes.txt](excludes.txt) | Files to ignore entirely | perl regular expression | [excludes](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-excludes)
+[only.txt](only.txt) | Only check matching files (applied after excludes) | perl regular expression | [only](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-only)
+[patterns.txt](patterns.txt) | Patterns to ignore from checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns)
+[candidate.patterns](candidate.patterns) | Patterns that might be worth adding to [patterns.txt](patterns.txt) | perl regular expression with optional comment block introductions (all matches will be suggested) | [candidates](https://github.com/check-spelling/check-spelling/wiki/Feature:-Suggest-patterns)
+[line_forbidden.patterns](line_forbidden.patterns) | Patterns to flag in checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns)
+[expect.txt](expect.txt) | Expected words that aren't in the dictionary | one word per line (sorted, alphabetically) | [expect](https://github.com/check-spelling/check-spelling/wiki/Configuration#expect)
+[advice.md](advice.md) | Supplement for GitHub comment when unrecognized words are found | GitHub Markdown | [advice](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice)
+
+Note: you can replace any of these files with a directory by the same name (minus the suffix)
+and then include multiple files inside that directory (with that suffix) to merge multiple files together.
index c83423a8ef6109ede98d9a44ecbc47c6a5d972c5..1004eeaa60443016724d29d2a8ffdc3be3c6a0b0 100644 (file)
@@ -1,7 +1,17 @@
 <!-- See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice --> <!-- markdownlint-disable MD033 MD041 -->
-<details><summary>If the flagged items do not appear to be text</summary>
+<details><summary>If the flagged items are :exploding_head: false positives</summary>
 
 If items relate to a ...
+* binary file (or some other file you wouldn't want to check at all).
+
+  Please add a file path to the `excludes.txt` file matching the containing file.
+
+  File paths are Perl 5 Regular Expressions - you can [test](
+https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files.
+
+  `^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md](
+../tree/HEAD/README.md) (on whichever branch you're using).
+
 * well-formed pattern.
 
   If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it,
@@ -12,14 +22,4 @@ https://www.regexplanet.com/advanced/perl/) yours before committing to verify it
 
   Note that patterns can't match multiline strings.
 
-* binary file.
-
-  Please add a file path to the `excludes.txt` file matching the containing file.
-
-  File paths are Perl 5 Regular Expressions - you can [test](
-https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files.
-
-  `^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md](
-../tree/HEAD/README.md) (on whichever branch you're using).
-
 </details>
index 1300aa6cff090a5155e5b01f61654e63a3a37ac5..1a47b4467f0a01e054a0c3ac0f04f75d26c72866 100644 (file)
@@ -175,6 +175,7 @@ authconfdir
 authdir
 authdirs
 authdomain
+authip
 authlog
 authname
 authoritatives
@@ -239,6 +240,7 @@ batchmode
 baz
 bbnew
 bbold
+bbr
 bbsv
 bccaf
 bdr
@@ -415,6 +417,7 @@ cfile
 cft
 cgit
 CHACHA
+charset
 chartocode
 checkbox
 checkfunc
@@ -425,9 +428,11 @@ checkrr
 checkzone
 chgrp
 childstat
-chkconfig
+chmod
+chown
 chr
 chrono
+cidr
 cin
 cinttypes
 ciphersuites
@@ -750,6 +755,8 @@ dnsdistrules
 dnsdisttcp
 dnsdisttests
 dnserrors
+DNSHEADER
+dnsheader
 DNSID
 DNSIP
 dnskeyr
@@ -823,6 +830,7 @@ dontdrop
 dontinclude
 dontqueries
 DONTWAIT
+DOQ
 doquery
 dosec
 dotests
@@ -1054,7 +1062,6 @@ ETIMEDOUT
 Eto
 etree
 etry
-eturn
 eui
 evah
 eventkey
@@ -1080,7 +1087,6 @@ exitvalue
 Expat
 expectedlen
 expf
-explicitely
 expr
 expungebyname
 expungebynameandtype
@@ -1819,6 +1825,7 @@ localdata
 localname
 localsock
 localstatedir
+localwho
 loctext
 locwild
 logaction
@@ -2305,6 +2312,8 @@ Nro
 nrr
 nsa
 nscount
+NSD
+nsd
 nsdname
 nsdomain
 nseconds
@@ -2541,7 +2550,6 @@ PBpq
 pbtag
 pcall
 PCDNSSEC
-PCKS
 PCmissing
 pcomp
 pcount
@@ -2656,6 +2664,8 @@ posix
 postdata
 POSTFIELDS
 POSTFIELDSIZE
+postgre
+Postgre
 postoutquery
 postpol
 postprepare
@@ -2663,7 +2673,7 @@ postqueries
 postun
 postvars
 potentialsupermasters
-powerdn
+powerdns
 poweroften
 powertools
 pparent
@@ -2879,6 +2889,7 @@ realpath
 realqps
 realreferral
 realrr
+realtime
 realzone
 RECCONTROL
 reccount
@@ -3015,6 +3026,7 @@ rmdir
 rmtree
 RMz
 rnameservers
+rng
 rnow
 rollbackmarker
 romap
@@ -3290,11 +3302,10 @@ smokeyjoe
 smp
 smt
 smtarg
+smtp
 SMTs
 smvmlo
-SMy
 smysql
-Smz
 sname
 snaplen
 snd
@@ -3325,7 +3336,6 @@ sockname
 sockowner
 socktype
 sodbc
-sodcrypto
 sodiumsigners
 sokolov
 somedata
@@ -3380,6 +3390,7 @@ sscanf
 SSetsockopt
 ssh
 ssize
+ssl
 sslctx
 SSLECDSADNS
 SSLEDDSADNS
@@ -3387,7 +3398,6 @@ SSLRSADNS
 sslsock
 SSLTLS
 SSLTLSIO
-SSLv
 SSocket
 ssql
 ssqlite
@@ -3538,7 +3548,7 @@ tcpavgqueriesperconnection
 tcpbench
 tcpbytesanswered
 tcpclient
-tcpclientimeouts
+tcpclienttimeouts
 tcpclientthreads
 tcpcurrentconnections
 tcpdiedreaddingresponse
@@ -3648,6 +3658,8 @@ timersonly
 Timespan
 timespec
 timespent
+timezone
+timezones
 tinfo
 tini
 TINYDNSDATA
@@ -3728,7 +3740,6 @@ totremove
 tottime
 toupper
 toxml
-toysdig
 tozero
 TPL
 tpl
@@ -3826,7 +3837,6 @@ uhi
 uhry
 uintptr
 uio
-uitoa
 ulddquehrj
 ulen
 ulong
@@ -3846,6 +3856,9 @@ UNDOC
 unescape
 unhexlify
 uninstall
+unicode
+Unicode
+UNICODE
 uniq
 uniquw
 UNIREGISTRYMARKET
@@ -3873,6 +3886,7 @@ uppercasing
 upq
 upto
 urc
+url
 urljoin
 urllib
 urlmap
@@ -3882,8 +3896,10 @@ uroot
 useconds
 uselessdrc
 useradd
+useragent
 userdata
 userfriendly
+username
 usermsec
 userperc
 USHRT
@@ -3993,6 +4009,8 @@ WEBPORT
 webrick
 webserv
 webserveropts
+website
+websites
 wednserrors
 weekno
 weirdtxt
@@ -4042,6 +4060,7 @@ wshash
 WSIZE
 WTERMSIG
 wtest
+www
 wwwds
 wwwezdnsit
 wwwpowerdnscom
@@ -4062,10 +4081,14 @@ xffverylongstring
 XFRd
 XFRM
 xfrserver
+xhtml
+XHTML
 xhr
 xit
 xlabel
 xluajit
+xml
+XML
 XMy
 xno
 Xof
diff --git a/.github/actions/spell-check/candidate.patterns b/.github/actions/spell-check/candidate.patterns
new file mode 100644 (file)
index 0000000..7325ad6
--- /dev/null
@@ -0,0 +1,645 @@
+# marker to ignore all code on line
+^.*/\* #no-spell-check-line \*/.*$
+# 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}\.\.[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 url in quotes
+([`'"])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,}
+
+# magnet urls
+magnet:[?=:\w]+
+
+# magnet urls
+"magnet:[^"]+"
+
+# obs:
+"obs:[^"]*"
+
+# The `\b` here means a break, it's the fancy way to handle urls, but it makes things harder to read
+# In this examples content, I'm using a number of different ways to match things to show various approaches
+# asciinema
+\basciinema\.org/a/[0-9a-zA-Z]+
+
+# asciinema v2
+^\[\d+\.\d+, "[io]", ".*"\]$
+
+# apple
+\bdeveloper\.apple\.com/[-\w?=/]+
+# Apple music
+\bembed\.music\.apple\.com/fr/playlist/usr-share/[-\w.]+
+
+# appveyor api
+\bci\.appveyor\.com/api/projects/status/[0-9a-z]+
+# appveyor project
+\bci\.appveyor\.com/project/(?:[^/\s"]*/){2}builds?/\d+/job/[0-9a-z]+
+
+# Amazon
+
+# Amazon
+\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|)
+# AWS S3
+\b\w*\.s3[^.]*\.amazonaws\.com/[-\w/&#%_?:=]*
+# AWS execute-api
+\b[0-9a-z]{10}\.execute-api\.[-0-9a-z]+\.amazonaws\.com\b
+# AWS ELB
+\b\w+\.[-0-9a-z]+\.elb\.amazonaws\.com\b
+# AWS SNS
+\bsns\.[-0-9a-z]+.amazonaws\.com/[-\w/&#%_?:=]*
+# AWS VPC
+vpc-\w+
+
+# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there
+# YouTube url
+\b(?:(?:www\.|)youtube\.com|youtu.be)/(?:channel/|embed/|user/|playlist\?list=|watch\?v=|v/|)[-a-zA-Z0-9?&=_%]*
+# YouTube music
+\bmusic\.youtube\.com/youtubei/v1/browse(?:[?&]\w+=[-a-zA-Z0-9?&=_]*)
+# YouTube tag
+<\s*youtube\s+id=['"][-a-zA-Z0-9?_]*['"]
+# YouTube image
+\bimg\.youtube\.com/vi/[-a-zA-Z0-9?&=_]*
+# Google Accounts
+\baccounts.google.com/[-_/?=.:;+%&0-9a-zA-Z]*
+# Google Analytics
+\bgoogle-analytics\.com/collect.[-0-9a-zA-Z?%=&_.~]*
+# Google APIs
+\bgoogleapis\.(?:com|dev)/[a-z]+/(?:v\d+/|)[a-z]+/[-@:./?=\w+|&]+
+# Google Storage
+\b[-a-zA-Z0-9.]*\bstorage\d*\.googleapis\.com(?:/\S*|)
+# Google Calendar
+\bcalendar\.google\.com/calendar(?:/u/\d+|)/embed\?src=[@./?=\w&%]+
+\w+\@group\.calendar\.google\.com\b
+# Google DataStudio
+\bdatastudio\.google\.com/(?:(?:c/|)u/\d+/|)(?:embed/|)(?:open|reporting|datasources|s)/[-0-9a-zA-Z]+(?:/page/[-0-9a-zA-Z]+|)
+# The leading `/` here is as opposed to the `\b` above
+# ... a short way to match `https://` or `http://` since most urls have one of those prefixes
+# Google Docs
+/docs\.google\.com/[a-z]+/(?:ccc\?key=\w+|(?:u/\d+|d/(?:e/|)[0-9a-zA-Z_-]+/)?(?:edit\?[-\w=#.]*|/\?[\w=&]*|))
+# Google Drive
+\bdrive\.google\.com/(?:file/d/|open)[-0-9a-zA-Z_?=]*
+# Google Groups
+\bgroups\.google\.com(?:/[a-z]+/(?:#!|)[^/\s"]+)*
+# Google Maps
+\bmaps\.google\.com/maps\?[\w&;=]*
+# Google themes
+themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+.
+# Google CDN
+\bclients2\.google(?:usercontent|)\.com[-0-9a-zA-Z/.]*
+# Goo.gl
+/goo\.gl/[a-zA-Z0-9]+
+# Google Chrome Store
+\bchrome\.google\.com/webstore/detail/[-\w]*(?:/\w*|)
+# Google Books
+\bgoogle\.(?:\w{2,4})/books(?:/\w+)*\?[-\w\d=&#.]*
+# Google Fonts
+\bfonts\.(?:googleapis|gstatic)\.com/[-/?=:;+&0-9a-zA-Z]*
+# Google Forms
+\bforms\.gle/\w+
+# Google Scholar
+\bscholar\.google\.com/citations\?user=[A-Za-z0-9_]+
+# Google Colab Research Drive
+\bcolab\.research\.google\.com/drive/[-0-9a-zA-Z_?=]*
+
+# GitHub SHAs (api)
+\bapi.github\.com/repos(?:/[^/\s"]+){3}/[0-9a-f]+\b
+# GitHub SHAs (markdown)
+(?:\[`?[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
+/[-a-z0-9]+\.githubusercontent\.com/[-a-zA-Z0-9?&=_\/.]*
+# githubassets
+\bgithubassets.com/[0-9a-f]+(?:[-/\w.]+)
+# gist github
+\bgist\.github\.com/[^/\s"]+/[0-9a-f]+
+# git.io
+\bgit\.io/[0-9a-zA-Z]+
+# GitHub JSON
+"node_id": "[-a-zA-Z=;:/0-9+_]*"
+# Contributor
+\[[^\]]+\]\(https://github\.com/[^/\s"]+/?\)
+# GHSA
+GHSA(?:-[0-9a-z]{4}){3}
+
+# GitLab commit
+\bgitlab\.[^/\s"]*/\S+/\S+/commit/[0-9a-f]{7,16}#[0-9a-f]{40}\b
+# GitLab merge requests
+\bgitlab\.[^/\s"]*/\S+/\S+/-/merge_requests/\d+/diffs#[0-9a-f]{40}\b
+# GitLab uploads
+\bgitlab\.[^/\s"]*/uploads/[-a-zA-Z=;:/0-9+]*
+# GitLab commits
+\bgitlab\.[^/\s"]*/(?:[^/\s"]+/){2}commits?/[0-9a-f]+\b
+
+# 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]+
+# bitbucket repositories commits
+\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}commits?/[0-9a-f]+
+# bitbucket commits
+\bbitbucket\.org/(?:[^/\s"]+/){2}commits?/[0-9a-f]+
+
+# bit.ly
+\bbit\.ly/\w+
+
+# bitrise
+\bapp\.bitrise\.io/app/[0-9a-f]*/[\w.?=&]*
+
+# bootstrapcdn.com
+\bbootstrapcdn\.com/[-./\w]+
+
+# cdn.cloudflare.com
+\bcdnjs\.cloudflare\.com/[./\w]+
+
+# circleci
+\bcircleci\.com/gh(?:/[^/\s"]+){1,5}.[a-z]+\?[-0-9a-zA-Z=&]+
+
+# gitter
+\bgitter\.im(?:/[^/\s"]+){2}\?at=[0-9a-f]+
+
+# gravatar
+\bgravatar\.com/avatar/[0-9a-f]+
+
+# ibm
+[a-z.]*ibm\.com/[-_#=:%!?~.\\/\d\w]*
+
+# imgur
+\bimgur\.com/[^.]+
+
+# Internet Archive
+\barchive\.org/web/\d+/(?:[-\w.?,'/\\+&%$#_:]*)
+
+# discord
+/discord(?:app\.com|\.gg)/(?:invite/)?[a-zA-Z0-9]{7,}
+
+# Disqus
+\bdisqus\.com/[-\w/%.()!?&=_]*
+
+# medium link
+\blink\.medium\.com/[a-zA-Z0-9]+
+# medium
+\bmedium\.com/\@?[^/\s"]+/[-\w]+
+
+# microsoft
+\b(?:https?://|)(?:(?:download\.visualstudio|docs|msdn2?|research)\.microsoft|blogs\.msdn)\.com/[-_a-zA-Z0-9()=./%]*
+# powerbi
+\bapp\.powerbi\.com/reportEmbed/[^"' ]*
+# vs devops
+\bvisualstudio.com(?::443|)/[-\w/?=%&.]*
+# microsoft store
+\bmicrosoft\.com/store/apps/\w+
+
+# mvnrepository.com
+\bmvnrepository\.com/[-0-9a-z./]+
+
+# now.sh
+/[0-9a-z-.]+\.now\.sh\b
+
+# oracle
+\bdocs\.oracle\.com/[-0-9a-zA-Z./_?#&=]*
+
+# chromatic.com
+/\S+.chromatic.com\S*[")]
+
+# codacy
+\bapi\.codacy\.com/project/badge/Grade/[0-9a-f]+
+
+# compai
+\bcompai\.pub/v1/png/[0-9a-f]+
+
+# mailgun api
+\.api\.mailgun\.net/v3/domains/[0-9a-z]+\.mailgun.org/messages/[0-9a-zA-Z=@]*
+# mailgun
+\b[0-9a-z]+.mailgun.org
+
+# /message-id/
+/message-id/[-\w@./%]+
+
+# Reddit
+\breddit\.com/r/[/\w_]*
+
+# requestb.in
+\brequestb\.in/[0-9a-z]+
+
+# sched
+\b[a-z0-9]+\.sched\.com\b
+
+# Slack url
+slack://[a-zA-Z0-9?&=]+
+# Slack
+\bslack\.com/[-0-9a-zA-Z/_~?&=.]*
+# Slack edge
+\bslack-edge\.com/[-a-zA-Z0-9?&=%./]+
+# Slack images
+\bslack-imgs\.com/[-a-zA-Z0-9?&=%.]+
+
+# shields.io
+\bshields\.io/[-\w/%?=&.:+;,]*
+
+# stackexchange -- https://stackexchange.com/feeds/sites
+\b(?:askubuntu|serverfault|stack(?:exchange|overflow)|superuser).com/(?:questions/\w+/[-\w]+|a/)
+
+# Sentry
+[0-9a-f]{32}\@o\d+\.ingest\.sentry\.io\b
+
+# Twitter markdown
+\[\@[^[/\]:]*?\]\(https://twitter.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|)\)
+# Twitter hashtag
+\btwitter\.com/hashtag/[\w?_=&]*
+# Twitter status
+\btwitter\.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|)
+# Twitter profile images
+\btwimg\.com/profile_images/[_\w./]*
+# Twitter media
+\btwimg\.com/media/[-_\w./?=]*
+# Twitter link shortened
+\bt\.co/\w+
+
+# facebook
+\bfburl\.com/[0-9a-z_]+
+# facebook CDN
+\bfbcdn\.net/[\w/.,]*
+# facebook watch
+\bfb\.watch/[0-9A-Za-z]+
+
+# dropbox
+\bdropbox\.com/sh?/[^/\s"]+/[-0-9A-Za-z_.%?=&;]+
+
+# ipfs protocol
+ipfs://[0-9a-zA-Z]{3,}
+# ipfs url
+/ipfs/[0-9a-zA-Z]{3,}
+
+# w3
+\bw3\.org/[-0-9a-zA-Z/#.]+
+
+# loom
+\bloom\.com/embed/[0-9a-f]+
+
+# regex101
+\bregex101\.com/r/[^/\s"]+/\d+
+
+# figma
+\bfigma\.com/file(?:/[0-9a-zA-Z]+/)+
+
+# freecodecamp.org
+\bfreecodecamp\.org/[-\w/.]+
+
+# image.tmdb.org
+\bimage\.tmdb\.org/[/\w.]+
+
+# mermaid
+\bmermaid\.ink/img/[-\w]+|\bmermaid-js\.github\.io/mermaid-live-editor/#/edit/[-\w]+
+
+# 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]+
+
+# HyperKitty lists
+/archives/list/[^@/]+\@[^/\s"]*/message/[^/\s"]*/
+
+# lists
+/thread\.html/[^"\s]+
+
+# list-management
+\blist-manage\.com/subscribe(?:[?&](?:u|id)=[0-9a-f]+)+
+
+# kubectl.kubernetes.io/last-applied-configuration
+"kubectl.kubernetes.io/last-applied-configuration": ".*"
+
+# pgp
+\bgnupg\.net/pks/lookup[?&=0-9a-zA-Z]*
+
+# Spotify
+\bopen\.spotify\.com/embed/playlist/\w+
+
+# Mastodon
+\bmastodon\.[-a-z.]*/(?:media/|\@)[?&=0-9a-zA-Z_]*
+
+# scastie
+\bscastie\.scala-lang\.org/[^/]+/\w+
+
+# images.unsplash.com
+\bimages\.unsplash\.com/(?:(?:flagged|reserve)/|)[-\w./%?=%&.;]+
+
+# pastebin
+\bpastebin\.com/[\w/]+
+
+# heroku
+\b\w+\.heroku\.com/source/archive/\w+
+
+# quip
+\b\w+\.quip\.com/\w+(?:(?:#|/issues/)\w+)?
+
+# badgen.net
+\bbadgen\.net/badge/[^")\]'\s]+
+
+# statuspage.io
+\w+\.statuspage\.io\b
+
+# media.giphy.com
+\bmedia\.giphy\.com/media/[^/]+/[\w.?&=]+
+
+# tinyurl
+\btinyurl\.com/\w+
+
+# codepen
+\bcodepen\.io/[\w/]+
+
+# registry.npmjs.org
+\bregistry\.npmjs\.org/(?:@[^/"']+/|)[^/"']+/-/[-\w@.]+
+
+# getopts
+\bgetopts\s+(?:"[^"]+"|'[^']+')
+
+# ANSI color codes
+(?:\\(?:u00|x)1[Bb]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+|)m
+
+# URL escaped characters
+\%[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)
+0x[0-9a-fA-F](?:\.[0-9a-fA-F]*|)[pP]
+# Punycode
+\bxn--[-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}
+# hex runs
+\b[0-9a-fA-F]{16,}\b
+# hex in url queries
+=[0-9a-fA-F]*?(?:[A-F]{3,}|[a-f]{3,})[0-9a-fA-F]*?&
+# ssh
+(?:ssh-\S+|-nistp256) [-a-zA-Z=;:/0-9+]{12,}
+
+# PGP
+\b(?:[0-9A-F]{4} ){9}[0-9A-F]{4}\b
+# GPG keys
+\b(?:[0-9A-F]{4} ){5}(?: [0-9A-F]{4}){5}\b
+# 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}|[iu]\d+)\b
+# integrity
+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]
+# '/"
+\\\([ad]q
+
+# .desktop mime types
+^MimeTypes?=.*$
+# .desktop localized entries
+^[A-Z][a-z]+\[[a-z]+\]=.*$
+# Localized .desktop content
+Name\[[^\]]+\]=.*
+
+# IServiceProvider / isAThing
+#\b(?:I|isA)(?=(?:[A-Z][a-z]{2,})+\b)
+
+# crypt
+(['"])\$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+]*=\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 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,})
+
+# Regular expressions for (P|p)assword
+\([A-Z]\|[a-z]\)[a-z]+
+
+# JavaScript regular expressions
+# javascript test regex
+/.*/[gim]*\.test\(
+# javascript match regex
+\.match\(/[^/\s"]*/[gim]*\s*
+# javascript match regex
+\.match\(/\\[b].*?/[gim]*\s*\)(?:;|$)
+# javascript regex
+^\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+
+
+# kubectl - pods in CrashLoopBackOff
+\w+-[0-9a-f]+-\w+\s+\d+/\d+\s+CrashLoopBackOff\s+
+
+# kubernetes object suffix
+-[0-9a-f]{10}-\w{5}\s
+
+# posthog secrets
+([`'"])phc_[^"',]+\g{-1}
+
+# xcode
+
+# xcodeproject scenes
+(?: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:
+## Operation "substitution (s///)" returns its argument for non-Unicode code point 0x1C19AE (the code point will vary).
+## You could manually change `(?i)X...` to use `[Xx]...`
+## or you could add the files to your `excludes` file (a version after 0.0.19 should identify the file path)
+# Lorem
+(?:\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]{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 (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
+
+# version suffix <word>v#
+(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))
+
+# 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
+\bset(?:\s+-[abefimouxE]{1,2})*\s+-[abefimouxE]{3,}(?:\s+-[abefimouxE]+)*
+# tar arguments
+\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
+# tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long...
+\btput\s+(?:(?:-[SV]|-T\s*\w+)\s+)*\w{3,5}\b
+# macOS temp folders
+/var/folders/\w\w/[+\w]+/(?:T|-Caches-)/
index 54d92a7cdb60a32a91697da053a3f6ef8684509f..4cb137b107db349b8ec58334df813ae43c422482 100644 (file)
@@ -2,73 +2,98 @@
 # want to include regression-tests.*/description
 (?:^|/)(?i)COPYRIGHT
 (?:^|/)(?i)LICEN[CS]E
+(?:^|/)3rdparty/
 (?:^|/)ext/
 (?:^|/)go\.mod$
 (?:^|/)go\.sum$
 (?:^|/)m4/
 (?:^|/)package(?:-lock|)\.json$
-(?:^|/)package-lock\.json$
-(?:^|/)requirements\.txt$
+(?:^|/)Pipfile$
+(?:^|/)pyproject.toml
+(?:^|/)requirements(?:-dev|-doc|-test|)\.txt$
 (?:^|/)vendor/
 /expected_result
 /test-base64_cc\.cc$
 /test-dnsrecords_cc\.cc$
 ignore$
 SUMS$
+\.a$
 \.ai$
 \.asc$
+\.all-contributorsrc$
 \.avi$
 \.bmp$
+\.bz2$
 \.cer$
 \.class$
+\.coveragerc$
 \.crl$
 \.crt$
 \.csr$
 \.dll$
+\.docx?$
+\.drawio$
 \.DS_Store$
 \.eot$
 \.eps$
 \.exe$
 \.gif$
+\.git-blame-ignore-revs$
+\.gitattributes$
+\.gitkeep$
 \.graffle$
 \.gz$
 \.icns$
 \.ico$
+\.ipynb$
 \.jar$
-\.jpeg$
-\.jpg$
+\.jks$
+\.jpe?g$
 \.keys?$
 \.lib$
 \.lock$
 \.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?$
 \.ttf$
 \.wav$
-\.woff
+\.webm$
+\.webp$
+\.woff2?$
 \.xcf$
-\.xls
+\.xlsx?$
 \.xpm$
+\.xz$
 \.yml$
 \.zip$
 ^codedocs/doxygen\.conf$
+^docs/http-api/tsigkey\.rst$
 ^docs/lua-records/reference/index\.rst$
 ^modules/remotebackend/example\.rb$
 ^modules/remotebackend/test-remotebackend-keys\.hh$
@@ -78,3 +103,7 @@ SUMS$
 ^pdns/recursordist/html/js/
 ^regression-tests\.recursor-dnssec/test_ReadTrustAnchorsFromFile\.py$
 ^\.github/actions/spell-check/
+^\.github/actions/spelling/
+^\.github/workflows/spelling\d*\.yml$
+^\Qdocs/lua-records/reference/index.rst\E$
+^\Qpdns/dnsdistdist/docs/_static/dnsdist-keyblock.asc\E$
index db0c7250cbc02d4caddc8646056e69b3a8a47dcf..8a8f9ede0f43aaa052f9e8c1ec1d553d91845742 100644 (file)
@@ -1,14 +1,7 @@
 aaaarecord
-aae
 aaldering
-ababd
-abbb
-abcde
-abi
 aborttransaction
 Abraitis
-abspath
-acl
 ACLTo
 ACPI
 activatedomainkey
@@ -17,7 +10,6 @@ addingrecords
 addprefix
 addsuffix
 Adiscon
-aef
 Afek
 afl
 afnic
@@ -30,69 +22,51 @@ Aips
 Aki
 Alenichev
 alexa
-algo
 algoroll
-aliceblue
 allocs
 Altpeter
-amd
 Anderton
 anewid
 anid
 anonymization
-Anonymize
 anotherid
 ANOTHERIPADDRESS
 anothertype
 ansible
 ANSSI
 Antoin
-anycast
-api
 apikey
+apizones
 AQAB
 ARCHFLAGS
 arecord
 arecvfrom
 Arentz
-arial
 Arjen
 Arjo
 Arnoud
 arpa
 Arsen
 aruba
-asc
 Ascio
 Asenov
 ASEP
 Ashish
-ASIN
-asnum
-aspx
 associateddomain
 asyncresolve
+ATHENE
 Atlassian
-atoi
 Atomia
 aton
-attr
 atype
 authanswers
-AUTHIP
 Authoritativedoc
 auths
 authzone
 autobuilt
 autocalculation
-autocomplete
-autoconf
-autodetect
 autodetecting
-autodetection
-autodoc
 autofilling
-autogenerated
 automagically
 automake
 Automattic
@@ -104,11 +78,9 @@ autoserial
 autotools
 axfrfilter
 Baan
-backend
 backgrounding
-backport
-Backtick
-backtraces
+backported
+backticks
 BADALG
 BADCOOKIE
 badips
@@ -118,7 +90,6 @@ BADSIG
 BADTIME
 BADTRUNC
 BADVERS
-baf
 Bakhos
 Bakker
 Baltus
@@ -128,8 +99,6 @@ Bastiaan
 bayour
 bbc
 bbd
-bdd
-bea
 bearggg
 beenthere
 bellis
@@ -140,10 +109,8 @@ Bernd
 bert
 Besselink
 bestwho
-bgcolor
 Biege
 bigbank
-bigint
 BIGSERIAL
 Bilik
 bindbackend
@@ -151,13 +118,12 @@ binddn
 BINDTODEVICE
 Binero
 binlog
+bitfield
 bitmaps
 bla
 blackhole
 Bleker
 blockfilter
-blockquote
-blog
 blogpost
 blogspot
 bmigrate
@@ -184,28 +150,21 @@ Brynjar
 Brzeski
 bsd
 Btw
-bufsize
-bugfix
-bugfixes
 bugzilla
+builddeb
 bulc
 bulletinc
 burstable
-bytestring
+byteslimit
 bzero
-bzip
 caa
 cachekey
 Cairney
 calculatesoaserial
 calidns
 Cauquil
-CBF
-Cbjr
 ccache
 ccc
-ccd
-cce
 ccounts
 cdb
 CDBKV
@@ -213,41 +172,29 @@ cdnskey
 cds
 Cegetel
 Cerb
-certs
 certusage
-cfea
-CFLAGS
-cgi
 CGNAT
-changelog
 changeme
-changeset
 changetype
-charset
 chashed
 chbruyand
-chdir
 Chiavacci
-chmod
 chopoff
-chown
 Christof
-chroot
+chrooted
 chrooting
-CIDR
-classmethod
 clientanswers
 Cloos
 closesocket
 clusions
 cmouse
-cmsg
 cmsghdr
-cname
+cmsgs
 cnamechainresolution
 CNAMEd
 CNAMEDNS
 cnamerecord
+cnames
 cnf
 cnn
 cockroachlabs
@@ -256,23 +203,19 @@ codebgcolor
 codeninja
 codetextcolor
 Colemarcus
-colgroup
 collapsiblesidebar
 colm
 comboaddress
 commandline
 committransaction
 conaxis
-config
 configfile
 configname
 configsetting
-configurability
 confs
 conntrack
 Conntracking
 Consolas
-constexpr
 controllen
 controlsocket
 coprocess
@@ -281,85 +224,65 @@ coredumps
 cornercases
 corpit
 costypetrisor
-cout
 coverity
-cpp
 cppcheck
-createdb
 createslavedomain
 Cremers
 criteo
-cron
 crowley
 crv
-cryptokey
+cryptokeys
 Cryptoki
 cryptopp
 cryptoshop
-css
 csum
-csv
 csync
-ctime
-ctor
-ctx
+Cunha
+custommetrics
 cve
 cvename
 cvs
 cvstrac
-CWD
 CXXFLAGS
 daemonizing
 daemontools
-daf
 Daganoto
 Danerklint
-dankamongmen
 Darilion
 darix
 Darron
 dataformat
-datasource
-datastore
 datatracker
 Daugaard
 Davids
 Dayneko
 dbfile
 dblfilename
-dbname
+dblookup
 dbpf
 dbr
-DBX
-dcde
-DCF
 dcobject
-dde
 ddns
-DDo
+ddos
 deactivatedomainkey
 debian
 deboynepollard
-decls
 Deduktiva
 dedup
 defcontent
 defpol
 defttl
+Dehaine
 DENIC
-deref
 descclassname
 descname
 Dessel
-dest
 destname
 Detlef
 devicename
+devonly
 devtoolset
-dfb
-dfd
 DHCID
-DHCP
 dhcpd
 dhcpdupdate
 diffs
@@ -370,19 +293,14 @@ dilinger
 Dimitrios
 Directi
 Disqus
-distro
 djbdns
-DKIM
 dlerror
 dlg
-DLLs
 dlmalloc
 DLV
-dmesg
 Dmitry
 dname
 Dnn
-dns
 dnsapi
 dnsbulktest
 dnscache
@@ -395,13 +313,12 @@ dnsdistdoc
 dnsdomain
 dnsext
 dnsgram
-dnsheader
 dnskey
-dnsmessage
 dnsname
 dnsnameset
 dnsop
 dnspacket
+dnsparser
 dnspcap
 dnsquestion
 DNSR
@@ -420,13 +337,10 @@ dnsupdate
 dnsviz
 dnswasher
 dnszone
-dnt
 Dobrawy
+docdefault
 docnamecachelookup
-documentclass
 documentwrapper
-doesnotexist
-dofile
 Dohmen
 domaininfo
 domainmetadata
@@ -434,6 +348,7 @@ domainname
 domainrelatedobject
 Donatas
 dontcare
+doq
 downsides
 downstreams
 dport
@@ -442,7 +357,6 @@ Draschl
 droprate
 dscontent
 dsrecord
-dst
 DTS
 Dufberg
 dumpluaraw
@@ -454,23 +368,15 @@ dynbpf
 dyndns
 dynhandler
 dynmodules
-eaa
 eachother
-EAGAIN
 easydns
-ebfd
 ebpf
 ebpfblocklist
 ECCN
-ecdsa
 ech
 econds
-ECONNRESET
-ecs
 ECSDA
 ecswho
-edb
-EDE
 editline
 edns
 ednsbufsiz
@@ -479,41 +385,24 @@ ednsoptionview
 ednssubnet
 EDNSTo
 edu
-eea
-eec
-EED
-efbf
-Eieb
-EINTR
 ejones
 Ekkelenkamp
 elgoog
-Emph
-endblock
+endbr
 Enden
-endian
-endif
-endl
-ENOENT
-ENOTCONN
+enp
 ent
-entrypoint
-enum
 envoutput
 epel
-epoll
-epub
 eqno
 Eriksson
 errlog
-errno
 errorlevels
 esr
 EUips
 evildomain
 examplekey
 exceedfuncs
-execfile
 Exort
 externalrefs
 extrahead
@@ -521,36 +410,22 @@ Exx
 Ezbu
 ezdns
 Faerch
-faf
 failedservers
-failover
 farsightsec
-favicon
-FBAE
-fbe
-fcbd
-fcc
-fcf
-fcff
-fcgi
 fcontext
-fda
-fdopen
-fds
 fedoraproject
 feedents
 feedrecord
 ffi
 ffipolicy
 filedescriptor
+finalised
 findclientpolicy
 Firefox
 firewalled
 firewalls
 fixednow
 Florus
-flto
-fontname
 footerbgcolor
 footertextcolor
 forfun
@@ -559,33 +434,29 @@ Fortinet
 forwardzone
 framestream
 freakshow
-freebsd
-freedesktop
 Freenet
 freesans
 freetds
 freshports
 Froemel
-frontend
+frontends
 fstrm
-fullname
 fulltoc
 fullycapable
-func
 Furnell
 Fusl
+fwzones
 FYhvws
 FZq
 gaba
 gacogne
 gatech
 Gavarret
-gcc
+Gbps
 gdpr
 Geijn
 genindex
 geobackend
-geoip
 geoipbackend
 geolocated
 Georgeto
@@ -593,7 +464,6 @@ Gergely
 Gerritsen
 Gervai
 Gerwin
-getaddrinfo
 getaddrs
 getalldomainmetadata
 getbeforeandafternamesabsolute
@@ -601,26 +471,21 @@ getcarbonhostname
 getdomaininfo
 getdomainkeys
 getdomainmetadata
-gethostname
 getifaddrs
 getlocaladdress
 getn
 getrandom
 getregisteredname
 gettag
-gettext
 gettime
-gettimeofday
 gettsigkey
 Geuze
 GFm
+Ghz
 Gibheer
 Gieben
 Gillstrom
-github
 Gkey
-glibc
-gmail
 gmake
 Gmb
 gmtime
@@ -632,8 +497,6 @@ godbc
 godbcbackend
 Godwottery
 goodmatch
-google
-googleapis
 goracle
 goraclebackend
 gouv
@@ -641,30 +504,17 @@ gpgsql
 gpgsqlbackend
 gprof
 gpsqlbackend
-GQNy
-grep
-grepping
 grepq
 GSQ
 gsql
 gsqlite
 gss
 gssapi
-gsub
 gtld
 guilabel
-Gyh
 Gyselinck
-gzip
-gzipped
-hackerone
 Hakulinen
 Hannu
-haproxy
-hardcode
-hardcoded
-hardcoding
-hardlink
 Harker
 Hausberger
 headbgcolor
@@ -673,13 +523,12 @@ headfont
 headlinkcolor
 headtextcolor
 healthcheck
+Heftrig
 Heimhilcher
 Helbekkmo
-HELO
 Hendriks
 Henk
 Hensbergen
-Heredoc
 Heuer
 hidesoadetails
 hidettl
@@ -689,71 +538,46 @@ hinfo
 hitrate
 hkraal
 hll
-hlmann
-hmac
-Hmi
 Hoentjen
 Hofstaedtler
 Holger
-homepage
 Hooimeijer
-hostmaster
-hostname
 Hotmail
+Houtworm
 howto
 hpecorp
 hpiers
-hpp
-href
-hsm
 htbp
-html
 htmlescape
 htmlhelp
-http
 httpapi
 httpdomain
 hubert
-hyperlink
 iana
 icann
-ico
 ict
 idprotect
-idx
 iers
 ietf
-ifdef
 ifportup
 ifurlextup
 ifurlup
 ihsinme
-IJajghd
-illumos
-img
 Imhard
 incbin
 includeboilerplate
 includerings
 indextable
-inet
 infolog
 infosecinstitute
-ini
-inited
 initscript
 Inno
 innodb
-inode
 installable
 internic
-interop
-interoperability
 interoperation
-iostream
 iowait
 IPbackend
-ipc
 ipcipher
 ipcom
 ipcrypt
@@ -761,16 +585,12 @@ ipdecrypt
 ipencrypt
 ipfilter
 IPSECKEY
-iptables
 iputils
-IQuery
 irc
 isane
-isc
 ismaster
 isoc
-isp
-ispell
+ISPs
 isql
 ixfr
 ixfrdist
@@ -781,42 +601,35 @@ janeczku
 Jatko
 Jaury
 Jauvin
-javascript
 Jeftovic
 Jelte
 Jermar
 Jeroen
 jessie
-Joaqu
 jonathaneen
 Jong
 Jorn
 journalctl
-journald
 jpmens
-json
 jsonstat
+jsrender
 Juergen
 jumpbox
 Juraj
-jye
 Kaminsky
 Kaseorg
-KCtsq
 Kdhcp
 Kdhcpdupdate
 Kees
-kerberos
 Kerkhof
 KEYBITS
 keyblock
 keydir
-keyfile
-keygen
 keyname
 keypair
 keypairgen
 keyroll
+keyroller
 keysearch
 keysize
 keytab
@@ -829,18 +642,19 @@ koch
 Kockum
 Kolkman
 kom
-Konqueror
 Koos
 Kovacic
-kqueue
 krb
 Krist
 Krul
 ksk
 kskroll
 kskrollcdnskey
+ktls
+KTNAME
 Kuehrer
 kvs
+kxdpgun
 Ladot
 Lafon
 Lakkas
@@ -858,24 +672,21 @@ Laurient
 Laursen
 LCUP
 LDA
-ldap
 ldapbackend
+ldaps
 ldflags
 ldif
 ldns
 Leen
 Lemoine
-len
 lessthan
 Lesuisse
 lethalgroup
 letsencrypt
 letterpaper
 libatomic
-libc
 libcrypto
 libcryptopp
-libcurl
 libdecaf
 libdir
 libedit
@@ -895,39 +706,33 @@ libressl
 librt
 libsodium
 libsofthsm
-libssl
 libsystemd
 libtdsodbc
+libxdp
 libyaml
 libzmq
+lightningstream
 Lindqvist
 linenos
 linenum
 linkcolor
 lintian
-linux
 linuxnetworks
 Lior
 listinfo
 literalinclude
-llvm
 lmdb
 lmdbbackend
 LMDBKV
 loadbalancer
 loadbalancing
 localaddr
-localhost
 localip
-localtime
 localtoc
 locaweb
 lochiiconnectivity
-logfile
-loglevel
+loglevels
 logmessage
-logrotate
-lon
 Loopia
 Lorbach
 lordievader
@@ -936,8 +741,7 @@ loweralpha
 lowerroman
 Lrhazi
 lsock
-lte
-lua
+lto
 luaaction
 luabackend
 luac
@@ -947,8 +751,8 @@ luarule
 luawrapper
 Lutter
 Luuk
-Lwz
 LYg
+Machard
 Maik
 Maikel
 MAILA
@@ -956,11 +760,9 @@ MAILB
 Majer
 Makefiles
 malcrafted
-malloc
+mallocs
 malware
 Mamane
-Mandriva
-manpage
 mapasync
 Mapbox
 mariadb
@@ -969,7 +771,6 @@ Massar
 matchtype
 Mavrommatis
 maxdepth
-MAXINT
 maxlistdepth
 maxmind
 maxqps
@@ -980,11 +781,11 @@ MBOXFW
 mbytes
 Meerwald
 Mekking
-MEMLOCK
+memlock
 Memusage
 menuselection
-metadata
 metadatabase
+metadatas
 metainformation
 metricnames
 metricscarbon
@@ -997,16 +798,11 @@ Milas
 Mimimization
 minbody
 mindex
-MINFO
 minipatch
 Mischan
-misconfigurations
-misconfigured
 mjt
 mkuchar
-mmap
 mmdb
-mname
 mnordhoff
 MOADNS
 Modderman
@@ -1022,19 +818,14 @@ mrtg
 msdcs
 MSDNS
 msphinx
-mssql
+msrv
 mtasker
 mthread
-MUar
+mtid
 Mulholland
-multiline
 multimaster
-multithreading
-mundsson
 munmap
 Muraro
-musl
-mutex
 Mwaikambo
 mxrecord
 mybackend
@@ -1048,16 +839,12 @@ mypassword
 mypgsql
 myset
 myspecialmetric
-mysql
 mysqlbackend
 mysqld
-mytsigkey
 myuser
 mywebapp
 namedroppers
-nameserver
 nameserving
-namespace
 naptr
 Nauck
 Navarrete
@@ -1075,46 +862,46 @@ Netblock
 netfilter
 netherlabs
 netinet
-netmask
 netmaskgroup
+netmasks
 netsnmp
 NETWORKMASK
 Neue
 Neuf
 newcontent
-nextval
-nghttp
-nginx
+nftables
 nic
+Niklas
+Nilsen
 nimber
 Nixu
 nkey
 nmg
-Nncqx
 NNNN
 noaction
 noad
 noall
-nocache
 nocookie
-nodata
-NODELAY
+NODCACHEDIRNOD
+NODCACHEDIRUDR
 noedns
 noerrors
 NOLOCK
 nometasync
 Nominet
+noncompliantqueries
+noncompliantresponses
 nonexist
 Nonnekes
 noout
 noping
 noport
-nosniff
+norve
 nostrip
 NOSUBDIR
 nosync
 Notaras
-NOTAUTH
+notauth
 NOTIFYs
 NOTPARALLEL
 notrack
@@ -1122,12 +909,10 @@ NOTZONE
 Novell
 nproxy
 NPTL
-nsd
 NSes
 nsid
 nsis
 nsl
-nsname
 nsrecord
 nsset
 nsspeeds
@@ -1136,24 +921,20 @@ nsupdate
 nta
 ntlworld
 Nuitari
-nullptr
 NULs
 NUMA
 numreceived
+nvd
 nxd
 NXDATA
 nxdomain
 nzlosh
 oarc
-oauth
 Obermayer
 obidos
 objectclass
-objs
 Obser
 obspm
-ocsp
-odbc
 odbcbackend
 odbcinst
 Oddy
@@ -1164,12 +945,11 @@ oftc
 OIDs
 Olafur
 Omroep
-openapi
-openbsd
+openapis
 opendbx
 openpgpkey
+openports
 opensc
-openssl
 opensuse
 openwall
 Opmeer
@@ -1177,12 +957,10 @@ OPNUM
 optcode
 Opteron
 optmem
-optout
 oraclebackend
 ordername
 orsn
 Oservers
-ostringstream
 OSX
 otherdomain
 otherpool
@@ -1199,10 +977,6 @@ packethandler
 papersize
 paramater
 PARAMKEYWORDS
-params
-passphrase
-passthrough
-passthru
 PATC
 patchlevels
 pathconfig
@@ -1212,7 +986,6 @@ Pbackend
 PCache
 pcap
 PCAPFILE
-pdf
 pdns
 pdnsbackend
 pdnscontrol
@@ -1220,45 +993,38 @@ pdnsldap
 pdnslog
 pdnsodbx
 pdnsrandom
+pdnsrec
 pdnssec
 pdnsutil
 Peeters
 Pels
-pem
 Penev
-perl
 Perroud
 Pertubation
-pez
 Pfetzing
 pgmysql
 pgmysqlbackend
 pgp
 pgpsql
-pgsql
 phishing
 phonedph
-php
+pickchashed
 pickclosest
+pickhashed
+picknamehashed
 pickrandom
+pickrandomsample
 pickwhashed
 pickwrandom
-pid
 piddir
 pidfile
-pieter
 pilindex
 Pinski
 pipebackend
 pipermail
-PIV
-pkcs
-placeholders
 Plusnet
 plzz
 pmtmr
-pnds
-png
 Poelov
 pointsize
 polarssl
@@ -1273,70 +1039,54 @@ poolers
 poolname
 portnum
 portnumber
-postgre
 postgresql
 postinst
 postresolve
-powerdns
 powerdnsrecursor
 powerdnssec
 powerldap
-powerpc
-ppc
-pragma
 Predota
 preoutquery
 Preproc
 prequery
-prereleases
 prerpz
-presigned
 presignedness
 PRId
 primetime
 princ
 prioritization
-privatekey
 privs
-PRNG
-progid
 protobuf
 protozero
 providername
+proxymapping
 proxyprotocol
 proxyprotocolvalues
 pseudonymize
 pseudorecord
-psql
 pthread
-ptr
 ptrrecord
 Publieke
 publishdomainkey
 pullreq
-pygments
 Pyry
-Qag
 qclass
 qdcount
 qdomain
 qgen
-Qkj
 qlen
 Qlim
 qname
 qperq
-qps
 QPSIP
 qpslimits
 QRate
-qsize
 qthread
 qtype
 qtypelist
 querycache
 querycount
-quickstart
+querytime
 qytpe
 ragel
 randombackend
@@ -1346,7 +1096,6 @@ randomisation
 randomises
 randomloader
 rapidjson
-raspbian
 rawaddress
 RBL
 rcode
@@ -1356,32 +1105,22 @@ rcvmmsg
 rdata
 rdqueries
 rdynamic
-readline
-README
-readonly
-realtime
 reconnections
+recordcache
 recursor
+recursord
 recursordist
 Recursordoc
 Recuweb
-recv
 recvbuf
 recverr
-recvfrom
 recvmmsg
-recvmsg
 redelegations
 redhat
 redjack
 reentrantly
-refactor
-Refactoring
-refcount
 refman
-refreh
 refuseds
-regex
 reid
 reimplementation
 Reinier
@@ -1401,17 +1140,15 @@ removedomainkey
 replacerrset
 requery
 resolv
-respawn
+respawned
 respawning
 respout
 respsizes
-Resync
 resynchronise
 retransfering
 reuseds
 reuseport
-Reuwiei
-rfc
+RFCs
 rhel
 Rietz
 rightsidebar
@@ -1420,33 +1157,27 @@ ringbuffer
 rkey
 rmem
 rname
-rng
 rocommunity
 Roel
 Rosmalen
 roundrobin
-RPATH
 rping
 rpms
 rpz
 rpzstatistics
 rrcontent
-rrd
 rrdata
 rrdtool
 rrname
 rrs
 rrset
 rrsig
-rrtype
-rsa
+RRtypes
 rsasha
-RSP
-rst
-rsync
 Rueckert
 rulesets
 runtimedir
+rustup
 Ruthensteiner
 Rvd
 rwlock
@@ -1457,20 +1188,16 @@ sandboxing
 Sangwhan
 Saunalahti
 saxfr
-sbin
-scalability
 Scheffler
 Schlich
 Scholten
 Schryver
 Schueler
+Schulmann
 schwer
 scopebits
 scopemask
-scriptable
-scriptlets
-scrypt
-sdb
+sdfn
 sdfoijdfio
 sdig
 secpoll
@@ -1479,35 +1206,30 @@ securitypolling
 seealso
 segfault
 selectmplexer
-selinux
 senderrors
 Sendetzky
-sendmsg
 sensistive
 Sergey
-servername
 serverpools
 serverselection
 servfail
 setaffinity
 setcontent
 setdomainmetadata
-setgid
 seting
 setkey
 setnotified
 SETPIPE
 settting
-setuptools
 setvariable
-Sgs
+Shabanov
 Shafir
 shantikulkarni
 shinsterneck
+shnya
 showdetails
 showflags
 Shukla
-sid
 sidebarbgcolor
 sidebarbtncolor
 sidebarbutton
@@ -1518,22 +1240,16 @@ sidebartextcolor
 sidebarwidth
 sidn
 SIGABR
-sigint
 Sigmod
 signedness
 Signingpiper
 signpipe
 signttl
 signzone
-SIGPIPE
-sigs
-SIGUSR
 singlethreaded
 Sipek
 siphash
-sizeof
 Sjoerd
-slapd
 slapindex
 slaveness
 SLES
@@ -1541,22 +1257,16 @@ smartcard
 smellyspice
 smimea
 smn
-smtp
 Smurthwaite
 Snarked
 sndbuf
-snmp
-snmpd
 snprintf
 soa
 soadata
 soarecord
-sockaddr
 socketdir
 softhsm
-solaris
 Soldaat
-SOMAXCONN
 somedomain
 Sonix
 Soref
@@ -1568,8 +1278,7 @@ sourceforge
 sourceware
 Spaans
 Spackages
-spam
-spf
+spams
 SPHINXBUILD
 sphinxcontrib
 sphinxjsondomain
@@ -1578,27 +1287,16 @@ SPHINXPROJ
 sphinxsidebar
 sphinxsidebarwrapper
 splitsetup
-sprezzos
 Spruyt
-SQk
-sql
-sqlite
+SQLs
 srandom
-src
 srcname
 SRecord
 Srule
-srv
 sshfp
 ssi
-ssl
-SSLKEYLOGFILE
-sslmode
-sslrootcert
 SSQ
 stacksize
-standalone
-starttls
 starttransaction
 Stasic
 statbag
@@ -1606,10 +1304,6 @@ statbas
 statisticitem
 statm
 stbuehler
-stderr
-stdin
-stdio
-stdout
 Stef
 Steinbuch
 stek
@@ -1617,26 +1311,19 @@ Stichting
 stickysidebar
 Stillaway
 Stirnimann
-stmt
 Stolte
 Storbeck
+Storesund
 stou
-stoul
 strcasestr
 stringmatch
-strlen
-strpos
 stubquery
 stubresolver
-stunnel
 Stussy
 stutiredboy
-stylesheet
-subdomain
 subkey
 submitters
 subnetmask
-sudo
 suffixmatchtree
 Sukhbir
 supermaster
@@ -1650,38 +1337,33 @@ supervisord
 Surfnet
 swapcontext
 swoga
+syncer
 syncres
-sys
 sysadmin
-syscall
-sysctl
+syscalls
 Sysdream
-syslog
 systemcall
-systemctl
-systemd
 sysv
-tarball
+tarballs
 Tarjei
 Tarnell
 taskqueue
 tbhandler
-tbody
 tcely
-tcp
+TCounters
+tcpconnecttimeouts
 tcpdump
 TCPKEEPALIVE
+tcplatency
+tcpmaxconcurrentconnections
+tcpnewconnections
+tcpreusedconnections
+tcptoomanyconcurrentconnections
 tds
 teeaction
 Telenet
-testrunner
 testsdir
-texinfo
-textcolor
-tfoot
 Tful
-TGJGVc
-thead
 thel
 thelog
 Thessalonikefs
@@ -1689,52 +1371,44 @@ Thiago
 thinko
 Thomassen
 threadmessage
-threadsafe
 throttlemap
 thrysoee
 timedipsetrule
 timedout
 timeframe
-timeline
 timesource
 timestamped
 timeval
-timezone
 tinycdb
 tinydns
 tinydnsbackend
 tisr
-tls
 tlsa
-tmp
 tmpfs
 tobool
-toc
 toctree
-todo
+todos
 toint
 tokenuser
-tolower
 Tolstov
 Toosarani
 Toshifumi
-tostring
 Travaille
 tribool
-trunc
 trustanchor
 trusteer
 trx
 trxid
-tsc
+TSAN
 tsig
 tsigalgo
 tsigkey
 tsigname
 tsigsecret
+Tsinghua
 tstamp
 TSU
-ttl
+ttls
 Tuinder
 tunables
 Tuomi
@@ -1742,26 +1416,20 @@ Tushuizen
 Tuxis
 TVJRU
 tylerneylon
-typedefs
 typenames
 ualberta
-ubuntu
-udp
 udpqueryresponse
 udr
 Ueber
 Ueli
-uid
-uint
+UIDs
 Uisms
-ulimit
+UMEM
 unauth
 unbreak
-uncached
 unescaping
 unfresh
 unhash
-unicode
 uninett
 uninitialised
 Uninstaller
@@ -1770,77 +1438,56 @@ unitialized
 unixodbc
 unixtime
 unparseable
-Unprocessable
-unpublish
+UNPRIV
 unpublishdomainkey
 unreachables
-unregister
 unshadowing
 untruncated
 unzero
 updatepolicy
+upgradeable
 upperalpha
 upperroman
 urandom
-uri
-url
-urlencoded
-usec
 usecase
-useragent
 userbase
-username
-userspace
-usr
-utf
-UUHJWZg
-uuid
 uwaterloo
 Valentei
 Valentini
 valgrind
 validationstates
-validators
 Valkenburg
 Vandalon
 vandergaast
 Vandoren
-varname
 Vasiliy
-VBG
 VDz
 Veldhuyzen
-venv
 Verheijen
 Verschuren
-versionadded
-versionchanged
 versionmodified
+Viala
 viewcode
-virtualized
 visitedlinkcolor
 vixie
 Voegeli
 Volker
 voxel
 Vranken
-Vsgoi
 vulns
-Vwgbclzx
+Waidner
 WAITFORONE
 wal
 wallclock
 warnlog
-Wbq
 wds
 webbased
 webdocs
 webhandler
 webpassword
-webserver
-website
+webservice
 Webspider
-weightparams
+Wegener
 Weimer
 Welzel
 Wessels
@@ -1848,78 +1495,68 @@ westes
 Wevers
 wextra
 whashed
-whitelist
 Wieger
 Wielicki
 Wijk
 Wijnand
 Wijngaards
-wiki
-wikipedia
 wil
 wildcarded
-wildcards
 Willcott
 windr
 Winfried
 wireformat
 wirelength
 Wisiol
-Wmissing
+wmem
 Wojas
-workaround
+workarounds
 Worldnic
 would've
 wouter
 wpad
 wproduction
 wrandom
-Wrange
-Wredundant
-writev
-Wshadow
 wuh
-www
 Wzs
 Xander
 xchacha
 xdb
-XDP
+xdp
 Xek
 Xeon
 XForwarded
-xfr
-xhtml
-xml
+Xiang
 xorbooter
 xpf
 XRecord
-xss
+xsk
+xskmap
 XXXXXX
 yahttp
-yaml
+yamlconversion
+yamlsettings
 Yehuda
+yeswehack
 Yiu
-YLCOy
 Ylitalo
-yml
 YMMV
+Yogesh
 yourcompany
 yourdomain
 yourorganization
 yoursecret
 yubikey
 YYYYMMD
-YYYYMMDD
 YYYYMMDDSS
-wmem
+Zash
 Zealey
 zeha
 Zengers
 Zengin
 zeromq
 zilopbg
-Zmd
+zjs
 zonecryptokey
 zonefile
 zonemd
diff --git a/.github/actions/spell-check/line_forbidden.patterns b/.github/actions/spell-check/line_forbidden.patterns
new file mode 100644 (file)
index 0000000..3067a57
--- /dev/null
@@ -0,0 +1,122 @@
+# 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,
+# you might not want to check in code where you were debugging w/ `fit()`, in which case, you might want
+# to use this:
+#\bfit\(
+
+# s.b. anymore
+\bany more[,.]
+
+# s.b. cannot
+\b[Cc]an not\b
+
+# s.b. GitHub
+(?<![&*.]|// |\btype )\bGithub\b(?![{)])
+
+# s.b. GitLab
+(?<![&*.]|// |\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
+# 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
+(?<!\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. preempt
+[Pp]re[- ]empt\b
+
+# 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. 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
+
+# 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 927645883c2246c255b7db64813a23c8ebdc4fce..25f42486e73b2cac4d2b1242540fe52819ae3405 100644 (file)
@@ -2,7 +2,8 @@
 
 # GitHub SHAs
 \bapi.github\.com/repos/[^/]+/[^/]+/[^/]+/[0-9a-f]+\b
-(?:\[[0-9a-f]+\]\(https:/|)/github\.com/[^/]+/[^/]+/[^/]+/[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b
+\b[0-9a-f]+ <https://github\.com/[^/]+/[^/]+/[^/]+/[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b>\`
+(?:\[[0-9a-f]+\]\(?:https:/|)/github\.com/[^/]+/[^/]+/[^/]+/[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b
 \bgithub\.com/[^/]+/[^/]+[@#][0-9a-f]+\b
 # githubusercontent
 /[-a-z0-9]+\.githubusercontent\.com/[-a-zA-Z0-9?&=_\/.]*
@@ -22,6 +23,9 @@ msdn\.microsoft\.com/(?:[^/]+/|)library/\S*\.aspx?
 \b[0-9A-F]{8,}\b
 \bcommit [0-9a-f]+\b
 
+# HMAC
+hmac-sha512(:\w+|\.|)\s+[-a-zA-Z=;:/0-9+]*
+
 addNSECRecordToLW.*DNSName.*powerdnt.com.*QType::NS.*res->d_records
 data:[a-zA-Z=;,/0-9+]+
 BOOST_CHECK_EQUAL\(b64, "[a-zA-Z=;,/0-9+]+"
@@ -35,6 +39,10 @@ nsec3param\s*=\s*'[^']*'
 g_rootDS\s*=\s*"[^"]*"
 DSRecordContent\("[^"]*"\)
 ^[0-9a-z.]*example\.\s+\d+\s+IN\s+(?:DS\s+\d+|OPT)\s+\d+\s+\d+\s+[0-9a-f]+$
+
+# PowerDNS version strings
+Server: PowerDNS/(?:\d+\.)+\w+\.ge[0-9a-f]+
+
 # regression-tests/backends/geoip-master
 ^2\s+\.\s+IN\s+(?:DS|OPT)\s+\d+\s+[a-zA-Z0-9]+$
 SimpleMatch\("[^"]*"\).match\(std::string\("[^"]*"\)
@@ -61,6 +69,81 @@ C0FFEE
 DoT
 DoH
 
-# Contributors with non-ascii characters in their name
-Hoffst[^[:ascii:]]+tte
-Gri[^[:ascii:]]
+# Automatically suggested patterns
+# hit-count: 30 file-count: 20
+# GitHub SHAs (markdown)
+(?:\[`?[0-9a-f]+`?\]\(https:/|)/(?:www\.|)github\.com(?:/[^/\s"]+){2,}(?:/[^/\s")]+)(?:[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b|)
+
+# hit-count: 27 file-count: 11
+# IPv6
+\b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b
+
+# hit-count: 11 file-count: 4
+# Non-English
+[a-zA-Z]*[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*
+
+# hit-count: 9 file-count: 7
+# Compiler flags
+(?:^|[\t ,"'`=(])-[DWL](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
+(?:^|[\t ,"'`=(])-f(?!ield|ile|ilter|orce|unction)(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
+(?:^|[\t ,"'`=(])-l(?!imited)(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
+
+# hit-count: 9 file-count: 2
+# 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)
+
+# hit-count: 1 file-count: 1
+# Amazon
+\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|)
+
+# hit-count: 1 file-count: 1
+# Google Fonts
+\bfonts\.(?:googleapis|gstatic)\.com/[-/?=:;+&0-9a-zA-Z]*
+
+# Automatically suggested patterns
+# hit-count: 1 file-count: 1
+# Wikipedia
+\ben\.wikipedia\.org/wiki/[-\w%.#]+
+
+# 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|[Ss]ign) in to\b
+
+# to opt in
+\bto opt in\b
+
+# acceptable duplicates
+# ls directory listings
+[-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(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|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*$
+
+# Autogenerated revert commit message
+^This reverts commit [0-9a-f]{40}\.$
+
+# ignore long runs of a single character:
+\b([A-Za-z])\g{-1}{3,}\b
index a5ba6f6390ef0b7d463ffcbe85ce24d57819c729..e5e4c3eef82e44e60c6891a7a5363f41d5aeced7 100644 (file)
@@ -1,7 +1,11 @@
 ^attache$
+^bellow$
 benefitting
-occurence
+occurences?
+^dependan.*
+^oer$
 Sorce
-^[Ss]pae
-^untill
-^wether
+^[Ss]pae.*
+^untill$
+^untilling$
+^wether.*
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644 (file)
index 0000000..ae7979c
--- /dev/null
@@ -0,0 +1,16 @@
+# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+- package-ecosystem: github-actions
+  directory: "/"
+  schedule:
+    interval: daily
+    timezone: Europe/Amsterdam
+  open-pull-requests-limit: 5
+- package-ecosystem: pip
+  directory: "/"
+  schedule:
+    interval: daily
+    timezone: Europe/Amsterdam
+  open-pull-requests-limit: 0
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..7becc60
--- /dev/null
@@ -0,0 +1,68 @@
+---
+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-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-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 14b98c2a674ea8b2a43dec577d424f14ed128ae2..cbf691718dc509aa736242c2f569067f84d20a81 100644 (file)
@@ -4,98 +4,171 @@ 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
-    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
+    if: ${{ !github.event.schedule || vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+    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: --sysctl net.ipv6.conf.all.disable_ipv6=0
+    defaults:
+      run:
+        working-directory: ./pdns-${{ env.BUILDER_VERSION }}
     steps:
-      - uses: actions/checkout@v2.3.4
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
+          ref: ${{ inputs.branch-name }}
       - name: get timestamp for cache
         id: get-stamp
         run: |
-          echo "::set-output name=stamp::$(/bin/date +%s)"
+          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@v2
+        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@v2 # 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
-    runs-on: ubuntu-20.04
+    if: ${{ !github.event.schedule || vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+    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
-      SANITIZERS: ${{ matrix.sanitizers }}
+    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: --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: actions/checkout@v2.3.4
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
+          ref: ${{ inputs.branch-name }}
       - name: get timestamp for cache
         id: get-stamp
         run: |
-          echo "::set-output name=stamp::$(/bin/date +%s)"
+          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@v2
+        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
+      - run: inv ci-install-rust ${{ env.REPO_HOME }}
+        working-directory: ./pdns/recursordist/
       - run: inv ci-autoconf
+        working-directory: ./pdns/recursordist/
+      - run: inv ci-rec-configure
+        working-directory: ./pdns/recursordist/
+      - run: inv ci-make-distdir
+        working-directory: ./pdns/recursordist/
       - run: inv ci-rec-configure
-      - run: inv ci-rec-make
+      - 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.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@v2 # 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.sanitizers }}-${{ env.normalized-branch-name }}
           path: /opt/pdns-recursor
           retention-days: 1
 
   build-dnsdist:
     name: build dnsdist
-    runs-on: ubuntu-20.04
+    if: ${{ !github.event.schedule || vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+    runs-on: ubuntu-22.04
     strategy:
       matrix:
         sanitizers: [ubsan+asan, tsan]
@@ -103,53 +176,82 @@ jobs:
         exclude:
           - sanitizers: tsan
             features: least
-    env:
-      UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp"
-      ASAN_OPTIONS: detect_leaks=0
-      SANITIZERS: ${{ matrix.sanitizers }}
+    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: --sysctl net.ipv6.conf.all.disable_ipv6=0
     defaults:
       run:
-        working-directory: ./pdns/dnsdistdist/
+        working-directory: ./pdns/dnsdistdist/dnsdist-${{ env.BUILDER_VERSION }}
     steps:
-      - uses: actions/checkout@v2.3.4
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
+          ref: ${{ inputs.branch-name }}
       - name: get timestamp for cache
         id: get-stamp
         run: |
-          echo "::set-output name=stamp::$(/bin/date +%s)"
+          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@v2
+        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@v2 # 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: --sysctl net.ipv6.conf.all.disable_ipv6=0
     strategy:
       matrix:
         include:
@@ -176,221 +278,381 @@ jobs:
         options: >-
           --restart always
     steps:
-      - uses: actions/checkout@v2.3.4
+      - 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@v2
+        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
+    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: --sysctl net.ipv6.conf.all.disable_ipv6=0
     strategy:
       matrix:
         include:
           - backend: remote
             image: coscale/docker-sleep
+            env: {}
+            ports: []
           - backend: gmysql
             image: mysql:5
+            env:
+              MYSQL_ALLOW_EMPTY_PASSWORD: 1
+            ports:
+            - 3306:3306
           - backend: gmysql
             image: mariadb:10
+            env:
+              MYSQL_ALLOW_EMPTY_PASSWORD: 1
+            ports:
+            - 3306:3306
           - backend: gpgsql
             image: postgres:9
+            env:
+              POSTGRES_USER: runner
+              POSTGRES_HOST_AUTH_METHOD: trust
+            ports:
+            - 5432:5432
           - backend: gsqlite3  # this also runs regression-tests.nobackend and pdnsutil test-algorithms
             image: coscale/docker-sleep
+            env: {}
+            ports: []
           - backend: lmdb
             image: coscale/docker-sleep
+            env: {}
+            ports: []
           - backend: bind
             image: coscale/docker-sleep
+            env: {}
+            ports: []
           - backend: geoip
             image: coscale/docker-sleep
+            env: {}
+            ports: []
           - backend: lua2
             image: coscale/docker-sleep
+            env: {}
+            ports: []
           - backend: tinydns
             image: coscale/docker-sleep
+            env: {}
+            ports: []
           - backend: authpy
             image: coscale/docker-sleep
+            env: {}
+            ports: []
+          - backend: godbc_sqlite3
+            image: coscale/docker-sleep
+            env: {}
+            ports: []
+          - backend: godbc_mssql
+            image: mcr.microsoft.com/mssql/server:2017-GA-ubuntu
+            env:
+              ACCEPT_EULA: Y
+              SA_PASSWORD: 'SAsa12%%-not-a-secret-password'
+            ports:
+              - 1433:1433
+          - backend: ldap
+            image: powerdns/ldap-regress:1.2.4-1
+            env:
+              LDAP_LOG_LEVEL: 0
+              CONTAINER_LOG_LEVEL: 4
+            ports:
+              - 389:389
+          - backend: geoip_mmdb
+            image: coscale/docker-sleep
+            env: {}
+            ports: []
       fail-fast: false
     services:
       database:
         image: ${{ matrix.image }}
-        env:
-          POSTGRES_USER: runner
-          POSTGRES_HOST_AUTH_METHOD: trust
-          MYSQL_ALLOW_EMPTY_PASSWORD: 1
-        ports:
-          - 3306:3306
-          - 5432:5432
+        env: ${{ matrix.env }}
+        ports: ${{ matrix.ports }}
         # FIXME: this works around dist-upgrade stopping all docker containers. dist-upgrade is huge on these images anyway. Perhaps we do want to run our tasks in a Docker container too.
         options: >-
           --restart always
     steps:
-      - uses: actions/checkout@v2.3.4
+      - 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@v2
+        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: --sysctl net.ipv6.conf.all.disable_ipv6=0
     steps:
-      - uses: actions/checkout@v2.3.4
+      - 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@v2
+        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: --sysctl net.ipv6.conf.all.disable_ipv6=0
     steps:
-      - uses: actions/checkout@v2.3.4
+      - 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@v2
+        uses: actions/download-artifact@v4
         with:
-          name: pdns-recursor-${{ matrix.sanitizers }}
+          name: pdns-recursor-${{ 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
+          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: --sysctl net.ipv6.conf.all.disable_ipv6=0
     steps:
-      - uses: actions/checkout@v2.3.4
+      # - 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@v2
+        uses: actions/download-artifact@v4
         with:
-          name: pdns-recursor-${{ matrix.sanitizers }}
+          name: pdns-recursor-${{ 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
+          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]
-        threads: [1, 2, 3, 4, 8, 16]
-        mthreads: [2048, 4096]
+        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: --sysctl net.ipv6.conf.all.disable_ipv6=0
     steps:
-      - uses: actions/checkout@v2.3.4
+      - 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@v2
+        uses: actions/download-artifact@v4
         with:
-          name: pdns-recursor-${{ matrix.sanitizers }}
+          name: pdns-recursor-${{ 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
+          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"
-      ASAN_OPTIONS: detect_leaks=0
-      TSAN_OPTIONS: "halt_on_error=1: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: actions/checkout@v2.3.4
+      - 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@v2
+        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:
-    runs-on: ubuntu-20.04
+    if: ${{ !github.event.schedule || vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+    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: actions/checkout@v2.3.4
+      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
+      - 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
@@ -408,19 +670,28 @@ jobs:
       - test-recursor-api
       - test-recursor-regression
       - test-recursor-bulk
-    runs-on: ubuntu-20.04
+    if: success() || failure()
+    runs-on: ubuntu-22.04
     steps:
-      - uses: actions/checkout@v2.3.4
+      - 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@v4
         with:
           fetch-depth: 5
           submodules: recursive
-      - name: Install yq
-        run: sudo wget https://github.com/mikefarah/yq/releases/download/v4.9.6/yq_linux_amd64 -O /usr/bin/yq && sudo chmod +x /usr/bin/yq
-      - name: Get full list of jobs for this workflow
-        run: yq e '.jobs | keys' .github/workflows/build-and-test-all.yml | grep -v '^- collect' | sort | tee /tmp/workflow-jobs-list.yml
-      - name: Get list of jobs the collect job depends on
-        run: yq e '.jobs.collect.needs | ... comments=""' .github/workflows/build-and-test-all.yml | sort | tee /tmp/workflow-collect-dependencies.yml
-      - name: Diff them
-        run: diff -u /tmp/workflow-jobs-list.yml /tmp/workflow-collect-dependencies.yml
+          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
+        run: "echo '${{ toJSON(needs) }}' | jq 'keys | .[]' | tr -d '\"' | sort | tee /tmp/workflow-needs-list.yml"
+      - name: Fail if there is a job missing on the needs list
+        run: "if ! diff -q /tmp/workflow-jobs-list.yml /tmp/workflow-needs-list.yml; then exit 1; fi"
 
 # FIXME: if we can make upload/download-artifact fasts, running unit tests outside of build can let regression tests start earlier
diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml
new file mode 100644 (file)
index 0000000..13ab3a3
--- /dev/null
@@ -0,0 +1,214 @@
+---
+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
+      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 }}
+      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.9.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.9.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..6431ec9
--- /dev/null
@@ -0,0 +1,46 @@
+---
+name: Build packages for tags
+
+on:
+  push:
+    tags:
+    - 'auth-*'
+    - 'dnsdist-*'
+    - 'rec-*'
+
+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 6d5e8b06cbc4df63a0a5b083ccabe576307ec4b5..0e680324b41c47eec64879c5aa00e68152f1b678 100644 (file)
@@ -12,27 +12,43 @@ on:
         - recursor
         - dnsdist
       os:
-        description: OS to build for
+        description: OSes to build for, space separated
         type: string
+        # please remember to update build-packages.yml as well
+        default: >-
+          el-7
+          el-8
+          el-9
+          debian-buster
+          debian-bullseye
+          debian-bookworm
+          ubuntu-focal
+          ubuntu-jammy
+      ref:
+        description: git ref to checkout
+        type: string
+        default: master
+      is_release:
+        description: is this a release build?
+        type: choice
+        options:
+        - 'NO'
+        - 'YES'
+
+permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
+  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:
-  build:
-    name: build ${{ github.event.inputs.product }} for ${{ github.event.inputs.os }}
-    # on a ubuntu-20.04 VM
-    runs-on: ubuntu-20.04
-    steps:
-      - uses: actions/checkout@v2.3.4
-        with:
-          fetch-depth: 0 # for correct version numbers
-          submodules: recursive
-      # this builds packages and runs our unit tests (make check)
-      - run: builder/build.sh -v -m ${{ github.event.inputs.product }} ${{ github.event.inputs.os }}
-      - name: Get version number
-        run: 'echo ::set-output name=version::$(readlink builder/tmp/latest)'
-        id: getversion
-      - name: Upload packages
-        uses: actions/upload-artifact@v2
-        with:
-          name: ${{ github.event.inputs.product }}-${{ github.event.inputs.os }}-${{ steps.getversion.outputs.version }}
-          path: built_pkgs/
-          retention-days: 7
+  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..2e96756
--- /dev/null
@@ -0,0 +1,68 @@
+---
+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-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-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 448539b80562a7c91d3b570de23e1e7798e26eaf..759229824548012c24b4cf11f9e95e8be745b513 100644 (file)
@@ -2,12 +2,23 @@
 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 * * *'
 
+permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
+  contents: read
+
 jobs:
   build:
     name: build.sh
+    if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
     # on a ubuntu-20.04 VM
     runs-on: ubuntu-20.04
     strategy:
@@ -15,24 +26,30 @@ jobs:
         product: ['authoritative', 'recursor', 'dnsdist']
         os:
           - centos-7
-          - ubuntu-bionic
           - el-8
           - centos-8-stream
-          - debian-bullseye
-          - ubuntu-jammy
+          - centos-9-stream
+          - ubuntu-lunar
+          - ubuntu-mantic
+          - ubuntu-noble
+          - debian-bookworm
+          - debian-trixie
+          - amazon-2023
       fail-fast: false
     steps:
-      - uses: actions/checkout@v2.3.4
+      - 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@v2
+        uses: actions/upload-artifact@v4
         with:
           name: ${{ matrix.product }}-${{ matrix.os }}-${{ steps.getversion.outputs.version }}
           path: built_pkgs/
index cdaf72292ae4457ff3ce2f92abba318f3e12fbb5..168f1c23389d3743e90e4107af41dfd186891632 100644 (file)
@@ -1,4 +1,4 @@
-name: "CodeQL"
+name: "CodeQL and clang-tidy"
 
 on:
   push:
@@ -6,10 +6,32 @@ on:
   schedule:
     - cron: '0 22 * * 2'
 
+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
-    runs-on: ubuntu-20.04
+    if: ${{ !github.event.schedule || vars.SCHEDULED_CODEQL_ANALYSIS }}
+    runs-on: ubuntu-22.04
+
+    permissions:
+      actions: read # for github/codeql-action/init to get workflow details
+      contents: read  # for actions/checkout to fetch code
+      security-events: write  # for github/codeql-action/analyze to upload SARIF results
 
     strategy:
       fail-fast: false
@@ -21,36 +43,48 @@ 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@v2
+      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@v2
-      with:
-        python-version: '3.8'
-
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@v1
+      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
 
     # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
     # If this step fails, then you should remove it and run the build manually (see below)
     # - name: Autobuild
-    #   uses: github/codeql-action/autobuild@v1
+    #   uses: github/codeql-action/autobuild@v2
 
     # ℹ️ Command-line programs to run using the OS shell.
     # 📚 https://git.io/JvXDl
@@ -62,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 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
+    - name: Build rec
+      if: matrix.product == 'rec'
+      working-directory: ./pdns/recursordist/
+      run: |
+        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: |
-        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 -j8 pdns_recursor rec_control
+        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@v1
+      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 09c3dd9049bcb022dba1c75e13d8d62565a834ce..6493b8529ffdc8462b5f1b3d92d7be48178f9ef8 100644 (file)
@@ -5,16 +5,20 @@ on:
   schedule:
     - cron: '0 4 * * *'
 
+permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
+  contents: read
+
 jobs:
   build:
     name: docker build
+    if: ${{ vars.SCHEDULED_DOCKER }}
     # on a ubuntu-20.04 VM
     runs-on: ubuntu-20.04
     strategy:
       matrix:
         product: ['auth', 'recursor', 'dnsdist']
     steps:
-      - uses: actions/checkout@v2.3.4
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml
new file mode 100644 (file)
index 0000000..cb6828e
--- /dev/null
@@ -0,0 +1,114 @@
+---
+name: 'Documentation'
+
+on:
+  push:
+    branches: [master]
+  pull_request:
+    branches: [master]
+
+permissions:
+  contents: read
+
+jobs:
+  build-upload-docs:
+    name: Build and upload docs
+    runs-on: ubuntu-22.04
+    steps:
+      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
+      - 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
+
+      - id: setup-ssh
+        run: |-
+          inv ci-docs-add-ssh --ssh-key="$SSH_KEY" --host-key="$HOST_KEY"
+          echo "have_ssh_key=yes" >> $GITHUB_OUTPUT
+        env:
+          SSH_KEY: ${{secrets.WEB1_DOCS_SECRET}}
+          HOST_KEY: ${{vars.WEB1_HOSTKEY}}
+        if: ${{github.ref_name == 'master' && env.SSH_KEY != ''}}
+
+      # Auth
+      - run: inv ci-docs-build
+      - run: mv html auth-html-docs
+        working-directory: ./docs/_build
+      - run: tar cf auth-html-docs.tar auth-html-docs
+        working-directory: ./docs/_build
+      - uses: actions/upload-artifact@v4
+        with:
+          name: authoritative-html-docs-${{steps.get-version.outputs.pdns_version}}
+          path: ./docs/_build/auth-html-docs.tar
+      - run: bzip2 auth-html-docs.tar
+        if: ${{github.ref_name == 'master'}}
+        working-directory: ./docs/_build
+      - run: inv ci-docs-build-pdf
+      - uses: actions/upload-artifact@v4
+        with:
+          name: PowerDNS-Authoritative-${{steps.get-version.outputs.pdns_version}}.pdf
+          path: ./docs/_build/latex/PowerDNS-Authoritative.pdf
+      - run: inv ci-docs-upload-master --docs-host="${DOCS_HOST}" --pdf="PowerDNS-Authoritative.pdf" --username="docs_powerdns_com" --product="auth" --directory="/${AUTH_DOCS_DIR}/"
+        env:
+          DOCS_HOST: ${{vars.DOCS_HOST}}
+          AUTH_DOCS_DIR: ${{vars.AUTH_DOCS_DIR}}
+        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@v4
+        with:
+          name: recursor-html-docs-${{steps.get-version.outputs.pdns_version}}
+          path: ./pdns/recursordist/docs/_build/rec-html-docs.tar
+      - run: bzip2 rec-html-docs.tar
+        if: ${{github.ref_name == 'master'}}
+        working-directory: ./pdns/recursordist/docs/_build
+      - run: inv ci-docs-build-pdf
+        working-directory: ./pdns/recursordist
+      - 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
+      - run: inv ci-docs-upload-master --docs-host="${DOCS_HOST}" --pdf="PowerDNS-Recursor.pdf" --username="docs_powerdns_com" --product="rec" --directory="/${REC_DOCS_DIR}/"
+        env:
+          DOCS_HOST: ${{vars.DOCS_HOST}}
+          REC_DOCS_DIR: ${{vars.REC_DOCS_DIR}}
+        if: ${{github.ref_name == 'master' && steps.setup-ssh.outputs.have_ssh_key != ''}}
+        working-directory: ./pdns/recursordist
+
+      # DNSdist
+      - run: inv ci-docs-build
+        working-directory: ./pdns/dnsdistdist
+      - run: mv html dnsdist-html-docs
+        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@v4
+        with:
+          name: dnsdist-html-docs-${{steps.get-version.outputs.pdns_version}}
+          path: ./pdns/dnsdistdist/docs/_build/dnsdist-html-docs.tar
+      - run: bzip2 dnsdist-html-docs.tar
+        if: ${{github.ref_name == 'master'}}
+        working-directory: ./pdns/dnsdistdist/docs/_build
+      - run: inv ci-docs-build-pdf
+        working-directory: ./pdns/dnsdistdist
+      - uses: actions/upload-artifact@v4
+        with:
+          name: dnsdist-${{steps.get-version.outputs.pdns_version}}.pdf
+          path: ./pdns/dnsdistdist/docs/_build/latex/dnsdist.pdf
+      - run: inv ci-docs-upload-master --docs-host="${DOCS_HOST}" --pdf="dnsdist.pdf" --username="dnsdist_org" --product="dnsdist"
+        env:
+          DOCS_HOST: ${{vars.DOCS_HOST}}
+        if: ${{github.ref_name == 'master' && steps.setup-ssh.outputs.have_ssh_key != ''}}
+        working-directory: ./pdns/dnsdistdist
index 38395c5e299320fa00ab3148250c4087a48c0c60..cedeef12696860e115cdc12db60b7e08fb22c383 100644 (file)
@@ -5,13 +5,16 @@ on:
   push:
   pull_request:
 
+permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
+  contents: read
+
 jobs:
   build:
     name: verify formatting and Makefile.am sort order
     # on a ubuntu-20.04 VM
     runs-on: ubuntu-20.04
     steps:
-      - uses: actions/checkout@v2.3.4
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
index c42bd8f93ea19711403b38c58d517efb6f0a1a8a..afcf8898abbb2430efbc992b6b4e840b4fa36321 100644 (file)
@@ -1,9 +1,18 @@
 name: CIFuzz
 on: [pull_request]
+
+permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
+  contents: read
+
 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:
@@ -16,7 +25,7 @@ jobs:
         fuzz-seconds: 600
         dry-run: false
     - name: Upload Crash
-      uses: actions/upload-artifact@v1
+      uses: actions/upload-artifact@v4
       if: failure()
       with:
         name: artifacts
diff --git a/.github/workflows/misc-dailies.yml b/.github/workflows/misc-dailies.yml
new file mode 100644 (file)
index 0000000..ea31204
--- /dev/null
@@ -0,0 +1,128 @@
+name: "Various daily checks"
+
+on:
+  schedule:
+    - cron: '34 4 * * *'
+
+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 }}
+    runs-on: ubuntu-22.04
+
+    steps:
+    - name: Check whether a newer devtoolset exists
+      run: |
+        if docker run --rm centos:7 bash -c 'yum install -y centos-release-scl-rh && yum info devtoolset-12-gcc-c++'
+        then
+          echo "::warning file=builder-support/dockerfiles/Dockerfile.rpmbuild::A newer devtoolset exists. Please edit builder-support/dockerfiles/Dockerfile.rpmbuild, builder-support/dockerfiles/Dockerfile.rpmbuild, and .github/workflows/dailies.yml"
+          exit 1
+        else
+          echo "::notice ::No newer devtoolset exists (good)"
+          exit 0
+        fi
+
+  check-debian-autoremovals:
+    if: ${{ vars.SCHEDULED_MISC_DAILIES }}
+    runs-on: ubuntu-22.04
+    steps:
+    - uses: actions/checkout@v4
+      with:
+        fetch-depth: 5
+        submodules: recursive
+
+    - name: Check if Debian is about to toss us off a balcony
+      run: ./build-scripts/check-debian-autoremovals.py
+
+  coverity-auth:
+    name: coverity scan of the auth
+    if: ${{ vars.SCHEDULED_MISC_DAILIES }}
+    runs-on: ubuntu-22.04
+    env:
+      COVERITY_TOKEN: ${{ secrets.coverity_auth_token }}
+      FUZZING_TARGETS: no
+      SANITIZERS:
+      UNIT_TESTS: no
+    steps:
+      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
+      - uses: actions/checkout@v4
+        with:
+          fetch-depth: 5
+          submodules: recursive
+      - 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
+      - run: inv coverity-clang-configure
+      - run: inv ci-autoconf
+      - run: inv ci-auth-configure
+      - run: inv coverity-make
+      - run: inv coverity-tarball auth.tar.bz2
+      - run: inv coverity-upload ${{ secrets.coverity_email }} PowerDNS auth.tar.bz2
+
+  coverity-dnsdist:
+    name: coverity scan of dnsdist
+    if: ${{ vars.SCHEDULED_MISC_DAILIES }}
+    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@v4
+        with:
+          fetch-depth: 5
+          submodules: recursive
+      - run: build-scripts/gh-actions-setup-inv-no-dist-upgrade
+      - run: inv install-clang
+      - 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
+        working-directory: ./pdns/dnsdistdist/
+      - run: inv coverity-tarball dnsdist.tar.bz2
+        working-directory: ./pdns/dnsdistdist/
+      - run: inv coverity-upload ${{ secrets.coverity_email }} dnsdist dnsdist.tar.bz2
+        working-directory: ./pdns/dnsdistdist/
+
+  coverity-rec:
+    name: coverity scan of the rec
+    if: ${{ vars.SCHEDULED_MISC_DAILIES }}
+    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@v4
+        with:
+          fetch-depth: 5
+          submodules: recursive
+      - 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
+        working-directory: ./pdns/recursordist/
+      - run: inv coverity-make
+        working-directory: ./pdns/recursordist/
+      - run: inv coverity-tarball recursor.tar.bz2
+        working-directory: ./pdns/recursordist/
+      - run: inv coverity-upload ${{ secrets.coverity_email }} 'PowerDNS+Recursor' recursor.tar.bz2
+        working-directory: ./pdns/recursordist/
index cb1151792dca745c9bdc3903c5e75567b2a859d7..57278d764973905d40a222a5da0eedd79ce32e84 100644 (file)
@@ -5,13 +5,17 @@ on:
   push:
   pull_request:
 
+permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
+  contents: read
+
 jobs:
   build:
     name: check secpoll zone
     # on a ubuntu-20.04 VM
     runs-on: ubuntu-20.04
     steps:
-      - uses: actions/checkout@v2.3.4
+      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
index 7cf39614917a33a79262a2499de716fa56bc4e9a..c250cd1046a13d2a0effd1e88fd12b8b3d959063 100644 (file)
@@ -4,6 +4,9 @@ on:
   push:
     branches: ''
 
+permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
+  contents: read
+
 jobs:
   placeholder:
     name: Should be disabled
index e24b9dfd1b80c166412d80480fe05fb1197e23b3..055dd173040f7d99f9d29c95f0510f52571c4d91 100644 (file)
@@ -1,32 +1,16 @@
-# spelling.yml is blocked per https://github.com/check-spelling/check-spelling/security/advisories/GHSA-g86g-chm8-7r2p
-name: Spell checking
+# spelling2.yml is disabled per https://github.com/check-spelling/check-spelling/security/advisories/GHSA-p8r9-69g4-jwqq
+name: Workflow should not run!
 on:
   push:
-    branches:
-      - "**"
-    tags-ignore:
-      - "**"
-  pull_request_target:
-    branches:
-      - "**"
-    tags-ignore:
-      - "**"
-    types: ['opened', 'reopened', 'synchronize']
+    branches: ''
 
 jobs:
-  spelling:
-    name: Spell checking
+  placeholder:
+    name: Should be disabled
     runs-on: ubuntu-latest
+    if: false
     steps:
-    - name: checkout-merge
-      if: "contains(github.event_name, 'pull_request')"
-      uses: actions/checkout@v2
-      with:
-        ref: refs/pull/${{github.event.pull_request.number}}/merge
-    - name: checkout
-      if: ${{ github.event_name == 'push' }}
-      uses: actions/checkout@v2
-    - uses: check-spelling/check-spelling@v0.0.19
-      id: spelling
-      with:
-        config: .github/actions/spell-check
+    - name: Task
+      run: |
+        echo 'Running this task would be bad'
+        exit 1
diff --git a/.github/workflows/spelling3.yml b/.github/workflows/spelling3.yml
new file mode 100644 (file)
index 0000000..9df9009
--- /dev/null
@@ -0,0 +1,72 @@
+# spelling.yml is blocked per https://github.com/check-spelling/check-spelling/security/advisories/GHSA-g86g-chm8-7r2p
+# spelling2.yml is blocked per https://github.com/check-spelling/check-spelling/security/advisories/GHSA-p8r9-69g4-jwqq
+name: Spell checking
+
+on:
+  push:
+    branches:
+      - "**"
+    tags-ignore:
+      - "**"
+  pull_request:
+    branches:
+      - "**"
+    types:
+      - 'opened'
+      - 'reopened'
+      - 'synchronize'
+
+jobs:
+  spelling:
+    name: Spell checking
+    permissions:
+      # contents-read is needed to checkout in private repositories
+      contents: read
+      # actions-read is needed (possibly only for private repositories)
+      # to identify the workflow's filename until
+      # https://github.com/actions/runner/issues/853 is fixed
+      actions: read
+      # security-events-write is needed according to the documentation:
+      # https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/uploading-a-sarif-file-to-github#uploading-a-code-scanning-analysis-with-github-actions
+      security-events: write
+    outputs:
+      followup: ${{ steps.spelling.outputs.followup }}
+    runs-on: ubuntu-latest
+    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
+      cancel-in-progress: true
+    steps:
+    - name: check-spelling
+      id: spelling
+      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: 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/dict/softwareTerms.txt
+          cspell:node/dict/node.txt
+          cspell:python/src/common/extra.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:cpp/src/stdlib-cpp.txt
+          cspell:filetypes/filetypes.txt
+          cspell:python/src/python/python.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 730fe47615308f8e3c215937c65b14b104c9119d..07dd058c9e930d5bbea4cf4e2cff7043f43f0a37 100644 (file)
@@ -56,6 +56,7 @@ __pycache__
 .circleci/config.yml-local
 .env
 compile_commands.*
-/.cache
+.cache
 /.clang-tidy
 .gdb_history
+.venv
index 0dfb64d2544a28b2f6af2e3d0090bb5636cd6e6a..759460e7fd5bde10c872c992674c7c110cc391e2 100644 (file)
@@ -4,9 +4,6 @@
 ./ext/lmdb-safe/lmdb-typed.hh
 ./ext/probds/murmur3.cc
 ./pdns/anadns.hh
-./pdns/arguments.cc
-./pdns/arguments.hh
-./pdns/ascii.hh
 ./pdns/auth-caches.cc
 ./pdns/auth-carbon.cc
 ./pdns/auth-packetcache.cc
 ./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/bindparserclasses.hh
 ./pdns/bpf-filter.cc
 ./pdns/bpf-filter.hh
-./pdns/cachecleaner.hh
 ./pdns/calidns.cc
 ./pdns/capabilities.cc
 ./pdns/cdb.cc
 ./pdns/cdb.hh
 ./pdns/comfun.cc
 ./pdns/comment.hh
-./pdns/common_startup.cc
-./pdns/common_startup.hh
-./pdns/communicator.cc
-./pdns/communicator.hh
 ./pdns/dbdnsseckeeper.cc
-./pdns/decafsigners.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-carbon.cc
 ./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
@@ -83,9 +63,6 @@
 ./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
@@ -95,7 +72,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/filterpo.cc
-./pdns/filterpo.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/inflighter.cc
-./pdns/ipcipher.cc
 ./pdns/iputils.cc
 ./pdns/iputils.hh
 ./pdns/ixfr.cc
 ./pdns/json.cc
 ./pdns/json.hh
 ./pdns/kvresp.cc
-./pdns/lazy_allocator.hh
 ./pdns/libssl.cc
 ./pdns/libssl.hh
 ./pdns/lock.hh
 ./pdns/lua-base4.cc
 ./pdns/lua-base4.hh
 ./pdns/lua-record.cc
-./pdns/lwres.cc
-./pdns/lwres.hh
 ./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/mtasker_context.hh
-./pdns/mtasker_fcontext.cc
-./pdns/mtasker_ucontext.cc
 ./pdns/nameserver.cc
 ./pdns/nameserver.hh
 ./pdns/namespaces.hh
-./pdns/nod.cc
-./pdns/nod.hh
 ./pdns/noinitvector.hh
 ./pdns/notify.cc
 ./pdns/nproxy.cc
 ./pdns/nsec3dig.cc
 ./pdns/nsecrecords.cc
-./pdns/opensslsigners.cc
 ./pdns/packetcache.hh
 ./pdns/packethandler.cc
 ./pdns/packethandler.hh
 ./pdns/query-local-address.hh
 ./pdns/rcpgenerator.cc
 ./pdns/rcpgenerator.hh
-./pdns/receiver.cc
 ./pdns/remote_logger.cc
 ./pdns/remote_logger.hh
-./pdns/resolve-context.hh
 ./pdns/resolver.cc
 ./pdns/resolver.hh
 ./pdns/responsestats-auth.cc
 ./pdns/rfc2136handler.cc
-./pdns/root-addresses.hh
 ./pdns/root-dnssec.hh
-./pdns/rpzloader.cc
 ./pdns/saxfr.cc
 ./pdns/sdig.cc
 ./pdns/secpoll-auth.cc
 ./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/sodiumsigners.cc
 ./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/syncres.cc
-./pdns/syncres.hh
 ./pdns/tcpiohandler.cc
 ./pdns/tcpiohandler.hh
 ./pdns/tcpreceiver.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-dnsrecordcontent.cc
 ./pdns/test-dnsrecords_cc.cc
 ./pdns/test-dnswriter_cc.cc
-./pdns/test-ipcrypt_cc.cc
 ./pdns/test-iputils_hh.cc
 ./pdns/test-ixfr_cc.cc
 ./pdns/test-lock_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-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-zoneparser_tng_cc.cc
-./pdns/testrunner.cc
 ./pdns/threadname.cc
 ./pdns/tkey.cc
-./pdns/toysdig.cc
 ./pdns/trusted-notification-proxy.cc
 ./pdns/trusted-notification-proxy.hh
 ./pdns/tsig-tests.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 3e7008cb3c1505101c36c414687e638abdea8c06..3e01d514caad47628027fdc03a64b83ff8ddd3c3 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:
 
@@ -12,6 +12,9 @@ The Dockerfiles:
 
 Other data involved in the Docker build process can be found at https://github.com/PowerDNS/pdns/tree/master/dockerdata
 
+> **Note**
+> If you are building the Dockerfiles directly from the git repo, make sure you run `git submodule init` followed by `git submodule update` first.
+
 # Usage
 
 The images are ready to run with limited functionality.
@@ -21,7 +24,7 @@ For the dnsdist console, make sure that your API key is in a format suitable for
 
 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 or the command line, or via a volume mount into `/etc/powerdns` or the `.d` dir.
+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.
@@ -39,8 +42,8 @@ In a plain Docker or Compose setup, this can be done by using the host PID names
 
 # Compose example
 
-We have a Docker Compose example at https://github.com/PowerDNS/pdns/blob/master/docker-compose.yml .
-It brings up all three services, and exposes them to eachother by name (using Docker's internal DNS).
+We have a Docker Compose example at https://github.com/PowerDNS/pdns/blob/master/docker-compose.yml.
+It brings up all three services, and exposes them to each other by name (using Docker's internal DNS).
 In the dockerdata dir, you can find an example dnsdist Lua config (with Python helper to make DNS lookups non-blocking for dnsdist) for managing your auth/rec backends by name.
 
 # Privileged ports
@@ -54,4 +57,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. 
\ No newline at end of file
+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
\ No newline at end of file
index 576cffd910c4acb1ced9219d98903ea23542b21d..c60020061525dc24d008a970ed56e7de3d475586 100644 (file)
@@ -1,5 +1,5 @@
 # our chosen base image
-FROM debian:10-slim AS builder
+FROM debian:11-slim AS builder
 
 ENV NO_LUA_JIT="s390x arm64"
 
@@ -66,14 +66,14 @@ RUN cd /tmp && mkdir /build/tmp/ && mkdir debian && \
     dpkg-deb -I equivs-dummy_1.0_all.deb && cp equivs-dummy_1.0_all.deb /build/tmp/
 
 # Runtime
-FROM debian:10-slim
+FROM debian:11-slim
 
 # Reusable layer for base update - Should be cached from builder
 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 468c03975928d1dd6d8a4f76116195cfecd8efb9..2b96ab3ef91964c4a3f0841f60403463186036a9 100644 (file)
@@ -1,5 +1,5 @@
 # our chosen base image
-FROM debian:10-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:10-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 c6e17bef4004e9786fec503dbbed5ed9ce6d4589..9b118cd866fc00172c4f8b688684c6d46f2ee9ea 100644 (file)
@@ -5,7 +5,7 @@
 #   dig a www.example.com @0 -p 1053
 
 # Builder
-FROM debian:10-slim AS builder
+FROM debian:11-slim AS builder
 
 ENV NO_LUA_JIT="s390x arm64"
 
@@ -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
 
@@ -66,7 +69,7 @@ RUN cd /tmp && mkdir /build/tmp/ && mkdir debian && \
     dpkg-deb -I equivs-dummy_1.0_all.deb && cp equivs-dummy_1.0_all.deb /build/tmp/
 
 # Runtime
-FROM debian:10-slim
+FROM debian:11-slim
 
 # Reusable layer for base update - Should be cached from builder
 RUN apt-get update && apt-get -y dist-upgrade && apt-get clean
index d5bb1f0bc3f83dc7f690d54b095cb3ac6cdbd5fc..db54310797fdf2a8c161ab69b44f25d935836538 100644 (file)
@@ -30,4 +30,3 @@ dvi: # do nothing to build dvi
 
 format-code:
        ./build-scripts/format-code `find . -type f -name '*.[ch][ch]' | LANG=C sort | LANG=C comm -23 - .not-formatted`
-
index 89ca946dad1aa709f472c84efa893c70551dfd8b..5594817e99998f66901bbcff8203c8d7ac17414c 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-PowerDNS is copyright © 2001-2021 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 7ec7c58e40c248284bee2b21b64799239eccd927..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.
@@ -10,10 +10,10 @@ We fully credit reporters of security issues, and respond quickly, but please al
 We remind PowerDNS and dnsdist users that under the terms of the GNU General Public License, PowerDNS and dnsdist come with ABSOLUTELY NO WARRANTY.
 This license is included in this documentation.
 
-HackerOne
----------
-Security issues can also be reported on [our HackerOne page](https://hackerone.com/powerdns) and might fetch a bounty.
-Do note that only the PowerDNS software (PowerDNS Authoritative Server, the PowerDNS Recursor and dnsdist) is in scope for the HackerOne program, not our websites or other infrastructure.
+Yes We Hack
+-----------
+Security issues can also be reported on [our YesWeHack page](https://yeswehack.com/programs/powerdns) and might fetch a bounty.
+Do note that only the PowerDNS software (PowerDNS Authoritative Server, the PowerDNS Recursor and dnsdist) is in scope for the YesWeHack program, not our websites or other infrastructure.
 
 Disclosure Policy
 -----------------
diff --git a/auth-tsan.supp b/auth-tsan.supp
new file mode 100644 (file)
index 0000000..660f2ec
--- /dev/null
@@ -0,0 +1,6 @@
+# Statistic data-race not relevant right now
+# Due to performance issue of atomics
+# Discussion:
+# https://github.com/PowerDNS/pdns/issues/11814
+race:avg_latency
+race:send_latency
diff --git a/build-scripts/check-debian-autoremovals.py b/build-scripts/check-debian-autoremovals.py
new file mode 100755 (executable)
index 0000000..0249ecf
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/python3
+import re
+import sys
+
+import requests
+import yaml
+
+
+PACKAGE_NAMES = ["pdns", "pdns-recursor", "dnsdist"]
+
+
+def get_bugs_titles(bugs):
+    ret = []
+    title_regex = re.compile(r"<title>(.*) - Debian Bug report logs</title>")
+    for bug in bugs:
+        bug_r = requests.get("https://bugs.debian.org/cgi-bin/bugreport.cgi", params={"bug": bug})
+        lines = bug_r.text.split("\n")
+        for line in lines:
+            match = title_regex.search(line)
+            if match:
+                ret.append(match.group(1))
+                break
+    return ret
+
+
+def get_all_autoremovals():
+    r = requests.get("https://udd.debian.org/cgi-bin/autoremovals.yaml.cgi")
+    return yaml.load(r.text, Loader=yaml.SafeLoader)
+
+
+def warn_removal(all_autoremovals, package_name):
+    removal = all_autoremovals.get(package_name)
+    if not removal:
+        return False
+
+    reason = "there are bugged dependencies." if removal["dependencies_only"] else "of a bug in PowerDNS!"
+    msg = (
+        f"::warning ::{package_name} slated for removal from Debian on "
+        f"{removal['removal_date']} (https://tracker.debian.org/pkg/{package_name}) "
+        f"because {reason}\n\n"
+    )
+
+    bugs = get_bugs_titles(removal.get("bugs_dependencies", []))
+    if len(bugs):
+        msg += f"This is caused by the following dependency bug{'s' if len(bugs) > 1 else ''}:\n  "
+        msg += ("\n  ".join(bugs)) + "\n"
+
+    bugs = get_bugs_titles(removal["bugs"])
+    if len(bugs):
+        msg += f"This is caused by the following bug{'s' if len(bugs) > 1 else ''} in PowerDNS:\n  "
+        msg += ("\n  ".join(bugs)) + "\n"
+
+    print(msg)
+    return True
+
+
+def main():
+    all_autoremovals = get_all_autoremovals()
+    removals = []
+    for package_name in PACKAGE_NAMES:
+        if warn_removal(all_autoremovals, package_name):
+            removals.append(package_name)
+
+    if removals:
+        sys.exit(1)
+    else:
+        print("::notice ::No packages marked for autoremoval from Debian (yay!)")
+        sys.exit(0)
+
+
+if __name__ == "__main__":
+    main()
index e624be55076b535b60510d89c9b8332b10e085cb..7ebe25bd3f4d739f4ae537918b610edac850598f 100755 (executable)
@@ -13,6 +13,7 @@
 # Modules
 
 import argparse
+import re
 import subprocess
 import sys
 
@@ -24,7 +25,7 @@ from jinja2 import Environment, FileSystemLoader
 
 # Globals
 
-g_version = '0.0.1'
+g_version = '1.0.3'
 
 g_verbose = False
 
@@ -43,14 +44,14 @@ def init_argparser():
                                                  'test PowerDNS repositories.')
     parser.add_argument('release', metavar='RELEASE',
                         choices=[# Authoritative Server
-                                 'auth-43', 'auth-44', 'auth-45', 'auth-46',
-                                 'auth-47', 'auth-master',
+                                 'auth-44', 'auth-45', 'auth-46', 'auth-47',
+                                 'auth-48', 'auth-master',
                                  # Recursor
-                                 'rec-43', 'rec-44', 'rec-45', 'rec-46',
-                                 'rec-47', 'rec-master',
+                                 'rec-46', 'rec-47', 'rec-48', 'rec-49',
+                                 'rec-master',
                                  # DNSDist
                                  'dnsdist-15', 'dnsdist-16', 'dnsdist-17',
-                                 'dnsdist-master'
+                                 'dnsdist-18', 'dnsdist-master'
                                  ],
                         help='the release to generate Docker files for: ' +
                              '%(choices)s')
@@ -140,46 +141,50 @@ def write_release_files (release):
     if g_verbose:
         print("Writing release files...")
 
-    if release in ['auth-43', 'auth-master']:
-        write_dockerfile('centos', '6', release)
-
-    if release in ['auth-43', 'auth-44', 'auth-45', 'auth-46', 'auth-47',
+    if release in ['auth-44', 'auth-45', 'auth-46', 'auth-47', 'auth-48',
                    'auth-master',
-                   'rec-43', 'rec-44', 'rec-45', 'rec-46', 'rec-47',
+                   'rec-46', 'rec-47', 'rec-48', 'rec-49',
                    'rec-master',
-                   'dnsdist-15', 'dnsdist-16', 'dnsdist-17', 'dnsdist-master']:
-        write_dockerfile('centos', '7', release)
-        write_dockerfile('ubuntu', 'bionic', release)
-        write_list_file('ubuntu', 'bionic', release)
+                   'dnsdist-15', 'dnsdist-16', 'dnsdist-17', 'dnsdist-18',
+                   'dnsdist-master']:
         write_pkg_pin_file(release)
-
-    if release in ['auth-43', 'rec-43', 'dnsdist-15']:
-        write_dockerfile('raspbian', 'buster', release)
-        write_list_file('raspbian', 'buster', release)
-
-    if release in ['auth-43', 'auth-44', 'auth-45', 'auth-46', 'auth-47',
-                   'auth-master',
-                   'rec-43', 'rec-44', 'rec-45', 'rec-46', 'rec-47',
-                   'rec-master',
-                   'dnsdist-15', 'dnsdist-16', 'dnsdist-17', 'dnsdist-master']:
+        write_dockerfile('centos', '7', release)
         write_dockerfile('el', '8', release)
         write_dockerfile('debian', 'buster', release)
         write_list_file('debian', 'buster', release)
-
-    if release in ['auth-43', 'auth-44', 'auth-45', 'auth-46', 'auth-47',
-                   'auth-master',
-                   'rec-43', 'rec-44', 'rec-45', 'rec-46', 'rec-47',
-                   'rec-master',
-                   'dnsdist-15', 'dnsdist-16', 'dnsdist-17', 'dnsdist-master']:
         write_dockerfile('ubuntu', 'focal', release)
         write_list_file('ubuntu', 'focal', release)
 
-    if release in ['auth-46', 'auth-47', 'auth-master',
-                   'rec-45', 'rec-46', 'rec-47', 'rec-master',
-                   'dnsdist-16', 'dnsdist-17', 'dnsdist-master']:
+    if release in ['dnsdist-15']:
+        write_dockerfile('raspbian', 'buster', release)
+        write_list_file('raspbian', 'buster', release)
+
+    if release in ['auth-46', 'auth-47', 'auth-48', 'auth-master',
+                   'rec-46', 'rec-47', 'rec-48', 'rec-49', 'rec-master',
+                   'dnsdist-16', 'dnsdist-17', 'dnsdist-18', 'dnsdist-master']:
         write_dockerfile('debian', 'bullseye', release)
         write_list_file('debian', 'bullseye', release)
 
+    if release in ['auth-46', 'auth-47', 'auth-master',
+                   'rec-46', 'rec-47', 'rec-48', 'rec-49', 'rec-master',
+                   'dnsdist-15', 'dnsdist-16', 'dnsdist-17', 'dnsdist-master']:
+        write_dockerfile('ubuntu', 'bionic', release)
+        write_list_file('ubuntu', 'bionic', release)
+
+    if release in ['auth-46', 'auth-47', 'auth-48', 'auth-master',
+                   'rec-46', 'rec-47', 'rec-48', 'rec-49', 'rec-master',
+                   'dnsdist-17', 'dnsdist-18', 'dnsdist-master']:
+        write_dockerfile('ubuntu', 'jammy', release)
+        write_list_file('ubuntu', 'jammy', release)
+
+    if release in ['auth-47', 'auth-48', 'auth-master',
+                   'rec-47', 'rec-48', 'rec-49', 'rec-master',
+                   '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
 
@@ -209,13 +214,21 @@ def run (tag):
         capture_run_output = not(g_verbose)
     print('Running Docker container tagged {}...'.format(tag))
     cp = subprocess.run(['docker', 'run', tag],
-                        capture_output=capture_run_output)
+                        stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+    version = re.search('(PowerDNS Authoritative Server|PowerDNS Recursor|' +
+                         'dnsdist) (\d+\.\d+\.\d+(-\w+)?)',
+                        cp.stdout.decode())
+    if g_verbose:
+        print(cp.stdout.decode())
     # for some reason 99 is returned on  `cmd --version` :shrug:
     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
+        return cp.returncode, None
+    if version and version.group(2):
+        return cp.returncode, version.group(2)
+    else:
+        return cp.returncode, None
 
 
 def collect_dockerfiles (release):
@@ -234,6 +247,7 @@ def test_release (release):
     dockerfiles = sorted(collect_dockerfiles(release))
     failed_builds = []
     failed_runs = []
+    returned_versions = []
     print('=== testing {} ==='.format(release))
     for df in dockerfiles:
         if g_verbose:
@@ -247,10 +261,13 @@ def test_release (release):
             print('Skipping running {} due to undetermined tag.'.format(df))
             failed_builds.append((str(df), returncode))
         else:
-            returncode = run(tag)
-            # for some reason 99 is returned on  `cmd --version` :shrug:
+            (returncode, return_version) = run(tag)
+            # for some reason 99 is returned on `cmd --version` :shrug:
+            # (not sure if this is true since using `stdout=PIPE...`)
             if returncode != 0 and returncode != 99:
                 failed_runs.append((tag, returncode))
+            if return_version:
+                returned_versions.append((tag, return_version))
     print('Test done.')
     if len(failed_builds) > 0:
         print('- failed builds:')
@@ -260,6 +277,12 @@ def test_release (release):
         print('- failed runs:')
         for fr in failed_runs:
             print('    - {}'.format(fr))
+    if len(returned_versions) > 0:
+        print('- returned versions:')
+        for rv in returned_versions:
+            print('    - {}: {}'.format(rv[0], rv[1]))
+    else:
+        print('- ERROR: no returned versions (unsupported product?)')
 
 
 # Main Program
index 5e7254fad7bc3b664f1e248c190e4cdc622a3235..5e78b7b59b7103331b19c9d93c610967c53775b7 100644 (file)
@@ -1,6 +1,8 @@
 FROM {{ os_image }}:{{ os_version }}
 
-RUN yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm bind-utils
+{% if os_version == '8' or os_version == '9' %}
+RUN yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ os_version }}.noarch.rpm bind-utils
+{% endif %}
 
 {% if os_version == '7' %}
 RUN yum install -y yum-plugin-priorities
index 2a5f441351376ff0250859155902df2cf78af655..189461af67f6e8ef45ef754d1489dbab6d086f62 100755 (executable)
@@ -6,6 +6,9 @@ EOF
 "
 sudo chmod 755 /usr/sbin/policy-rc.d
 sudo apt-get update
-sudo apt-get -qq -y dist-upgrade
-sudo apt-get -qq -y --no-install-recommends install python3-pip
-sudo pip3 install git+https://github.com/pyinvoke/invoke@faa5728a6f76199a3da1750ed952e7efee17c1da
+# 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 python3-invoke
diff --git a/build-scripts/gh-actions-setup-inv-no-dist-upgrade b/build-scripts/gh-actions-setup-inv-no-dist-upgrade
new file mode 100755 (executable)
index 0000000..fc31dfe
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/bash -x
+sudo sh -c "cat > /usr/sbin/policy-rc.d << EOF
+#!/bin/sh
+exit 101
+EOF
+"
+sudo chmod 755 /usr/sbin/policy-rc.d
+sudo apt-get update
+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
index c91a9a3b6c3209a8f9b24ec4733600c64b83c283..e4258e2da0288961d58b36aaaaecf4b6b94aadb2 100755 (executable)
@@ -23,13 +23,16 @@ if [ ! -z "$1" ]; then
   numdomains="$1"
 fi
 
+if false; then
 set +x
 for prefix in 'www' 'wildcard'; do
+  rm -f ${prefix}.csv
   for num in $(seq 0 1000000); do
     echo "${num},${prefix}.powerdnssec.org" >> ${prefix}.csv
   done
 done
 set -x
+fi
 
 EXIT=0
 
diff --git a/builder b/builder
index c66dee9bfa5a676ebd8543c7c111a93242043db6..16bc1604c48821c12b708d7afa88d9612ebdd0da 160000 (submodule)
--- a/builder
+++ b/builder
@@ -1 +1 @@
-Subproject commit c66dee9bfa5a676ebd8543c7c111a93242043db6
+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 5dd51f643b527f5c477e0bc79371e3e327751efa..09ca0fb8d2a4cffcca502159d9ea54584ac9cb6d 100755 (executable)
@@ -32,6 +32,7 @@ override_dh_auto_configure:
                --enable-unit-tests \
                --enable-lua-records \
                --enable-experimental-pkcs11 \
+               --enable-dns-over-tls \
                --disable-silent-rules \
                $(CONFIGURE_ARGS)
 
@@ -62,3 +63,6 @@ override_dh_fixperms:
        dh_fixperms
        # these files often contain passwords.
        chmod 0640 debian/pdns-server/etc/powerdns/pdns.conf
+
+override_dh_builddeb:
+       dh_builddeb -- -Zgzip
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 3207e619bde2a09a04f98540563fd9596ab9a64a..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) \
@@ -47,25 +45,32 @@ override_dh_auto_configure:
          --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-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
@@ -74,7 +79,7 @@ else
 endif
 
 override_dh_installexamples:
-       cp dnsdistconf.lua dnsdist.conf
+       cp dnsdist.conf-dist dnsdist.conf
        dh_installexamples
        rm -f dnsdist.conf
 
@@ -86,3 +91,6 @@ override_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/recursor/debian-buster/README.source b/builder-support/debian/recursor/debian-buster/README.source
deleted file mode 100644 (file)
index cf42723..0000000
+++ /dev/null
@@ -1 +0,0 @@
-See /usr/share/doc/quilt/README.source
diff --git a/builder-support/debian/recursor/debian-buster/configure-helpers/net-snmp-config b/builder-support/debian/recursor/debian-buster/configure-helpers/net-snmp-config
new file mode 100755 (executable)
index 0000000..6d8d6e7
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+if [ "$1" = "--cflags" ]; then
+  FLAGS=$(/usr/bin/net-snmp-config --cflags)
+  MYFLAGS=""
+  for flag in $FLAGS; do
+    if [[ "$flag" =~ -DNETSNMP* ]]; then
+      MYFLAGS="$MYFLAGS $flag"
+    fi
+  done
+  echo "$MYFLAGS"
+  exit 0
+
+elif [ "$1" = "--netsnmp-agent-libs" ]; then
+  /usr/bin/net-snmp-config "$@"
+  exit $?
+
+else
+  echo "E: debian/configure-helpers/net-snmp-config: unknown flag $1" >&2
+  exit 1
+fi
index dcc305e440b6b1a1ee83b3ad6f89b486e284047e..f7e5349a5d142d0520728acf2bd2e75dee7620bf 100644 (file)
@@ -1,28 +1,37 @@
 Source: pdns-recursor
 Section: net
-Priority: extra
-Standards-Version: 4.1.2
-Maintainer: PowerDNS.COM BV <powerdns.support@powerdns.com>
+Maintainer: PowerDNS Autobuilder <powerdns.support@powerdns.com>
+Priority: optional
+Standards-Version: 4.5.1
+Build-Conflicts: libboost-context-dev [mips mipsel]
 Build-Depends: debhelper (>= 10),
                dh-autoreconf,
-               libboost-all-dev,
+               libboost-context-dev [amd64 arm64 armel armhf i386 ppc64el],
+               libboost-dev,
+               libboost-filesystem-dev,
+               libboost-program-options-dev,
+               libboost-system-dev,
+               libboost-test-dev,
+               libboost-thread-dev,
                libcap-dev,
                libcurl4-openssl-dev,
-               libluajit-5.1-dev [!arm64 !s390x],
-               liblua5.3-dev [arm64 s390x],
                libfstrm-dev,
+               libluajit-5.1-dev (>= 2.1.0~beta3+dfsg-5.3) [amd64 arm64] | libluajit-5.1-dev [amd64] | liblua5.3-dev,
+               libprotobuf-dev,
+               libsnmp-dev,
                libsodium-dev,
                libssl-dev,
-               libsystemd-dev [linux-any],
+               libsystemd-dev,
                pkg-config,
+               protobuf-compiler,
                ragel,
-               systemd [linux-any]
-Vcs-Git: https://anonscm.debian.org/git/pkg-dns/pdns-recursor.git
-Vcs-Browser: https://anonscm.debian.org/cgit/pkg-dns/pdns-recursor.git
+               systemd
 Homepage: https://www.powerdns.com/
+Rules-Requires-Root: no
 
 Package: pdns-recursor
 Architecture: any
+Pre-Depends: ${misc:Pre-Depends}
 Depends: adduser,
          dns-root-data,
          ${misc:Depends},
index 8aba47378c53e7f8d974e782879268ab4e2612d9..b8e649b70869dfcf09bf228ff3a63a2c908b3358 100644 (file)
@@ -1,6 +1,7 @@
 Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
 Upstream-Name: PowerDNS
 Source: https://www.powerdns.com/downloads.html
+Upstream-Contact: https://mailman.powerdns.com/mailman/listinfo/pdns-users
 
 Files: *
 Copyright: 2002 - 2022 PowerDNS.COM BV and contributors
@@ -30,8 +31,8 @@ Files: debian/*
 Copyright: 2002 - 2004 Wichert Akkermann <wichert@wiggy.net>
  2004 - 2013 Matthijs Möhlmann <matthijs@cacholong.nl>
  2012 - 2013 Marc Haber <mh+debian-packages@zugschlus.de>
- 2014 - 2016 Chris Hofstaedtler <zeha@debian.org>
- 2016 PowerDNS.COM BV and contributors
+ 2014 - 2018 Chris Hofstaedtler <zeha@debian.org>
+ 2016 - 2018 PowerDNS.COM BV and contributors
 License: GPL-2
 
 Files: ext/yahttp/*
diff --git a/builder-support/debian/recursor/debian-buster/pdns-recursor.default b/builder-support/debian/recursor/debian-buster/pdns-recursor.default
deleted file mode 100644 (file)
index db03e54..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-# Variables for PowerDNS recursor init script.
-# Not honored when systemd is the running init.
-#
-# Set START to yes to start the pdns-recursor
-START=yes
-# Run resolvconf? (Deprecated feature.)
-RESOLVCONF=no
diff --git a/builder-support/debian/recursor/debian-buster/pdns-recursor.init b/builder-support/debian/recursor/debian-buster/pdns-recursor.init
deleted file mode 100644 (file)
index 8b0f44e..0000000
+++ /dev/null
@@ -1,175 +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 - Recursive DNS Server
-# Description:       PowerDNS Recursor - Recursive DNS Server
-### END INIT INFO
-
-#
-# Authors:     Matthijs Möhlmann <matthijs@cacholong.nl>
-#           Christoph Haas <haas@debian.org>
-# 
-# Thanks to:
-# Thomas Hood <jdthood@aglu.demon.nl>
-#
-# initscript for PowerDNS recursor
-
-# Load lsb stuff for systemd redirection (if available).
-if [ -e /lib/lsb/init-functions ]; then
-  . /lib/lsb/init-functions
-fi
-
-PATH=/sbin:/bin:/usr/sbin:/usr/bin
-DESC="PowerDNS Recursor"
-NAME=pdns_recursor
-DAEMON=/usr/sbin/$NAME
-# Derive the socket-dir setting from /etc/powerdns/recursor.conf
-# or fall back to the default /var/run if not specified there.
-PIDDIR=$(awk -F= '/^socket-dir=/ {print $2}' /etc/powerdns/recursor.conf)
-if [ -z "$PIDDIR" ]; then PIDDIR=/var/run/pdns-recursor; mkdir -p $PIDDIR; fi
-PIDFILE=$PIDDIR/$NAME.pid
-
-# Gracefully exit if the package has been removed.
-test -x $DAEMON || exit 0
-
-# Read config file if it is present.
-if [ -r /etc/default/pdns-recursor ]; then
-  . /etc/default/pdns-recursor
-fi
-
-start() {
-# Return
-#  0 if daemon has been started / was already running
-#  >0 if daemon could not be started
-  start-stop-daemon --start --oknodo --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null || return 0
-  start-stop-daemon --start --oknodo --quiet --pidfile $PIDFILE --exec $DAEMON -- --daemon=yes || return 2
-}
-
-start_resolvconf() {
-  if [ "X$RESOLVCONF" = "Xyes" ] && [ -x /sbin/resolvconf ]; then
-    echo "nameserver 127.0.0.1" | /sbin/resolvconf -a lo.pdns-recursor
-  fi
-  return 0
-}
-
-stop() {
-# Return
-#  0 if daemon has been stopped
-#  1 if daemon was already stopped
-#  2 if daemon could not be stopped
-#  other if a failure occurred
-  start-stop-daemon --stop --quiet --pidfile $PIDFILE --name $NAME
-  RETVAL="$?"
-  [ "$RETVAL" = 2 ] && return 2
-  rm -f $PIDFILE
-  return "$RETVAL"
-}
-
-stop_resolvconf() {
-  if [ "X$RESOLVCONF" = "Xyes" ] && [ -x /sbin/resolvconf ]; then
-    /sbin/resolvconf -d lo.pdns-recursor
-  fi
-  return 0
-}
-
-isrunning()
-{
-  /usr/bin/rec_control ping > /dev/null
-  return $?
-}
-
-case "$1" in
-  start)
-    if [ "$START" != "yes" ]; then
-      echo "Not starting $DESC -- disabled."
-      exit 0
-    fi
-    echo -n "Starting $DESC: $NAME ..."
-    start
-    case "$?" in
-      0)
-        start_resolvconf
-        echo done
-        break
-        ;;
-      1)
-        echo "already running"
-        break
-        ;;
-      *)
-        echo "failed"
-        exit 1
-        ;;
-    esac
-  ;;
-  stop)
-    stop_resolvconf
-    echo -n "Stopping $DESC: $NAME ..."
-    stop
-    case "$?" in
-      0)
-        echo done
-        break
-        ;;
-      1)
-        echo "not running"
-        break
-        ;;
-      *)
-        echo "failed"
-        exit 1
-        ;;
-    esac
-  ;;
-  restart|force-reload)
-    if [ "$START" != "yes" ]; then
-      $0 stop
-      exit 0
-    fi
-    echo -n "Restarting $DESC ..."
-    stop
-    case "$?" in
-      0|1)
-        start
-        case "$?" in
-          0)
-            echo done
-            exit 0
-            ;;
-          1)
-            echo "failed -- old process still running"
-            exit 1
-            ;;
-          *)
-            echo "failed to start"
-            exit 1
-            ;;
-        esac
-      ;;
-      *)
-        echo "failed to stop"
-        exit 1
-      ;;
-    esac
-  ;;
-  status)
-    if isrunning; then
-      echo "$NAME is running"
-      exit 0
-    else
-      echo "$NAME is not running or not responding"
-      exit 3
-    fi
-  ;;
-  *)
-    echo "Usage: $0 {start|stop|restart|force-reload|status}" >&2
-    exit 3
-  ;;
-esac
-
-exit 0
-
index b7f625e555c60fbcc526e88695b46c9255cd2efb..d6aeec23c146125f613c10a9d4bcd47ccb56ef30 100644 (file)
@@ -1,4 +1,2 @@
 # Source carries OpenSSL Exception
 pdns-recursor: possible-gpl-code-linked-with-openssl
-# We load lsb-functions conditionally.
-pdns-recursor: init.d-script-needs-depends-on-lsb-base
index 4e1da7099252129ed5d1b5683cd92ee006ce0983..5f83e9d07f37b945ac04413ec984f06a85ef614c 100644 (file)
@@ -3,17 +3,8 @@ set -e
 
 case "$1" in
   configure)
-    if [ -z "`getent group pdns`" ]; then
-      addgroup --system pdns
-    fi
-    if [ -z "`getent passwd pdns`" ]; then
-      adduser --system --home /var/spool/powerdns --shell /bin/false --ingroup pdns --disabled-password --disabled-login --gecos "PowerDNS" pdns
-    fi
-    if [ "`stat -c '%U:%G' /etc/powerdns/recursor.conf`" = "root:root" ]; then
-      chown root:pdns /etc/powerdns/recursor.conf
-      # Make sure that pdns can read it; the default used to be 0600
-      chmod g+r /etc/powerdns/recursor.conf
-    fi
+    addgroup --system pdns
+    adduser --system --home /var/spool/powerdns --shell /bin/false --ingroup pdns --disabled-password --disabled-login --gecos "PowerDNS" pdns
   ;;
 
   *)
@@ -22,11 +13,6 @@ case "$1" in
   ;;
 esac
 
-# Startup errors should never cause dpkg to fail.
-initscript_error() {
-    return 0
-}
-
 #DEBHELPER#
 
 exit 0
diff --git a/builder-support/debian/recursor/debian-buster/pdns-recursor.preinst b/builder-support/debian/recursor/debian-buster/pdns-recursor.preinst
new file mode 100644 (file)
index 0000000..691107c
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/sh
+set -e
+
+delete_unchanged() {
+  if [ -e "$1" ] && echo "$2 $1" | md5sum --check --status; then
+    echo "Removing unchanged configuration file $1"
+    rm -f "$1"
+  fi
+}
+
+backup_conffile() {
+  if [ -e "$1" ]; then
+    echo "Moving configuration file $1 to $1.dpkg-bak"
+    mv -f "$1" "$1".dpkg-bak
+  fi
+}
+
+case "$1" in
+  install|upgrade)
+    # clean up files we no longer ship
+    delete_unchanged "/etc/default/pdns-recursor" a09916ceb17db9a49ac8cfa84790bf3b
+    delete_unchanged "/etc/default/pdns-recursor" 076b21b9b76d7ffecc918af47d2963c6
+    backup_conffile "/etc/default/pdns-recursor"
+    delete_unchanged "/etc/init.d/pdns-recursor" e2ea0586c3d99fdbafb76483a769b964
+    delete_unchanged "/etc/init.d/pdns-recursor" fb608ec5edc3d068213bac3480782355
+    backup_conffile "/etc/init.d/pdns-recursor"
+  ;;
+esac
+
+#DEBHELPER#
diff --git a/builder-support/debian/recursor/debian-buster/pdns-recursor.prerm b/builder-support/debian/recursor/debian-buster/pdns-recursor.prerm
deleted file mode 100644 (file)
index e78608c..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/sh
-set -e
-
-# Startup errors should never cause dpkg to fail.
-initscript_error() {
-    return 0
-}
-
-#DEBHELPER#
-
-exit 0
index cb024403b6d891988cb29965c3fdde5e986a630c..9e122d632667bf1dcb901b70ec3a1906757282c4 100755 (executable)
@@ -1,77 +1,67 @@
 #!/usr/bin/make -f
-include /usr/share/dpkg/architecture.mk
-include /usr/share/dpkg/pkg-info.mk
 
-# Enable hardening features for daemons
+# Turn on all hardening flags, as we're a networked daemon.
 # Note: blhc (build log hardening check) will find these false positives: CPPFLAGS 2 missing, LDFLAGS 1 missing
-export DEB_BUILD_MAINT_OPTIONS=hardening=+bindnow,+pie
+export DEB_BUILD_MAINT_OPTIONS = hardening=+all
+# see EXAMPLES in dpkg-buildflags(1) and read /usr/share/dpkg/*
 DPKG_EXPORT_BUILDFLAGS = 1
-# Include buildflags.mk so we can append to the vars it sets.
-include /usr/share/dpkg/buildflags.mk
+include /usr/share/dpkg/default.mk
 
-# 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 disable luajit on arm64
-ifneq ($(DEB_HOST_ARCH),arm64)
-CONFIGURE_ARGS += --with-lua=luajit
-else
-CONFIGURE_ARGS += --with-lua=lua5.3
-endif
 
-# Use new build system
 %:
-       dh $@ \
-         --with autoreconf \
-         $(DH_ARGS)
+       dh $@
+
+override_dh_auto_clean:
+       dh_auto_clean
+       rm -f dnslabeltext.cc
+       chmod +x mkpubsuffixcc || true
 
 override_dh_auto_configure:
-       dh_auto_configure -- \
+       PATH=debian/configure-helpers/:$$PATH dh_auto_configure -- \
                --sysconfdir=/etc/powerdns \
+               --enable-systemd --with-systemd=/lib/systemd/system \
                --enable-unit-tests \
+               --disable-silent-rules \
+               --with-service-user=pdns \
+               --with-service-group=pdns \
                --with-libcap \
                --with-libsodium \
+               --with-lua \
+               --with-net-snmp \
                --enable-dns-over-tls \
                --enable-dnstap \
-               --without-net-snmp \
-               --disable-silent-rules \
-               --with-service-user=pdns \
-               --with-service-group=pdns \
-               $(CONFIGURE_ARGS)
+               --enable-nod
 
 override_dh_auto_install:
        dh_auto_install
        install -d debian/pdns-recursor/usr/share/pdns-recursor/lua-config
        install -m 644 -t debian/pdns-recursor/usr/share/pdns-recursor/lua-config debian/lua-config/rootkeys.lua
        install -m 644 -t debian/pdns-recursor/etc/powerdns debian/recursor.lua
+       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
-       ./pdns_recursor --config=default | sed \
-               -e 's!# config-dir=.*!config-dir=/etc/powerdns!' \
-               -e 's!# include-dir=.*!&\ninclude-dir=/etc/powerdns/recursor.d!' \
-               -e 's!# local-address=.*!local-address=127.0.0.1!' \
-               -e 's!# lua-config-file=.*!lua-config-file=/etc/powerdns/recursor.lua!' \
-               -e 's!# quiet=.*!quiet=yes!' \
-               -e 's!# setgid=.*!setgid=pdns!' \
-               -e 's!# setuid=.*!setuid=pdns!' \
-               -e 's!# hint-file=.*!&\nhint-file=/usr/share/dns/root.hints!' \
+       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!' \
+               -e 's!^# include-dir=.*!&\ninclude-dir=/etc/powerdns/recursor.d!' \
+               -e 's!^# local-address=.*!local-address=127.0.0.1!' \
+               -e 's!^# lua-config-file=.*!lua-config-file=/etc/powerdns/recursor.lua!' \
+               -e 's!^# quiet=.*!quiet=yes!' \
                -e '/^# version-string=.*/d' \
                > debian/pdns-recursor/etc/powerdns/recursor.conf
 
-override_dh_strip:
-       dh_strip --ddeb-migration='pdns-recursor-dbg'
-
-override_dh_installinit:
-       dh_installinit --error-handler=initscript_error
+override_dh_auto_test:
+ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
+       dh_auto_test
+       -cat testrunner.log
+endif
 
 override_dh_gencontrol:
        dh_gencontrol -- $(SUBSTVARS)
 
-override_dh_fixperms:
-       dh_fixperms
-# these files often contain passwords. 640 as it is chowned to root:pdns
-       chmod 0640 debian/pdns-recursor/etc/powerdns/recursor.conf
+# Explicitly set a compression method, as Debian and Ubuntu defaults vary widely,
+# and xz support is not available in all tools yet. Removing this override can
+# make reprepro fail.
+override_dh_builddeb:
+       dh_builddeb -- -Zgzip
diff --git a/builder-support/debian/recursor/debian-buster/source.lintian-overrides b/builder-support/debian/recursor/debian-buster/source.lintian-overrides
deleted file mode 100644 (file)
index 700fed0..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-# Source is in html/js/d3.js
-pdns-recursor source: source-is-missing html/js/d3.v3.js line length is 32005 characters (>512)
index a0a6fc4a76c1ea62ba6307c592d73033dbbc8af7..bf44d57917264cb38729f55e05571137de3ab19c 100644 (file)
@@ -1,3 +1,4 @@
 Tests: smoke
-Depends: @, dnsutils
+Depends: dnsutils,
+         @
 Restrictions: needs-root
index 797073364207a42caa696d420dcf2ecc78055f51..23f78fefe0fb05d55ee145c16e9cf7f4546db07d 100755 (executable)
@@ -2,6 +2,12 @@
 exec 2>&1
 set -ex
 
+restart_failed() {
+    echo E: service restart failed
+    journalctl -n200 --no-pager
+    exit 1
+}
+
 cat <<EOF >>/etc/powerdns/recursor.conf
 auth-zones=example.org=/etc/powerdns/example.org.zone
 EOF
@@ -12,11 +18,11 @@ example.org.           172800  IN      NS      ns1.example.org.
 smoke.example.org.     172800  IN      A       127.0.0.123
 EOF
 
-service pdns-recursor restart
+service pdns-recursor restart || restart_failed
 
 TMPFILE=$(mktemp)
 cleanup() {
-  rm -f "$TMPFILE"
+    rm -f "$TMPFILE"
 }
 trap cleanup EXIT
 
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 cd339437c0cb17adef7e6121fb19a5265ff8f3c3..64a5d401c5cfacd722f71f8b399f794cabcf4a0c 100644 (file)
@@ -1,11 +1,22 @@
 FROM dist-base as package-builder
 ARG APT_URL
-RUN DEBIAN_FRONTEND=noninteractive apt-get -y install 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 f4b579f578828df601a432bbbf2d649ee4ecaae3..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
@@ -23,7 +42,7 @@ RUN find /pdns/builder-support/specs/ -not -name '*.spec' -exec ln -s {} /root/r
 
 @IF [ -n "$M_authoritative$M_all" ]
 RUN touch /var/lib/rpm/* && if $(grep -q 'release 7' /etc/redhat-release); then \
-      scl enable devtoolset-8 -- builder/helpers/build-specs.sh builder-support/specs/pdns.spec; \
+      scl enable devtoolset-11 -- builder/helpers/build-specs.sh builder-support/specs/pdns.spec; \
     else \
       builder/helpers/build-specs.sh builder-support/specs/pdns.spec; \
     fi
@@ -31,26 +50,37 @@ RUN touch /var/lib/rpm/* && if $(grep -q 'release 7' /etc/redhat-release); then
 
 @IF [ -n "$M_recursor$M_all" ]
 RUN touch /var/lib/rpm/* &&  if $(grep -q 'release 7' /etc/redhat-release); then \
-      scl enable devtoolset-8 -- builder/helpers/build-specs.sh builder-support/specs/pdns-recursor.spec; \
+      scl enable devtoolset-11 -- builder/helpers/build-specs.sh builder-support/specs/pdns-recursor.spec; \
     else \
       builder/helpers/build-specs.sh builder-support/specs/pdns-recursor.spec; \
     fi
 @ENDIF
 
 @IF [ -n "$M_dnsdist$M_all" ]
+
+# --allowerasing does not exist on el7, so we fall back to just installing
+# 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 curl 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 && \
+      yum install -y --allowerasing curl libcurl openssl-devel cmake || yum install -y curl libcurl openssl-devel cmake && \
+      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 \
-      scl enable devtoolset-8 -- builder/helpers/build-specs.sh builder-support/specs/dnsdist.spec; \
+      scl enable devtoolset-11 -- builder/helpers/build-specs.sh builder-support/specs/dnsdist.spec; \
     else \
       builder/helpers/build-specs.sh builder-support/specs/dnsdist.spec; \
     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/
diff --git a/builder-support/dockerfiles/Dockerfile.target.amazon-2023 b/builder-support/dockerfiles/Dockerfile.target.amazon-2023
new file mode 100644 (file)
index 0000000..f47d6d7
--- /dev/null
@@ -0,0 +1,14 @@
+# First do the source builds
+@INCLUDE Dockerfile.target.sdist
+
+# This defines the distribution base layer
+# Put only the bare minimum of common commands here, without dev tools
+FROM amazonlinux:2023 as dist-base
+ARG BUILDER_CACHE_BUSTER=
+
+# Do the actual rpm build
+@INCLUDE Dockerfile.rpmbuild
+
+# Do a test install and verify
+# Can be skipped with skiptests=1 in the environment
+# @EXEC [ "$skiptests" = "" ] && include Dockerfile.rpmtest
index 9deb58b99c6d448ab53b4253847ea99f875b68e9..9bd7c2a1243e76a4915b9c73b9ac0b83771c7e92 100644 (file)
@@ -12,7 +12,7 @@ FROM amd64/centos:7 as dist-base
 
 ARG BUILDER_CACHE_BUSTER=
 RUN touch /var/lib/rpm/* && yum install -y epel-release centos-release-scl-rh
-RUN touch /var/lib/rpm/* && yum install -y --nogpgcheck devtoolset-8-gcc-c++
+RUN touch /var/lib/rpm/* && yum install -y --nogpgcheck devtoolset-11-gcc-c++
 
 # Do the actual rpm build
 @INCLUDE Dockerfile.rpmbuild
diff --git a/builder-support/dockerfiles/Dockerfile.target.centos-9-stream b/builder-support/dockerfiles/Dockerfile.target.centos-9-stream
new file mode 100644 (file)
index 0000000..873f63f
--- /dev/null
@@ -0,0 +1,19 @@
+# First do the source builds
+@INCLUDE Dockerfile.target.sdist
+
+# This defines the distribution base layer
+# Put only the bare minimum of common commands here, without dev tools
+FROM quay.io/centos/centos:stream9 as dist-base
+
+ARG BUILDER_CACHE_BUSTER=
+
+RUN touch /var/lib/rpm/* && dnf install -y epel-release && \
+    dnf install -y 'dnf-command(config-manager)' && \
+    dnf config-manager --set-enabled crb
+
+# Do the actual rpm build
+@INCLUDE Dockerfile.rpmbuild
+
+# Do a test install and verify
+# Can be skipped with skiptests=1 in the environment
+# @EXEC [ "$skiptests" = "" ] && include Dockerfile.rpmtest
diff --git a/builder-support/dockerfiles/Dockerfile.target.debian-bookworm b/builder-support/dockerfiles/Dockerfile.target.debian-bookworm
new file mode 100644 (file)
index 0000000..073bb76
--- /dev/null
@@ -0,0 +1,36 @@
+# First do the source builds
+@INCLUDE Dockerfile.target.sdist
+
+@IF [ ${BUILDER_TARGET} = debian-bookworm ]
+FROM debian:bookworm as dist-base
+@ENDIF
+@IF [ ${BUILDER_TARGET} = debian-bookworm-amd64 ]
+FROM amd64/debian:bookworm as dist-base
+@ENDIF
+@IF [ ${BUILDER_TARGET} = debian-bookworm-arm64 ]
+FROM arm64v8/debian:bookworm 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.debian-bookworm-amd64 b/builder-support/dockerfiles/Dockerfile.target.debian-bookworm-amd64
new file mode 120000 (symlink)
index 0000000..3e463e2
--- /dev/null
@@ -0,0 +1 @@
+Dockerfile.target.debian-bookworm
\ No newline at end of file
diff --git a/builder-support/dockerfiles/Dockerfile.target.debian-bookworm-arm64 b/builder-support/dockerfiles/Dockerfile.target.debian-bookworm-arm64
new file mode 120000 (symlink)
index 0000000..3e463e2
--- /dev/null
@@ -0,0 +1 @@
+Dockerfile.target.debian-bookworm
\ No newline at end of file
diff --git a/builder-support/dockerfiles/Dockerfile.target.debian-trixie b/builder-support/dockerfiles/Dockerfile.target.debian-trixie
new file mode 100644 (file)
index 0000000..8642f32
--- /dev/null
@@ -0,0 +1,36 @@
+# First do the source builds
+@INCLUDE Dockerfile.target.sdist
+
+@IF [ ${BUILDER_TARGET} = debian-trixie ]
+FROM debian:trixie as dist-base
+@ENDIF
+@IF [ ${BUILDER_TARGET} = debian-trixie-amd64 ]
+FROM amd64/debian:trixie as dist-base
+@ENDIF
+@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
+
+@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.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 \
diff --git a/builder-support/dockerfiles/Dockerfile.target.el-9 b/builder-support/dockerfiles/Dockerfile.target.el-9
new file mode 100644 (file)
index 0000000..c5766a8
--- /dev/null
@@ -0,0 +1,26 @@
+# First do the source builds
+@INCLUDE Dockerfile.target.sdist
+
+# This defines the distribution base layer
+# Put only the bare minimum of common commands here, without dev tools
+@IF [ ${BUILDER_TARGET} = oraclelinux-9 -o ${BUILDER_TARGET} = el-9 ]
+FROM oraclelinux:9 as dist-base
+@ENDIF
+@IF [ ${BUILDER_TARGET} = oraclelinux-9-amd64 -o ${BUILDER_TARGET} = el-9-amd64 ]
+FROM amd64/oraclelinux:9 as dist-base
+@ENDIF
+@IF [ ${BUILDER_TARGET} = oraclelinux-9-arm64 -o ${BUILDER_TARGET} = el-9-arm64 ]
+FROM arm64v8/oraclelinux:9 as dist-base
+@ENDIF
+
+ARG BUILDER_CACHE_BUSTER=
+RUN touch /var/lib/rpm/* && dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm && \
+    dnf install -y 'dnf-command(config-manager)' yum && \
+    dnf config-manager --set-enabled ol9_codeready_builder
+
+# Do the actual rpm build
+@INCLUDE Dockerfile.rpmbuild
+
+# Do a test install and verify
+# Can be skipped with skiptests=1 in the environment
+# @EXEC [ "$skiptests" = "" ] && include Dockerfile.rpmtest
diff --git a/builder-support/dockerfiles/Dockerfile.target.el-9-amd64 b/builder-support/dockerfiles/Dockerfile.target.el-9-amd64
new file mode 120000 (symlink)
index 0000000..46968c4
--- /dev/null
@@ -0,0 +1 @@
+Dockerfile.target.el-9
\ No newline at end of file
diff --git a/builder-support/dockerfiles/Dockerfile.target.el-9-arm64 b/builder-support/dockerfiles/Dockerfile.target.el-9-arm64
new file mode 120000 (symlink)
index 0000000..46968c4
--- /dev/null
@@ -0,0 +1 @@
+Dockerfile.target.el-9
\ No newline at end of file
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
similarity index 75%
rename from builder-support/dockerfiles/Dockerfile.target.ubuntu-bionic
rename to builder-support/dockerfiles/Dockerfile.target.ubuntu-lunar
index fe38051feda3765b9e4ac6600ff67fd7e9b3202a..ce71c39c03afa09a7fb870da0c866e5cf18b6e3c 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} = ubuntu-lunar ]
+FROM ubuntu:lunar as dist-base
 @ENDIF
-@IF [ ${BUILDER_TARGET} = ubuntu-bionic-amd64 ]
-FROM amd64/ubuntu:bionic as dist-base
+@IF [ ${BUILDER_TARGET} = ubuntu-lunar-amd64 ]
+FROM amd64/ubuntu:lunar as dist-base
 @ENDIF
-@IF [ ${BUILDER_TARGET} = ubuntu-bionic-arm64 ]
-FROM arm64v8/ubuntu:bionic as dist-base
+@IF [ ${BUILDER_TARGET} = ubuntu-lunar-arm64 ]
+FROM arm64v8/ubuntu:lunar as dist-base
 @ENDIF
+
 ARG BUILDER_CACHE_BUSTER=
 ARG APT_URL
 RUN apt-get update && apt-get -y dist-upgrade
diff --git a/builder-support/dockerfiles/Dockerfile.target.ubuntu-lunar-amd64 b/builder-support/dockerfiles/Dockerfile.target.ubuntu-lunar-amd64
new file mode 120000 (symlink)
index 0000000..5ab4dd2
--- /dev/null
@@ -0,0 +1 @@
+Dockerfile.target.ubuntu-lunar
\ No newline at end of file
diff --git a/builder-support/dockerfiles/Dockerfile.target.ubuntu-lunar-arm64 b/builder-support/dockerfiles/Dockerfile.target.ubuntu-lunar-arm64
new file mode 120000 (symlink)
index 0000000..5ab4dd2
--- /dev/null
@@ -0,0 +1 @@
+Dockerfile.target.ubuntu-lunar
\ 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
diff --git a/builder-support/dockerfiles/Dockerfile.target.ubuntu-noble b/builder-support/dockerfiles/Dockerfile.target.ubuntu-noble
new file mode 100644 (file)
index 0000000..e25bddf
--- /dev/null
@@ -0,0 +1,38 @@
+# First do the source builds
+@INCLUDE Dockerfile.target.sdist
+
+@IF [ ${BUILDER_TARGET} = ubuntu-noble ]
+FROM ubuntu:noble as dist-base
+@ENDIF
+@IF [ ${BUILDER_TARGET} = ubuntu-noble-amd64 ]
+FROM amd64/ubuntu:noble as dist-base
+@ENDIF
+@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
+
+@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-buster/ dnsdist-${BUILDER_VERSION}/debian/
+@ENDIF
+
+@INCLUDE Dockerfile.debbuild
+
+# Do a test install and verify
+# Can be skipped with skiptests=1 in the environment
+# @EXEC [ "$skiptests" = "" ] && include Dockerfile.debtest
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..77a3fea
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+set -v
+set -e
+
+readonly QUICHE_VERSION='0.20.0'
+readonly QUICHE_TARBALL="${QUICHE_VERSION}.tar.gz"
+readonly QUICHE_TARBALL_URL="https://github.com/cloudflare/quiche/archive/${QUICHE_TARBALL}"
+readonly QUICHE_TARBALL_HASH='7125bc82ddcf38fbfbc69882ccb2723bfb4d5bfeb42718b8291d26ec06042e38'
+
+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 2192c0373d9440f93cc5a38b68506c5a46826e5d..407a03d3cd7a4a8f31a7b26c62c02febfd6cf1ac 100644 (file)
@@ -17,18 +17,17 @@ BuildRequires: systemd-units
 BuildRequires: systemd-devel
 %endif
 
-%if 0%{?rhel} < 8
+%if 0%{?rhel} < 8 && 0%{?amzn} != 2023
 BuildRequires: boost169-devel
 %else
 BuildRequires: boost-devel
 %endif
 
-%if 0%{?rhel} >= 7
+%if 0%{?rhel} >= 7 || 0%{?amzn} == 2023
 BuildRequires: gnutls-devel
 BuildRequires: libcap-devel
 BuildRequires: libnghttp2-devel
 BuildRequires: lmdb-devel
-BuildRequires: libsodium-devel
 %ifarch aarch64
 BuildRequires: lua-devel
 %define lua_implementation lua
@@ -36,23 +35,30 @@ BuildRequires: lua-devel
 BuildRequires: luajit-devel
 %define lua_implementation luajit
 %endif
-BuildRequires: net-snmp-devel
 BuildRequires: re2-devel
 BuildRequires: systemd
 BuildRequires: systemd-devel
 BuildRequires: systemd-units
 BuildRequires: tinycdb-devel
+%if 0%{?amzn} != 2023
+BuildRequires: libsodium-devel
+BuildRequires: net-snmp-devel
+%endif
 %endif
 
 %if 0%{?suse_version}
 Requires(pre): shadow
 %systemd_requires
 %endif
-%if 0%{?rhel} >= 7
+%if 0%{?rhel} >= 7 || 0%{?amzn} == 2023
 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.
@@ -60,15 +66,15 @@ 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
 export LDFLAGS=-L/usr/lib64/boost169
 %endif
 
+export AR=gcc-ar
+export RANLIB=gcc-ranlib
+
 %configure \
   --enable-option-checking=fatal \
   --sysconfdir=/etc/dnsdist \
@@ -76,30 +82,38 @@ export LDFLAGS=-L/usr/lib64/boost169
   --disable-dependency-tracking \
   --disable-silent-rules \
   --enable-unit-tests \
+  --enable-lto=thin \
   --enable-dns-over-tls \
+  --with-h2o \
 %if 0%{?suse_version}
   --disable-dnscrypt \
   --without-libsodium \
   --without-re2 \
-  --enable-systemd --with-systemd=/lib/systemd/system \
+  --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=/lib/systemd/system \
+  --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}
-mv dnsdistconf.lua dnsdist.conf.sample
 
 %check
 make %{?_smp_mflags} check || (cat test-suite.log && false)
@@ -107,8 +121,11 @@ make %{?_smp_mflags} check || (cat test-suite.log && false)
 %install
 %make_install
 install -d %{buildroot}/%{_sysconfdir}/dnsdist
-sed -i "s,/^\(ExecStart.*\)dnsdist\(.*\)\$,\1dnsdist -u dnsdist -g dnsdist\2," %{buildroot}/lib/systemd/system/dnsdist.service
-sed -i "s,/^\(ExecStart.*\)dnsdist\(.*\)\$,\1dnsdist -u dnsdist -g dnsdist\2," %{buildroot}/lib/systemd/system/dnsdist@.service
+%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
 
 %pre
 getent group dnsdist >/dev/null || groupadd -r dnsdist
@@ -144,9 +161,13 @@ systemctl daemon-reload ||:
 
 %files
 %{!?_licensedir:%global license %%doc}
-%doc dnsdist.conf.sample
 %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
-/lib/systemd/system/dnsdist*
+%attr(-, root, dnsdist) %config(noreplace) %{_sysconfdir}/%{name}/dnsdist.conf
+%{_unitdir}/dnsdist*
index 983312690e60032a429d3a3f292b8a1ba50c9fa1..8408500e98a8c4ef8ed4a6134e4331be20a5b85e 100644 (file)
@@ -9,7 +9,7 @@ Source0: %{name}-%{getenv:BUILDER_VERSION}.tar.bz2
 
 Provides: powerdns-recursor = %{version}-%{release}
 
-%if 0%{?rhel} < 8
+%if 0%{?rhel} < 8 && 0%{?amzn} != 2023
 BuildRequires: boost169-devel
 %else
 BuildRequires: boost-devel
@@ -18,11 +18,14 @@ BuildRequires: libcap-devel
 BuildRequires: systemd
 BuildRequires: systemd-devel
 BuildRequires: openssl-devel
-BuildRequires: net-snmp-devel
-BuildRequires: libsodium-devel
 BuildRequires: fstrm-devel
 BuildRequires: libcurl-devel
 
+%if 0%{?amzn} != 2023
+BuildRequires: net-snmp-devel
+BuildRequires: libsodium-devel
+%endif
+
 %ifarch aarch64
 BuildRequires: lua-devel
 %define lua_implementation lua
@@ -44,7 +47,7 @@ package if you need a dns cache for your network.
 
 
 %prep
-%autosetup -p1 -n %{name}-%{getenv:BUILDER_VERSION} 
+%autosetup -p1 -n %{name}-%{getenv:BUILDER_VERSION}
 
 %build
 %if 0%{?rhel} < 8
@@ -55,8 +58,6 @@ export LDFLAGS=-L/usr/lib64/boost169
 %configure \
     --enable-option-checking=fatal \
     --sysconfdir=%{_sysconfdir}/%{name} \
-    --with-libsodium \
-    --with-net-snmp \
     --disable-silent-rules \
     --disable-static \
     --enable-unit-tests \
@@ -64,7 +65,12 @@ export LDFLAGS=-L/usr/lib64/boost169
     --enable-dnstap \
     --with-libcap \
     --with-lua=%{lua_implementation} \
-    --enable-systemd --with-systemd=%{_unitdir}
+%if 0%{?amzn} != 2023
+    --with-libsodium \
+    --with-net-snmp \
+%endif
+    --enable-systemd --with-systemd=%{_unitdir} \
+    --enable-nod
 
 make %{?_smp_mflags}
 
@@ -75,11 +81,13 @@ make %{?_smp_mflags} check || (cat test-suite.log && false)
 make install DESTDIR=%{buildroot}
 
 %{__mv} %{buildroot}%{_sysconfdir}/%{name}/recursor.conf{-dist,}
+%{__mkdir} %{buildroot}%{_sysconfdir}/%{name}/recursor.d
 
-# change user and group to pdns-recursor
+# change user and group to pdns-recursor and add default include-dir
 sed -i \
     -e 's/# setuid=/setuid=pdns-recursor/' \
     -e 's/# setgid=/setgid=pdns-recursor/' \
+    -e 's!# include-dir=.*!&\ninclude-dir=%{_sysconfdir}/%{name}/recursor.d!' \
     %{buildroot}%{_sysconfdir}/%{name}/recursor.conf
 
 # The EL7 and 8 systemd actually supports %t, but its version number is older than that, so we do use seperate runtime dirs, but don't rely on RUNTIME_DIRECTORY
@@ -115,5 +123,7 @@ systemctl daemon-reload ||:
 %{_unitdir}/pdns-recursor.service
 %{_unitdir}/pdns-recursor@.service
 %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 754ccc59031ae22367187866775c8a3a4a7febe5..9f265eda1d543b040e40aa13cb45315941d6dd7a 100644 (file)
@@ -10,25 +10,28 @@ License: GPLv2
 URL: https://powerdns.com
 Source0: %{name}-%{getenv:BUILDER_VERSION}.tar.bz2
 
-Requires(post): systemd-sysv
-Requires(post): systemd-units
-Requires(preun): systemd-units
-Requires(postun): systemd-units
+Requires(post): systemd
+Requires(preun): systemd
+Requires(postun): systemd
 BuildRequires: systemd
 BuildRequires: systemd-units
 BuildRequires: systemd-devel
 
+BuildRequires: krb5-devel
 BuildRequires: p11-kit-devel
 BuildRequires: libcurl-devel
-%if 0%{?rhel} < 8
+%if 0%{?rhel} < 8 && 0%{?amzn} != 2023
 BuildRequires: boost169-devel
 %else
 BuildRequires: boost-devel
 %endif
-BuildRequires: libsodium-devel
 BuildRequires: bison
 BuildRequires: openssl-devel
 
+%if 0%{?amzn} != 2023
+BuildRequires: libsodium-devel
+%endif
+
 Requires(pre): shadow-utils
 
 %ifarch aarch64
@@ -59,7 +62,7 @@ This package contains the extra tools for %{name}
 Summary: MySQL backend for %{name}
 Group: System Environment/Daemons
 Requires: %{name}%{?_isa} = %{version}-%{release}
-%if 0%{?rhel} < 8
+%if 0%{?rhel} < 8 && 0%{?amzn} != 2023
 BuildRequires: mysql-devel
 %else
 BuildRequires: mariadb-connector-c-devel
@@ -141,7 +144,9 @@ Summary: Geo backend for %{name}
 Group: System Environment/Daemons
 Requires: %{name}%{?_isa} = %{version}-%{release}
 BuildRequires: yaml-cpp-devel
+%if 0%{?rhel} < 9 && 0%{?amzn} != 2023
 BuildRequires: geoip-devel
+%endif
 BuildRequires: libmaxminddb-devel
 %global backends %{backends} geoip
 
@@ -201,13 +206,16 @@ export LDFLAGS=-L/usr/lib64/boost169
   --with-lua=%{lua_implementation} \
   --with-dynmodules='%{backends}' \
   --enable-tools \
+%if 0%{?amzn} != 2023
   --with-libsodium \
+%endif
 %if 0%{?amzn} != 2
   --enable-ixfrdist \
 %endif
   --enable-unit-tests \
   --enable-lua-records \
   --enable-experimental-pkcs11 \
+  --enable-dns-over-tls \
   --enable-systemd
 
 make %{?_smp_mflags}
@@ -335,6 +343,7 @@ systemctl daemon-reload ||:
 %doc modules/gmysqlbackend/3.4.0_to_4.1.0_schema.mysql.sql
 %doc modules/gmysqlbackend/4.1.0_to_4.2.0_schema.mysql.sql
 %doc modules/gmysqlbackend/4.2.0_to_4.3.0_schema.mysql.sql
+%doc modules/gmysqlbackend/4.3.0_to_4.7.0_schema.mysql.sql
 %doc modules/gmysqlbackend/enable-foreign-keys.mysql.sql
 %{_libdir}/%{name}/libgmysqlbackend.so
 
@@ -345,6 +354,7 @@ systemctl daemon-reload ||:
 %doc modules/gpgsqlbackend/3.4.0_to_4.1.0_schema.pgsql.sql
 %doc modules/gpgsqlbackend/4.1.0_to_4.2.0_schema.pgsql.sql
 %doc modules/gpgsqlbackend/4.2.0_to_4.3.0_schema.pgsql.sql
+%doc modules/gpgsqlbackend/4.3.0_to_4.7.0_schema.pgsql.sql
 %{_libdir}/%{name}/libgpgsqlbackend.so
 
 %files backend-pipe
@@ -370,12 +380,14 @@ systemctl daemon-reload ||:
 %doc modules/gsqlite3backend/4.0.0_to_4.2.0_schema.sqlite3.sql
 %doc modules/gsqlite3backend/4.2.0_to_4.3.0_schema.sqlite3.sql
 %doc modules/gsqlite3backend/4.3.0_to_4.3.1_schema.sqlite3.sql
+%doc modules/gsqlite3backend/4.3.1_to_4.7.0_schema.sqlite3.sql
 %{_libdir}/%{name}/libgsqlite3backend.so
 
 %files backend-odbc
 %doc modules/godbcbackend/schema.mssql.sql
 %doc modules/godbcbackend/4.0.0_to_4.2.0_schema.mssql.sql
 %doc modules/godbcbackend/4.2.0_to_4.3.0_schema.mssql.sql
+%doc modules/godbcbackend/4.3.0_to_4.7.0_schema.mssql.sql
 %{_libdir}/%{name}/libgodbcbackend.so
 
 %files backend-geoip
index efa0adfa7884a9782695652f711b08c050234441..348127854d4e8611e7c77aecdc7167c1a3a34b6a 100644 (file)
@@ -4,7 +4,7 @@ AC_INIT([pdns], m4_esyscmd([builder-support/gen-version]))
 AC_CONFIG_AUX_DIR([build-aux])
 AM_INIT_AUTOMAKE([foreign dist-bzip2 no-dist-gzip tar-ustar -Wno-portability subdir-objects parallel-tests 1.11])
 AM_SILENT_RULES([yes])
-AC_CONFIG_SRCDIR([pdns/receiver.cc])
+AC_CONFIG_SRCDIR([pdns/auth-main.cc])
 AC_CONFIG_MACRO_DIR([m4])
 
 AC_USE_SYSTEM_EXTENSIONS
@@ -12,8 +12,8 @@ AC_CONFIG_HEADERS([config.h])
 
 AC_CANONICAL_HOST
 # Add some default CFLAGS and CXXFLAGS, can be appended to using the environment variables
-CFLAGS="-g -O2 -Wall -Wextra -Wshadow -Wno-unused-parameter -Wmissing-declarations -Wredundant-decls $CFLAGS"
-CXXFLAGS="-g -O2 -Wall -Wextra -Wshadow -Wno-unused-parameter -Wmissing-declarations -Wredundant-decls $CXXFLAGS"
+CFLAGS="-g -O2 -Wall -Wextra -Wshadow -Wmissing-declarations -Wredundant-decls $CFLAGS"
+CXXFLAGS="-g -O2 -Wall -Wextra -Wshadow -Wmissing-declarations -Wredundant-decls $CXXFLAGS"
 
 AC_SUBST([pdns_configure_args], ["$ac_configure_args"])
 AC_DEFINE_UNQUOTED([PDNS_CONFIG_ARGS],
@@ -39,6 +39,8 @@ 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
@@ -83,6 +85,7 @@ AC_CHECK_HEADERS(
        )],
        [have_mmap=no]
 )
+AC_CHECK_HEADERS([sys/random.h])
 
 PDNS_WITH_LIBSODIUM
 PDNS_WITH_LIBDECAF
@@ -127,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
 
@@ -151,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"])
 
@@ -163,6 +169,7 @@ AC_SUBST([LIBDL], [$lt_cv_dlopen_libs])
 
 PDNS_ENABLE_VERBOSE_LOGGING
 PDNS_ENABLE_PKCS11
+PDNS_ENABLE_GSS_TSIG
 
 AC_SUBST([socketdir])
 socketdir="/var/run"
@@ -303,11 +310,17 @@ 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
+PDNS_INIT_AUTO_VARS
 PDNS_ENABLE_SANITIZERS
 PDNS_ENABLE_MALLOC_TRACE
+PDNS_ENABLE_LTO
 
 AC_SUBST(LIBS)
 
@@ -323,6 +336,9 @@ AC_SUBST([IPCRYPT_LIBS], ['$(top_builddir)/ext/ipcrypt/libipcrypt.la'])
 CFLAGS="$SANITIZER_FLAGS $CFLAGS"
 CXXFLAGS="$SANITIZER_FLAGS $CXXFLAGS"
 
+CCVERSION=`$CC --version | head -1`
+CXXVERSION=`$CXX --version | head -1`
+
 AC_ARG_VAR(PACKAGEVERSION, [The version used in secpoll queries])
 AS_IF([test "x$PACKAGEVERSION" != "x"],
   [AC_DEFINE_UNQUOTED([PACKAGEVERSION], "$PACKAGEVERSION", [Set to the package version used for secpoll])]
@@ -336,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
@@ -363,8 +379,8 @@ AC_MSG_NOTICE([=====================])
 AC_MSG_NOTICE([])
 AC_MSG_NOTICE([Configured with: $pdns_configure_args])
 AC_MSG_NOTICE([])
-AC_MSG_NOTICE([CC: $CC])
-AC_MSG_NOTICE([CXX: $CXX])
+AC_MSG_NOTICE([CC: $CC ($CCVERSION)])
+AC_MSG_NOTICE([CXX: $CXX ($CXXVERSION)])
 AC_MSG_NOTICE([LD: $LD])
 AC_MSG_NOTICE([CFLAGS: $CFLAGS])
 AC_MSG_NOTICE([CPPFLAGS: $CPPFLAGS])
@@ -414,6 +430,9 @@ AS_IF([test "x$LUAPC" != "x"],
 AS_IF([test "x$enable_experimental_pkcs11" = "xyes"],
   [AC_MSG_NOTICE([PKCS-11: yes])]
 )
+AS_IF([test "x$enable_experimental_gss_tsig" = "xyes"],
+  [AC_MSG_NOTICE([GSS-TSIG: yes])]
+)
 AS_IF([test "x$enable_lua_records" = "xyes"],
   [AC_MSG_NOTICE([LUA records: yes])]
 )
index 27884d07ae8bf231f5e69e4b3ad2e595f1836142..dbba1822c83b0e505537b4687185546f3212757c 100644 (file)
@@ -242,7 +242,7 @@ class PDNSPBConnHandler(object):
             requestorId = msg.requestorId
 
         nod = 0
-        if (msg.HasField('newlyObservedDomain')):
+        if msg.HasField('newlyObservedDomain'):
             nod = msg.newlyObservedDomain
 
         print('[%s] %s of size %d: %s%s%s -> %s%s(%s) id: %d uuid: %s%s '
@@ -264,6 +264,15 @@ class PDNSPBConnHandler(object):
                                                     serveridstr,
                                                     nod))
 
+        for mt in msg.meta:
+            values = ''
+            for entry in mt.value.stringVal:
+                values = ', '.join([values, entry]) if values != '' else entry
+            for entry in mt.value.intVal:
+                values = ', '.join([values, str(entry)]) if values != '' else str(entry)
+
+            print('- %s -> %s' % (mt.key, values))
+
     def getRequestorSubnet(self, msg):
         requestorstr = None
         if msg.HasField('originalRequestorSubnet'):
@@ -303,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()
diff --git a/contrib/_pdnsutil.zsh_completion b/contrib/_pdnsutil.zsh_completion
new file mode 100644 (file)
index 0000000..f4d4060
--- /dev/null
@@ -0,0 +1,323 @@
+#compdef pdnsutil
+
+#
+# put file '_pdnsutil' somewhere into $fpath,
+#       e.g. /usr/share/zsh/vendor-completions/
+#
+# command completion for pdns-auth
+#
+
+(( $+functions[_pdnsutil_commands] )) ||
+    _pdnsutil_commands() {
+        local -a _pdnsutil_cmds
+        _pdnsutil_cmds=(
+            'activate-tsig-key:Enable TSIG authenticated AXFR using the key NAME for ZONE'
+            'activate-zone-key:Activate the key with key id KEY-ID in ZONE'
+            'add-record:Add one or more records to ZONE'
+            'add-autoprimary:Add a new autoprimary'
+            'remove-autoprimary:Remove an autoprimary'
+            'list-autoprimaries:List all autoprimaries'
+            'add-zone-key:Add a ZSK or KSK to zone and specify algo&bits'
+            'backend-cmd:Perform one or more backend commands'
+            'b2b-migrate:Move all data from one backend to another'
+            'bench-db:Bench database backend with queries, one zone per line'
+            'check-zone:Check a zone for correctness'
+            'check-all-zones:Check all zones for correctness'
+            'clear-zone:Clear all records of a zone, but keep everything else'
+            'create-bind-db:Create DNSSEC db for BIND backend (bind-dnssec-db)'
+            'create-secondary-zone:Create secondary zone ZONE with primary IP address primary-ip'
+            'change-secondary-zone-primary:Change secondary zone ZONE primary IP address to primary-ip'
+            'create-zone:Create empty zone ZONE'
+            'deactivate-tsig-key:Disable TSIG authenticated AXFR using the key NAME for ZONE'
+            'deactivate-zone-key:Deactivate the key with key id KEY-ID in ZONE'
+            'delete-rrset:Delete named RRSET from zone'
+            'delete-tsig-key:Delete TSIG key (warning! will not unmap key!)'
+            'delete-zone:Delete the zone'
+            'disable-dnssec:Deactivate all keys and unset PRESIGNED in ZONE'
+            'edit-zone:Edit zone contents using $EDITOR'
+            'export-zone-dnskey:Export to stdout the public DNSKEY described'
+            'export-zone-ds:Export to stdout all KSK DS records for ZONE'
+            'export-zone-key:Export to stdout the private key described'
+            'export-zone-key-pem:Export to stdout in PEM the private key described'
+            'generate-tsig-key:Generate new TSIG key'
+            'generate-zone-key:Generate a ZSK or KSK to stdout with specified ALGORITHM and BITS'
+            'get-meta:Get zone metadata. If no KIND given, lists all known'
+            'hash-password:Ask for a plaintext password or api key and output a hashed and salted version'
+            'hash-zone-record:Calculate the NSEC3 hash for RNAME in ZONE'
+            'increase-serial:Increases the SOA-serial by 1. Uses SOA-EDIT'
+            'import-tsig-key:Import TSIG key'
+            'import-zone-key:Import from a file a private key, ZSK or KSK'
+            'import-zone-key-pem:Import from a file a private key in PEM, ZSK or KSK'
+            'ipdecrypt:Decrypt IP address using passphrase or base64 key'
+            'ipencrypt:Encrypt IP address using passphrase or base64 key'
+            'load-zone:Load ZONE from FILE, possibly creating zone or atomically replacing contents'
+            'list-algorithms:List all DNSSEC algorithms supported, optionally also listing the crypto library used'
+            'list-keys:List DNSSEC keys for ZONE. When ZONE is unset, display all keys for all active zones'
+            'list-member-zones:List all members of catalog zone CATALOG'
+            'list-zone:List zone contents'
+            'list-all-zones:List all active zone names'
+            'list-tsig-keys:List all TSIG keys'
+            'publish-zone-key:Publish the zone key with key id KEY-ID in ZONE'
+            'rectify-zone:Fix up DNSSEC fields (order, auth)'
+            'rectify-all-zones:Rectify all zones. Optionally quiet output with errors only'
+            'remove-zone-key:Remove key with KEY-ID from ZONE'
+            'replace-rrset:Replace named RRSET from zone'
+            'secure-all-zones:Secure all zones without keys'
+            'secure-zone:Add DNSSEC to zone ZONE'
+            'set-kind:Change the kind of ZONE to KIND (priamry, secondary, native)'
+            'set-account:Change the account (owner) of ZONE to ACCOUNT'
+            'set-nsec3:Enable NSEC3 with PARAMS. Optionally narrow'
+            'set-presigned:Use presigned RRSIGs from storage'
+            'set-publish-cdnskey:Enable sending CDNSKEY responses for ZONE. Add "delete" to publish a CDNSKEY with a DNSSEC delete algorithm'
+            'set-publish-cds:Enable sending CDS responses for ZONE, using DIGESTALGOS as signature algorithms'
+            'add-meta:Add zone metadata, this adds to the existing KIND'
+            'set-meta:Set zone metadata, optionally providing a value. *No* value clears meta'
+            'show-zone:Show DNSSEC (public) key details about a zone'
+            'unpublish-zone-key:Unpublish the zone key with key id KEY-ID in ZONE'
+            'unset-nsec3:Switch back to NSEC'
+            'unset-presigned:No longer use presigned RRSIGs'
+            'unset-publish-cdnskey:Disable sending CDNSKEY responses for ZONE'
+            'unset-publish-cds:Disable sending CDS responses for ZONE'
+            'test-schema:Test DB schema - will create ZONE'
+            "raw-lua-from-content:Display record contents in a form suitable for dnsdist's \`SpoofRawAction\`"
+            'zonemd-verify-file:Validate ZONEMD for ZONE'
+        )
+
+        if (( CURRENT == 1 )); then
+            _describe -t commands 'pdnsutil command' _pdnsutil_cmds
+        else
+            local curcontext="$curcontext"
+            cmd="${${_pdnsutil_cmds[(r)$words[1]:*]%%:*}}"
+
+            # command dispatcher
+            case $cmd in
+                (activate-zone-key|add-record|check-zone|clear-zone|create-secondary-zone|change-secondary-zone-primary|create-zone|deactivate-zone-key|delete-rrset|delete-zone|disable-dnssec|edit-zone|export-zone-dnskey|export-zone-ds|export-zone-key|export-zone-key-pem|hash-zone-record|increase-serial|list-keys|list-zone|publish-zone-key|remove-zone-key|replace-rrset|set-account|set-nsec3|set-presigned|set-publish-cds|show-zone|unpublish-zone-key|unset-nsec3|unset-presigned|unset-publish-cdnskey|unset-publish-cds|test-schema)
+                    _pdnsutil_cmd_singlezonearg
+                    ;;
+                (rectify-zone|secure-zone)
+                    _pdnsutil_cmd_multizonearg
+                    ;;
+                (bench-db|create-bind-db)
+                    _pdnsutil_cmd_filearg
+                    ;;
+                (activate-tsig-key|add-zone-key|check-all-zones|deactivate-tsig-key|delete-tsig-key|generate-tsig-key|generate-zone-key|get-meta|import-tsig-key|import-zone-key|import-zone-key-pem|load-zone|list-algorithms|list-all-zones|rectify-all-zones|secure-all-zones|set-kind|set-publish-cdnskey|add-meta|set-meta|zonemd-verify-file)
+                    _pdnsutil_cmd_$cmd
+                    ;;
+                *)
+                    # no completion for everything else
+                    ;;
+            esac
+        fi
+    }
+
+# fetch available zones for completion
+(( $+functions[_pdnsutil_zones] )) ||
+    _pdnsutil_zones() {
+        local -a _zones
+        _zones=( "${(f)$(pdnsutil list-all-zones 2> /dev/null)}" )
+        if [[ -n "$_zones" ]]; then
+            _describe -t zones 'zones' _zones
+        else
+            _message "no zones"
+        fi
+    }
+
+# fetch available tsig keys for completion
+(( $+functions[_pdnsutil_tsigkeys] )) ||
+    _pdnsutil_tsigkeys() {
+        local -a _tsigkeys
+        _tsigkeys=( ${"${(f)$(pdnsutil list-tsig-keys 2> /dev/null)}"%%. *} )
+        if [[ -n "$_tsigkeys" ]]; then
+            _describe -t tsigkeys 'tsigkeys' _tsigkeys
+        else
+            _message "no tsigkeys"
+        fi
+    }
+
+# all subcommands with only a single zone argument to complete
+(( $+functions[_pdnsutil_cmd_singlezonearg] )) ||
+    _pdnsutil_cmd_singlezonearg() {
+        _arguments ":zone:_pdnsutil_zones"
+    }
+
+# all subcommands with multiple zones as argument to complete
+(( $+functions[_pdnsutil_cmd_multizonearg] )) ||
+    _pdnsutil_cmd_multizonearg() {
+        _arguments "*:zone:_pdnsutil_zones"
+    }
+
+# all subcommands with a filename as argument
+(( $+functions[_pdnsutil_cmd_filearg] )) ||
+    _pdnsutil_cmd_filearg() {
+        _arguments ":filename:_files"
+    }
+
+# command-specific functions below
+#
+
+(( $+functions[_pdnsutil_cmd_activate-tsig-key] )) ||
+    _pdnsutil_cmd_activate-tsig-key() {
+        _arguments \
+            ':zone:_pdnsutil_zones' \
+            ':key:_pdnsutil_tsigkeys' \
+            ':type:(primary secondary)'
+    }
+
+(( $+functions[_pdnsutil_cmd_add-zone-key] )) ||
+    _pdnsutil_cmd_add-zone-key() {
+        _arguments \
+            ':zone:_pdnsutil_zones' \
+            ':type:(zsk ksk)' \
+            ':bits:' \
+            ':state:(active inactive)' \
+            ':pub:(published unpublished)' \
+            ':algorithm:(rsasha1 rsasha1-nsec3-sha1 rsasha256 rsasha512 ecdsa256 ecdsa384 ed25519 ed448)'
+    }
+
+(( $+functions[_pdnsutil_cmd_check-all-zones] )) ||
+    _pdnsutil_cmd_check-all-zones() {
+        _arguments \
+            ':flag:(exit-on-error)'
+    }
+
+(( $+functions[_pdnsutil_cmd_deactivate-tsig-key] )) ||
+    _pdnsutil_cmd_deactivate-tsig-key() {
+        _arguments \
+            ':zone:_pdnsutil_zones' \
+            ':key:_pdnsutil_tsigkeys' \
+            ':type:(primary secondary)'
+    }
+
+(( $+functions[_pdnsutil_cmd_delete-tsig-key] )) ||
+    _pdnsutil_cmd_delete-tsig-key() {
+        _arguments \
+            ':key:_pdnsutil_tsigkeys'
+    }
+
+(( $+functions[_pdnsutil_cmd_generate-tsig-key] )) ||
+    _pdnsutil_cmd_generate-tsig-key() {
+        _arguments \
+            ':name:' \
+            ':algorithm:(hmac-md5 hmac-sha1 hmac-sha224 hmac-sha256 hmac-sha384 hmac-sha512)'
+    }
+
+(( $+functions[_pdnsutil_cmd_generate-zone-key] )) ||
+    _pdnsutil_cmd_generate-zone-key() {
+        _arguments \
+            ':type:(zsk ksk)' \
+            ':algorithm:(rsasha1 rsasha1-nsec3-sha1 rsasha256 rsasha512 ecdsa256 ecdsa384 ed25519 ed448)' \
+            ':bits:'
+    }
+
+(( $+functions[_pdnsutil_cmd_get-meta] )) ||
+    _pdnsutil_cmd_get-meta() {
+        _arguments \
+            ':zone:_pdnsutil_zones' \
+            ':kind:(ALLOW-AXFR-FROM API-RECTIFY AXFR-SOURCE ALLOW-DNSUPDATE-FROM TSIG-ALLOW-DNSUPDATE FORWARD-DNSUPDATE SOA-EDIT-DNSUPDATE NOTIFY-DNSUPDATE ALSO-NOTIFY AXFR-MASTER-TSIG GSS-ALLOW-AXFR-PRINCIPAL GSS-ACCEPTOR-PRINCIPAL IXFR LUA-AXFR-SCRIPT NSEC3NARROW NSEC3PARAM PRESIGNED PUBLISH-CDNSKEY PUBLISH-CDS SLAVE-RENOTIFY SOA-EDIT SOA-EDIT-API TSIG-ALLOW-AXFR TSIG-ALLOW-DNSUPDATE)'
+    }
+
+(( $+functions[_pdnsutil_cmd_import-tsig-key] )) ||
+    _pdnsutil_cmd_import-tsig-key() {
+        _arguments \
+            ':name:' \
+            ':algorithm:(hmac-md5 hmac-sha1 hmac-sha224 hmac-sha256 hmac-sha384 hmac-sha512)' \
+            ':key:'
+    }
+
+(( $+functions[_pdnsutil_cmd_import-zone-key] )) ||
+    _pdnsutil_cmd_import-zone-key() {
+        _arguments \
+            ':zone:_pdnsutil_zones' \
+            ':filename:_files' \
+            ':state:(active inactive)' \
+            ':type:(zsk ksk)' \
+            ':pub:(published unpublished)'
+    }
+
+(( $+functions[_pdnsutil_cmd_import-zone-key-pem] )) ||
+    _pdnsutil_cmd_import-zone-key-pem() {
+        _arguments \
+            ':zone:_pdnsutil_zones' \
+            ':filename:_files' \
+            ':algorithm:(rsasha1 rsasha1-nsec3-sha1 rsasha256 rsasha512 ecdsa256 ecdsa384 ed25519 ed448)' \
+            ':type:(zsk ksk)'
+    }
+
+(( $+functions[_pdnsutil_cmd_load-zone] )) ||
+    _pdnsutil_cmd_load-zone() {
+        _arguments \
+            ':zone:_pdnsutil_zones' \
+            ':filename:_files'
+    }
+
+(( $+functions[_pdnsutil_cmd_list-algorithms] )) ||
+    _pdnsutil_cmd_list-algorithms() {
+        _arguments \
+            ':flag:(with-backend)'
+    }
+
+(( $+functions[_pdnsutil_cmd_list-all-zones] )) ||
+    _pdnsutil_cmd_list-all-zones() {
+        _arguments \
+            ':type:(primary secondary native)'
+    }
+
+(( $+functions[_pdnsutil_cmd_rectify-all-zones] )) ||
+    _pdnsutil_cmd_rectify-all-zones() {
+        _arguments \
+            ':flag:(quiet)'
+    }
+
+(( $+functions[_pdnsutil_cmd_secure-all-zones] )) ||
+    _pdnsutil_cmd_secure-all-zones() {
+        _arguments \
+            ':flag:(increase-serial)'
+    }
+
+(( $+functions[_pdnsutil_cmd_set-kind] )) ||
+    _pdnsutil_cmd_set-kind() {
+        _arguments \
+            ':zone:_pdnsutil_zones' \
+            ':type:(primary secondary native)'
+    }
+
+(( $+functions[_pdnsutil_cmd_set-publish-cdnskey] )) ||
+    _pdnsutil_cmd_set-publish-cdnskey() {
+        _arguments \
+            ':zone:_pdnsutil_zones' \
+            ':flag:(delete)'
+    }
+
+(( $+functions[_pdnsutil_cmd_add-meta] )) ||
+    _pdnsutil_cmd_add-meta() {
+        _arguments \
+            ':zone:_pdnsutil_zones' \
+            ':kind:(ALLOW-AXFR-FROM API-RECTIFY AXFR-SOURCE ALLOW-DNSUPDATE-FROM TSIG-ALLOW-DNSUPDATE FORWARD-DNSUPDATE SOA-EDIT-DNSUPDATE NOTIFY-DNSUPDATE ALSO-NOTIFY AXFR-MASTER-TSIG GSS-ALLOW-AXFR-PRINCIPAL GSS-ACCEPTOR-PRINCIPAL IXFR LUA-AXFR-SCRIPT NSEC3NARROW NSEC3PARAM PRESIGNED PUBLISH-CDNSKEY PUBLISH-CDS SLAVE-RENOTIFY SOA-EDIT SOA-EDIT-API TSIG-ALLOW-AXFR TSIG-ALLOW-DNSUPDATE)' \
+            '*:value:'
+    }
+
+(( $+functions[_pdnsutil_cmd_set-meta] )) ||
+    _pdnsutil_cmd_set-meta() {
+        _arguments \
+            ':zone:_pdnsutil_zones' \
+            ':kind:(ALLOW-AXFR-FROM API-RECTIFY AXFR-SOURCE ALLOW-DNSUPDATE-FROM TSIG-ALLOW-DNSUPDATE FORWARD-DNSUPDATE SOA-EDIT-DNSUPDATE NOTIFY-DNSUPDATE ALSO-NOTIFY AXFR-MASTER-TSIG GSS-ALLOW-AXFR-PRINCIPAL GSS-ACCEPTOR-PRINCIPAL IXFR LUA-AXFR-SCRIPT NSEC3NARROW NSEC3PARAM PRESIGNED PUBLISH-CDNSKEY PUBLISH-CDS SLAVE-RENOTIFY SOA-EDIT SOA-EDIT-API TSIG-ALLOW-AXFR TSIG-ALLOW-DNSUPDATE)' \
+            '*:value:'
+    }
+
+(( $+functions[_pdnsutil_cmd_zonemd-verify-file] )) ||
+    _pdnsutil_cmd_zonemd-verify-file() {
+        _arguments \
+            ':zone:_pdnsutil_zones' \
+            ':filename:_files'
+    }
+
+
+# pre-subcmd arguments
+_arguments \
+    '(- *)'{-h,--help}'[produce help message]' \
+    '(- *)--version[show version]' \
+    {-v,--verbose}'[be verbose]' \
+    '--force[force an action]' \
+    '--config-name[virtual configuration name]:filename:_files' \
+    '--config-dir[location of pdns.conf]:dirname:_files -/' \
+    '*::pdnsutil commands:_pdnsutil_commands'
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 f5554f22ac7c48f2fa555c9fc05d1d249dd1dea8..ad2a8afd56291d0abbfd626c705a56ba055c4015 100644 (file)
@@ -21,7 +21,7 @@ have pdnsutil && {
                               hash-zone-record increase-serial import-tsig-key import-zone-key load-zone list-algorithms list-keys list-zone list-all-zones
                               list-tsig-keys rectify-zone rectify-all-zones remove-zone-key replace-rrset secure-all-zones secure-zone set-kind set-nsec3 set-presigned
                               set-publish-cdnskey set-publish-cds set-meta show-zone unset-nsec3 unset-presigned unset-publish-cdnskey unset-publish-cds test-schema
-                              import-zone-key-pem export-zone-key-pem"
+                              import-zone-key-pem export-zone-key-pem list-member-zones"
     COMPREPLY=()
     cur="${COMP_WORDS[COMP_CWORD]}"
     prev="${COMP_WORDS[COMP_CWORD-1]}"
index 5ae8a84020d4911383e80ae2b9a97823fdbc9f22..ec0d07145d262d0e1081c2cf9d3344b13bae023e 100644 (file)
-#include <net/sock.h>
-#include <uapi/linux/udp.h>
-#include <uapi/linux/ip.h>
-#include <uapi/linux/ipv6.h>
+#include "xdp.h"
 
-#define DNS_PORT      53
-
-// do not use libc includes because this causes clang
-// to include 32bit headers on 64bit ( only ) systems.
-typedef __u8  uint8_t;
-typedef __u16 uint16_t;
-typedef __u32 uint32_t;
-typedef __u64 uint64_t;
-#define memcpy __builtin_memcpy
-
-/*
- * Helper pointer to parse the incoming packets
- * Copyright 2020, NLnet Labs, All rights reserved.
- */
-struct cursor {
-  void *pos;
-  void *end;
-};
-
-/*
- * Store the VLAN header
- * Copyright 2020, NLnet Labs, All rights reserved.
- */
-struct vlanhdr {
-  uint16_t tci;
-  uint16_t encap_proto;
-};
-
-/*
- * Store the DNS header
- * Copyright 2020, NLnet Labs, All rights reserved.
- */
-struct dnshdr {
-  uint16_t id;
-  union {
-       struct {
-#if BYTE_ORDER == LITTLE_ENDIAN
-               uint8_t  rd     : 1;
-               uint8_t  tc     : 1;
-               uint8_t  aa     : 1;
-               uint8_t  opcode : 4;
-               uint8_t  qr     : 1;
-
-               uint8_t  rcode  : 4;
-               uint8_t  cd     : 1;
-               uint8_t  ad     : 1;
-               uint8_t  z      : 1;
-               uint8_t  ra     : 1;
-#elif BYTE_ORDER == BIG_ENDIAN || BYTE_ORDER == PDP_ENDIAN
-               uint8_t  qr     : 1;
-               uint8_t  opcode : 4;
-               uint8_t  aa     : 1;
-               uint8_t  tc     : 1;
-               uint8_t  rd     : 1;
-
-               uint8_t  ra     : 1;
-               uint8_t  z      : 1;
-               uint8_t  ad     : 1;
-               uint8_t  cd     : 1;
-               uint8_t  rcode  : 4;
-#endif
-       }        as_bits_and_pieces;
-       uint16_t as_value;
-  } flags;
-  uint16_t qdcount;
-  uint16_t ancount;
-  uint16_t nscount;
-  uint16_t arcount;
-};
-
-/*
- * Store the qname and qtype
- */
-struct dns_qname
-{
-  uint8_t qname[255];
-  uint16_t qtype;
-};
-
-/*
- * The possible actions to perform on the packet
- * PASS: XDP_PASS
- * DROP: XDP_DROP
- * TC: set TC bit and XDP_TX
- */
-enum dns_action : uint8_t {
-  PASS = 0,
-  DROP = 1,
-  TC = 2
-};
-
-/*
- * Store the matching counter and the associated action for a blocked element
- */
-struct map_value
-{
-  uint64_t counter;
-  enum dns_action action;
-};
+#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 */
 
 /*
- * Initializer of a cursor pointer
- *  Copyright 2020, NLnet Labs, All rights reserved.
+ * bcc has added BPF_TABLE_PINNED7 to the latest commit of the master branch, but it has not yet been released.
+ * https://github.com/iovisor/bcc/commit/fff25a8d4d445c6156b65aa8a4016ce0d78ab7fb
  */
-static inline void cursor_init(struct cursor *c, struct xdp_md *ctx)
-{
-  c->end = (void *)(long)ctx->data_end;
-  c->pos = (void *)(long)ctx->data;
-}
-
-/* 
- * Header parser functions
- * Copyright 2020, NLnet Labs, All rights reserved.
- */
-#define PARSE_FUNC_DECLARATION(STRUCT)                            \
-static inline struct STRUCT *parse_ ## STRUCT (struct cursor *c)  \
-{                                                                 \
-  struct STRUCT *ret = c->pos;                                    \
-  if (c->pos + sizeof(struct STRUCT) > c->end)                    \
-       return 0;                                                 \
-  c->pos += sizeof(struct STRUCT);                                \
-  return ret;                                                     \
-}
-
-PARSE_FUNC_DECLARATION(ethhdr)
-PARSE_FUNC_DECLARATION(vlanhdr)
-PARSE_FUNC_DECLARATION(iphdr)
-PARSE_FUNC_DECLARATION(ipv6hdr)
-PARSE_FUNC_DECLARATION(udphdr)
-PARSE_FUNC_DECLARATION(dnshdr)
-
-/*
- * Parse ethernet frame and fill the struct
- * Copyright 2020, NLnet Labs, All rights reserved.
- */
-static inline struct ethhdr *parse_eth(struct cursor *c, uint16_t *eth_proto)
-{
-  struct ethhdr  *eth;
-
-  if (!(eth = parse_ethhdr(c)))
-       return 0;
-
-  *eth_proto = eth->h_proto;
-  if (*eth_proto == bpf_htons(ETH_P_8021Q)
-  ||  *eth_proto == bpf_htons(ETH_P_8021AD)) {
-       struct vlanhdr *vlan;
-
-       if (!(vlan = parse_vlanhdr(c)))
-               return 0;
+#ifndef BPF_TABLE_PINNED7
+#define BPF_TABLE_PINNED7(_table_type, _key_type, _leaf_type, _name, _max_entries, _pinned, _flags) \
+  BPF_F_TABLE(_table_type ":" _pinned, _key_type, _leaf_type, _name, _max_entries, _flags)
+#endif
 
-       *eth_proto = vlan->encap_proto;
-       if (*eth_proto == bpf_htons(ETH_P_8021Q)
-       ||  *eth_proto == bpf_htons(ETH_P_8021AD)) {
-               if (!(vlan = parse_vlanhdr(c)))
-                       return 0;
-
-               *eth_proto = vlan->encap_proto;
-       }
-  }
-  return eth;
-}
+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
@@ -191,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;
 
@@ -201,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;
 }
 
 /*
@@ -217,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;
@@ -260,171 +132,254 @@ 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, uint32_t 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);
+  struct map_value* value = v4filter.lookup(&key.addr);
+
+  if (value) {
+    goto res;
+  }
 
+  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);
-    } else {
-      return value->action;
+    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 {
-    enum dns_action action = check_qname(c);
-       if (action == TC) {
-               return set_tc_bit(udp, dns);
-       } else {
-      return action;
+
+    if (value->action == DROP) {
+#ifndef DISABLE_LOGGING
+      progsarray.call(ctx, 0);
+#endif /* DISABLE_LOGGING */
+      return XDP_DROP;
     }
   }
 
-  return PASS;
+  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 in6_addr 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);
+  struct map_value* value = v6filter.lookup(&key.addr);
+  if (value) {
+    goto res;
+  }
+
+  key.cidr = 128;
+  value = cidr6filter.lookup(&key);
+  if (value) {
+    goto res;
+  }
 
+  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);
-    } else {
-      return value->action;
+    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 {
-    enum dns_action action = check_qname(c);
-       if (action == TC) {
-               return set_tc_bit(udp, dns);
-       } else {
-      return action;
+    if (value->action == DROP) {
+#ifndef DISABLE_LOGGING
+      progsarray.call(ctx, 0);
+#endif /* DISABLE_LOGGING */
+      return XDP_DROP;
     }
   }
-
-  return PASS;
+  return XDP_REDIRECT;
 }
 
-int xdp_dns_filter(struct xdp_md *ctx)
+int xdp_dns_filter(struct xdp_mdctx)
 {
   // store variables
   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);
 
   // 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;
-               }
-      // if TC bit must not be set, apply the action
-               if ((r = udp_dns_reply_v4(&c, bpf_htonl(ipv4->saddr))) != TC) {
-           return r == DROP ? XDP_DROP : XDP_PASS;
-               }
-  
-      // swap src/dest IP addresses
-               uint32_t swap_ipv4 = ipv4->daddr;
-               ipv4->daddr = ipv4->saddr;
-               ipv4->saddr = swap_ipv4;
-       }
-       // 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;
-               } 
-      // if TC bit must not be set, apply the action
-               if ((r = udp_dns_reply_v6(&c, ipv6->saddr)) != TC) {
-                       return r == DROP ? XDP_DROP : XDP_PASS;
-               }
-  
-      // swap src/dest IP addresses
-               struct in6_addr swap_ipv6 = ipv6->daddr;
-               ipv6->daddr = ipv6->saddr;
-               ipv6->saddr = swap_ipv6;
-
-       }
-       // pass all non-IP packets
-       else {
-               return XDP_PASS;
-       }
-  } else {
-       return XDP_PASS;
+    // IPv4 packets
+    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)) {
+      r = parseIPV6(ctx, &c);
+      goto res;
+    }
+    // pass all non-IP packets
+    return XDP_PASS;
+  }
+  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);
-
-  // bounce the request
-  return XDP_TX;
 }
diff --git a/contrib/xdp-logging-middleware.ebpf.src b/contrib/xdp-logging-middleware.ebpf.src
new file mode 100644 (file)
index 0000000..66c6fd8
--- /dev/null
@@ -0,0 +1,100 @@
+#include "xdp.h"
+
+BPF_TABLE_PINNED("prog", int, int, progsarray, 2, "/sys/fs/bpf/dnsdist/progs");
+BPF_PERF_OUTPUT(events);
+
+/*
+ * Parse DNS QName and fill the struct
+ */
+static inline void parse_qname(struct cursor* c, struct dns_qname* query) {
+  uint8_t qname_byte;
+  uint16_t qtype;
+  int length = 0;
+
+  for (int i = 0; i < 255; i++) {
+    bpf_probe_read_kernel(&qname_byte, sizeof(qname_byte), c->pos);
+
+    c->pos += 1;
+    if (length == 0) {
+      if (qname_byte == 0 || qname_byte > 63) {
+        break;
+      }
+      length += qname_byte;
+    } else {
+      length--;
+    }
+    if (qname_byte >= 'A' && qname_byte <= 'Z') {
+      query->qname[i] = qname_byte + ('a' - 'A');
+    } else {
+      query->qname[i] = qname_byte;
+    }
+  }
+
+  bpf_probe_read_kernel(&(query->qtype), sizeof(query->qtype), c->pos);
+}
+
+/*
+ * Push data regarding the dropped/redirected packet to a perf buffer
+ */
+static inline void log_packet(struct xdp_md* ctx, enum dns_action action) {
+  // store variables
+  struct cursor c;
+  struct ethhdr* eth;
+  uint16_t eth_proto;
+  struct iphdr* ipv4;
+  struct ipv6hdr* ipv6;
+  struct udphdr* udp;
+  struct dnshdr* dns;
+  int r = 0;
+
+  struct pktdata {
+    uint32_t ipv4_src;
+    uint8_t ipv6_src[16];
+    struct dns_qname query;
+  } packet_info = {0};
+
+  // initialise the cursor
+  cursor_init(&c, ctx);
+
+  if ((eth = parse_eth(&c, &eth_proto))) {
+    if (eth_proto == bpf_htons(ETH_P_IP)) {
+      if ((ipv4 = parse_iphdr(&c))) {
+        if (action == DROP) {
+          memcpy(&(packet_info.ipv4_src), &(ipv4->saddr), sizeof(packet_info.ipv4_src));
+        } else if (action == TC) {
+          memcpy(&(packet_info.ipv4_src), &(ipv4->daddr), sizeof(packet_info.ipv4_src));
+        }
+        if ((udp = parse_udphdr(&c))) {
+          if ((dns = parse_dnshdr(&c))) {
+            parse_qname(&c, &(packet_info.query));
+          }
+        }
+      }
+
+    } else if (eth_proto == bpf_htons(ETH_P_IPV6)) {
+      if ((ipv6 = parse_ipv6hdr(&c))) {
+        if (action == DROP) {
+          memcpy(&(packet_info.ipv6_src), &(ipv6->saddr.in6_u.u6_addr8), 16);
+        } else if (action == TC) {
+          memcpy(&(packet_info.ipv6_src), &(ipv6->daddr.in6_u.u6_addr8), 16);
+        }
+        if ((udp = parse_udphdr(&c))) {
+          if ((dns = parse_dnshdr(&c))) {
+            parse_qname(&c, &(packet_info.query));
+          }
+        }
+      }
+    }
+  }
+  events.perf_submit(ctx, &packet_info, sizeof(packet_info));
+}
+
+int log_drop(struct xdp_md* ctx) {
+  log_packet(ctx, DROP);
+  return XDP_DROP;
+}
+
+int log_tc(struct xdp_md* ctx) {
+  log_packet(ctx, TC);
+  return XDP_TX;
+}
diff --git a/contrib/xdp-logging.py b/contrib/xdp-logging.py
new file mode 100644 (file)
index 0000000..1c0e012
--- /dev/null
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+
+from bcc import BPF
+import ctypes as ct
+import netaddr
+import socket
+
+class DNSQuery(ct.Structure):
+    _fields_ = [
+        ("qname", ct.c_uint8 * 255),
+        ("qtype", ct.c_uint16)
+    ]
+
+class PacketInfo(ct.Structure):
+    _fields_ = [
+        ("ipv4_src", ct.c_uint32),
+        ("ipv6_src", ct.c_uint8 * 16),
+        ("query", DNSQuery)
+    ]
+
+def decode_qname(qname_array):
+    qname = ""
+    length = 0
+    for qname_byte in qname_array:
+        if length == 0:
+            if int(qname_byte) == 0:
+                break
+            else:
+                length = int(qname_byte)
+                if qname != "":
+                    qname += '.'
+        else:
+            qname += chr(int(qname_byte))
+            length -= 1
+    return qname
+
+def print_event(cpu, data, size):
+    event = ct.cast(data, ct.POINTER(PacketInfo)).contents
+    if event.ipv4_src != 0:
+        src_ip = str(netaddr.IPAddress(socket.htonl(event.ipv4_src)))
+    else:
+        src_ip = str(netaddr.IPAddress(sum([byte << 8*(15-index) for index, byte in enumerate(event.ipv6_src)]), 6))
+    qtype = INV_QTYPES[socket.htons(event.query.qtype)]
+    qname = decode_qname(event.query.qname)
+    print(f"{src_ip}|{qtype}|{qname}")
+
+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()}
+
+# Main
+xdp = BPF(src_file="xdp-logging-middleware.ebpf.src")
+
+fn_drop = xdp.load_func("log_drop", BPF.XDP)
+fn_tc = xdp.load_func("log_tc", BPF.XDP)
+
+progs = xdp.get_table("progsarray")
+events = xdp.get_table("events")
+
+progs[ct.c_int(0)] = ct.c_int(fn_drop.fd)
+progs[ct.c_int(1)] = ct.c_int(fn_tc.fd)
+
+events.open_perf_buffer(print_event)
+
+print("Filter is ready")
+while True:
+    try:
+        xdp.perf_buffer_poll()
+    except KeyboardInterrupt:
+        break
+
+if progs[ct.c_int(0)]:
+    del(progs[ct.c_int(0)])
+if progs[ct.c_int(1)]:
+    del(progs[ct.c_int(1)])
diff --git a/contrib/xdp.h b/contrib/xdp.h
new file mode 100644 (file)
index 0000000..0d63fcf
--- /dev/null
@@ -0,0 +1,195 @@
+#ifndef __XDP_H__
+#define __XDP_H__
+
+#include <net/sock.h>
+#include <uapi/linux/udp.h>
+#include <uapi/linux/ip.h>
+#include <uapi/linux/ipv6.h>
+
+#define DNS_PORT      53
+
+// do not use libc includes because this causes clang
+// to include 32bit headers on 64bit ( only ) systems.
+typedef __u8  uint8_t;
+typedef __u16 uint16_t;
+typedef __u32 uint32_t;
+typedef __u64 uint64_t;
+#define memcpy __builtin_memcpy
+
+/*
+ * Helper pointer to parse the incoming packets
+ * Copyright 2020, NLnet Labs, All rights reserved.
+ */
+struct cursor {
+  void *pos;
+  void *end;
+};
+
+/*
+ * Store the VLAN header
+ * Copyright 2020, NLnet Labs, All rights reserved.
+ */
+struct vlanhdr {
+  uint16_t tci;
+  uint16_t encap_proto;
+};
+
+/*
+ * Store the DNS header
+ * Copyright 2020, NLnet Labs, All rights reserved.
+ */
+struct dnshdr {
+  uint16_t id;
+  union {
+       struct {
+#if BYTE_ORDER == LITTLE_ENDIAN
+               uint8_t  rd     : 1;
+               uint8_t  tc     : 1;
+               uint8_t  aa     : 1;
+               uint8_t  opcode : 4;
+               uint8_t  qr     : 1;
+
+               uint8_t  rcode  : 4;
+               uint8_t  cd     : 1;
+               uint8_t  ad     : 1;
+               uint8_t  z      : 1;
+               uint8_t  ra     : 1;
+#elif BYTE_ORDER == BIG_ENDIAN || BYTE_ORDER == PDP_ENDIAN
+               uint8_t  qr     : 1;
+               uint8_t  opcode : 4;
+               uint8_t  aa     : 1;
+               uint8_t  tc     : 1;
+               uint8_t  rd     : 1;
+
+               uint8_t  ra     : 1;
+               uint8_t  z      : 1;
+               uint8_t  ad     : 1;
+               uint8_t  cd     : 1;
+               uint8_t  rcode  : 4;
+#endif
+       }        as_bits_and_pieces;
+       uint16_t as_value;
+  } flags;
+  uint16_t qdcount;
+  uint16_t ancount;
+  uint16_t nscount;
+  uint16_t arcount;
+};
+
+/*
+ * Store the qname and qtype
+ */
+struct dns_qname
+{
+  uint8_t qname[255];
+  uint16_t qtype;
+};
+
+/*
+ * The possible actions to perform on the packet
+ * PASS: XDP_PASS
+ * DROP: XDP_DROP
+ * TC: set TC bit and XDP_TX
+ */
+enum dns_action : uint8_t {
+  PASS = 0,
+  DROP = 1,
+  TC = 2
+};
+
+struct CIDR4
+{
+  uint32_t cidr;
+  uint32_t addr;
+};
+struct CIDR6
+{
+  uint32_t cidr;
+  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
+ */
+struct map_value
+{
+  uint64_t counter;
+  enum dns_action action;
+};
+
+
+/*
+ * Initializer of a cursor pointer
+ *  Copyright 2020, NLnet Labs, All rights reserved.
+ */
+static inline void cursor_init(struct cursor *c, struct xdp_md *ctx)
+{
+  c->end = (void *)(long)ctx->data_end;
+  c->pos = (void *)(long)ctx->data;
+}
+
+/*
+ * Header parser functions
+ * Copyright 2020, NLnet Labs, All rights reserved.
+ */
+#define PARSE_FUNC_DECLARATION(STRUCT)                            \
+static inline struct STRUCT *parse_ ## STRUCT (struct cursor *c)  \
+{                                                                 \
+  struct STRUCT *ret = c->pos;                                    \
+  if (c->pos + sizeof(struct STRUCT) > c->end)                    \
+       return 0;                                                 \
+  c->pos += sizeof(struct STRUCT);                                \
+  return ret;                                                     \
+}
+
+PARSE_FUNC_DECLARATION(ethhdr)
+PARSE_FUNC_DECLARATION(vlanhdr)
+PARSE_FUNC_DECLARATION(iphdr)
+PARSE_FUNC_DECLARATION(ipv6hdr)
+PARSE_FUNC_DECLARATION(udphdr)
+PARSE_FUNC_DECLARATION(dnshdr)
+
+/*
+ * Parse ethernet frame and fill the struct
+ * Copyright 2020, NLnet Labs, All rights reserved.
+ */
+static inline struct ethhdr *parse_eth(struct cursor *c, uint16_t *eth_proto)
+{
+  struct ethhdr  *eth;
+
+  if (!(eth = parse_ethhdr(c)))
+       return 0;
+
+  *eth_proto = eth->h_proto;
+  if (*eth_proto == bpf_htons(ETH_P_8021Q)
+  ||  *eth_proto == bpf_htons(ETH_P_8021AD)) {
+       struct vlanhdr *vlan;
+
+       if (!(vlan = parse_vlanhdr(c)))
+               return 0;
+
+       *eth_proto = vlan->encap_proto;
+       if (*eth_proto == bpf_htons(ETH_P_8021Q)
+       ||  *eth_proto == bpf_htons(ETH_P_8021AD)) {
+               if (!(vlan = parse_vlanhdr(c)))
+                       return 0;
+
+               *eth_proto = vlan->encap_proto;
+       }
+  }
+  return eth;
+}
+
+#endif
index c84893ae8e116f3442f575b7a2cf72ac0f394b0d..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,26 +14,47 @@ 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)
 # QName format : (QName, QType, Action)
 blocked_ipv4 = [("192.0.2.1", TC_ACTION)]
 blocked_ipv6 = [("2001:db8::1", TC_ACTION)]
+blocked_cidr4 = [("192.0.1.1/24", TC_ACTION)]
+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")
+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))
@@ -51,6 +73,30 @@ for ip in blocked_ipv6:
   leaf.action = ip[1]
   v6filter[key] = leaf
 
+for item in blocked_cidr4:
+  print(f"Blocking {item}")
+  key = cidr4filter.Key()
+  network = netaddr.IPNetwork(item[0])
+  key.cidr = network.prefixlen
+  key.addr = socket.htonl(network.network.value)
+  leaf = cidr4filter.Leaf()
+  leaf.counter = 0
+  leaf.action = item[1]
+  cidr4filter[key] = leaf
+
+for item in blocked_cidr6:
+  print(f"Blocking {item}")
+  key = cidr6filter.Key()
+  network = netaddr.IPNetwork(item[0])
+  key.cidr = network.prefixlen
+  ipv6_int = int(network.network.value)
+  ipv6_bytes = bytearray([(ipv6_int & (255 << 8*(15-i))) >> (8*(15-i)) for i in range(16)])
+  key.addr.in6_u.u6_addr8 = (ct.c_uint8 * 16).from_buffer(ipv6_bytes)
+  leaf = cidr6filter.Leaf()
+  leaf.counter = 0
+  leaf.action = item[1]
+  cidr6filter[key] = leaf
+
 for qname in blocked_qnames:
   print(f"Blocking {qname}")
   key = qnamefilter.Key()
@@ -67,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
 
@@ -77,7 +124,12 @@ for item in v4filter.items():
   print(f"{str(netaddr.IPAddress(item[0].value))} ({ACTIONS[item[1].action]}): {item[1].counter}")
 for item in v6filter.items():
   print(f"{str(socket.inet_ntop(socket.AF_INET6, item[0]))} ({ACTIONS[item[1].action]}): {item[1].counter}")
+for item in cidr4filter.items():
+  addr = netaddr.IPAddress(socket.ntohl(item[0].addr))
+  print(f"{str(addr)}/{str(item[0].cidr)} ({ACTIONS[item[1].action]}): {item[1].counter}")
+for item in cidr6filter.items():
+  print(f"{str(socket.inet_ntop(socket.AF_INET6, item[0].addr))}/{str(item[0].cidr)} ({ACTIONS[item[1].action]}): {item[1].counter}")
 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..e84055151a60aafc861a552a06ef5c26a3c600f8 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')
index 276818114e8967ea1bd3ac774f85fd6bc92ec4f1..2813aedf5257071e04e4f66cbcdf2d5e907c6a53 100644 (file)
@@ -18,3 +18,4 @@
 html-docs.tar.bz2
 /mans
 /mans.tmp
+/_build
index f53735a5c578ff7b3c53769970c721146d1471bd..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 \
@@ -120,5 +120,3 @@ html-docs:
        @echo "You need Python 3 and the 'venv' module to generate the HTML docs"
        exit 1
 endif
-
-
index fe0fbb180e0beb1e2219e56cea2f9200e3938c81..ea1663aa7dc0ed71c80e0b31e690846318798e9d 100644 (file)
@@ -23,5 +23,3 @@ help: .venv
        python3 -m venv .venv
        .venv/bin/pip install -U pip setuptools setuptools-git wheel
        .venv/bin/pip install -r requirements.txt
-
-
index 3465739bb63fa3e4ee663d282bd6b3384910bd41..44d8619ed2fb9bd5180286ee1f66160b054eb2cd 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.6.
+The currently supported release train of PowerDNS Authoritative Server is 4.8.
 
-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 4.10 is released.
 
-PowerDNS Authoritative Server 4.4 will only receive critical updates and will be end of life after PowerDNS Authoritative Server 4.7 is released.
+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.0 through 4.3, 3.x, and 2.x are End of Life.
+PowerDNS Authoritative Server 4.0 through 4.5, 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,18 +26,26 @@ such a user, these EOL statements do not apply to you.
      - Release date
      - Critical-Only updates
      - End of Life
+   * - 4.8
+     - 1st of June 2023
+     - ~ December 2023
+     - ~ December 2024
+   * - 4.7
+     - 20th of October 2022
+     - ~ April 2023
+     - ~ April 2024
    * - 4.6
      - 25th of January 2022
-     - ~ July 2022
-     - ~ July 2023
+     - 20th of October 2022
+     - ~ October 2023
    * - 4.5
      - July 13 2021
      - 25th of January 2022
-     - ~ January 2023
+     - EOL June 2023
    * - 4.4
      - December 18 2020
      - 25th of January 2022
-     - ~ June 2022
+     - EOL 20th of October 2022
    * - 4.3
      - April 7 2020
      - ~ April 2021
index 24220dfc93b75981cadcb12c5ba81464f4644940..d9ffd26cfbf466c6775d6d4c38473fd9e3da7c6d 100644 (file)
@@ -369,12 +369,16 @@ URI
 The URI record, specified in :rfc:`7553`, is used to publish
 mappings from hostnames to URIs.
 
+ZONEMD
+------
+
+The ZONEMD record, specified in :rfc:`8976`, is used to validate zones.
+
 Other types
 -----------
 
 The following, rarely used or obsolete record types, are also supported:
 
--  A6 (:rfc:`2874`, obsolete)
 -  DHCID (:rfc:`4701`)
 -  DLV (:rfc:`4431`)
 -  EUI48/EUI64 (:rfc:`7043`)
@@ -384,14 +388,10 @@ The following, rarely used or obsolete record types, are also supported:
 -  L32 (:rfc:`6742`)
 -  L64 (:rfc:`6742`)
 -  LP (:rfc:`6742`)
--  MAILA (:rfc:`1035`)
--  MAILB (:rfc:`1035`)
 -  MINFO (:rfc:`1035`)
 -  MR (:rfc:`1035`)
 -  NID (:rfc:`6742`)
 -  RKEY (`draft-reid-dnsext-rkey-00.txt <https://tools.ietf.org/html/draft-reid-dnsext-rkey-00>`__)
--  SIG (:rfc:`2535`, obsolete)
--  WKS (:rfc:`1035`)
 
 .. _types-unknown:
 
index 16a8f19b529d19ebb2995df91797f6c09d09ea03..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,16 +127,31 @@ 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-autoprimary-config:
 
-.. _setting-bind-supermaster-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-autoprimary-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.
+
 .. _bind-operation:
 
 Operation
index 53d5fb974e805571a874ff06605bcd548f26a424..3c86bc9dc067babdaf0db820c8134786da5801b5 100644 (file)
@@ -158,7 +158,6 @@ Only enable this if you are certain you need to. For more discussion, see https:
 Default Schema
 --------------
 
-This is the 4.3 schema.
-The `4.2 schema <https://github.com/PowerDNS/pdns/blob/rel/auth-4.2.x/modules/gmysqlbackend/schema.mysql.sql>`_ and `the 4.1 schema <https://github.com/PowerDNS/pdns/blob/rel/auth-4.1.x/modules/gmysqlbackend/schema.mysql.sql>`_ are available on GitHub.
+This is the 4.7 schema.
 
 .. literalinclude:: ../../modules/gmysqlbackend/schema.mysql.sql
index 92d75bd4e70855bd47604840a114b8dc9e56008a..b092f59d9841c7c5ddbf974a314634e51d399097 100644 (file)
@@ -105,8 +105,7 @@ Default: yes.
 Default schema
 --------------
 
-This is the 4.3 schema.
-The `4.2 schema <https://github.com/PowerDNS/pdns/blob/rel/auth-4.2.x/modules/gpgsqlbackend/schema.pgsql.sql>`_ and the `the 4.1 schema <https://github.com/PowerDNS/pdns/blob/rel/auth-4.1.x/modules/gpgsqlbackend/schema.pgsql.sql>`_ is available on GitHub.
+This is the 4.7 schema.
 
 .. literalinclude:: ../../modules/gpgsqlbackend/schema.pgsql.sql
    :language: SQL
@@ -176,4 +175,4 @@ taken from the generic MySQL backend, and modified for syntax:
 References
 ^^^^^^^^^^
 
-See `this Github issue <https://github.com/PowerDNS/pdns/issues/5375#issuecomment-644771800>`__ for the original tests and a full working schema.
+See `this GitHub issue <https://github.com/PowerDNS/pdns/issues/5375#issuecomment-644771800>`__ for the original tests and a full working schema.
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 e8e055c692b39d2d131e3dcbfbb7232753765ba4..b6a02b132429958eb7e557ec09401f9aff60a103 100644 (file)
@@ -34,8 +34,7 @@ Setting up the database
 ------------------------
 
 Before you can use this backend you first have to set it up and fill it
-with data. The default setup conforms to the following schema in 4.3.
-If you have not upgraded to 4.3, please use `the 4.2 schema <https://github.com/PowerDNS/pdns/blob/rel/auth-4.2.x/modules/gsqlite3backend/schema.sqlite3.sql>`_ or `the 4.1 schema <https://github.com/PowerDNS/pdns/blob/rel/auth-4.1.x/modules/gsqlite3backend/schema.sqlite3.sql>`_ on GitHub.
+with data. The default setup conforms to the following schema in 4.7.
 
 .. literalinclude:: ../../modules/gsqlite3backend/schema.sqlite3.sql
 
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 d72f52efbd8506c688b3af6b7643167a22a60ec0..0bf1df1fdcc1a39f504e16f3cc87fea0efcad318 100644 (file)
@@ -3,35 +3,35 @@ Backends
 
 The following table describes the supported backends and some of their capabilities.
 
-+------------------------------------------------+--------+--------+-------+--------------+------------------------------------------+---------------------------------+--------------+
-| Name                                           | Native | Master | Slave | Super slave  | :doc:`Dynamic DNS Update <../dnsupdate>` | :doc:`DNSSEC <../dnssec/index>` | Launch       |
-+================================================+========+========+=======+==============+==========================================+=================================+==============+
-| :doc:`BIND <bind>`                             | Yes    | Yes    | Yes   | Experimental | No                                       | Yes                             | ``bind``     |
-+------------------------------------------------+--------+--------+-------+--------------+------------------------------------------+---------------------------------+--------------+
-| :doc:`Generic Mysql <generic-mysql>`           | Yes    | Yes    | Yes   | Yes          | Yes                                      | Yes                             | ``gmysql``   |
-+------------------------------------------------+--------+--------+-------+--------------+------------------------------------------+---------------------------------+--------------+
-| :doc:`Generic ODBC <generic-odbc>`             | Yes    | Yes    | Yes   | Yes          | Yes                                      | Yes                             | ``godbc``    |
-+------------------------------------------------+--------+--------+-------+--------------+------------------------------------------+---------------------------------+--------------+
-| :doc:`Generic Postgresql <generic-postgresql>` | Yes    | Yes    | Yes   | Yes          | Yes                                      | Yes                             | ``gpgsql``   |
-+------------------------------------------------+--------+--------+-------+--------------+------------------------------------------+---------------------------------+--------------+
-| :doc:`Generic SQLite3 <generic-sqlite3>`       | Yes    | Yes    | Yes   | Yes          | Yes                                      | Yes                             | ``gsqlite3`` |
-+------------------------------------------------+--------+--------+-------+--------------+------------------------------------------+---------------------------------+--------------+
-| :doc:`GeoIP <geoip>`                           | Yes    | No     | No    | No           | No                                       | Yes                             | ``geoip``    |
-+------------------------------------------------+--------+--------+-------+--------------+------------------------------------------+---------------------------------+--------------+
-| :doc:`LDAP <ldap>`                             | Yes    | Yes    | No    | No           | No                                       | No                              | ``ldap``     |
-+------------------------------------------------+--------+--------+-------+--------------+------------------------------------------+---------------------------------+--------------+
-| :doc:`LMDB <lmdb>`                             | Yes    | Yes    | Yes   | No           | No                                       | Yes                             | ``lmdb``     |
-+------------------------------------------------+--------+--------+-------+--------------+------------------------------------------+---------------------------------+--------------+
-| :doc:`Lua2 <lua2>`                             | Yes    | Yes    | No    | No           | No                                       | Yes                             | ``lua2``     |
-+------------------------------------------------+--------+--------+-------+--------------+------------------------------------------+---------------------------------+--------------+
-| :doc:`Pipe <pipe>`                             | Yes    | No     | No    | No           | No                                       | Partial                         | ``pipe``     |
-+------------------------------------------------+--------+--------+-------+--------------+------------------------------------------+---------------------------------+--------------+
-| :doc:`Random <random>`                         | Yes    | No     | No    | No           | No                                       | Partial                         | ``random``   |
-+------------------------------------------------+--------+--------+-------+--------------+------------------------------------------+---------------------------------+--------------+
-| :doc:`Remote <remote>`                         | Yes    | Yes\*  | Yes\* | Yes\*        | No                                       | Yes\*                           | ``remote``   |
-+------------------------------------------------+--------+--------+-------+--------------+------------------------------------------+---------------------------------+--------------+
-| :doc:`TinyDNS <tinydns>`                       | Yes    | Yes    | No    | No           | No                                       | Partial                         | ``tinydns``  |
-+------------------------------------------------+--------+--------+-------+--------------+------------------------------------------+---------------------------------+--------------+
++------------------------------------------------+--------+---------+-----------+----------+----------+--------------+----------------------------------+---------------------------------+--------------+
+| Name                                           | Native | Primary | Secondary | Producer | Consumer | Autoprimary  | :doc:`DNS Update <../dnsupdate>` | :doc:`DNSSEC <../dnssec/index>` | Launch       |
++================================================+========+=========+===========+==========+==========+==============+==================================+=================================+==============+
+| :doc:`BIND <bind>`                             | Yes    | Yes     | Yes       | No       | No       | Yes          | No                               | Yes                             | ``bind``     |
++------------------------------------------------+--------+---------+-----------+----------+----------+--------------+----------------------------------+---------------------------------+--------------+
+| :doc:`Generic Mysql <generic-mysql>`           | Yes    | Yes     | Yes       | Yes      | Yes      | Yes          | Yes                              | Yes                             | ``gmysql``   |
++------------------------------------------------+--------+---------+-----------+----------+----------+--------------+----------------------------------+---------------------------------+--------------+
+| :doc:`Generic ODBC <generic-odbc>`             | Yes    | Yes     | Yes       | Yes      | Yes      | Yes          | Yes                              | Yes                             | ``godbc``    |
++------------------------------------------------+--------+---------+-----------+----------+----------+--------------+----------------------------------+---------------------------------+--------------+
+| :doc:`Generic Postgresql <generic-postgresql>` | Yes    | Yes     | Yes       | Yes      | Yes      | Yes          | Yes                              | Yes                             | ``gpgsql``   |
++------------------------------------------------+--------+---------+-----------+----------+----------+--------------+----------------------------------+---------------------------------+--------------+
+| :doc:`Generic SQLite3 <generic-sqlite3>`       | Yes    | Yes     | Yes       | Yes      | Yes      | Yes          | Yes                              | Yes                             | ``gsqlite3`` |
++------------------------------------------------+--------+---------+-----------+----------+----------+--------------+----------------------------------+---------------------------------+--------------+
+| :doc:`GeoIP <geoip>`                           | Yes    | No      | No        | No       | No       | No           | No                               | Yes                             | ``geoip``    |
++------------------------------------------------+--------+---------+-----------+----------+----------+--------------+----------------------------------+---------------------------------+--------------+
+| :doc:`LDAP <ldap>`                             | Yes    | Yes     | No        | No       | No       | No           | No                               | No                              | ``ldap``     |
++------------------------------------------------+--------+---------+-----------+----------+----------+--------------+----------------------------------+---------------------------------+--------------+
+| :doc:`LMDB <lmdb>`                             | Yes    | Yes     | Yes       | Yes      | Yes      | No           | No                               | Yes                             | ``lmdb``     |
++------------------------------------------------+--------+---------+-----------+----------+----------+--------------+----------------------------------+---------------------------------+--------------+
+| :doc:`Lua2 <lua2>`                             | Yes    | Yes     | No        | No       | No       | No           | No                               | Yes                             | ``lua2``     |
++------------------------------------------------+--------+---------+-----------+----------+----------+--------------+----------------------------------+---------------------------------+--------------+
+| :doc:`Pipe <pipe>`                             | Yes    | No      | No        | No       | No       | No           | No                               | No                              | ``pipe``     |
++------------------------------------------------+--------+---------+-----------+----------+----------+--------------+----------------------------------+---------------------------------+--------------+
+| :doc:`Random <random>`                         | Yes    | No      | No        | No       | No       | No           | No                               | No                              | ``random``   |
++------------------------------------------------+--------+---------+-----------+----------+----------+--------------+----------------------------------+---------------------------------+--------------+
+| :doc:`Remote <remote>`                         | Yes    | Yes\*   | Yes\*     | No       | No       | Yes\*        | No                               | Yes\*                           | ``remote``   |
++------------------------------------------------+--------+---------+-----------+----------+----------+--------------+----------------------------------+---------------------------------+--------------+
+| :doc:`TinyDNS <tinydns>`                       | Yes    | Yes     | No        | No       | No       | No           | No                               | No                              | ``tinydns``  |
++------------------------------------------------+--------+---------+-----------+----------+----------+--------------+----------------------------------+---------------------------------+--------------+
 
 All the generic SQL backends have similar functionality, apart from the database they communicate with.
 These backends have :doc:`features unique <generic-sql>` to the generic SQL backends.
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 474cc3cdac82332624b514ad6b05d138f2836a17..178dcf738cd27c9082cb64d587576a8a5eb74af3 100644 (file)
@@ -7,7 +7,7 @@ LMDB backend
 * Superslave: No
 * Case: All lower
 * DNSSEC: Yes
-* Disabled data: No
+* Disabled data: Yes
 * Comments: No
 * Multiple instances: No
 * Zone caching: Yes
@@ -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,
@@ -72,7 +72,7 @@ Default is 2 on 32 bits systems, and 64 on 64 bits systems.
 ``lmdb-schema-version``
 ^^^^^^^^^^^^^^^^^^^^^^^
 
-Determines the maximum schema version LMDB is allowed to upgrade to. If the on disk LMDB database has a lower version that the current version of the LMDB schema the backend will not start, unless this setting allows it to upgrade the schema. If the version of the DB is already the same as the current schema version this setting is not checked and the backend starts normally.
+Determines the maximum schema version LMDB is allowed to upgrade to. If the on disk LMDB database has a lower version than the current version of the LMDB schema the backend will not start, unless this setting allows it to upgrade the schema. If the version of the DB is already the same as the current schema version this setting is not checked and the backend starts normally.
 
 The default value for this setting is the highest supported schema version for the version of PowerDNS you are starting. if you want to prevent automatic schema upgrades, explicitly set this setting to the current default before upgrading PowerDNS.
 
@@ -81,7 +81,9 @@ PowerDNS Version  LMDB Schema version
 ================  ===================
 4.2.x             1
 4.3.x             2
-4.4.x and up      3
+4.4.x to 4.6.x    3
+4.7.x and up      4
+4.8.x and up      5
 ================  ===================
 
 .. _settings-lmdb-random-ids:
@@ -91,8 +93,12 @@ PowerDNS Version  LMDB Schema version
 
   .. versionadded:: 4.7.0
 
+-  Boolean
+-  Default: no
+
 Numeric IDs inside the database are generated randomly instead of sequentially.
 If some external process is synchronising databases between systems, this will avoid conflicts when objects (domains, keys, etc.) get added.
+This will also improve the detection of recreated zones for :doc:`Catalog Zones <../catalog>` producers.
 
 .. _settings-lmdb-map-size:
 
@@ -105,6 +111,34 @@ Size, in megabytes, of each LMDB database.
 This number can be increased later, but never decreased.
 Defaults to 100 on 32 bit systems, and 16000 on 64 bit systems.
 
+.. _settings-lmdb-flag-deleted:
+
+``lmdb-flag-deleted``
+^^^^^^^^^^^^^^^^^^^^^
+
+  .. 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..7588684c59f0b8330a681efa3a0c0dd8586c02ed 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
diff --git a/docs/catalog.rst b/docs/catalog.rst
new file mode 100644 (file)
index 0000000..c3b6f5f
--- /dev/null
@@ -0,0 +1,142 @@
+Catalog Zones (RFC 9432)
+========================
+
+Starting with the PowerDNS Authoritative Server 4.7.0, catalog zone support is available.
+
+Supported catalog versions
+--------------------------
+
++-----------------+----------+----------+
+| Catalog version | Producer | Consumer |
++=================+==========+==========+
+| 1 (ISC)         | No       | Yes      |
++-----------------+----------+----------+
+| 2 (:rfc:`9432`) | Yes      | Yes      |
++-----------------+----------+----------+
+
+All the important features of catalog zones version "2" are supported.
+There are however a few properties where support is limited:
+
+-  There is no support for group templates on consumers;
+-  There is no support for custom extensions;
+
+The implementation requires the backend to support a number of new operations.
+Currently, the following backends have been modified to support catalog zones:
+
+- :doc:`gmysql <backends/generic-mysql>`
+- :doc:`gpgsql <backends/generic-postgresql>`
+- :doc:`gsqlite3 <backends/generic-sqlite3>`
+- :doc:`godbc <backends/generic-odbc>`
+- :doc:`lmdb <backends/lmdb>`
+
+.. _catalog-configuration-options:
+
+Configuration options
+---------------------
+
+None really.
+
+.. _catalog-metadata:
+
+Per zone settings
+-----------------
+
+It is highly recommended to protect catalog zones with :doc:`TSIG <../tsig>`
+
+CATALOG-HASH
+~~~~~~~~~~~~
+
+Producer zones store the member state as a hash in this metadata setting.
+This setting is managed by the authoritative server.
+Modifying or deleting this value will result in a serial increase of the producer zone and the update or recreation of this value.
+
+Setting up catalog zones
+------------------------
+
+.. note::
+  Catalog zone specification and operation is described in :rfc:`9432`.
+
+Setting up a producer zone
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Setting up a producer zone is not very different from a regular primary zone.
+A producer zone is a minimal zone of type PRODUCER with only SOA and NS records at apex.
+All the records in a producer zone are ignored while generating a catalog.
+
+An initial producer zone may look like this:
+
+::
+
+  $TTL 3600
+  $ORIGIN catalog.example.
+  @               IN      SOA     invalid. hostmaster.invalid. (
+                          1     ; serial
+                          1H    ; refresh
+                          10M   ; retry
+                          1W    ; expire
+                          1800  ; negTTL
+                          )
+
+  @               IN      NS      invalid.
+
+An interesting detail is the SOA serial:
+since the serial of a producer zone is automatically updated, it is important for the initial serial to be equal or lower than epoch.
+This serial is increased to EPOCH after each relevant member update.
+
+Create a producer zone:
+
+.. code-block:: shell
+
+  pdnsutil load-zone catalog.example zones/catalog.example ZONEFILE
+  pdnsutil set-kind catalog.example producer
+
+Creating producer zones is supported in the :doc:`API <http-api/zone>`, using type ``PRODUCER``.
+
+Assigning members to a producer zone
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+After the producer zone is created it is necessary to assign member zones to it.
+In the example below ``example.com`` is the member and ``catalog.example`` is the catalog.
+
+.. code-block:: shell
+
+  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 RFC.
+PowerDNS currently supports the following properties:
+
+- coo - A single DNSName
+- group - Multiple string values for group are allowed
+
+.. code-block:: shell
+
+  pdnsutil set-option example.com producer coo other-catalog.example
+  pdnsutil set-option example.com producer group pdns-group-x pdns-group-y
+
+There is also an option to set a specific <unique-N> value for a zone. This is done by setting a the ``unique`` value.
+This is used to signal a state reset to the consumer.
+The value for ``unique`` is a single DNS label.
+
+.. code-block:: shell
+
+  pdnsutil --config-dir=. --config-name=gmysql set-option test.com producer unique 123
+
+Setting options is not yet supported in the API.
+
+Setting up a consumer zone
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Setting up a consumer zone on a secondary server is almost identical to a normal secondary zone.
+The only difference is the type, which is now set to CONSUMER.
+
+.. code-block:: shell
+
+  pdnsutil create-secondary-zone catalog.example 192.0.2.42
+  pdnsutil set-kind catalog.example consumer
+
+Creating consumer zones is supported in the :doc:`API <http-api/zone>`, using type ``CONSUMER``.
+
+New member zones on the consumer adopt their primaries from the consumer zone.
index fa23a403d9442a1111e873edcf45d030f0f3c488..225a2cb7e0ab3fbc5d981b9cfc6989c329908070 100644 (file)
@@ -671,7 +671,7 @@ Changelogs for 4.1.x
     :tags: Internals, Bug Fixes
     :pullreq: 5917
 
-    Use ``_exit()`` when we really really want to exit, for example
+    Use ``_exit()`` when we *really* want to exit, for example
     after a fatal error. This stops us dying while we die. A call to
     ``exit()`` will trigger destructors, which may paradoxically stop
     the process from exiting, taking down only one thread, but harming
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 eab80f0e2b30b3e2fe5cb0654d24b9be49cdf322..f192483ddccd08f5b29f3f9ee5018c01adddc51f 100644 (file)
@@ -1,5 +1,17 @@
 Changelogs for 4.4.x
 ====================
+.. changelog::
+  :version: 4.4.3
+  :released: 25th of March 2022
+
+  This is a security fix release for :doc:`PowerDNS Security Advisory 2022-01 <../security-advisories/powerdns-advisory-2022-01>`.
+  Additionally, because CentOS 8 is End Of Life now, we have switched those builds to Oracle Linux 8. The resulting packages are compatible with RHEL and all derivatives.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11453
+
+    Fix validation of incremental zone transfers (IXFRs).
 
 .. changelog::
   :version: 4.4.2
@@ -312,7 +324,7 @@ Changelogs for 4.4.x
     :tags: New Features
     :pullreq: 9239
 
-    Add pdns_control command to the the list of XFR domains in queue
+    Add pdns_control command to the list of XFR domains in queue
 
 .. changelog::
   :version: 4.4.0-alpha3
index f25926a02a2056b524a192d250fa3310f17141b8..2970a3075d8f7682904adf6f89b4fe56f41b672d 100644 (file)
@@ -1,6 +1,50 @@
 Changelogs for 4.5.x
 ====================
 
+.. changelog::
+  :version: 4.5.5
+  :released: 9th of December 2022
+
+  This is release 4.5.5 of the Authoritative Server.
+  It contains various small fixes.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12032
+
+    axfr-retriever: abort on chunk with TC set
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12034
+
+    LUA records: we only need one IsUpOracle checker thread
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11979
+
+    docker: upgrade to bullseye
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11454
+
+    IXFR-in: Fix a case where an incomplete read caused by network error might result in a truncated zone
+
+.. changelog::
+  :version: 4.5.4
+  :released: 25th of March 2022
+
+  This is a security fix release for :doc:`PowerDNS Security Advisory 2022-01 <../security-advisories/powerdns-advisory-2022-01>`.
+  Additionally, because CentOS 8 is End Of Life now, we have switched those builds to Oracle Linux 8. The resulting packages are compatible with RHEL and all derivatives.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11454
+
+    Fix validation of incremental zone transfers (IXFRs).
+
 .. changelog::
   :version: 4.5.3
   :released: 21th of January 2022
@@ -374,10 +418,16 @@ Changelogs for 4.5.x
 
   .. change::
     :tags: Bug Fixes
-    :pullreq: 9766, 9844, 9919, 10074
+    :pullreq: 9766, 9844, 9919
 
     Fixed bugs in the implementations of the ``SVCB``, ``HTTPS``, ``IPSECKEY`` and ``APL`` types.
 
+  .. change::
+    :tags: New Features
+    :pullreq: 10074
+
+    ``SVCB`` improvements, including a new ``svc-autohints`` setting
+
   .. change::
     :tags: New Features
     :pullreq: 10078, 10172, 10121, 10256, 10234
index cdb7bffd06b9f2a5375fbe947ad5e70b28d5858b..f2d6ab102d7783d87bb2c095801960952ef8db41 100644 (file)
@@ -1,6 +1,124 @@
 Changelogs for 4.6.x
 ====================
 
+.. changelog::
+  :version: 4.6.4
+  :released: 9th of December 2022
+
+  This is release 4.6.4 of the Authoritative Server.
+  It contains various small fixes.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12303
+
+    pdnsutil edit-zone, detect capitalization changes in LUA, TXT and SPF records
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11945
+
+    initialize zone cache after dropping privileges; Detect invalid bytes in makeBytesFromHex()  (Kees Monshouwer)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12035
+
+    Log "NULL" for nullptr-bound properties instead of dereferencing
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12033
+
+    LUA records: we only need one IsUpOracle checker thread
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12031
+
+    axfr-retriever: abort on chunk with TC set
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11978
+
+    docker: upgrade to bullseye
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11871
+
+    use getInnerRemote() for the remotes ring
+
+
+.. changelog::
+  :version: 4.6.3
+  :released: 13th of July 2022
+
+  This is version 4.6.3 of the Authoritative Server.
+  It contains a few bug fixes, and marks the appearance of Ubuntu Jammy packages for the 4.6 branch.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11765
+
+    fix deleteDomain() in lmdb backend (Kees Monshouwer)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11746
+
+    RFC2136: match autosplit TXT correctly
+
+.. changelog::
+  :version: 4.6.2
+  :released: 12th of April 2022
+
+  This is version 4.6.2 of the Authoritative Server.
+  It contains a carefully selected set of new features, plus a few bug fixes.
+
+  .. change::
+    :tags: Improvements, New Features
+    :pullreq: 11406
+
+    LMDB backports:
+
+    * each LMDB database now gets a UUID
+    * lmdbbackend can now (optionally: :ref:`settings-lmdb-random-ids`) use random IDs instead of incremental IDs for objects
+    * LMDB map size is now configurable (:ref:`settings-lmdb-map-size`)
+    * one uninitialised memory issue that was fixed
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11409
+
+    API: fetch individual rrsets
+
+  .. change::
+    :tags: Bug Fixes, Enhancements
+    :pullreq: 11407
+
+    fix proxy protocol query statistics and add more detailed latency metrics
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11408
+
+    LUA: add ifurlextup function
+
+.. changelog::
+  :version: 4.6.1
+  :released: 25th of March 2022
+
+  This is a security fix release for :doc:`PowerDNS Security Advisory 2022-01 <../security-advisories/powerdns-advisory-2022-01>`.
+  Additionally, because CentOS 8 is End Of Life now, we have switched those builds to Oracle Linux 8. The resulting packages are compatible with RHEL and all derivatives.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11455
+
+    Fix validation of incremental zone transfers (IXFRs).
+
 .. changelog::
   :version: 4.6.0
   :released: 25th of January 2022
index 9b46cf7a23a7867052a546aae6d0f254d949229c..818e6e24d26891499ff676c301dfa4f5ebc0ffbe 100644 (file)
@@ -1,6 +1,477 @@
 Changelogs for 4.7.x
 ====================
 
+.. changelog::
+  :version: 4.7.4
+  :released: 17th of April 2023
+
+  This is release 4.7.4 of the Authoritative Server.
+  It contains various bug fixes, some performance improvements, and one new feature (``pdnsutil list-member-zones``).
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12742
+
+    Properly encode json string containing binary data
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12741
+
+    Prevent a race during the processing of SVC auto-hints
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12676
+
+    pdnsutil, implement list-member-zones (Kees Monshouwer)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12675
+
+    lmdb delete fixes and tests (Kees Monshouwer)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12429
+
+    minicurl: stop leaking hostlist memory
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12521
+
+    ixfrdist fixes and improvements
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12458
+
+    lock.hh: include <stdexcept>
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12746
+
+    Pick the right signer name when a NSEC name is also a delegation point
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12745
+
+    calm down the communicator loop
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12744
+
+    Fix multiple-version IXFR request handling in ixfrdist
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12743
+
+    timeout handling for IXFRs as a client
+
+.. changelog::
+  :version: 4.7.3
+  :released: 9th of December 2022
+
+  This is release 4.7.3 of the Authoritative Server.
+  It contains various fixes.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12299
+
+    lmdb: make outgoing notifications work
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12266
+
+    lmdb: implement alsoNotifies
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12291
+
+    API: do not create SOA and NS records for consumer zones
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12296
+
+    API: slightly clearer message when a backend cannot create domains
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12273
+
+    API: fix newly created zone not rectified
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12272
+
+    fix invalid catalog zone sql query for gpgsqlbackend
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12181
+
+    fix pdns_control list-zones
+
+
+.. changelog::
+  :version: 4.7.2
+  :released: 1st of November 2022
+
+  This is version 4.7.2 of the Authoritative Server.
+  It fixes one bug spotted after the release of 4.7.0, for which we forgot to include the fix in 4.7.1.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12130
+
+    Un-reverse xfr freshness check
+
+.. changelog::
+  :version: 4.7.1
+  :released: 31st of October 2022
+
+  This is version 4.7.1 of the Authoritative Server.
+  It fixes a few bugs spotted after the release of 4.7.0.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12110
+
+    include auth 4.7 schema upgrade files in tarballs and packages
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12124
+
+    catalog zones: avoid bulk zone reset while migrating to a catalog (Kees Monshouwer)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12124
+
+    catalog zones: stop wasting options update queries (Kees Monshouwer)
+
+.. changelog::
+  :version: 4.7.0
+  :released: 20th of October 2022
+
+  This is version 4.7.0 of the Authoritative Server.
+
+  4.7.0 brings support for :doc:`Catalog Zones <../catalog>`, developed by Kees Monshouwer.
+  As part of that development, the freshness checks in the Primary code were reworked, reducing them from doing potentially thousands of SQL queries (if you have thousands of domains) to only a few.
+  Installations with lots of domains will benefit greatly from this, even without using catalog zones.
+
+  4.7.0 also brings back GSS-TSIG support, previously removed for quality reasons, now reworked with many stability improvements.
+
+  Other things of note:
+
+  * LUA records, when queried over TCP, can now re-use a Lua state, giving a serious performance boost.
+  * lmdbbackend databases now get a UUID assigned, making it easy for external software to spot if a database was completely replaced
+  * lmdbbackend databases now optionally use random IDs for objects
+  * a new LUA function called ``ifurlextup``, and improvements in other LUA record functions
+  * autoprimary management in ``pdnsutil`` and the HTTP API
+  * in beta, a key roller daemon, currently not packaged
+
+  Please make sure to read the :doc:`upgrade notes <../upgrading>` before upgrading.
+
+  Besides that, various other smaller features and improvements have landed - please browse the list below.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12069
+
+    Fix compilation of the event ports multiplexer (Jonathan Perkin)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12085
+
+    pdnsutil check-zone, skip metadata check for backends without getAllDomainMetadata() (Kees Monshouwer)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12098
+
+    fix axfr for tinydns and pipe backend (Kees Monshouwer). Note that this was only broken since 4.7.0-beta2.
+
+.. changelog::
+  :version: 4.7.0-rc1
+  :released: 3rd of October 2022
+
+  This is the first release candidate for Authoritative Server 4.7.0.
+
+  4.7.0 brings support for :doc:`Catalog Zones <../catalog>`, developed by Kees Monshouwer.
+  As part of that development, the freshness checks in the Primary code were reworked, reducing them from doing potentially thousands of SQL queries (if you have thousands of domains) to only a few.
+  Installations with lots of domains will benefit greatly from this, even without using catalog zones.
+
+  4.7.0 also brings back GSS-TSIG support, previously removed for quality reasons, now reworked with many stability improvements.
+
+  Other things of note:
+
+  * LUA records, when queried over TCP, can now re-use a Lua state, giving a serious performance boost.
+  * lmdbbackend databases now get a UUID assigned, making it easy for external software to spot if a database was completely replaced
+  * lmdbbackend databases now optionally use random IDs for objects
+  * a new LUA function called ``ifurlextup``, and improvements in other LUA record functions
+  * autoprimary management in ``pdnsutil`` and the HTTP API
+  * in beta, a key roller daemon, currently not packaged
+
+  Please make sure to read the :doc:`upgrade notes <../upgrading>` before upgrading.
+
+  Besides that, various other smaller features and improvements have landed - please browse the list below.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12043
+
+    AXFR server: abort on chunk with TC set
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12042
+
+    add keyroller
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12040
+
+    pdnsutil edit-zone, detect capitalization changes in LUA, TXT and SPF records (Kees Monshouwer)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12030
+
+    axfr-retriever: abort on chunk with TC set
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12029
+
+    clang14 has reached macOS
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11972
+
+    docker: upgrade to bullseye
+
+.. changelog::
+  :version: 4.7.0-beta2
+  :released: 13th of September 2022
+
+  This is the first published beta for Authoritative Server 4.7.0.
+  (beta1 was never released because of bugs found during the release process).
+
+  4.7.0 brings support for :doc:`Catalog Zones <../catalog>`, developed by Kees Monshouwer.
+  As part of that development, the freshness checks in the Primary code were reworked, reducing them from doing potentially thousands of SQL queries (if you have thousands of domains) to only a few.
+  Installations with lots of domains will benefit greatly from this, even without using catalog zones.
+
+  4.7.0 also brings back GSS-TSIG support, previously removed for quality reasons, now reworked with many stability improvements.
+
+  Other things of note:
+
+  * LUA records, when queried over TCP, can now re-use a Lua state, giving a serious performance boost.
+  * lmdbbackend databases now get a UUID assigned, making it easy for external software to spot if a database was completely replaced
+  * lmdbbackend databases now optionally use random IDs for objects
+  * a new LUA function called ``ifurlextup``, and improvements in other LUA record functions
+  * autoprimary management in ``pdnsutil`` and the HTTP API
+
+  Please make sure to read the :doc:`upgrade notes <../upgrading>` before upgrading.
+
+  Besides that, various other smaller features and improvements have landed - please browse the list below.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11918
+
+    some small NSEC3PARAM-related fixes to the REST API (Kees Monshouwer)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11842
+
+    use getInnerRemote() for the remotes ring (Kees Monshouwer)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11760, 11929, 11933
+
+    LUA records: make shared mode work for TCP queries
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11815
+
+    make sure a notified zone is in the zone cache (Kees Monshouwer)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11759, 11755
+
+    getTSIGKey(s) cleanup (Kees Monshouwer)
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11772, 11822, 11825, 11836
+
+    Implement catalog zones in the authoritative server (Kees Monshouwer)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11764
+
+    fix deleteDomain() in lmdb backend (Kees Monshouwer)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11738
+
+    2136: match autosplit TXT more usefully
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11588
+
+    Extend LUA records (rage4)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11727
+
+    Also allow generic record format in zone parsing for pdnsutil zonemd-verify-file
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11340
+
+    pdnsutil flush prompt (norve)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11350
+
+    no ALIAS and LUA record expansion in presigned zones (Kees Monshouwer)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11655
+
+    Change dns_tolower() and dns_toupper() to use a table
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11639
+
+    auth packaging: add DoT support to sdig
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11599
+
+    Tweak for Coverity 1488422
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11590, 11493, 11432, 11414, 11426
+
+    RSA, ECDSA PEM import/export
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11562
+
+    Try harder to find libdecaf headers
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11466
+
+    ixfr: Fix a case where an incomplete read caused by network error might result in a truncated zone
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11389
+
+    auth API: fetch individual rrsets
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11314
+
+    fix proxy protocol query statistics (Kees Monshouwer)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11354
+
+    lmdb random-ids: stop generating negative numbers
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11328
+
+    lmdb: make map size configurable
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11143
+
+    reintroduce GSS-TSIG support
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11882
+
+    Log "NULL" for nullptr-bound properties instead of dereferencing
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11813
+
+    web: stop sending Server: header
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11862
+
+    libssl: Properly load ciphers and digests with OpenSSL 3.0
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11908
+
+    initialize zone cache after dropping privileges
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11860
+
+    Fix libcrypto handling in automake files
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11508
+
+    New setting compare-signatures-on-zone-freshness-check to disable DO flag for SOA checks
+
+.. changelog::
+  :version: 4.7.0-beta1
+  :released: never
+
+  Bugs were found after beta1 was tagged.
+  Authoritative server 4.7.0-beta1 was never released.
+
 .. changelog::
   :version: 4.7.0-alpha1
   :released: 17th of February 2022
@@ -76,4 +547,3 @@ Changelogs for 4.7.x
     :pullreq: 11101
 
     save errno value as close(2) might clobber it
-
diff --git a/docs/changelog/4.8.rst b/docs/changelog/4.8.rst
new file mode 100644 (file)
index 0000000..a687865
--- /dev/null
@@ -0,0 +1,371 @@
+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>`.
+
+  `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.
+
+  This version also contains various other small fixes and improvements.
+  Except for very minor ones, they are listed below.
+
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12546
+
+    new lmdbbackend storage schema that is compatible with Lightning Stream
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11101
+
+    save errno value as close(2) might clobber it
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11600, 12401, 12414, 12423, 12462, 12501, 12502, 12513, 12515, 12516, 12524, 12527, 12540, 12550
+
+    cleanup for OpenSSL 3.0 API    
+
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12127
+
+    Fix multiple-version IXFR request handling in ixfrdist (Håkan Lindqvist)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12260
+
+    Properly encode json strings containing binary data
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12322
+
+    lmdb, fix TSIG key removal (Kees Monshouwer)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11065
+
+    service files: Add more sandboxing options
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12277
+
+    add byteslimit support to lua ifurlup() function
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12265
+
+    move alsoNotifies up into DNSBackend
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12252
+
+    Update supported record types: remove A6 MAILA MAILB SIG and WKS, add ZONEMD
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11346
+
+    Basic abstraction for handling colored terminal output, respecting isatty(), --no-colors and NO_COLOR
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12066
+
+    Detect invalid bytes in `makeBytesFromHex()`
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11858
+
+    change sdig output order (Kees Monshouwer)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11908
+
+    initialize zone cache after dropping privileges (Kees Monshouwer)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12659
+
+    Prevent a race during the processing of SVC auf-hints
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12406
+
+    pdnsutil, implement list-member-zones (Kees Monshouwer)
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12349
+
+    LUA: accept more hex formats in createForward[6]
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12331
+
+    fix ColumnSize argument in SQLBindParameter #12324 (v1shnya)
diff --git a/docs/changelog/4.9.rst b/docs/changelog/4.9.rst
new file mode 100644 (file)
index 0000000..7b546ca
--- /dev/null
@@ -0,0 +1,224 @@
+Changelogs for 4.9.x
+====================
+
+.. 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 6d5dbfcbe0d68c08ed6a63e906aaeae7ecc79c69..87cc4a2d5f772ee6f5e4fd4cc5d9b9d51300e2c5 100644 (file)
@@ -6,6 +6,8 @@ The changelogs for the PowerDNS Authoritative Server are split between release t
 .. toctree::
     :maxdepth: 2
 
+    4.9
+    4.8
     4.7
     4.6
     4.5
index c552916f19db5b87f33992874fff0f9a6181df23..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
@@ -3288,7 +3284,7 @@ New features
 -  Added support for DHCID, IPSECKEY and KX records, thanks Norbert
    Sendetzky for the hint. Implemented in `commit
    1144 <http://wiki.powerdns.com/projects/trac/changeset/1144>`__.
--  Norbert Sendetzky has has added support for all record types
+-  Norbert Sendetzky has added support for all record types
    supported by PowerDNS to the LDAPBackend. Furthermore, the detection
    of OpenLDAP in autoconf has been improved. Finally, debian has
    supplied some fixes to PowerLDAP. Implemented in `commit
@@ -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
@@ -5246,7 +5242,7 @@ Changes
 ^^^^^^^
 
 -  The monitor command **set** no longer allows the changing of
-   non-existent variables.
+   nonexistent variables.
 -  IBM Universal Database DB2 backend now included in source
    distribution (untested!)
 -  Oracle backend now included in source distribution (slightly tested!)
@@ -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.
@@ -5916,7 +5912,7 @@ Bugs fixed
 -  PostgreSQL backend was case sensitive and returned only answers in
    case an exact match was found. The Generic PostgreSQL backend is now
    officially all lower case and zone2sql in PostgreSQL mode enforces
-   this. Documentation has been been updated to reflect the case change.
+   this. Documentation has been updated to reflect the case change.
    Thanks to Maikel Verheijen of Ladot for spotting this!
 -  Documentation bug - postgresql create/index statements created a
    duplicate index. If you've previously copy pasted the commands and
@@ -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 3f457a9f4433cd5ca9fd54024d9aef1d2c72e26e..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.
@@ -12,10 +12,10 @@ This :doc:`license <../common/license>`  is included in this documentation.
 
 If you believe you have found a security vulnerability that applies to DNS implementations generally, and you want to report this responsibly to a number of implementers, you might consider also using the `Open Source DNS Vulnerability mailing list <https://www.dns-oarc.net/oarc/oss-dns-vulns/>`_, managed by `DNS-OARC <https://www.dns-oarc.net/>`_.
 
-HackerOne
+YesWeHack
 ^^^^^^^^^
-Security issues can also be reported on `our HackerOne page <https://hackerone.com/powerdns>`_ and might fetch a bounty.
-Do note that only the PowerDNS software is in scope for the HackerOne program, not our websites or other infrastructure.
+Security issues can also be reported on `our YesWeHack page <https://yeswehack.com/programs/powerdns>`_ and might fetch a bounty.
+Do note that only the PowerDNS software is in scope for the YesWeHack program, not our websites or other infrastructure.
 
 Disclosure Policy
 ^^^^^^^^^^^^^^^^^
index 22a5285a07bab70e35d910f69a499d5cf5a8bea8..2c8b05f40d03fa36b28f2c76678dbad7a44293e2 100644 (file)
@@ -1,6 +1,13 @@
 * `FBAE 0323 821C 7706 A5CA 151B DCF5 13FA 7EED 19F3 <https://pgp.mit.edu/pks/lookup?op=get&search=0xDCF513FA7EED19F3>`_
+* `D630 0CAB CBF4 69BB E392 E503 A208 ED4F 8AF5 8446 <https://pgp.mit.edu/pks/lookup?op=get&search=0xA208ED4F8AF58446>`_
+* `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://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:
+
 * `1628 90D0 689D D12D D33E 4696 1C5E E990 D2E7 1575 <https://pgp.mit.edu/pks/lookup?op=get&search=0x1C5EE990D2E71575>`_
 * `B76C D467 1C09 68BA A87D E61C 5E50 715B F2FF E1A7 <https://pgp.mit.edu/pks/lookup?op=get&search=0x5E50715BF2FFE1A7>`_
-* `16E1 2866 B773 8C73 976A 5743 6FFC 3343 9B0D 04DF <https://pgp.mit.edu/pks/lookup?op=get&search=0x6FFC33439B0D04DF>`_
 
-There is a PGP keyblock with these keys available on `https://www.powerdns.com/powerdns-keyblock.asc <https://www.powerdns.com/powerdns-keyblock.asc>`_.
+
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 b7ad8da7bcb39f10b388255b8cf0e453965e3aa9..b1f95f1ba0a81f07afd5efa58dbfdbf6e983b747 100644 (file)
@@ -2,7 +2,7 @@ PKCS#11 support
 ===============
 
 .. note::
-  This feature is experimental, use at your own risk!
+  This is an experimental feature, use at your own risk!
 
 To enable it, compile PowerDNS Authoritative Server using ``--enable-experimental-pkcs11`` flag on configure.
 This requires you to have the p11-kit libraries and headers.
index c46e6d91919b796bac904429e201bffcfc9ca53a..f1f946fa680fd96221715fe9d4aadf0f6e8c94d7 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:
@@ -110,30 +110,48 @@ TSIG-ALLOW-DNSUPDATE
 
 This setting allows you to set the TSIG key required to do an DNS
 update. If you have GSS-TSIG enabled, you can use Kerberos principals
-here. An example, using :program:`pdnsutil` to create the key::
+here. Here is an example using :program:`pdnsutil` to create a key named
+`test`::
 
-    $ pdnsutil generate-tsig-key test hmac-md5
-    Create new TSIG key test hmac-md5 kp4/24gyYsEzbuTVJRUMoqGFmN3LYgVDzJ/3oRSP7ys=
+    $ pdnsutil generate-tsig-key test hmac-sha512
+    Create new TSIG key test hmac-sha512 [base64-encoded key]
 
-Then adding that key with the name `test` and add the metadata::
+    $ pdnsutil list-tsig-keys | grep test
+    test. hmac-sha512. [base64-encoded key]
 
-    pdnsutil import-tsig-key test hmac-md5 'kp4/24gyYsEzbuTVJRUMoqGFmN3LYgVDzJ/3oRSP7ys='
-    pdnsutil set-meta example.org TSIG-ALLOW-DNSUPDATE test
+This adds the key with the name `test` to the zone's metadata. Note, the
+keys need to be added separately with `add-meta`, not as a comma or
+space-separated list::
 
-An example of how to use a TSIG key with the :program:`nsupdate` command::
+    $ pdnsutil add-meta example.org TSIG-ALLOW-DNSUPDATE test
+    Set 'example.org' meta TSIG-ALLOW-DNSUPDATE = test
 
-    nsupdate <<!
-    server <ip> <port>
+    $ pdnsutil get-meta example.org TSIG-ALLOW-DNSUPDATE
+    TSIG-ALLOW-DNSUPDATE = test
+
+This is an example of using the new `test` TSIG key with the :program:`nsupdate`
+command (see the manpage for :program:`nsupdate` for full details)::
+
+    $ nsupdate <<!
+    server 127.0.0.1 53
     zone example.org
-    update add test1.example.org 3600 A 203.0.113.1
-    key test kp4/24gyYsEzbuTVJRUMoqGFmN3LYgVDzJ/3oRSP7ys=
+    update add test1.example.org 3600 A 1.2.3.4
+    update add test1.example.org 3600 TXT "this is a test"
+    key hmac-sha512:test [base64-encoded key]
     send
     !
 
-If a TSIG key is set for the domain, it is required to be used for the
-update. The TSIG is an alternative means of securing updates, instead of using the
-``ALLOW-DNSUPDATE-FROM`` setting. If a TSIG key is set, and if ``ALLOW-DNSUPDATE-FROM`` is set,
-the IP(-range) of the updater still needs to be allowed via ``ALLOW-DNSUPDATE-FROM``. 
+    $ dig +noall +answer -t any test1.example.org @127.0.0.1
+    test1.example.org. 3600    IN      A       1.2.3.4
+    test1.example.org. 3600    IN      TXT     "this is a test"
+
+If any TSIG keys are listed in a zone's ``TSIG-ALLOW-DNSUPDATE`` metadata, one
+of them is required for updates. If ``ALLOW-DNSUPDATE-FROM`` is also set,
+both requirements need to be satisfied before an update will be accepted.
+
+By default, an update can add, update or delete any resource records in
+the zone.  See :ref:`dnsupdate-update-policy` for finer-grained
+control of what an update is allowed to do.
 
 .. _metadata-forward-dnsupdate:
 
@@ -186,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::
@@ -297,7 +315,7 @@ should update and on which master domain server it is running.
 This tells **dhcpd** a number of things:
 
 1. Which domain to use (**ddns-domainname "example.org";**)
-2. Which reverse-domain to use (**dnssec-rev-domainname
+2. Which reverse-domain to use (**ddns-rev-domainname
    "in-addr.arpa.";**)
 3. For the zones, where the primary master is located (**primary
    127.0.0.1;**)
index 382e68c053b77d2a0086d15cfb05dfbe360a6a1e..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:
 
@@ -73,7 +67,7 @@ This metadata item controls whether or not a zone is fully rectified on changes
 to the contents of a zone made through the :doc:`API <http-api/index>`.
 
 When the ``API-RECTIFY`` value is "1", the zone will be rectified on changes.
-Any other other value means that it will not be rectified. If this is not set
+Any other value means that it will not be rectified. If this is not set
 at all, rectifying of the zone depends on the config variable
 :ref:`setting-default-api-rectify`.
 
@@ -96,7 +90,7 @@ ALSO-NOTIFY
 -----------
 
 When notifying this domain, also notify this nameserver (can occur
-multiple times). The nameserver may have contain an optional port
+multiple times). The nameserver may contain an optional port
 number. e.g.:
 
 .. code-block:: shell
@@ -112,8 +106,13 @@ Use this named TSIG key to retrieve this zone from its master, see :ref:`tsig-pr
 
 GSS-ALLOW-AXFR-PRINCIPAL
 ------------------------
-  .. versionchanged:: 4.3.1
-    GSS support was removed
+.. versionchanged:: 4.3.1
+
+   GSS support was removed
+
+.. versionchanged:: 4.7.0
+
+   GSS support was added back
 
 Allow this GSS principal to perform AXFR retrieval. Most commonly it is
 ``host/something@REALM``, ``DNS/something@REALM`` or ``user@REALM``.
@@ -121,8 +120,6 @@ Allow this GSS principal to perform AXFR retrieval. Most commonly it is
 
 GSS-ACCEPTOR-PRINCIPAL
 ----------------------
-  .. versionchanged:: 4.4.0
-    GSS support was removed
 
 Use this principal for accepting GSS context.
 (See :ref:`tsig-gss-tsig`).
index 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 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 27179cc09e1447fa203d77002dfb7db3a829f6a7..b0ca7e0da067ace663bc45eed154d3f2c9f106ca 100644 (file)
@@ -11,6 +11,9 @@ Automatic hints
 PowerDNS can automatically fill in ``ipv4hint`` and ``ipv6hint`` parameters in SVCB records based on A and AAAA records already present in the zone.
 This can be enabled by setting :ref:`setting-svc-autohints` to 'yes'.
 
+.. versionadded:: 4.5.0
+  The ``svc-autohints`` setting was added in 4.5.0
+
 Consider the following zone content::
 
   example.org      IN HTTPS 0 www.example.org
@@ -63,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 ce64211896b66a82f51b75759b7cbaaa1f3d129c..00b1de982e890682f6d21f7302f10adbfac6642c 100644 (file)
@@ -15,3 +15,40 @@ Objects
 -------
 .. openapi:: swagger/authoritative-api-swagger.yaml
   :definitions: Server
+
+Examples
+--------
+
+Listing all servers
+^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: http
+
+  GET /api/v1/servers HTTP/1.1
+  X-API-Key: secret
+
+Will yield a response similar to this (several headers omitted):
+
+.. code-block:: http
+  
+  HTTP/1.1 200 OK
+  Content-Type: application/json
+  
+  [{"autoprimaries_url": "/api/v1/servers/localhost/autoprimaries{/autoprimary}", "config_url": "/api/v1/servers/localhost/config{/config_setting}", "daemon_type": "authoritative", "id": "localhost", "type": "Server", "url": "/api/v1/servers/localhost", "version": "4.6.1", "zones_url": "/api/v1/servers/localhost/zones{/zone}"}]
+
+Listing a server
+^^^^^^^^^^^^^^^^
+
+.. code-block:: http
+
+  GET /api/v1/servers/localhost HTTP/1.1
+  X-API-Key: secret
+
+Will yield a response similar to this (several headers omitted):
+
+.. code-block:: http
+  
+  HTTP/1.1 200 OK
+  Content-Type: application/json
+  
+  {"autoprimaries_url": "/api/v1/servers/localhost/autoprimaries{/autoprimary}", "config_url": "/api/v1/servers/localhost/config{/config_setting}", "daemon_type": "authoritative", "id": "localhost", "type": "Server", "url": "/api/v1/servers/localhost", "version": "4.6.1", "zones_url": "/api/v1/servers/localhost/zones{/zone}"}
index 709a7f0ec76e52b284c3a45223592e518b443bc8..c231b9d1fdd1b571adb54559a726b75ccdb44006 100644 (file)
@@ -250,7 +250,7 @@ paths:
 
     put:
       summary: Modifies basic zone data.
-      description: 'The only fields in the zone structure which can be modified are: kind, masters, account, soa_edit, soa_edit_api, api_rectify, dnssec, and nsec3param. All other fields are ignored.'
+      description: 'The only fields in the zone structure which can be modified are: kind, masters, catalog, account, soa_edit, soa_edit_api, api_rectify, dnssec, and nsec3param. All other fields are ignored.'
       operationId: putZone
       tags:
         - zones
@@ -625,7 +625,7 @@ paths:
           required: true
           description: The kind of metadata
       responses:
-        '200':
+        '204':
           description: OK
         <<: *commonErrors
 
@@ -988,7 +988,9 @@ definitions:
           - 'Native'
           - 'Master'
           - 'Slave'
-        description: 'Zone kind, one of “Native”, “Master”, “Slave”'
+          - 'Producer'
+          - 'Consumer'
+        description: 'Zone kind, one of “Native”, “Master”, “Slave”, “Producer”, “Consumer”'
       rrsets:
         type: array
         items:
@@ -1028,10 +1030,13 @@ definitions:
         description: 'The SOA-EDIT-API metadata item'
       api_rectify:
         type: boolean
-        description: ' Whether or not the zone will be rectified on data changes via the API'
+        description: 'Whether or not the zone will be rectified on data changes via the API'
       zone:
         type: string
         description: 'MAY contain a BIND-style zone file when creating a zone'
+      catalog:
+        type: string
+        description: 'The catalog this zone is a member of'
       account:
         type: string
         description: 'MAY be set. Its value is defined by local policy'
index dce4448684c70bb51a3ebfd3d259ae93dcf5b72e..0c1bab429843c20a8a83fd856340e5dc35fe16e4 100644 (file)
@@ -4,6 +4,18 @@ TSIGKeys
 
 TSIGKeys can be manipulated via the API.
 
+TSIGKey Endpoints
+-----------------
+
+.. openapi:: swagger/authoritative-api-swagger.yaml
+  :paths: /servers/{server_id}/tsigkeys /servers/{server_id}/tsigkeys/{tsigkey_id}
+
+Objects
+-------
+
+.. openapi:: swagger/authoritative-api-swagger.yaml
+  :definitions: TSIGKey
+
 Examples
 --------
 
@@ -44,16 +56,4 @@ Modifying the key material
   Content-Type: application/json
 
   {"algorithm": "hmac-sha256", "id": "mytsigkey.", "key": "GQNyFy1QagMUarHmiSgsIJajghdTGJGVcN5TRVwgbclzxGyhQR1uYLCOyJ/uj9uj12jyeLwzJuW12wCI9PYv7Q==", "name": "mytsigkey", "type": "TSIGKey"}
-
-
-TSIGKey Endpoints
------------------
-
-.. openapi:: swagger/authoritative-api-swagger.yaml
-  :paths: /servers/{server_id}/tsigkeys /servers/{server_id}/tsigkeys/{tsigkey_id}
-
-Objects
--------
-
-.. openapi:: swagger/authoritative-api-swagger.yaml
-  :definitions: TSIGKey
+  
index ddda71559fb61fcdf4310ac0c3281d705203f742..3adb26042a473cda455d807e80f23c85f7f82140 100644 (file)
@@ -44,3 +44,126 @@ These things are not supported through the API.
 
 When creating a slave zone, it is recommended to not set any of
 ``nameservers``, ``rrsets`` or ``zone``.
+
+Examples
+--------
+
+Listing all zones
+^^^^^^^^^^^^^^^^^
+
+.. code-block:: http
+
+  GET /api/v1/servers/localhost/zones HTTP/1.1
+  X-API-Key: secret
+
+Will yield a response similar to this (several headers omitted):
+
+.. code-block:: http
+  
+  HTTP/1.1 200 OK
+  Content-Type: application/json
+
+  [{"account": "", "dnssec": false, "edited_serial": 2022040504, "id": "example.org.", "kind": "Native", "last_check": 0, "masters": [], "name": "example.org.", "notified_serial": 0, "serial": 2022040504, "url": "/api/v1/servers/localhost/zones/example.org."}]
+
+Creating new zone
+^^^^^^^^^^^^^^^^^
+
+.. code-block:: http
+
+  POST /api/v1/servers/localhost/zones HTTP/1.1
+  X-API-Key: secret
+  Content-Type: application/json
+
+  {"name": "example.org.", "kind": "Native", "masters": [], "nameservers": ["ns1.example.org.", "ns2.example.org."]}
+
+Will yield a response similar to this (several headers omitted):
+
+.. code-block:: http
+  
+  HTTP/1.1 200 OK
+  Content-Type: application/json
+
+  {"account": "", "api_rectify": false, "dnssec": false, "edited_serial": 2022040501, "id": "example.org.", "kind": "Native", "last_check": 0, "master_tsig_key_ids": [], "masters": [], "name": "example.org.", "notified_serial": 0, "nsec3narrow": false, "nsec3param": "", "rrsets": [{"comments": [], "name": "example.org.", "records": [{"content": "a.misconfigured.dns.server.invalid. hostmaster.example.org. 2022040501 10800 3600 604800 3600", "disabled": false}], "ttl": 3600, "type": "SOA"}, {"comments": [], "name": "example.org.", "records": [{"content": "ns1.example.org.", "disabled": false}, {"content": "ns2.example.org.", "disabled": false}], "ttl": 3600, "type": "NS"}], "serial": 2022040501, "slave_tsig_key_ids": [], "soa_edit": "", "soa_edit_api": "DEFAULT", "url": "/api/v1/servers/localhost/zones/example.org."}
+
+Listing a zone
+^^^^^^^^^^^^^^
+
+.. code-block:: http
+
+  GET /api/v1/servers/localhost/zones/example.org. HTTP/1.1
+  X-API-Key: secret
+
+Will yield a response similar to this (several headers omitted):
+
+.. code-block:: http
+  
+  HTTP/1.1 200 OK
+  Content-Type: application/json
+
+  {"account": "", "api_rectify": false, "dnssec": false, "edited_serial": 2022040501, "id": "example.org.", "kind": "Native", "last_check": 0, "master_tsig_key_ids": [], "masters": [], "name": "example.org.", "notified_serial": 0, "nsec3narrow": false, "nsec3param": "", "rrsets": [{"comments": [], "name": "example.org.", "records": [{"content": "a.misconfigured.dns.server.invalid. hostmaster.example.org. 2022040501 10800 3600 604800 3600", "disabled": false}], "ttl": 3600, "type": "SOA"}, {"comments": [], "name": "example.org.", "records": [{"content": "ns1.example.org.", "disabled": false}, {"content": "ns2.example.org.", "disabled": false}], "ttl": 3600, "type": "NS"}], "serial": 2022040501, "slave_tsig_key_ids": [], "soa_edit": "", "soa_edit_api": "DEFAULT", "url": "/api/v1/servers/localhost/zones/example.org."}
+
+Deleting a zone
+^^^^^^^^^^^^^^^
+
+.. code-block:: http
+
+  DELETE /api/v1/servers/localhost/zones/example.org. HTTP/1.1
+  X-API-Key: secret
+
+Will yield a response similar to this (several headers omitted):
+
+.. code-block:: http
+  
+  HTTP/1.1 204 No Content
+  
+Creating new RRset
+^^^^^^^^^^^^^^^^^^
+
+.. code-block:: http
+
+  PATCH /api/v1/servers/localhost/zones/example.org. HTTP/1.1
+  X-API-Key: secret
+  Content-Type: application/json
+
+  {"rrsets": [{"name": "test.example.org.", "type": "A", "ttl": 3600, "changetype": "REPLACE", "records": [{"content": "192.168.0.5", "disabled": false}]}]}
+
+Will yield a response similar to this (several headers omitted):
+
+.. code-block:: http
+  
+  HTTP/1.1 204 No Content
+
+Deleting a RRset
+^^^^^^^^^^^^^^^^^^
+
+.. code-block:: http
+
+  PATCH /api/v1/servers/localhost/zones/example.org. HTTP/1.1
+  X-API-Key: secret
+  Content-Type: application/json
+
+  {"rrsets": [{"name": "test.example.org.", "type": "A", "changetype": "DELETE"}]}
+
+Will yield a response similar to this (several headers omitted):
+
+.. code-block:: http
+  
+  HTTP/1.1 204 No Content
+
+Rectifying a zone
+^^^^^^^^^^^^^^^^^
+
+.. code-block:: http
+
+  PUT /api/v1/servers/localhost/zones/example.org./rectify HTTP/1.1
+  X-API-Key: secret
+
+Will yield a response similar to this (several headers omitted):
+
+.. code-block:: http
+  
+  HTTP/1.1 200 OK
+  Content-Type: application/json
+
+  {"result": "Rectified"}
+  
index bad9bfcab980c7e72b7eb6266c69e6a1bed30b30..6301756ff0e9159ab4e39b2d995f738609321fc9 100644 (file)
@@ -16,6 +16,7 @@ PowerDNS Authoritative Server
     dnssec/index
     domainmetadata
     dnsupdate
+    catalog
     tsig
     lua-records/index
     guides/index
index b64d4099965d4d2c3d0dd050067fbd4fdfd9aa6e..9f8bc4b1820796d0dbe7be74c5c9a1f1b906d272 100644 (file)
@@ -33,9 +33,8 @@ required backend as follows:
 Redhat-based Systems
 ~~~~~~~~~~~~~~~~~~~~
 
-On RedHat based systems there are 3 options to install PowerDNS, from
-`EPEL <https://fedoraproject.org/wiki/EPEL>`__, the `repository from
-Kees Monshouwer <https://www.monshouwer.eu/download/3rd_party/pdns/>`__
+On RedHat based systems there are 2 options to install PowerDNS, from
+`EPEL <https://fedoraproject.org/wiki/EPEL>`__, 
 or from `the PowerDNS repositories <https://repo.powerdns.com>`__:
 
 Add either to your list of repositories and install PowerDNS by issuing:
index ce42be063706096d27b89da61a1e6aa3d38165ef..0c578c72d3834c1945aef1f1b9864b8572161153 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
 -------------------
@@ -86,6 +88,7 @@ Record creation functions
   - ``timeout``: Maximum time in seconds that you allow the check to take (default 2)
   - ``stringmatch``: check ``url`` for this string, only declare 'up' if found
   - ``useragent``: Set the HTTP "User-Agent" header in the requests. By default it is set to "PowerDNS Authoritative Server"
+  - ``byteslimit``: Limit the maximum download size to ``byteslimit`` bytes (default 0 meaning no limit).
 
   An example of a list of address sets:
 
@@ -117,11 +120,30 @@ Record creation functions
   The 404s will cause the first group of IPs to get marked as down, after which the URL in the second group is tested.
   The third IP will get marked up assuming ``https://example.net/`` responds with HTTP response code 200.
 
-.. function:: pickrandom(addresses)
+.. function:: pickrandom(values)
 
-  Returns a random IP address from the list supplied.
+  Returns a random value from the list supplied.
 
-  :param addresses: A list of strings with the possible IP addresses.
+  :param values: A list of strings such as IPv4 or IPv6 address.
+
+  This function also works for CNAME or TXT records.
+
+.. function:: pickrandomsample(number, values)
+
+  Returns N random values from the list supplied.
+
+  :param number: Number of values to return
+  :param values: A list of strings such as IPv4 or IPv6 address.
+
+  This function also works for CNAME or TXT records.
+
+.. function:: pickhashed(values)
+
+  Based on the hash of ``bestwho``, returns a random value from the list supplied.
+
+  :param values: A list of strings such as IPv4 or IPv6 address.
+
+  This function also works for CNAME or TXT records.
 
 .. function:: pickclosest(addresses)
 
@@ -159,6 +181,14 @@ Record creation functions
 
   Performs no uptime checking.
 
+.. function:: all(values)
+
+  Returns all values.
+
+  :param values: A list of strings such as IPv4 or IPv6 address.
+
+  This function also works for CNAME or TXT records.
+
 .. function:: view(pairs)
 
   Shorthand function to implement 'views' for all record types.
@@ -177,18 +207,45 @@ Record creation functions
 
   This function also works for CNAME or TXT records.
 
-.. function:: pickwhashed(weightparams)
+.. 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::
 
-  Based on the hash of ``bestwho``, returns an IP address from the list
+    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
   supplied, as weighted by the various ``weight`` parameters.
   Performs no uptime checking.
 
-  :param weightparams: table of weight, IP addresses.
+  :param values: table of weight, string (such as IPv4 or IPv6 address).
 
   Because of the hash, the same client keeps getting the same answer, but
   given sufficient clients, the load is still spread according to the weight
   factors.
 
+  This function also works for CNAME or TXT records.
+
   An example::
 
     mydomain.example.com    IN    LUA    A ("pickwhashed({                             "
@@ -196,16 +253,40 @@ 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.
 
-.. function:: pickwrandom(weightparams)
+  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.
 
-  Returns a random IP address from the list supplied, as weighted by the
+  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)
+
+  Returns a random string from the list supplied, as weighted by the
   various ``weight`` parameters. Performs no uptime checking.
 
-  :param weightparams: table of weight, IP addresses.
+  :param values: table of weight, string (such as IPv4 or IPv6 address).
 
   See :func:`pickwhashed` for an example.
 
+  This function also works for CNAME or TXT records.
+
 Reverse DNS functions
 ~~~~~~~~~~~~~~~~~~~~~
 
@@ -215,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``.
@@ -241,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
@@ -258,42 +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
@@ -306,43 +389,45 @@ 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
 
+  Since 4.8.0: a non-split full length format (``20010002000300040005000600070db8.example.com``) is also supported, optionally prefixed, in which case the last 32 characters will be considered.
+
 .. function:: filterForward(address, masks[, fallback])
 
   .. versionadded:: 4.5.0
@@ -378,6 +463,25 @@ Helper functions
   :param string country: A country code like "NL"
   :param [string] countries: A list of country codes
 
+.. function:: countryCode()
+
+  Returns two letter ISO country code based ``bestwho`` IP address, as described in :doc:`../backends/geoip`.
+  If the two letter ISO country code is unknown "--" will be returned.
+
+.. function:: region(region)
+              region(regions)
+
+  Returns true if the ``bestwho`` IP address of the client is within the
+  two letter ISO region code passed, as described in :doc:`../backends/geoip`.
+
+  :param string region: A region code like "CA"
+  :param [string] regions: A list of regions codes
+
+.. function:: regionCode()
+
+  Returns two letter ISO region code based ``bestwho`` IP address, as described in :doc:`../backends/geoip`.
+  If the two letter ISO region code is unknown "--" will be returned.
+
 .. function:: continent(continent)
               continent(continents)
 
@@ -387,8 +491,30 @@ Helper functions
   :param string continent: A continent code like "EU"
   :param [string] continents: A list of continent codes
 
+.. function:: continentCode()
+
+  Returns two letter ISO continent code based ``bestwho`` IP address, as described in :doc:`../backends/geoip`.
+  If the two letter ISO continent code is unknown "--" will be returned.
+
 .. function:: netmask(netmasks)
 
   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)
+
+  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', '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', 'A'), dblookup('www2.example.com', 'A'), dblookup('www3.example.com', 'A')})"
+
+  :param string name: Name to look up in the database
+  :param string type: DNS type to look for
index dad201f082c6adf8fbaa5c66a1bf4d017b601ac4..74367a21e83eed24bf820ced752b895b08aad772 100644 (file)
@@ -49,6 +49,14 @@ addresses listen on port 443.
 If either IP address stops listening, only the other address will be
 returned. If all IP addresses are down, all candidates are returned.
 
+You can also provide multiple sets of IP addresses to prioritize a set over the
+rest. If an IP address from the first set is available, it will be returned. If
+no addresses work in the first set, the second set is tried.
+
+For example::
+
+     www    IN    LUA    A    "ifportup(443, {{'192.0.2.1', '192.0.2.2'}, {'192.0.3.1'}})"
+
 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.
@@ -68,6 +76,20 @@ addresses.
 
 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 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.
+
+
 Using LUA Records with Generic SQL backends
 -------------------------------------------
 
@@ -184,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
@@ -213,7 +235,7 @@ The default mode of operation for LUA records is to create a fresh Lua state for
 This way, different LUA records cannot accidentally interfere with each other, by leaving around global objects, or perhaps even deleting relevant functions.
 However, creating a Lua state (and registering all our functions for it, see Reference below) takes measurable time.
 For users that are confident they can write Lua scripts that will not interfere with eachother, a mode is supported where Lua states are created on the first query, and then reused forever.
-Note that the state is per-thread, so while data sharing between LUA invocations is possible (useful for caching and reducing the cost of ``require``), there is not a single shared Lua environment.
+Note that the state is per-thread (for UDP, plus one shared state for all TCP), so while data sharing between LUA invocations is possible (useful for caching and reducing the cost of ``require``), there is no single shared Lua environment.
 In non-scientific testing this has yielded up to 10x QPS increases.
 
 To use this mode, set ``enable-lua-records=shared``.
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 15f796cb993d1dfffe42f129e3b93371583d7f64..86354980626caae8aa34bc940b2484d86eebefd9 100644 (file)
@@ -29,19 +29,27 @@ ADDRESS
 PORT
     if omitted, 53 will be used.
 
---help, -h               Show summary of options.
---ecs-mask <VAL>         When EDNS forwarding an IP address, mask out first octet with this value
---ecs-stamp <FLAG>       Add original IP address as EDNS Client Subnet Option when 
-                         forwarding to reference server
---pcap-dns-port <VAL>    Look at packets from or to this port in the PCAP. Default is 53.
---packet-limit <NUM>     Stop after replaying *NUM* packets. Default for *NUM* is 0, which
-                         means no limit.
---quiet <FLAG>           If *FLAG* is set to 1. dnsreplay will not be very noisy with its
-                         output. This is the default.
---recursive <FLAG>       If *FLAG* is set to 1. dnsreplay will only replay queries with
-                         recursion desired flag set. This is the default.
---speedup <FACTOR>       Replay queries with this speedup *FACTOR*. Default is 1.
---timeout-msec <MSEC>    Wait at least *MSEC* milliseconds for a reply. Default is 500.
+--help, -h                 Show summary of options.
+--ecs-mask <VAL>           When EDNS forwarding an IP address, mask out first octet with this value
+--ecs-stamp <FLAG>         Add original IP address as EDNS Client Subnet Option when 
+                           forwarding to reference server
+--packet-limit <NUM>       Stop after replaying *NUM* packets. Default for *NUM* is 0, which
+                           means no limit.
+--pcap-dns-port <VAL>      Look at packets from or to this port in the PCAP. Default is 53.
+--quiet <FLAG>             If *FLAG* is set to 1. dnsreplay will not be very noisy with its
+                           output. This is the default.
+--recursive <FLAG>         If *FLAG* is set to 1. dnsreplay will only replay queries with
+                           recursion desired flag set. This is the default.
+--source-from-pcap <FLAG>  If *FLAG* is set to 1. dnsreplay will send the replayed queries from the
+                           source IP address and port present in the PCAP file. This requires
+                           IP_TRANSPARENT support. Default is 0 which means replayed queries will be
+                           sent from a local address.
+--source-ip <VAL>          Send the replayed queries from the source IP specified in *VAL*. Default
+                           is to send them from a local address.
+--source-port <VAL>        Send the replayed queries from the source port specified in *VAL*.
+                           Default is to send from a random port selected by the kernel.
+--speedup <FACTOR>         Replay queries with this speedup *FACTOR*. Default is 1.
+--timeout-msec <MSEC>      Wait at least *MSEC* milliseconds for a reply. Default is 500.
 
 Bugs
 ----
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 d92511350d5d42386690e90b3a68a52f8dd7d3ae..4dcdfef79d756dd882a8441ec9d722a7998de2b1 100644 (file)
@@ -99,7 +99,7 @@ remove-zone-key *ZONE* *KEY-ID*
     Remove a key with id *KEY-ID* from a zone called *ZONE*.
 set-nsec3 *ZONE* ['*HASH-ALGORITHM* *FLAGS* *ITERATIONS* *SALT*'] [**narrow**]
     Sets NSEC3 parameters for this zone. The quoted parameters are 4
-    values that are used for the the NSEC3PARAM record and decide how
+    values that are used for the NSEC3PARAM record and decide how
     NSEC3 records are created. The NSEC3 parameters must be quoted on
     the command line. *HASH-ALGORITHM* must be 1 (SHA-1). Setting
     *FLAGS* to 1 enables NSEC3 opt-out operation. Only do this if you
@@ -149,11 +149,11 @@ commands require an *ALGORITHM*, the following are available:
 -  hmac-sha384
 -  hmac-sha512
 
-activate-tsig-key *ZONE* *NAME* {**primary**,\ **secondary**}
+activate-tsig-key *ZONE* *NAME* {**primary**,\ **secondary**,\ **producer**,\ **consumer**}
     Enable TSIG authenticated AXFR using the key *NAME* for zone *ZONE*.
-    This sets the ``TSIG-ALLOW-AXFR`` (primary) or ``AXFR-MASTER-TSIG``
-    (secondary) zone metadata.
-deactivate-tsig-key *ZONE* *NAME* {**primary**,\ **secondary**}
+    This sets the ``TSIG-ALLOW-AXFR`` (primary/producer) or ``AXFR-MASTER-TSIG``
+    (secondary/consumer) zone metadata.
+deactivate-tsig-key *ZONE* *NAME* {**primary**,\ **secondary**,\ **producer**,\ **consumer**}
     Disable TSIG authenticated AXFR using the key *NAME* for zone
     *ZONE*.
 delete-tsig-key *NAME*
@@ -180,10 +180,10 @@ list-autoprimaries
     List all autoprimaries.
 create-zone *ZONE*
     Create an empty zone named *ZONE*.
-create-secondary-zone *ZONE* *PRIMARY* [*PRIMARY*]..
+create-secondary-zone *ZONE* *PRIMARY* [*PRIMARY*]...
     Create a new secondary zone *ZONE* with primaries *PRIMARY*. All *PRIMARY*\ s
     need to to be space-separated IP addresses with an optional port.
-change-secondary-zone-primary *ZONE* *PRIMARY* [*PRIMARY*]..
+change-secondary-zone-primary *ZONE* *PRIMARY* [*PRIMARY*]...
     Change the primaries for secondary zone *ZONE* to new primaries *PRIMARY*. All
     *PRIMARY*\ s need to to be space-separated IP addresses with an optional port.
 check-all-zones
@@ -195,7 +195,7 @@ clear-zone *ZONE*
     settings unchanged
 delete-rrset *ZONE* *NAME* *TYPE*
     Delete named RRSET from zone.
-delete-zone *ZONE*:
+delete-zone *ZONE*
     Delete the zone named *ZONE*.
 edit-zone *ZONE*
     Opens *ZONE* in zonefile format (regardless of backend it was loaded
@@ -217,9 +217,11 @@ increase-serial *ZONE*
 list-keys [*ZONE*]
     List DNSSEC information for all keys or for *ZONE*. --verbose or -v will
     also include the keys for disabled or empty zones.
-list-all-zones:
+list-all-zones
     List all active zone names. --verbose or -v will also include disabled
     or empty zones.
+list-member-zones *CATALOG*
+    List all members of catalog zone *CATALOG*"
 list-zone *ZONE*
     Show all records for *ZONE*.
 load-zone *ZONE* *FILE*
@@ -234,7 +236,7 @@ rectify-all-zones
     Calculates the 'ordername' and 'auth' fields for all zones so they
     comply with DNSSEC settings. Can be used to fix up migrated data.
     Can always safely be run, it does no harm.
-replace-rrset *ZONE* *NAME* *TYPE* [*TTL*] *CONTENT* [*CONTENT*..]
+replace-rrset *ZONE* *NAME* *TYPE* [*TTL*] *CONTENT* [*CONTENT*...]
     Replace existing *NAME* in zone *ZONE* with a new set.
 secure-zone *ZONE*
     Configures a zone called *ZONE* with reasonable DNSSEC settings. You
@@ -245,7 +247,13 @@ secure-all-zones [**increase-serial**]
     serial of those zones too. You should manually run 'pdnsutil
     rectify-all-zones' afterwards.
 set-kind *ZONE* *KIND*
-    Change the kind of *ZONE* to *KIND* (primary, secondary, native).
+    Change the kind of *ZONE* to *KIND* (primary, secondary, native, producer, consumer).
+set-options-json *ZONE* *JSON*
+    Change the options of *ZONE* to *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*. 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*]...
@@ -271,7 +279,7 @@ zonemd-verify-file *ZONE* *FILE*
 DEBUGGING TOOLS
 ---------------
 
-backend-cmd *BACKEND* *CMD* [*CMD..*]
+backend-cmd *BACKEND* *CMD* [*CMD...*]
     Send a text command to a backend for execution. GSQL backends will
     take SQL commands, other backends may take different things. Be
     careful!
@@ -282,6 +290,10 @@ bench-db [*FILE*]
 
 OTHER TOOLS
 -----------
+b2b-migrate *OLD* *NEW*
+    Migrate data from one backend to another.
+    Needs ``launch=OLD,NEW`` in the configuration.
+
 ipencrypt *IP-ADDRESS* password
     Encrypt an IP address according to the 'ipcipher' standard
 
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 0db501729b720c2a92c738e1d1c8a18ec5c18acb..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:
 
@@ -142,7 +142,7 @@ Migrating Data from one Backend to Another Backend
 --------------------------------------------------
 
 .. note::
-  This is experimental feature.
+  This is an experimental feature.
 
 Syntax: ``pdnsutil b2b-migrate OLD NEW``
 
index 0b2be10f326caebacbf1a11963ffc3b6b3aa6365..2406a0b90b9318ab9db4d5fa2de6787492ffd15b 100644 (file)
@@ -95,12 +95,25 @@ Secondary operation
 On launch, PowerDNS requests from all backends a list of domains that
 have not been checked recently for changes. This should happen every
 '**refresh**' seconds, as specified in the SOA record. All domains that
-are unfresh are then checked for changes over at their master. If the
+are unfresh are then checked for changes over at their primary server. If the
 :ref:`types-SOA` serial number there is higher, the domain is
 retrieved and inserted into the database. In any case, after the check,
 the domain is declared 'fresh', and will only be checked again after
 '**refresh**' seconds have passed.
 
+If the serial is equal, PowerDNS as a secondary with a presigned zone
+will also compare the SOA RRSIG (signature). If the signatures are
+different, the zone is also queued for a zone transfer.
+This is useful when the primary server updates DNSSEC signatures without
+changing the zone serial. In some configurations, a PowerDNS primary can
+exhibit this behaviour.
+To allow for this check, the DO flag is set on the SOA query towards
+the primary server. In some conditions, some primary servers answer with
+a truncated SOA response (indicating TCP is required), and the freshness
+check will fail. As a workaround, the signature check and DO flag can be
+turned off by disabling
+:ref:`setting-secondary-check-signature-freshness`.
+
 When the freshness of a domain cannot be checked, e.g. because the
 master is offline, PowerDNS will retry the domain after
 :ref:`setting-xfr-cycle-interval` seconds.
@@ -187,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..d2730788339741d3a622cdf0a363289eb98059d0 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,7 +229,7 @@ Number of entries in the metadata cache
 .. _stat-open-tcp-connections:
 
 open-tcp-connections
-~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^
 Number of currently open TCP connections
 
 .. _stat-overload-drops:
@@ -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 274a600c1c681fd636e2ea1fab60f8f526c4b0ab..c0a05ffa694eb7ea38208af9dabf8950713022a6 100644 (file)
@@ -1,7 +1,327 @@
-Sphinx>=1.5.0,!=1.8.0,<2.0
-git+https://github.com/PowerDNS/sphinxcontrib-openapi@use-jsondomain-pdns
-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
+#
+# 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.3 \
+    --hash=sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1 \
+    --hash=sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825
+    # 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 691dce2158c32efcc707ced005d0b6b0f2c00fb0..e24bd70b53a4a793c5395331c195b927a1206b68 100644 (file)
@@ -1,13 +1,14 @@
 Running and Operating
 =====================
 
-PowerDNS is normally controlled via a SysV-style init.d script, often
-located in ``/etc/init.d`` or ``/etc/rc.d/init.d``. For Linux
-distributions with systemd, a service file is provided (either in the
-package or in the contrib directory of the tarball).
+On Linux, PowerDNS is controlled by a systemd service called ``pdns.service``.
+The service definition file should be installed by the binary package, and can also be found in the tarball (``pdns.service.in`` template file).
 
-Furthermore, PowerDNS can be run on the foreground for testing or in
-other init- systems that supervise processes.
+On non-Linux systems, a SysV-style init script can be used, and should be supplied by the operating system packages.
+
+Furthermore, PowerDNS can be run on the foreground for testing or for use with other init-systems that supervise processes.
+
+Also see :doc:`guides/virtual-instances`.
 
 .. _running-guardian:
 
@@ -31,7 +32,7 @@ inner process as well.
 Logging to syslog on systemd-based operating systems
 ----------------------------------------------------
 
-By default, logging to syslog is disabled in the the systemd unit file
+By default, logging to syslog is disabled in the systemd unit file
 to prevent the service logging twice, as the systemd journal picks up
 the output from the process itself.
 
index f4306335ba796d2fa3720fbad7ba60bd82453b47..8a24551c5df660b8712d83cb12cff3ce37530589 100644 (file)
@@ -1,4 +1,4 @@
-@       86400   IN  SOA pdns-public-ns1.powerdns.com. peter\.van\.dijk.powerdns.com. 2022022801 10800 3600 604800 10800
+@       86400   IN  SOA pdns-public-ns1.powerdns.com. peter\.van\.dijk.powerdns.com. 2024021306 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)"
@@ -74,36 +74,58 @@ auth-4.2.0-rc3.security-status                          60 IN TXT "3 Unsupported
 auth-4.2.0.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2020-05.html"
 auth-4.2.1.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2020-05.html"
 auth-4.2.2.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2020-05.html"
-auth-4.2.3.security-status                              60 IN TXT "1 OK"
+auth-4.2.3.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2022-01.html"
 auth-4.3.0-alpha1.security-status                       60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 auth-4.3.0-beta1.security-status                        60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 auth-4.3.0-beta2.security-status                        60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 auth-4.3.0-rc1.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 auth-4.3.0-rc2.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 auth-4.3.0.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2020-05.html"
-auth-4.3.1.security-status                              60 IN TXT "1 OK"
-auth-4.3.2.security-status                              60 IN TXT "1 OK"
-auth-4.4.0-alpha1.security-status                       60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-auth-4.4.0-alpha2.security-status                       60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-auth-4.4.0-alpha3.security-status                       60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-auth-4.4.0-beta1.security-status                        60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-auth-4.4.0-rc1.security-status                          60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-auth-4.4.0.security-status                              60 IN TXT "1 OK"
-auth-4.4.1.security-status                              60 IN TXT "1 OK"
-auth-4.4.2.security-status                              60 IN TXT "1 OK"
+auth-4.3.1.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2022-01.html"
+auth-4.3.2.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2022-01.html"
+auth-4.4.0-alpha1.security-status                       60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+auth-4.4.0-alpha2.security-status                       60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+auth-4.4.0-alpha3.security-status                       60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+auth-4.4.0-beta1.security-status                        60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+auth-4.4.0-rc1.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+auth-4.4.0.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2022-01.html"
+auth-4.4.1.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2022-01.html"
+auth-4.4.2.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2022-01.html"
+auth-4.4.3.security-status                              60 IN TXT "3 Unsupported release (EOL)"
 auth-4.5.0-alpha1.security-status                       60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 auth-4.5.0-beta1.security-status                        60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 auth-4.5.0-rc1.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 auth-4.5.0-rc2.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 auth-4.5.0.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2021-01.html"
-auth-4.5.1.security-status                              60 IN TXT "1 OK"
-auth-4.5.2.security-status                              60 IN TXT "1 OK"
-auth-4.5.3.security-status                              60 IN TXT "1 OK"
-auth-4.6.0-alpha1.security-status                       60 IN TXT "2 Unsupported pre-release, superseded by 4.6.0"
-auth-4.6.0-beta1.security-status                        60 IN TXT "2 Unsupported pre-release, superseded by 4.6.0"
-auth-4.6.0-rc1.security-status                          60 IN TXT "2 Unsupported pre-release, superseded by 4.6.0"
-auth-4.6.0.security-status                              60 IN TXT "1 OK"
-auth-4.7.0-alpha1.security-status                       60 IN TXT "1 Unsupported pre-release"
+auth-4.5.1.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2022-01.html"
+auth-4.5.2.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2022-01.html"
+auth-4.5.3.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2022-01.html"
+auth-4.5.4.security-status                              60 IN TXT "1 OK"
+auth-4.5.5.security-status                              60 IN TXT "1 OK"
+auth-4.6.0-alpha1.security-status                       60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+auth-4.6.0-beta1.security-status                        60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+auth-4.6.0-rc1.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+auth-4.6.0.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2022-01.html"
+auth-4.6.1.security-status                              60 IN TXT "1 OK"
+auth-4.6.2.security-status                              60 IN TXT "1 OK"
+auth-4.6.3.security-status                              60 IN TXT "1 OK"
+auth-4.6.4.security-status                              60 IN TXT "1 OK"
+auth-4.7.0-alpha1.security-status                       60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+auth-4.7.0-beta2.security-status                        60 IN TXT "3 Unsupported pre-release"
+auth-4.7.0-rc1.security-status                          60 IN TXT "3 Unsupported pre-release"
+auth-4.7.0.security-status                              60 IN TXT "1 OK"
+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 "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 "1 Unsupported pre-release (no known vulnerabilities)"
 
 ; 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/"
@@ -241,7 +263,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 "2 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)"
@@ -252,7 +274,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 "2 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)"
@@ -266,43 +288,90 @@ recursor-4.3.1.security-status                          60 IN TXT "3 Upgrade now
 recursor-4.3.2.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-07.html"
 recursor-4.3.3.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-07.html"
 recursor-4.3.4.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-07.html"
-recursor-4.3.5.security-status                          60 IN TXT "1 OK"
-recursor-4.3.6.security-status                          60 IN TXT "1 OK"
-recursor-4.3.7.security-status                          60 IN TXT "1 OK"
+recursor-4.3.5.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
+recursor-4.3.6.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
+recursor-4.3.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.0-alpha1.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
-recursor-4.4.0-alpha2.security-status                   60 IN TXT "3 Unsupported pre-release"
-recursor-4.4.0-beta1.security-status                    60 IN TXT "3 Unsupported pre-release"
-recursor-4.4.0-rc1.security-status                      60 IN TXT "3 Unsupported pre-release"
-recursor-4.4.0-rc2.security-status                      60 IN TXT "3 Unsupported pre-release"
-recursor-4.4.0.security-status                          60 IN TXT "1 OK"
-recursor-4.4.1.security-status                          60 IN TXT "1 OK"
-recursor-4.4.2.security-status                          60 IN TXT "1 OK"
-recursor-4.4.3.security-status                          60 IN TXT "1 OK"
-recursor-4.4.4.security-status                          60 IN TXT "1 OK"
-recursor-4.4.5.security-status                          60 IN TXT "1 OK"
-recursor-4.4.6.security-status                          60 IN TXT "1 OK"
-recursor-4.4.7.security-status                          60 IN TXT "1 OK"
-recursor-4.5.0-alpha1.security-status                   60 IN TXT "3 Unsupported pre-release"
-recursor-4.5.0-alpha2.security-status                   60 IN TXT "3 Unsupported pre-release"
-recursor-4.5.0-alpha3.security-status                   60 IN TXT "3 Unsupported pre-release"
-recursor-4.5.0-beta1.security-status                    60 IN TXT "3 Unsupported pre-release"
-recursor-4.5.0-beta2.security-status                    60 IN TXT "3 Unsupported pre-release"
-recursor-4.5.0-rc1.security-status                      60 IN TXT "3 Unsupported pre-release"
-recursor-4.5.0.security-status                          60 IN TXT "2 Unsupported pre-release"
-recursor-4.5.1.security-status                          60 IN TXT "1 OK"
-recursor-4.5.2.security-status                          60 IN TXT "1 OK"
-recursor-4.5.3.security-status                          60 IN TXT "2 Unsupported pre-release"
-recursor-4.5.4.security-status                          60 IN TXT "1 OK"
-recursor-4.5.5.security-status                          60 IN TXT "1 OK"
-recursor-4.5.6.security-status                          60 IN TXT "1 OK"
-recursor-4.5.7.security-status                          60 IN TXT "1 OK"
-recursor-4.6.0-alpha1.security-status                   60 IN TXT "2 Unsupported pre-release"
-recursor-4.6.0-alpha2.security-status                   60 IN TXT "2 Unsupported pre-release"
-recursor-4.6.0-beta1.security-status                    60 IN TXT "2 Unsupported pre-release"
-recursor-4.6.0-beta2.security-status                    60 IN TXT "2 Unsupported pre-release"
-recursor-4.6.0-rc1.security-status                      60 IN TXT "2 Unsupported pre-release"
-recursor-4.6.0.security-status                          60 IN TXT "1 OK"
-recursor-4.7.0-alpha1.security-status                   60 IN TXT "1 Unsupported pre-release"
+recursor-4.4.0-alpha2.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.4.0-beta1.security-status                    60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.4.0-rc1.security-status                      60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.4.0-rc2.security-status                      60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.4.0.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
+recursor-4.4.1.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
+recursor-4.4.2.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
+recursor-4.4.3.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
+recursor-4.4.4.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
+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 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)"
+recursor-4.5.0-beta1.security-status                    60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.5.0-beta2.security-status                    60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.5.0-rc1.security-status                      60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.5.0.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.5.1.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
+recursor-4.5.2.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
+recursor-4.5.3.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.5.4.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
+recursor-4.5.5.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
+recursor-4.5.6.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
+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 "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)"
+recursor-4.6.0-beta2.security-status                    60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.6.0-rc1.security-status                      60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.6.0.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
+recursor-4.6.1.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-02.html"
+recursor-4.6.2.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-02.html"
+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 "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)"
+recursor-4.7.0.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-02.html"
+recursor-4.7.1.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-02.html"
+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 "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)"
+recursor-4.8.0-rc1.security-status                      60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.8.0.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2023-01.html"
+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 "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.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-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 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/"
@@ -422,32 +491,49 @@ 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.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 "1 Unsupported pre-release (no known vulnerabilities)"
diff --git a/docs/security-advisories/powerdns-advisory-2022-01.rst b/docs/security-advisories/powerdns-advisory-2022-01.rst
new file mode 100644 (file)
index 0000000..2a3cda2
--- /dev/null
@@ -0,0 +1,22 @@
+PowerDNS Security Advisory 2022-01: incomplete validation of incoming IXFR transfer in Authoritative Server and Recursor
+========================================================================================================================
+
+- CVE: CVE-2022-27227
+- Date: 25th of March 2022.
+- Affects: PowerDNS Authoritative version 4.4.2, 4.5.3, 4.6.0 and PowerDNS Recursor 4.4.7, 4.5.7 and 4.6.0
+- Not affected: PowerDNS Authoritative Server 4.4.3, 4.5.4, 4.6.1 and PowerDNS Recursor 4.4.8, 4.5.8 and 4.6.1
+- Severity: Low
+- Impact: Denial of service
+- Exploit: This problem can be triggered by an attacker controlling the network path for IXFR transfers
+- Risk of system compromise: None
+- Solution: Upgrade to patched version, do not use IXFR in Authoritative Server
+
+- In the Authoritative server this issue only applies to secondary zones for which IXFR transfers have been enabled and the network path to the primary server is not trusted. Note that IXFR transfers are not enabled by default.
+-  In the Recursor it applies to setups retrieving one or more RPZ zones from a remote server if the network path to the server is not trusted.
+
+IXFR usually exchanges only the modifications between two versions of a zone, but sometimes needs to fall back to a full transfer of the current version.
+When IXFR falls back to a full zone transfer, an attacker in position of man-in-the-middle can cause the transfer to be prematurely interrupted. This interrupted transfer is mistakenly interpreted as a complete transfer, causing an incomplete zone to be processed.
+For the Authoritative Server, IXFR transfers are not enabled by default.
+The Recursor only uses IXFR for retrieving RPZ zones. An incomplete RPZ transfer results in missing policy entries, potentially causing some DNS names and IP addresses to not be properly intercepted.
+
+We would like to thank Nicolas Dehaine and Dmitry Shabanov from ThreatSTOP for reporting and initial analysis of this issue.
index 1ab8c9e49a6d0d5bc37b32bc204123ebda55e2d1..17d61bebdf2a8d43d0a3820943691c9ea3945ca5 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``
@@ -106,6 +110,10 @@ When notifying a zone, also notify these nameservers. Example:
 ``also-notify`` always receive a notification. Even if they do not match
 the list in :ref:`setting-only-notify`.
 
+You may specify an alternate port by appending :port. Example:
+``also-notify=192.0.2.1:5300``. If no port is specified, port 53
+is used.
+
 .. _setting-any-to-tcp:
 
 ``any-to-tcp``
@@ -269,6 +277,26 @@ Either don't ``chroot`` on these systems or set the 'Type' of the
 service to 'simple' instead of 'notify' (refer to the systemd
 documentation on how to modify unit-files).
 
+.. _setting-secondary-check-signature-freshness:
+
+``secondary-check-signature-freshness``
+---------------------------------------
+
+.. versionadded:: 4.7.0
+
+-  Boolean
+-  Default: yes
+
+Enabled by default, freshness checks for secondary zones will set the DO flag on SOA queries. PowerDNS
+can detect (signature) changes on the primary server without serial number bumps using the DNSSEC
+signatures in the SOA response.
+
+In some problematic scenarios, primary servers send truncated SOA responses. As a workaround, this setting
+can be turned off, and the DO flag as well as the signature checking will be disabled. To avoid additional
+drift, primary servers must then always increase the zone serial when it updates signatures.
+
+It is strongly recommended to keep this setting enabled (`yes`).
+
 .. _setting-config-dir:
 
 ``config-dir``
@@ -276,7 +304,7 @@ documentation on how to modify unit-files).
 
 -  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.
 
@@ -301,10 +329,15 @@ Name of this virtual configuration - will rename the binary image. See
 .. versionadded:: 4.4.0
 
 When this is set, PowerDNS assumes that any single zone lives in only one backend.
-This allows PowerDNS to send ANY lookups to its backends, instead of sometimes requesting the exact needed type.
+This allows PowerDNS to send ``ANY`` lookups to its backends, instead of sometimes requesting the exact needed type.
 This reduces the load on backends by retrieving all the types for a given name at once, adding all of them to the cache.
 It improves performance significantly for latency-sensitive backends, like SQL ones, where a round-trip takes serious time.
 
+.. warning::
+  This behaviour is only a meaningful optimization if the returned response to the ``ANY`` query can actually be cached,
+  which is not the case if it contains at least one record with a non-zero scope. For this reason ``consistent-backends``
+  should be disabled when at least one of the backends in use returns location-based records, like the GeoIP backend.
+
 .. note::
   Pre 4.5.0 the default was no.
 
@@ -337,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:
 
@@ -506,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``
@@ -649,6 +704,17 @@ This setting MUST be 32 hexadecimal characters, as the siphash algorithm's key u
 
 Enables EDNS subnet processing, for backends that support it.
 
+.. _setting-enable-gss-tsig:
+
+``enable-gss-tsig``
+-------------------
+
+-  Boolean
+-  Default: no
+
+Enable accepting GSS-TSIG signed messages.
+In addition to this setting, see :doc:`tsig`.
+
 .. _setting-enable-lua-records:
 
 ``enable-lua-records``
@@ -683,7 +749,7 @@ If this is enabled, ALIAS records are expanded (synthesized to their
 A/AAAA).
 
 If this is disabled (the default), ALIAS records will not be expanded and
-the server will will return NODATA for A/AAAA queries for such names.
+the server will return NODATA for A/AAAA queries for such names.
 
 .. note::
   :ref:`setting-resolver` must also be set for ALIAS expansion to work!
@@ -917,8 +983,22 @@ Do not pass names like 'local0'!
 -  Integer
 -  Default: 4
 
-Amount of logging. Higher is more. Do not set below 3. Corresponds to "syslog" level values,
-e.g. error = 3, warning = 4, notice = 5, info = 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-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:
 
@@ -930,6 +1010,30 @@ e.g. error = 3, warning = 4, notice = 5, info = 6
 
 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``
@@ -985,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
 
@@ -1236,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!
@@ -1247,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``
@@ -1660,6 +1773,8 @@ Turn on supermaster support. See :ref:`supermaster-operation`.
 - Boolean
 - Default: no
 
+.. versionadded:: 4.5.0
+
 Whether or not to enable IPv4 and IPv6 :ref:`autohints <svc-autohints>`.
 
 .. _setting-tcp-control-address:
@@ -1781,11 +1896,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:
@@ -1853,6 +1968,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::
@@ -1878,7 +1994,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
 
@@ -1938,6 +2054,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 bd0a12fb6c2fdd37b353be6607c5151d19663b30..197f50fa41ed2611dc9d06ed245ba405ca5692f0 100644 (file)
@@ -119,36 +119,32 @@ the master, not just those about AXFR requests.
 
 GSS-TSIG support
 ----------------
-  .. versionchanged:: 4.4.0
-    GSS support was removed
 
 GSS-TSIG allows authentication and authorization of DNS updates or AXFR
 using Kerberos with TSIG signatures.
 
 .. note::
-  This feature is experimental and subject to change in future releases.
+  This is an experimental feature and subject to change in future releases.
 
 Prerequisites
 ~~~~~~~~~~~~~
 
--  Working Kerberos environment. Please refer to your Kerberos vendor
-   documentation on how to setup it.
--  Principal (such as ``DNS/<your.dns.server.name>@REALM``) in either
-   per-user keytab or system keytab.
+-  Working Kerberos environment. Please refer to your Kerberos vendor documentation on how to set it up.
+-  Service Principal(s) (of the form ``DNS/your.dns.server.name@REALM``) in either per-user keytab or system keytab, where ``your.dns.server.name`` must match the nameserver name in the SOA record of the zone.
+   If a user keytab is used, specify it using the ``KRB5_KTNAME`` environment variable when starting up PDNS server, which must be able to read the keytab file.
 
-In particular, if something does not work, read logs and ensure that
-your kerberos environment is ok before filing an issue. Most common
-problems are time synchronization or changes done to the principal.
+
+In particular, if something does not work, read logs and ensure that your kerberos environment is ok before filing an issue.
+Most common problems are time synchronization or changes done to the principal.
 
 Setting up
 ~~~~~~~~~~
 
-To allow AXFR / DNS update to work, you need to configure
-``GSS-ACCEPTOR-PRINCIPAL`` in
-:doc:`domainmetadata`. This will define the
-principal that is used to accept any GSS context requests. This *must*
-match to your keytab. Next you need to define one or more
-``GSS-ALLOW-AXFR-PRINCIPAL`` entries for AXFR, or
-``TSIG-ALLOW-DNSUPDATE`` entries for DNS update. These must be set to
-the exact initiator principal names you intend to use. No wildcards
-accepted.
+To allow AXFR / DNS update to work, you need to set :ref:`setting-enable-gss-tsig` and configure ``GSS-ACCEPTOR-PRINCIPAL`` in :doc:`domainmetadata`.
+This will define the principal that is used to accept any GSS context requests for names in the specified domain.
+This *must* match to a principal in the keytab used by PDNS Server.
+Next you need to define one or more ``GSS-ALLOW-AXFR-PRINCIPAL`` entries for AXFR, or ``TSIG-ALLOW-DNSUPDATE`` entries for DNS update.
+These must be set to the exact initiator (client) principal names you intend to allow either AXFR or DNS update.
+No wildcards accepted.
+If a Lua update policy is defined (see :doc:`dnsupdate`) no ``TSIG-ALLOW-DNSUPDATE`` entries are needed, as the Lua policy defines which principals can update which records.
+
index 46abbafd0d6f273bfe07bb155877840ac3048d13..1fce623997f77a238a77bad82ce2c253e65de02f 100644 (file)
@@ -8,8 +8,88 @@ 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.5.x to 4.6.0 or master
-------------------------
+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`
+
+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
+^^^^^^^^^^^^
+
+Version 4.8.0-alpha1 ships a new version of the LMDB database schema (called version 5), for compatibility with `Lightning Stream <https://doc.powerdns.com/lightningstream>`_.
+This schema is somewhat experimental, and although we do intend to make databases portable/upgradeable to future releases in the 4.8 train, we currently make no promises.
+There is no downgrade process.
+If you upgrade your database (by starting 4.8.0 without ``lmdb-schema-version=4``), you cannot go back.
+
+Upgrading is only supported from database schema versions 3 and 4, that is, databases created/upgraded by version 4.4 and up.
+
+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
+^^^^^^^^^^^^^^
+
+The new Catalog Zones feature comes with a mandatory schema change for the gsql database backends.
+See files named ``4.3.x_to_4.7.0_schema.X.sql`` for your database backend in our Git repo, tarball, or distro-specific documentation path.
+For the LMDB backend, please review :ref:`setting-lmdb-schema-version`.
+The new LMDB schema version is 4.
+
+4.5.x to 4.6.0
+--------------
 
 Automatic conversion of ``@`` signs in SOA
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -70,7 +150,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`
@@ -265,7 +345,7 @@ Schema changes
 - The new 'unpublished DNSSEC keys' feature comes with a mandatory schema change for all database backends (including BIND with a DNSSEC database).
   See files named ``4.2.0_to_4.3.0_schema.X.sql`` for your database backend in our Git repo, tarball, or distro-specific documentation path.
   For the LMDB backend, please review :ref:`setting-lmdb-schema-version`.
-- If you are upgrading from beta2 or rc2, AND ONLY THEN, please read `pull request #8975 <https://github.com/PowerDNS/pdns/pull/8975>`__ very carefully.
+- If you are upgrading from 4.3.0-beta2 or 4.3.0-rc2, AND ONLY THEN, please read `pull request #8975 <https://github.com/PowerDNS/pdns/pull/8975>`__ very carefully.
 
 Implicit 5->7 algorithm upgrades
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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 f48833b1b8a5e002852264941c5102820c5c4bb7..a0ed9645da16e4b7fc3fe3c354f43c87951b5715 100644 (file)
@@ -43,15 +43,15 @@ using std::move;
  * it may not be orderable.
  */
 struct NullStruct {
-    bool operator==(NullStruct) const { return true; }
-    bool operator<(NullStruct) const { return false; }
+    bool operator==(NullStruct /*unused*/) const { return true; }
+    bool operator<(NullStruct /*unused*/) const { return false; }
 };
 
 /* * * * * * * * * * * * * * * * * * * *
  * Serialization
  */
 
-static void dump(NullStruct, string &out) {
+static void dump(NullStruct /*unused*/, string &out) {
     out += "null";
 }
 
@@ -152,7 +152,7 @@ protected:
 
     // Constructors
     explicit Value(const T &value) : m_value(value) {}
-    explicit Value(T &&value)      : m_value(move(value)) {}
+    explicit Value(T &&value)      : m_value(std::move(value)) {}
 
     // Get type tag
     Json::Type type() const override {
@@ -199,7 +199,7 @@ class JsonString final : public Value<Json::STRING, string> {
     const string &string_value() const override { return m_value; }
 public:
     explicit JsonString(const string &value) : Value(value) {}
-    explicit JsonString(string &&value)      : Value(move(value)) {}
+    explicit JsonString(string &&value)      : Value(std::move(value)) {}
 };
 
 class JsonArray final : public Value<Json::ARRAY, Json::array> {
@@ -207,7 +207,7 @@ class JsonArray final : public Value<Json::ARRAY, Json::array> {
     const Json & operator[](size_t i) const override;
 public:
     explicit JsonArray(const Json::array &value) : Value(value) {}
-    explicit JsonArray(Json::array &&value)      : Value(move(value)) {}
+    explicit JsonArray(Json::array &&value)      : Value(std::move(value)) {}
 };
 
 class JsonObject final : public Value<Json::OBJECT, Json::object> {
@@ -215,7 +215,7 @@ class JsonObject final : public Value<Json::OBJECT, Json::object> {
     const Json & operator[](const string &key) const override;
 public:
     explicit JsonObject(const Json::object &value) : Value(value) {}
-    explicit JsonObject(Json::object &&value)      : Value(move(value)) {}
+    explicit JsonObject(Json::object &&value)      : Value(std::move(value)) {}
 };
 
 class JsonNull final : public Value<Json::NUL, NullStruct> {
@@ -257,12 +257,12 @@ Json::Json(double value)               : m_ptr(make_shared<JsonDouble>(value)) {
 Json::Json(int value)                  : m_ptr(make_shared<JsonInt>(value)) {}
 Json::Json(bool value)                 : m_ptr(value ? statics().t : statics().f) {}
 Json::Json(const string &value)        : m_ptr(make_shared<JsonString>(value)) {}
-Json::Json(string &&value)             : m_ptr(make_shared<JsonString>(move(value))) {}
+Json::Json(string &&value)             : m_ptr(make_shared<JsonString>(std::move(value))) {}
 Json::Json(const char * value)         : m_ptr(make_shared<JsonString>(value)) {}
 Json::Json(const Json::array &values)  : m_ptr(make_shared<JsonArray>(values)) {}
-Json::Json(Json::array &&values)       : m_ptr(make_shared<JsonArray>(move(values))) {}
+Json::Json(Json::array &&values)       : m_ptr(make_shared<JsonArray>(std::move(values))) {}
 Json::Json(const Json::object &values) : m_ptr(make_shared<JsonObject>(values)) {}
-Json::Json(Json::object &&values)      : m_ptr(make_shared<JsonObject>(move(values))) {}
+Json::Json(Json::object &&values)      : m_ptr(make_shared<JsonObject>(std::move(values))) {}
 
 /* * * * * * * * * * * * * * * * * * * *
  * Accessors
@@ -356,7 +356,7 @@ struct JsonParser final {
      * Mark this parse as failed.
      */
     Json fail(string &&msg) {
-        return fail(move(msg), Json());
+        return fail(std::move(msg), Json());
     }
 
     template <typename T>
index 8002b7279a51179311c6cbaf7b6176b5932168c0..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 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 f159eafafa217861bbb1f5d1309d7849e4c1a7f6..67292869efea591cbce9a86100806cc432411a8a 100644 (file)
@@ -6,6 +6,10 @@
 #include <string.h>
 #include <map>
 
+#ifndef DNSDIST
+#include "../../pdns/gettime.hh"
+#endif
+
 using std::string;
 using std::runtime_error;
 using std::tuple;
@@ -16,14 +20,72 @@ static string MDBError(int rc)
   return mdb_strerror(rc);
 }
 
-MDBDbi::MDBDbi(MDB_env* env, MDB_txn* txn, const string_view dbname, int flags)
+#ifndef DNSDIST
+
+namespace LMDBLS {
+  // this also returns a pointer to the string's data. Do not hold on to it too long!
+  const LSheader* LSassertFixedHeaderSize(std::string_view val) {
+    // cerr<<"val.size()="<<val.size()<<endl;
+    if (val.size() < LS_MIN_HEADER_SIZE) {
+      throw std::runtime_error("LSheader too short");
+    }
+
+    return reinterpret_cast<const LSheader*>(val.data());
+  }
+
+  size_t LScheckHeaderAndGetSize(std::string_view val, size_t datasize) {
+    const LSheader* lsh = LSassertFixedHeaderSize(val);
+
+    if (lsh->d_version != 0) {
+      throw std::runtime_error("LSheader has wrong version (not zero)");
+    }
+
+    size_t headersize = LS_MIN_HEADER_SIZE;
+
+    unsigned char* tmp = (unsigned char*)val.data();
+    uint16_t numextra = (tmp[LS_NUMEXTRA_OFFSET] << 8) + tmp[LS_NUMEXTRA_OFFSET+1];
+
+    headersize += numextra * LS_BLOCK_SIZE;
+
+    if (val.size() < headersize) {
+      throw std::runtime_error("LSheader too short for promised extra data");
+    }
+
+    if (datasize && val.size() < (headersize+datasize)) {
+      throw std::runtime_error("Trailing data after LSheader has wrong size");
+    }
+
+    return headersize;
+  }
+
+  size_t LScheckHeaderAndGetSize(const MDBOutVal *val, size_t datasize) {
+    return LScheckHeaderAndGetSize(val->getNoStripHeader<string_view>(), datasize);
+  }
+
+  bool LSisDeleted(std::string_view val) {
+    const LSheader* lsh = LSassertFixedHeaderSize(val);
+
+    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) : 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.
-  
+
   int rc = mdb_dbi_open(txn, dbname.empty() ? 0 : &dbname[0], flags, &d_dbi);
   if(rc)
     throw std::runtime_error("Unable to open named database: " + MDBError(rc));
-  
+
   // Database names are keys in the unnamed database, and may be read but not written.
 }
 
@@ -95,10 +157,10 @@ std::shared_ptr<MDBEnv> getMDBEnv(const char* fname, int flags, int mode, uint64
     weak_ptr<MDBEnv> wp;
     int flags;
   };
-  
+
   static std::map<tuple<dev_t, ino_t>, Value> s_envs;
   static std::mutex mut;
-  
+
   struct stat statbuf;
   if(stat(fname, &statbuf)) {
     if(errno != ENOENT)
@@ -132,7 +194,7 @@ std::shared_ptr<MDBEnv> getMDBEnv(const char* fname, int flags, int mode, uint64
 
   auto fresh = std::make_shared<MDBEnv>(fname, flags, mode, mapsizeMB);
   s_envs[key] = {fresh, flags};
-  
+
   return fresh;
 }
 
@@ -145,7 +207,7 @@ MDBDbi MDBEnv::openDB(const string_view dbname, int flags)
     This function must not be called from multiple concurrent transactions in the same process. A transaction that uses this function must finish (either commit or abort) before any other transaction in the process may use this function.
   */
   std::lock_guard<std::mutex> l(d_openmut);
-  
+
   if(!(envflags & MDB_RDONLY)) {
     auto rwt = getRWTransaction();
     MDBDbi ret = rwt->openDB(dbname, flags);
@@ -155,7 +217,7 @@ MDBDbi MDBEnv::openDB(const string_view dbname, int flags)
 
   MDBDbi ret;
   {
-    auto rwt = getROTransaction(); 
+    auto rwt = getROTransaction();
     ret = rwt->openDB(dbname, flags);
   }
   return ret;
@@ -184,6 +246,13 @@ MDB_txn *MDBRWTransactionImpl::openRWTransaction(MDBEnv *env, MDB_txn *parent, i
 MDBRWTransactionImpl::MDBRWTransactionImpl(MDBEnv* parent, int flags):
   MDBRWTransactionImpl(parent, openRWTransaction(parent, nullptr, flags))
 {
+#ifndef DNSDIST
+  struct timespec tp;
+
+  gettime(&tp, true);
+
+  d_txtime = tp.tv_sec * (1000 * 1000 * 1000) + tp.tv_nsec;
+#endif
 }
 
 MDBRWTransactionImpl::~MDBRWTransactionImpl()
@@ -230,7 +299,7 @@ MDB_txn *MDBROTransactionImpl::openROTransaction(MDBEnv *env, MDB_txn *parent, i
 {
   if(env->getRWTX())
     throw std::runtime_error("Duplicate RO transaction");
-  
+
   /*
     A transaction and its cursors must only be used by a single thread, and a thread may only have a single transaction at a time. If MDB_NOTLS is in use, this does not apply to read-only transactions. */
   MDB_txn *result = nullptr;
@@ -262,7 +331,7 @@ MDBROTransactionImpl::MDBROTransactionImpl(MDBEnv *parent, int flags):
 MDBROTransactionImpl::~MDBROTransactionImpl()
 {
   // this is safe because C++ will not call overrides of virtual methods in destructors.
-  commit();
+  MDBROTransactionImpl::commit();
 }
 
 void MDBROTransactionImpl::abort()
@@ -301,9 +370,10 @@ MDBRWCursor MDBRWTransactionImpl::getRWCursor(const MDBDbi& dbi)
   MDB_cursor *cursor;
   int rc= mdb_cursor_open(d_txn, dbi, &cursor);
   if(rc) {
-    throw std::runtime_error("Error creating RO cursor: "+std::string(mdb_strerror(rc)));
+    throw std::runtime_error("Error creating RW cursor: "+std::string(mdb_strerror(rc)));
   }
-  return MDBRWCursor(d_rw_cursors, cursor);
+
+  return MDBRWCursor(d_rw_cursors, cursor, d_txn, d_txtime);
 }
 
 MDBRWCursor MDBRWTransactionImpl::getCursor(const MDBDbi &dbi)
index 33bfa8f131202de4bd2f74771a9b206197569ab1..1c62d88029643cc07ae0b2a9a5335c656d78c328 100644 (file)
@@ -1,4 +1,5 @@
 #pragma once
+#include <string_view>
 #include <lmdb.h>
 #include <iostream>
 #include <fstream>
 #include <vector>
 #include <algorithm>
 
+#include "config.h"
+
+#ifndef DNSDIST
+#include <boost/range/detail/common.hpp>
+#include <stdint.h>
+#include <netinet/in.h>
+#include <stdexcept>
+#include "../../pdns/misc.hh"
+#endif
+
 using std::string_view;
 
 /* open issues:
  *
  * - missing convenience functions (string_view, string)
- */ 
+ */
 
 /*
 The error strategy. Anything that "should never happen" turns into an exception. But things like 'duplicate entry' or 'no such key' are for you to deal with.
  */
 
 /*
-  Thread safety: we are as safe as lmdb. You can talk to MDBEnv from as many threads as you want 
+  Thread safety: we are as safe as lmdb. You can talk to MDBEnv from as many threads as you want
 */
 
 /** MDBDbi is our only 'value type' object, as 1) a dbi is actually an integer
@@ -35,13 +46,13 @@ public:
   MDBDbi(): d_dbi(-1)
   {
   }
-  explicit MDBDbi(MDB_env* env, MDB_txn* txn, string_view dbname, int flags);  
+  explicit MDBDbi(MDB_env* env, MDB_txn* txn, string_view dbname, int flags);
 
   operator const MDB_dbi&() const
   {
     return d_dbi;
   }
-  
+
   MDB_dbi d_dbi;
 };
 
@@ -64,7 +75,7 @@ public:
   }
 
   MDBDbi openDB(const string_view dbname, int flags);
-  
+
   MDBRWTransaction getRWTransaction();
   MDBROTransaction getROTransaction();
 
@@ -89,6 +100,70 @@ private:
 
 std::shared_ptr<MDBEnv> getMDBEnv(const char* fname, int flags, int mode, uint64_t mapsizeMB=(sizeof(void *)==4) ? 100 : 16000);
 
+#ifndef DNSDIST
+
+#if !defined(__BYTE_ORDER__) || !defined(__ORDER_LITTLE_ENDIAN__) || !defined(__ORDER_BIG_ENDIAN__)
+#error "your compiler did not define byte order macros"
+#endif
+
+// FIXME do something more portable than __builtin_bswap64
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define _LMDB_SAFE_BSWAP64MAYBE(x) __builtin_bswap64(x)
+#else
+#define _LMDB_SAFE_BSWAP64MAYBE(x) (x)
+#endif
+
+struct MDBOutVal; // forward declaration because of how the functions below tie in with MDBOutVal
+
+namespace LMDBLS {
+  class __attribute__((__packed__)) LSheader {
+  public:
+    uint64_t d_timestamp;
+    uint64_t d_txnid;
+    uint8_t d_version;
+    uint8_t d_flags;
+    uint32_t d_reserved;
+    uint16_t d_numextra;
+
+    LSheader(uint64_t timestamp, uint64_t txnid, uint8_t flags=0, uint8_t version=0, uint8_t numextra=0):
+      d_timestamp(_LMDB_SAFE_BSWAP64MAYBE(timestamp)),
+      d_txnid(_LMDB_SAFE_BSWAP64MAYBE(txnid)),
+      d_version(version),
+      d_flags(flags),
+      d_reserved(0),
+      d_numextra(htons(numextra))
+    {
+
+    }
+
+    std::string toString() {
+      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");
+
+  const size_t LS_MIN_HEADER_SIZE = sizeof(LSheader);
+  const size_t LS_BLOCK_SIZE = 8;
+  const size_t LS_NUMEXTRA_OFFSET = 22;
+  const uint8_t LS_FLAG_DELETED = 0x01;
+
+  const LSheader* LSassertFixedHeaderSize(std::string_view val);
+  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;
+}
+
+#undef _LMDB_SAFE_BSWAP64MAYBE
+
+#endif /* ifndef DNSDIST */
 
 
 struct MDBOutVal
@@ -98,54 +173,80 @@ struct MDBOutVal
     return d_mdbval;
   }
 
+#ifndef DNSDIST
   template <class T,
-          typename std::enable_if<std::is_arithmetic<T>::value,
+          typename std::enable_if<std::is_integral<T>::value,
                                   T>::type* = nullptr> const
   T get()
   {
     T ret;
-    if(d_mdbval.mv_size != sizeof(T))
-      throw std::runtime_error("MDB data has wrong length for type");
-    
-    memcpy(&ret, d_mdbval.mv_data, sizeof(T));
+
+    size_t offset = LMDBLS::LScheckHeaderAndGetSize(this, sizeof(T));
+
+    memcpy(&ret, reinterpret_cast<const char *>(d_mdbval.mv_data)+offset, sizeof(T));
+
+    static_assert(sizeof(T) == 4, "this code currently only supports 32 bit integers");
+    ret = ntohl(ret);
     return ret;
   }
 
   template <class T,
-            typename std::enable_if<std::is_class<T>::value,T>::type* = nullptr>
-  T get() const;
-
-  template<class T>
-  T get_struct() const
+          typename std::enable_if<std::is_integral<T>::value,
+                                  T>::type* = nullptr> const
+  T getNoStripHeader()
   {
     T ret;
     if(d_mdbval.mv_size != sizeof(T))
       throw std::runtime_error("MDB data has wrong length for type");
-    
+
     memcpy(&ret, d_mdbval.mv_data, sizeof(T));
+
+    static_assert(sizeof(T) == 4, "this code currently only supports 32 bit integers");
+    ret = ntohl(ret);
     return ret;
   }
 
-  template<class T>
-  const T* get_struct_ptr() const
-  {
-    if(d_mdbval.mv_size != sizeof(T))
-      throw std::runtime_error("MDB data has wrong length for type");
-    
-    return reinterpret_cast<const T*>(d_mdbval.mv_data);
-  }
-  
-  
+#endif /* ifndef DNSDIST */
+
+  template <class T,
+            typename std::enable_if<std::is_class<T>::value,T>::type* = nullptr>
+  T get() const;
+
+
+#ifndef DNSDIST
+  template <class T,
+            typename std::enable_if<std::is_class<T>::value,T>::type* = nullptr>
+  T getNoStripHeader() const;
+#endif
+
   MDB_val d_mdbval;
 };
 
 template<> inline std::string MDBOutVal::get<std::string>() const
 {
+#ifndef DNSDIST
+  size_t offset = LMDBLS::LScheckHeaderAndGetSize(this);
+
+  return std::string((char*)d_mdbval.mv_data+offset, d_mdbval.mv_size-offset);
+}
+
+template<> inline std::string MDBOutVal::getNoStripHeader<std::string>() const
+{
+#endif
   return std::string((char*)d_mdbval.mv_data, d_mdbval.mv_size);
 }
 
 template<> inline string_view MDBOutVal::get<string_view>() const
 {
+#ifndef DNSDIST
+  size_t offset = LMDBLS::LScheckHeaderAndGetSize(this);
+
+  return string_view((char*)d_mdbval.mv_data+offset, d_mdbval.mv_size-offset);
+}
+
+template<> inline string_view MDBOutVal::getNoStripHeader<string_view>() const
+{
+#endif
   return string_view((char*)d_mdbval.mv_data, d_mdbval.mv_size);
 }
 
@@ -156,35 +257,40 @@ public:
   {
   }
 
+#ifndef DNSDIST
   template <class T,
-            typename std::enable_if<std::is_arithmetic<T>::value,
+            typename std::enable_if<std::is_integral<T>::value,
                                     T>::type* = nullptr>
-  MDBInVal(T i) 
+  MDBInVal(T i)
   {
-    memcpy(&d_memory[0], &i, sizeof(i));
+    static_assert(sizeof(T) == 4, "this code currently only supports 32 bit integers");
+    auto j = htonl(i);    // all actual usage in our codebase is 32 bits. If that ever changes, this will break the build and avoid runtime surprises
+    memcpy(&d_memory[0], &j, sizeof(j));
+
     d_mdbval.mv_size = sizeof(T);
     d_mdbval.mv_data = d_memory;;
   }
+#endif
 
   MDBInVal(const char* s)
   {
     d_mdbval.mv_size = strlen(s);
     d_mdbval.mv_data = (void*)s;
   }
-  
-  MDBInVal(const string_view& v) 
+
+  MDBInVal(const string_view& v)
   {
     d_mdbval.mv_size = v.size();
     d_mdbval.mv_data = (void*)&v[0];
   }
 
-  MDBInVal(const std::string& v) 
+  MDBInVal(const std::string& v)
   {
     d_mdbval.mv_size = v.size();
     d_mdbval.mv_data = (void*)&v[0];
   }
 
-  
+
   template<typename T>
   static MDBInVal fromStruct(const T& t)
   {
@@ -193,7 +299,7 @@ public:
     ret.d_mdbval.mv_data = (void*)&t;
     return ret;
   }
-  
+
   operator MDB_val&()
   {
     return d_mdbval;
@@ -201,13 +307,11 @@ public:
   MDB_val d_mdbval;
 private:
   MDBInVal(){}
+#ifndef DNSDIST
   char d_memory[sizeof(double)];
-
+#endif
 };
 
-
-
-
 class MDBROCursor;
 
 class MDBROTransactionImpl
@@ -248,9 +352,20 @@ public:
 
     int rc = mdb_get(d_txn, dbi, const_cast<MDB_val*>(&key.d_mdbval),
                      const_cast<MDB_val*>(&val.d_mdbval));
-    if(rc && rc != MDB_NOTFOUND)
+
+    if(rc && rc != MDB_NOTFOUND) {
       throw std::runtime_error("getting data: " + std::string(mdb_strerror(rc)));
-    
+    }
+
+#ifndef DNSDIST
+    if(rc != MDB_NOTFOUND) {  // key was found, value was retrieved
+      std::string sval = val.getNoStripHeader<std::string>();
+      if (LMDBLS::LSisDeleted(sval)) {  // but it was deleted
+        rc = MDB_NOTFOUND;
+      }
+    }
+#endif
+
     return rc;
   }
 
@@ -263,7 +378,7 @@ public:
     return rc;
   }
 
-  
+
   // this is something you can do, readonly
   MDBDbi openDB(string_view dbname, int flags)
   {
@@ -272,7 +387,7 @@ public:
 
   MDBROCursor getCursor(const MDBDbi&);
   MDBROCursor getROCursor(const MDBDbi&);
-    
+
   operator MDB_txn*()
   {
     return d_txn;
@@ -288,8 +403,8 @@ public:
   }
 };
 
-/* 
-   A cursor in a read-only transaction must be closed explicitly, before or after its transaction ends. It can be reused with mdb_cursor_renew() before finally closing it. 
+/*
+   A cursor in a read-only transaction must be closed explicitly, before or after its transaction ends. It can be reused with mdb_cursor_renew() before finally closing it.
 
    "If the parent transaction commits, the cursor must not be used again."
 */
@@ -300,18 +415,23 @@ class MDBGenCursor
 private:
   std::vector<T*> *d_registry;
   MDB_cursor* d_cursor{nullptr};
-
 public:
+  MDB_txn* d_txn{nullptr}; // ew, public
+  uint64_t d_txtime{0};
+
   MDBGenCursor():
     d_registry(nullptr),
-    d_cursor(nullptr)
+    d_cursor(nullptr),
+    d_txn(nullptr)
   {
 
   }
 
-  MDBGenCursor(std::vector<T*> &registry, MDB_cursor *cursor):
+  MDBGenCursor(std::vector<T*> &registry, MDB_cursor *cursor, MDB_txn *txn=nullptr, uint64_t txtime=0):
     d_registry(&registry),
-    d_cursor(cursor)
+    d_cursor(cursor),
+    d_txn(txn),
+    d_txtime(txtime)
   {
     registry.emplace_back(static_cast<T*>(this));
   }
@@ -362,13 +482,101 @@ public:
     close();
   }
 
+  /*
+   to support (skip) entries marked deleted=1 in the LS header, we need to do some magic here
+   this table notes, for each cursor op:
+   * the maximum number of entries we may need to look at (1 or inf)
+   * the subsequent op that needs to be done to skip over a deleted entry (or MDB_NOTFOUND to give up and say no)
+   (table partially copied from http://www.lmdb.tech/doc/group__mdb.html#ga1206b2af8b95e7f6b0ef6b28708c9127 which I hope is a stable URL)
+   (ops only relevant for DUPSORT/DUPFIXED have been omitted)
+   (table is grouped by "skip op")
+
+  | base op            | maxentries | skip op      | doc description of base op
+  | MDB_FIRST          | inf        | MDB_NEXT     | Position at first key/data item
+  | MDB_NEXT           | inf        | MDB_NEXT     | Position at next data item
+  | MDB_SET_RANGE      | inf        | MDB_NEXT     | Position at first key greater than or equal to specified key.
+  | MDB_LAST           | inf        | MDB_PREV     | Position at last key/data item
+  | MDB_PREV           | inf        | MDB_PREV     | Position at previous data item
+  | MDB_GET_CURRENT    | 1          | MDB_NOTFOUND | Return key/data at current cursor position
+  | MDB_SET            | 1          | MDB_NOTFOUND | Position at specified key
+  | MDB_SET_KEY        | 1          | MDB_NOTFOUND | Position at specified key, return key + data
+  */
+
+private:
+  int skipDeleted(MDBOutVal& key, MDBOutVal& data, MDB_cursor_op op, int rc)
+  {
+#ifndef DNSDIST
+    // when we get here
+    // * mdb_cursor_get has been called once
+    // * it did not return an error, but it might have returned MDB_NOTFOUND
+    // * if it returned MDB_NOTFOUND, there is nothing for us to do and we pass that on
+
+    if (rc == MDB_NOTFOUND) {
+      return rc;
+    }
+
+    // when we get here
+    // * mdb_cursor_get has been called at least once
+    // * it found an entry, as far as LMDB is concerned, so key+data contain something
+    // * but that might be a LS deleted=1 entry
+    // * we know the cursor op that got us here
+
+    while (true) {
+      auto sval = data.getNoStripHeader<std::string_view>();
+
+      if (!LMDBLS::LSisDeleted(sval)) {
+        // done!
+
+        return rc;
+      }
+
+      // the found entry is set deleted, so we need to do something
+
+      // if this was a 1-entry op, this is the end
+      if (op == MDB_GET_CURRENT || op == MDB_SET || op == MDB_SET_KEY) {
+        return MDB_NOTFOUND;
+      }
+
+      // otherwise, we need to try to carry on
+      // all ops that do not map to NOTFOUND map to NEXT or PREV, including NEXT and PREV themselves
+      // so we just override the op to NEXT or PREV
+      if (op == MDB_FIRST || op == MDB_NEXT || op == MDB_SET_RANGE) {
+        op = MDB_NEXT;
+      }
+      else if (op == MDB_LAST || op == MDB_PREV) {
+        op = MDB_PREV;
+      }
+      else {
+        throw std::runtime_error("got unsupported mdb cursor op");
+      }
+
+      rc = mdb_cursor_get(d_cursor, &key.d_mdbval, &data.d_mdbval, op);
+      if(rc && rc != MDB_NOTFOUND) {
+         throw std::runtime_error("Unable to get from cursor: " + std::string(mdb_strerror(rc)));
+      }
+
+      if (rc == MDB_NOTFOUND) {
+        // we ended up finding nothing, so tell the caller
+        return rc;
+      }
+
+      // when we get here
+      // * the situation is just like the last time I wrote "when we get here"
+      // * except mdb_cursor_get has been called at least twice
+      // * so let's go back
+    }
+#else /* ifndef DNSDIST */
+    return rc;
+#endif
+  }
+
 public:
   int get(MDBOutVal& key, MDBOutVal& data, MDB_cursor_op op)
   {
     int rc = mdb_cursor_get(d_cursor, &key.d_mdbval, &data.d_mdbval, op);
     if(rc && rc != MDB_NOTFOUND)
        throw std::runtime_error("Unable to get from cursor: " + std::string(mdb_strerror(rc)));
-    return rc;
+    return skipDeleted(key, data, op, rc);
   }
 
   int find(const MDBInVal& in, MDBOutVal& key, MDBOutVal& data)
@@ -377,9 +585,9 @@ public:
     int rc=mdb_cursor_get(d_cursor, const_cast<MDB_val*>(&key.d_mdbval), &data.d_mdbval, MDB_SET);
     if(rc && rc != MDB_NOTFOUND)
        throw std::runtime_error("Unable to find from cursor: " + std::string(mdb_strerror(rc)));
-    return rc;
+    return skipDeleted(key, data, MDB_SET, rc);
   }
-  
+
   int lower_bound(const MDBInVal& in, MDBOutVal& key, MDBOutVal& data)
   {
     key.d_mdbval = in.d_mdbval;
@@ -387,16 +595,16 @@ public:
     int rc = mdb_cursor_get(d_cursor, const_cast<MDB_val*>(&key.d_mdbval), &data.d_mdbval, MDB_SET_RANGE);
     if(rc && rc != MDB_NOTFOUND)
        throw std::runtime_error("Unable to lower_bound from cursor: " + std::string(mdb_strerror(rc)));
-    return rc;
+    return skipDeleted(key, data, MDB_SET_RANGE, rc);
   }
 
-  
+
   int nextprev(MDBOutVal& key, MDBOutVal& data, MDB_cursor_op op)
   {
     int rc = mdb_cursor_get(d_cursor, const_cast<MDB_val*>(&key.d_mdbval), &data.d_mdbval, op);
     if(rc && rc != MDB_NOTFOUND)
        throw std::runtime_error("Unable to prevnext from cursor: " + std::string(mdb_strerror(rc)));
-    return rc;
+    return skipDeleted(key, data, op, rc);
   }
 
   int next(MDBOutVal& key, MDBOutVal& data)
@@ -414,7 +622,7 @@ public:
     int rc = mdb_cursor_get(d_cursor, const_cast<MDB_val*>(&key.d_mdbval), &data.d_mdbval, op);
     if(rc && rc != MDB_NOTFOUND)
        throw std::runtime_error("Unable to next from cursor: " + std::string(mdb_strerror(rc)));
-    return rc;
+    return skipDeleted(key, data, op, rc);
   }
 
   int current(MDBOutVal& key, MDBOutVal& data)
@@ -484,6 +692,8 @@ private:
 private:
   std::vector<MDBRWCursor*> d_rw_cursors;
 
+  uint64_t d_txtime{0};
+
   void closeRWCursors();
   inline void closeRORWCursors() {
     closeROCursors();
@@ -499,32 +709,47 @@ public:
   MDBRWTransactionImpl &operator=(MDBRWTransactionImpl&& rhs) = delete;
 
   ~MDBRWTransactionImpl() override;
-  
+
   void commit() override;
   void abort() override;
 
   void clear(MDB_dbi dbi);
-  
+
+#ifndef DNSDIST
   void put(MDB_dbi dbi, const MDBInVal& key, const MDBInVal& val, int flags=0)
   {
     if(!d_txn)
       throw std::runtime_error("Attempt to use a closed RW transaction for put");
     int rc;
+
+    size_t txid = mdb_txn_id(d_txn);
+
+    if (d_txtime == 0) { throw std::runtime_error("got zero txtime"); }
+
+    std::string ins =
+      LMDBLS::LSheader(d_txtime, txid).toString()+
+      std::string((const char*)val.d_mdbval.mv_data, val.d_mdbval.mv_size);
+
+    MDBInVal pval = ins;
+
     if((rc=mdb_put(d_txn, dbi,
                    const_cast<MDB_val*>(&key.d_mdbval),
-                   const_cast<MDB_val*>(&val.d_mdbval), flags)))
+                   const_cast<MDB_val*>(&pval.d_mdbval), flags))) {
       throw std::runtime_error("putting data: " + std::string(mdb_strerror(rc)));
+    }
   }
-
-
-  int del(MDBDbi& dbi, const MDBInVal& key, const MDBInVal& val)
+#else
+  void put(MDB_dbi dbi, const MDBInVal& key, const MDBInVal& val, int flags=0)
   {
+    if(!d_txn)
+      throw std::runtime_error("Attempt to use a closed RW transaction for put");
     int rc;
-    rc=mdb_del(d_txn, dbi, (MDB_val*)&key.d_mdbval, (MDB_val*)&val.d_mdbval);
-    if(rc && rc != MDB_NOTFOUND)
-      throw std::runtime_error("deleting data: " + std::string(mdb_strerror(rc)));
-    return rc;
+    if((rc=mdb_put(d_txn, dbi,
+                   const_cast<MDB_val*>(&key.d_mdbval),
+                   const_cast<MDB_val*>(&val.d_mdbval), flags)))
+      throw std::runtime_error("putting data: " + std::string(mdb_strerror(rc)));
   }
+#endif
 
   int del(MDBDbi& dbi, const MDBInVal& key)
   {
@@ -532,10 +757,30 @@ public:
     rc=mdb_del(d_txn, dbi, (MDB_val*)&key.d_mdbval, 0);
     if(rc && rc != MDB_NOTFOUND)
       throw std::runtime_error("deleting data: " + std::string(mdb_strerror(rc)));
+#ifndef DNSDIST
+    if(rc != MDB_NOTFOUND && LMDBLS::s_flag_deleted) {
+      // if it did exist, we need to mark it as deleted now
+
+      size_t txid = mdb_txn_id(d_txn);
+      if (d_txtime == 0) { throw std::runtime_error("got zero txtime"); }
+
+      std::string ins =
+        // std::string((const char*)&txid, sizeof(txid)) +
+        LMDBLS::LSheader(d_txtime, txid, LMDBLS::LS_FLAG_DELETED).toString();
+
+      MDBInVal pval = ins;
+
+      if((rc=mdb_put(d_txn, dbi,
+                     const_cast<MDB_val*>(&key.d_mdbval),
+                     const_cast<MDB_val*>(&pval.d_mdbval), 0))) {
+              throw std::runtime_error("marking data deleted: " + std::string(mdb_strerror(rc)));
+      }
+    }
+#endif
     return rc;
   }
 
+
   int get(MDBDbi& dbi, const MDBInVal& key, MDBOutVal& val)
   {
     if(!d_txn)
@@ -543,20 +788,22 @@ public:
 
     int rc = mdb_get(d_txn, dbi, const_cast<MDB_val*>(&key.d_mdbval),
                      const_cast<MDB_val*>(&val.d_mdbval));
-    if(rc && rc != MDB_NOTFOUND)
-      throw std::runtime_error("getting data: " + std::string(mdb_strerror(rc)));
-    return rc;
-  }
+    if(rc && rc != MDB_NOTFOUND) {
+          throw std::runtime_error("getting data: " + std::string(mdb_strerror(rc)));
+    }
+
+#ifndef DNSDIST
+    if(rc != MDB_NOTFOUND) {  // key was found, value was retrieved
+      auto sval = val.getNoStripHeader<std::string_view>();
+      if (LMDBLS::LSisDeleted(sval)) {  // but it was deleted
+        rc = MDB_NOTFOUND;
+      }
+    }
+#endif
 
-  int get(MDBDbi& dbi, const MDBInVal& key, string_view& val)
-  {
-    MDBOutVal out;
-    int rc = get(dbi, key, out);
-    if(!rc)
-      val = out.get<string_view>();
     return rc;
   }
-  
+
   MDBDbi openDB(string_view dbname, int flags)
   {
     return MDBDbi(environment().d_env, d_txn, dbname, flags);
@@ -567,9 +814,10 @@ public:
 
   MDBRWTransaction getRWTransaction();
   MDBROTransaction getROTransaction();
+
 };
 
-/* "A cursor in a write-transaction can be closed before its transaction ends, and will otherwise be closed when its transaction ends" 
+/* "A cursor in a write-transaction can be closed before its transaction ends, and will otherwise be closed when its transaction ends"
    This is a problem for us since it may means we are closing the cursor twice, which is bad
 */
 class MDBRWCursor : public MDBGenCursor<MDBRWTransactionImpl, MDBRWCursor>
@@ -583,28 +831,71 @@ public:
   MDBRWCursor &operator=(MDBRWCursor &&src) = default;
   ~MDBRWCursor() = default;
 
+#ifndef DNSDIST
   void put(const MDBOutVal& key, const MDBInVal& data)
   {
+    size_t txid = mdb_txn_id(this->d_txn);
+
+    if (d_txtime == 0) { throw std::runtime_error("got zero txtime"); }
+
+    std::string ins =
+      LMDBLS::LSheader(d_txtime, txid).toString()+
+      std::string((const char*)data.d_mdbval.mv_data, data.d_mdbval.mv_size);
+
+    MDBInVal pval = ins;
+
     int rc = mdb_cursor_put(*this,
                             const_cast<MDB_val*>(&key.d_mdbval),
-                            const_cast<MDB_val*>(&data.d_mdbval), MDB_CURRENT);
+                            const_cast<MDB_val*>(&pval.d_mdbval), MDB_CURRENT);
     if(rc)
       throw std::runtime_error("mdb_cursor_put: " + std::string(mdb_strerror(rc)));
   }
-
-  
-  int put(const MDBOutVal& key, const MDBOutVal& data, int flags=0)
+#else
+  void put(const MDBOutVal& key, const MDBInVal& data)
   {
-    // XXX check errors
-    return mdb_cursor_put(*this,
-                          const_cast<MDB_val*>(&key.d_mdbval),
-                          const_cast<MDB_val*>(&data.d_mdbval), flags);
+    int rc = mdb_cursor_put(*this,
+                            const_cast<MDB_val*>(&key.d_mdbval),
+                            const_cast<MDB_val*>(&data.d_mdbval), MDB_CURRENT);
+    if(rc)
+      throw std::runtime_error("mdb_cursor_put: " + std::string(mdb_strerror(rc)));
   }
+#endif
 
+#ifndef DNSDIST
   int del(int flags=0)
   {
-    return mdb_cursor_del(*this, flags);
-  }
+    MDBOutVal key, val;
 
-};
+    if (LMDBLS::s_flag_deleted) {
+      int rc_get = mdb_cursor_get (*this, &key.d_mdbval, &val.d_mdbval, MDB_GET_CURRENT);
 
+      if(rc_get) {
+              throw std::runtime_error("getting key to mark data as deleted: " + std::string(mdb_strerror(rc_get)));
+      }
+
+      size_t txid = mdb_txn_id(d_txn);
+      if (d_txtime == 0) { throw std::runtime_error("got zero txtime"); }
+
+      std::string ins =
+        LMDBLS::LSheader(d_txtime, txid, LMDBLS::LS_FLAG_DELETED).toString();
+
+      std::string skey((const char*)key.d_mdbval.mv_data, key.d_mdbval.mv_size);
+
+      MDBInVal pkey = MDBInVal(skey);
+      MDBInVal pval = ins;
+
+      int rc_put = mdb_cursor_put(*this,
+                     const_cast<MDB_val*>(&pkey.d_mdbval),
+                     const_cast<MDB_val*>(&pval.d_mdbval), 0 /* MDB_CURRENT */);
+      if(rc_put) {
+              throw std::runtime_error("marking data deleted: " + std::string(mdb_strerror(rc_put)));
+      }
+      return rc_put;
+    }
+    else {
+      // do a normal delete
+      return mdb_cursor_del(*this, flags);
+    }
+  }
+#endif
+};
index 7b0abd339aec8c7321e73902ff873bc6fa52ca5c..720285352db5632c1400168c5943ad1851dfe6ac 100644 (file)
@@ -7,7 +7,7 @@ unsigned int MDBGetMaxID(MDBRWTransaction& txn, MDBDbi& dbi)
   MDBOutVal maxidval, maxcontent;
   unsigned int maxid{0};
   if(!cursor.get(maxidval, maxcontent, MDB_LAST)) {
-    maxid = maxidval.get<unsigned int>();
+    maxid = maxidval.getNoStripHeader<unsigned int>();
   }
   return maxid;
 }
@@ -28,5 +28,3 @@ unsigned int MDBGetRandomID(MDBRWTransaction& txn, MDBDbi& dbi)
   }
   throw std::runtime_error("MDBGetRandomID() could not assign an unused random ID");
 }
-
-
index 8fdce352278b366aa67dc12abb98d299f19956e7..ae32d78fd67b26a37bdcd969a6c2f4b2253d74d5 100644 (file)
@@ -1,4 +1,6 @@
 #pragma once
+#include <stdexcept>
+#include <string_view>
 #include <iostream>
 #include "lmdb-safe.hh"
 #include <boost/archive/binary_oarchive.hpp>
@@ -14,7 +16,7 @@
 // using std::endl;
 
 
-/* 
+/*
    Open issues:
 
    Everything should go into a namespace
@@ -30,7 +32,7 @@
 
 
 /** Return the highest ID used in a database. Returns 0 for an empty DB.
-    This makes us start everything at ID=1, which might make it possible to 
+    This makes us start everything at ID=1, which might make it possible to
     treat id 0 as special
 */
 unsigned int MDBGetMaxID(MDBRWTransaction& txn, MDBDbi& dbi);
@@ -40,6 +42,7 @@ unsigned int MDBGetMaxID(MDBRWTransaction& txn, MDBDbi& dbi);
 */
 unsigned int MDBGetRandomID(MDBRWTransaction& txn, MDBDbi& dbi);
 
+typedef std::vector<uint32_t> LMDBIDvec;
 
 /** This is our serialization interface.
     You can define your own serToString for your type if you know better
@@ -51,7 +54,7 @@ std::string serToString(const T& t)
   boost::iostreams::back_insert_device<std::string> inserter(serial_str);
   boost::iostreams::stream<boost::iostreams::back_insert_device<std::string> > s(inserter);
   boost::archive::binary_oarchive oa(s, boost::archive::no_header | boost::archive::no_codecvt);
-  
+
   oa << t;
   return serial_str;
 }
@@ -83,7 +86,7 @@ inline std::string keyConv(const T& t)
   return std::string((char*)&t, sizeof(t));
 }
 
-// this is how to override specific types.. it is ugly 
+// this is how to override specific types.. it is ugly
 template<class T, typename std::enable_if<std::is_same<T, std::string>::value,T>::type* = nullptr>
 inline std::string keyConv(const T& t)
 {
@@ -91,7 +94,52 @@ inline std::string keyConv(const T& t)
 }
 
 
-/** This is a struct that implements index operations, but 
+namespace {
+  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");
+    }
+
+    MDBOutVal ret;
+    ret.d_mdbval.mv_data = combined.d_mdbval.mv_data;
+    ret.d_mdbval.mv_size = combined.d_mdbval.mv_size - sizeof(uint32_t);
+
+    return ret;
+  }
+
+  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");
+    }
+
+    MDBOutVal ret;
+    ret.d_mdbval.mv_data = (char*) combined.d_mdbval.mv_data + combined.d_mdbval.mv_size - sizeof(uint32_t);
+    ret.d_mdbval.mv_size = sizeof(uint32_t);
+
+    return ret;
+  }
+
+  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);
+    std::string sval((char*) val.d_mdbval.mv_data, val.d_mdbval.mv_size);
+
+    if (val.d_mdbval.mv_size != 0 &&  // empty val case, for range queries
+        val.d_mdbval.mv_size != 4) {   // uint32_t case
+      throw std::runtime_error("got wrong size value in makeCombinedKey");
+    }
+
+    uint16_t len = htons(skey.size());
+    memcpy(lenprefix.data(), &len, sizeof(len));
+    std::string scombined = lenprefix + skey + sval;
+
+    return scombined;
+  }
+}
+
+
+/** This is a struct that implements index operations, but
     only the operations that are broadcast to all indexes.
     Specifically, to deal with databases with less than the maximum
     number of interfaces, this only includes calls that should be
@@ -105,14 +153,25 @@ template<class Class,typename Type, typename Parent>
 struct LMDBIndexOps
 {
   explicit LMDBIndexOps(Parent* parent) : d_parent(parent){}
+
   void put(MDBRWTransaction& txn, const Class& t, uint32_t id, int flags=0)
   {
-    txn->put(d_idx, keyConv(d_parent->getMember(t)), id, flags);
+    std::string sempty("");
+    MDBInVal empty(sempty);
+
+    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);
   }
 
   void del(MDBRWTransaction& txn, const Class& t, uint32_t id)
   {
-    if(int rc = txn->del(d_idx, keyConv(d_parent->getMember(t)), id)) {
+    auto scombined = makeCombinedKey(keyConv(d_parent->getMember(t)), id);
+    MDBInVal combined(scombined);
+
+    if(int rc = txn->del(d_idx, combined)) {
       throw std::runtime_error("Error deleting from index: " + std::string(mdb_strerror(rc)));
     }
   }
@@ -123,6 +182,7 @@ struct LMDBIndexOps
   }
   MDBDbi d_idx;
   Parent* d_parent;
+
 };
 
 /** This is an index on a field in a struct, it derives from the LMDBIndexOps */
@@ -136,7 +196,7 @@ struct index_on : LMDBIndexOps<Class, Type, index_on<Class, Type, PtrToMember>>
   {
     return c.*PtrToMember;
   }
-  
+
   typedef Type type;
 };
 
@@ -152,41 +212,40 @@ struct index_on_function : LMDBIndexOps<Class, Type, index_on_function<Class, Ty
     return f(c);
   }
 
-  typedef Type type;           
+  typedef Type type;
 };
 
 /** nop index, so we can fill our N indexes, even if you don't use them all */
 struct nullindex_t
 {
   template<typename Class>
-  void put(MDBRWTransaction& txn, const Class& t, uint32_t id, int flags=0)
+  void put(MDBRWTransaction& /* txn */, const Class& /* t */, uint32_t /* id */, int /* flags */ =0)
   {}
   template<typename Class>
-  void del(MDBRWTransaction& txn, const Class& t, uint32_t id)
+  void del(MDBRWTransaction& /* txn */, const Class& /* t */, uint32_t /* id */)
   {}
-  
-  void openDB(std::shared_ptr<MDBEnv>& env, string_view str, int flags)
+
+  void openDB(std::shared_ptr<MDBEnv>& /* env */, string_view /* str */, int /* flags */)
   {
-    
+
   }
   typedef uint32_t type; // dummy
 };
 
-
 /** The main class. Templatized only on the indexes and typename right now */
 template<typename T, class I1=nullindex_t, class I2=nullindex_t, class I3 = nullindex_t, class I4 = nullindex_t>
 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 | MDB_INTEGERKEY);
+    d_main = d_env->openDB(name, MDB_CREATE);
 
     // now you might be tempted to go all MPL on this so we can get rid of the
     // ugly macro. I'm not very receptive to that idea since it will make things
     // EVEN uglier.
-#define openMacro(N) std::get<N>(d_tuple).openDB(d_env, std::string(name)+"_"#N, MDB_CREATE | MDB_DUPFIXED | MDB_DUPSORT);
+#define openMacro(N) std::get<N>(d_tuple).openDB(d_env, std::string(name)+"_"#N, MDB_CREATE);
     openMacro(0);
     openMacro(1);
     openMacro(2);
@@ -194,9 +253,9 @@ public:
 #undef openMacro
   }
 
-  
+
   // we get a lot of our smarts from this tuple, it enables get<0> etc
-  typedef std::tuple<I1, I2, I3, I4> tuple_t; 
+  typedef std::tuple<I1, I2, I3, I4> tuple_t;
   tuple_t d_tuple;
 
   // We support readonly and rw transactions. Here we put the Readonly operations
@@ -207,22 +266,22 @@ public:
     ReadonlyOperations(Parent& parent) : d_parent(parent)
     {}
 
-    //! Number of entries in main database
-    uint32_t size()
-    {
-      MDB_stat stat;
-      mdb_stat(**d_parent.d_txn, d_parent.d_parent->d_main, &stat);
-      return stat.ms_entries;
-    }
-
-    //! Number of entries in the various indexes - should be the same
-    template<int N>
-    uint32_t size()
-    {
-      MDB_stat stat;
-      mdb_stat(**d_parent.d_txn, std::get<N>(d_parent.d_parent->d_tuple).d_idx, &stat);
-      return stat.ms_entries;
-    }
+    // //! Number of entries in main database
+    // uint32_t size()
+    // {
+    //   MDB_stat stat;
+    //   mdb_stat(**d_parent.d_txn, d_parent.d_parent->d_main, &stat);
+    //   return stat.ms_entries;
+    // }
+
+    // //! Number of entries in the various indexes - should be the same
+    // template<int N>
+    // uint32_t size()
+    // {
+    //   MDB_stat stat;
+    //   mdb_stat(**d_parent.d_txn, std::get<N>(d_parent.d_parent->d_tuple).d_idx, &stat);
+    //   return stat.ms_entries;
+    // }
 
     //! Get item with id, from main table directly
     bool get(uint32_t id, T& t)
@@ -230,7 +289,7 @@ public:
       MDBOutVal data;
       if((*d_parent.d_txn)->get(d_parent.d_parent->d_main, id, data))
         return false;
-      
+
       serFromString(data.get<std::string>(), t);
       return true;
     }
@@ -239,29 +298,45 @@ public:
     template<int N>
     uint32_t get(const typename std::tuple_element<N, tuple_t>::type::type& key, T& out)
     {
-      MDBOutVal id;
-      if(!(*d_parent.d_txn)->get(std::get<N>(d_parent.d_parent->d_tuple).d_idx, keyConv(key), id)) {
-        if(get(id.get<uint32_t>(), out))
-          return id.get<uint32_t>();
+      // MDBOutVal out;
+      // uint32_t id;
+
+      // auto range = (*d_parent.d_txn)->prefix_range<N>(domain);
+
+      // auto range = prefix_range<N>(key);
+      LMDBIDvec 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;
       }
-      return 0;
-    }
 
-    //! Cardinality of index N
-    template<int N>
-    uint32_t cardinality()
-    {
-      auto cursor = (*d_parent.d_txn)->getCursor(std::get<N>(d_parent.d_parent->d_tuple).d_idx);
-      bool first = true;
-      MDBOutVal key, data;
-      uint32_t count = 0;
-      while(!cursor.get(key, data, first ? MDB_FIRST : MDB_NEXT_NODUP)) {
-        ++count;
-        first=false;
+      if (ids.size() == 1) {
+        if (get(ids[0], out)) {
+          return ids[0];
+        }
       }
-      return count;
+
+      throw std::runtime_error("in index get, found more than one item");
     }
 
+    // //! Cardinality of index N
+    // template<int N>
+    // uint32_t cardinality()
+    // {
+    //   auto cursor = (*d_parent.d_txn)->getCursor(std::get<N>(d_parent.d_parent->d_tuple).d_idx);
+    //   bool first = true;
+    //   MDBOutVal key, data;
+    //   uint32_t count = 0;
+    //   while(!cursor.get(key, data, first ? MDB_FIRST : MDB_NEXT_NODUP)) {
+    //     ++count;
+    //     first=false;
+    //   }
+    //   return count;
+    // }
+
     //! End iterator type
     struct eiter_t
     {};
@@ -280,15 +355,26 @@ public:
         d_one_key(one_key),   // should we stop at end of key? (equal range)
         d_end(end)
       {
-        if(d_end)
+        if(d_end) {
           return;
+        }
         d_prefix.clear();
-        
+
         if(d_cursor.get(d_key, d_id,  MDB_GET_CURRENT)) {
           d_end = true;
           return;
         }
 
+        if (d_id.d_mdbval.mv_size < LMDBLS::LS_MIN_HEADER_SIZE) {
+          throw std::runtime_error("got short value");
+        }
+
+        // MDBOutVal id = d_id;
+
+        // id.d_mdbval.mv_size -= LS_HEADER_SIZE;
+        // id.d_mdbval.mv_data = (char*)d_id.d_mdbval.mv_data + LS_HEADER_SIZE;
+
+
         if(d_on_index) {
           if((*d_parent->d_txn)->get(d_parent->d_parent->d_main, d_id, d_data))
             throw std::runtime_error("Missing id in constructor");
@@ -303,7 +389,7 @@ public:
         d_cursor(std::move(cursor)),
         d_on_index(true), // is this an iterator on main database or on index?
         d_one_key(false),
-        d_prefix(prefix),  
+        d_prefix(prefix),
         d_end(false)
       {
         if(d_end)
@@ -314,6 +400,8 @@ public:
           return;
         }
 
+        d_id = getIDFromCombinedKey(d_key);
+
         if(d_on_index) {
           if((*d_parent->d_txn)->get(d_parent->d_parent->d_main, d_id, d_data))
             throw std::runtime_error("Missing id in constructor");
@@ -323,62 +411,74 @@ public:
           serFromString(d_id.get<std::string>(), d_t);
       }
 
-      
-      std::function<bool(const MDBOutVal&)> filter;
+
+      // std::function<bool(const MDBOutVal&)> filter;
       void del()
       {
         d_cursor.del();
       }
-      
-      bool operator!=(const eiter_t& rhs) const
+
+      bool operator!=(const eiter_t& /* rhs */) const
       {
         return !d_end;
       }
-      
-      bool operator==(const eiter_t& rhs) const
+
+      bool operator==(const eiter_t& /* rhs */) const
       {
         return d_end;
       }
-      
+
       const T& operator*()
       {
         return d_t;
       }
-      
+
       const T* operator->()
       {
         return &d_t;
       }
 
       // implements generic ++ or --
-      iter_t& genoperator(MDB_cursor_op dupop, MDB_cursor_op op)
+      iter_t& genoperator(MDB_cursor_op op)
       {
         MDBOutVal data;
         int rc;
-      next:;
-        rc = d_cursor.get(d_key, d_id, d_one_key ? dupop : op);
-        if(rc == MDB_NOTFOUND) {
+      // next:;
+        if (!d_one_key) {
+          rc = d_cursor.get(d_key, d_id, op);
+        }
+        if(d_one_key || rc == MDB_NOTFOUND) {
           d_end = true;
         }
         else if(rc) {
           throw std::runtime_error("in genoperator, " + std::string(mdb_strerror(rc)));
         }
-        else if(!d_prefix.empty() && d_key.get<std::string>().rfind(d_prefix, 0)!=0) {
+        else if(!d_prefix.empty() &&
+          // d_key.getNoStripHeader<std::string>().rfind(d_prefix, 0)!=0 &&
+          getKeyFromCombinedKey(d_key).template getNoStripHeader<std::string>() != d_prefix) {
           d_end = true;
         }
         else {
+          // if (d_id.d_mdbval.mv_size < LS_HEADER_SIZE) throw std::runtime_error("got short value");
+
+          // MDBOutVal id = d_id;
+
+          // id.d_mdbval.mv_size -= LS_HEADER_SIZE;
+          // id.d_mdbval.mv_data = (char*)d_id.d_mdbval.mv_data+LS_HEADER_SIZE;
+
           if(d_on_index) {
+            d_id = getIDFromCombinedKey(d_key);
             if((*d_parent->d_txn)->get(d_parent->d_parent->d_main, d_id, data))
               throw std::runtime_error("Missing id field");
-            if(filter && !filter(data))
-              goto next;
-            
+            // if(filter && !filter(data))
+            //   goto next;
+
             serFromString(data.get<std::string>(), d_t);
           }
           else {
-            if(filter && !filter(data))
-              goto next;
-                        
+            // if(filter && !filter(data))
+            //   goto next;
+
             serFromString(d_id.get<std::string>(), d_t);
           }
         }
@@ -387,28 +487,31 @@ public:
 
       iter_t& operator++()
       {
-        return genoperator(MDB_NEXT_DUP, MDB_NEXT);
-      }
-      iter_t& operator--()
-      {
-        return genoperator(MDB_PREV_DUP, MDB_PREV);
+        return genoperator(MDB_NEXT);
       }
+      // iter_t& operator--()
+      // {
+      //   return genoperator(MDB_PREV);
+      // }
 
       // get ID this iterator points to
       uint32_t getID()
       {
-        if(d_on_index)
-          return d_id.get<uint32_t>();
-        else
-          return d_key.get<uint32_t>();
+        if(d_on_index) {
+          // return d_id.get<uint32_t>();
+          return d_id.getNoStripHeader<uint32_t>();
+        }
+        else {
+          return d_key.getNoStripHeader<uint32_t>();
+        }
       }
 
       const MDBOutVal& getKey()
       {
         return d_key;
       }
-      
-      
+
+
       // transaction we are part of
       Parent* d_parent;
       typename Parent::cursor_t d_cursor;
@@ -426,9 +529,9 @@ public:
     iter_t genbegin(MDB_cursor_op op)
     {
       typename Parent::cursor_t cursor = (*d_parent.d_txn)->getCursor(std::get<N>(d_parent.d_parent->d_tuple).d_idx);
-      
+
       MDBOutVal out, id;
-      
+
       if(cursor.get(out, id,  op)) {
                                              // on_index, one_key, end
         return iter_t{&d_parent, std::move(cursor), true, false, true};
@@ -452,11 +555,11 @@ public:
     iter_t begin()
     {
       typename Parent::cursor_t cursor = (*d_parent.d_txn)->getCursor(d_parent.d_parent->d_main);
-      
+
       MDBOutVal out, id;
-      
+
       if(cursor.get(out, id,  MDB_FIRST)) {
-                                              // on_index, one_key, end        
+                                              // on_index, one_key, end
         return iter_t{&d_parent, std::move(cursor), false, false, true};
       }
 
@@ -474,13 +577,13 @@ public:
     {
       typename Parent::cursor_t cursor = (*d_parent.d_txn)->getCursor(std::get<N>(d_parent.d_parent->d_tuple).d_idx);
 
-      std::string keystr = keyConv(key);
+      std::string keystr = makeCombinedKey(keyConv(key), MDBInVal(""));
       MDBInVal in(keystr);
       MDBOutVal out, id;
       out.d_mdbval = in.d_mdbval;
-      
+
       if(cursor.get(out, id,  op)) {
-                                              // on_index, one_key, end        
+                                              // on_index, one_key, end
         return iter_t{&d_parent, std::move(cursor), true, false, true};
       }
 
@@ -506,13 +609,13 @@ public:
     {
       typename Parent::cursor_t cursor = (*d_parent.d_txn)->getCursor(std::get<N>(d_parent.d_parent->d_tuple).d_idx);
 
-      std::string keyString=keyConv(key);
+      std::string keyString=makeCombinedKey(keyConv(key), MDBInVal(""));
       MDBInVal in(keyString);
       MDBOutVal out, id;
       out.d_mdbval = in.d_mdbval;
-      
+
       if(cursor.get(out, id,  MDB_SET)) {
-                                              // on_index, one_key, end        
+                                              // on_index, one_key, end
         return {iter_t{&d_parent, std::move(cursor), true, true, true}, eiter_t()};
       }
 
@@ -525,38 +628,91 @@ public:
     {
       typename Parent::cursor_t cursor = (*d_parent.d_txn)->getCursor(std::get<N>(d_parent.d_parent->d_tuple).d_idx);
 
-      std::string keyString=keyConv(key);
+      std::string keyString=makeCombined(keyConv(key), MDBInVal(""));
       MDBInVal in(keyString);
       MDBOutVal out, id;
       out.d_mdbval = in.d_mdbval;
-      
-      if(cursor.get(out, id,  MDB_SET_RANGE)) {
-                                              // on_index, one_key, end        
+
+      if(cursor.get(out, id,  MDB_SET_RANGE) ||
+         getKeyFromCombinedKey(out).template getNoStripHeader<std::string>() != keyString) {
+                                                    // on_index, one_key, end
         return {iter_t{&d_parent, std::move(cursor), true, true, true}, eiter_t()};
       }
 
       return {iter_t(&d_parent, std::move(cursor), keyString), eiter_t()};
     };
 
-    
+    template<int N>
+    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);
+
+      std::string keyString=makeCombinedKey(keyConv(key), MDBInVal(""));
+      MDBInVal in(keyString);
+      MDBOutVal out, id;
+      out.d_mdbval = in.d_mdbval;
+
+      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);
+        auto sthiskey = thiskey.getNoStripHeader<std::string>();
+
+        if (sout.find(keyString) != 0) {
+          // we are no longer in range, so we are done
+          break;
+        }
+
+        if (sthiskey == keyString) {
+          auto _id = getIDFromCombinedKey(out);
+          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);
+      }
+
+      if (rc != 0 && rc != MDB_NOTFOUND) {
+        throw std::runtime_error("error during get_multi");
+      }
+    };
+
+
     Parent& d_parent;
   };
-  
+
   class ROTransaction : public ReadonlyOperations<ROTransaction>
   {
   public:
-    explicit ROTransaction(TypedDBI* parent) : ReadonlyOperations<ROTransaction>(*this), d_parent(parent), d_txn(std::make_shared<MDBROTransaction>(d_parent->d_env->getROTransaction())) 
+    explicit ROTransaction(TypedDBI* parent) : ReadonlyOperations<ROTransaction>(*this), d_parent(parent), d_txn(std::make_shared<MDBROTransaction>(d_parent->d_env->getROTransaction()))
     {
     }
 
-    explicit ROTransaction(TypedDBI* parent, std::shared_ptr<MDBROTransaction> txn) : ReadonlyOperations<ROTransaction>(*this), d_parent(parent), d_txn(txn) 
+    explicit ROTransaction(TypedDBI* parent, std::shared_ptr<MDBROTransaction> txn) : ReadonlyOperations<ROTransaction>(*this), d_parent(parent), d_txn(txn)
     {
     }
 
-    
+
     ROTransaction(ROTransaction&& rhs) :
       ReadonlyOperations<ROTransaction>(*this), d_parent(rhs.d_parent),d_txn(std::move(rhs.d_txn))
-      
+
     {
       rhs.d_parent = 0;
     }
@@ -565,14 +721,14 @@ public:
     {
       return d_txn;
     }
-    
+
     typedef MDBROCursor cursor_t;
 
     TypedDBI* d_parent;
-    std::shared_ptr<MDBROTransaction> d_txn;    
-  };    
+    std::shared_ptr<MDBROTransaction> d_txn;
+  };
+
 
-  
   class RWTransaction :  public ReadonlyOperations<RWTransaction>
   {
   public:
@@ -585,7 +741,7 @@ public:
     {
     }
 
-    
+
     RWTransaction(RWTransaction&& rhs) :
       ReadonlyOperations<RWTransaction>(*this),
       d_parent(rhs.d_parent), d_txn(std::move(rhs.d_txn))
@@ -603,7 +759,8 @@ public:
         }
         else {
           id = MDBGetMaxID(*d_txn, d_parent->d_main) + 1;
-          flags = MDB_APPEND;
+          // FIXME: after dropping MDB_INTEGERKEY, we had to drop MDB_APPEND here. Check if this is an LMDB quirk.
+          // flags = MDB_APPEND;
         }
       }
       (*d_txn)->put(d_parent->d_main, id, serToString(t), flags);
@@ -622,10 +779,10 @@ public:
     void modify(uint32_t id, std::function<void(T&)> func)
     {
       T t;
-      if(!this->get(id, t)) 
+      if(!this->get(id, t))
         throw std::runtime_error("Could not modify id "+std::to_string(id));
       func(t);
-      
+
       del(id);  // this is the lazy way. We could test for changed index fields
       put(t, id);
     }
@@ -634,9 +791,9 @@ public:
     void del(uint32_t id)
     {
       T t;
-      if(!this->get(id, t)) 
+      if(!this->get(id, t))
         return;
-      
+
       (*d_txn)->del(d_parent->d_main, id);
       clearIndex(id, t);
     }
@@ -675,7 +832,7 @@ public:
       return d_txn;
     }
 
-    
+
   private:
     // clear this ID from all indexes
     void clearIndex(uint32_t id, const T& t)
@@ -685,7 +842,7 @@ public:
       clearMacro(1);
       clearMacro(2);
       clearMacro(3);
-#undef clearMacro      
+#undef clearMacro
     }
 
   public:
@@ -721,14 +878,9 @@ public:
   {
     return d_env;
   }
-  
+
 private:
   std::shared_ptr<MDBEnv> d_env;
   MDBDbi d_main;
   std::string d_name;
 };
-
-
-
-
-
index e3a87ee9b77688fdaf2c9024cdcc3cb101280fad..655375e7c9d6a8298cd8eee07bd66e8e1f5f80b1 100644 (file)
@@ -1639,12 +1639,19 @@ private:
 
             // writing structure for this type into the registry
             checkTypeRegistration(state, &typeid(TType));
+            try {
+              // 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));
+            }
+            catch (...) {
+              Pusher<std::exception_ptr>::push(state, std::current_exception()).release();
+              luaError(state);
+            }
 
-            // 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
-            const auto pointerLocation = static_cast<TType*>(lua_newuserdata(state, sizeof(TType)));
-            new (pointerLocation) TType(std::forward<TType2>(value));
             PushedObject obj{state, 1};
 
             // creating the metatable (over the object on the stack)
@@ -1770,7 +1777,8 @@ private:
         -> typename std::enable_if<IsOptional<TFirstType>::value, TRetValue>::type
     {
         if (index >= 0) {
-            Binder<TCallback, const TFirstType&> binder{ callback, {} };
+            static const TFirstType empty{};
+            Binder<TCallback, const TFirstType&> binder{ callback, empty };
             return readIntoFunction(state, retValueTag, binder, index + 1, othersTags...);
         }
 
@@ -2285,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));
 
@@ -2328,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 00ba545032bae0fed73a8f2940ff29d2046823e4..4db53bec85a1478f5ebdee563f415506388885fa 100644 (file)
@@ -1,4 +1,4 @@
-#ifdef HAVE_CXX11
+#if __cplusplus >= 201103L
 #include <functional>
 #define HAVE_CPP_FUNC_PTR
 namespace funcptr = std;
@@ -72,7 +72,7 @@ namespace YaHTTP {
       size_t operator()(const HTTPBase *doc __attribute__((unused)), std::ostream& os, bool chunked) const {
         char buf[4096];
         size_t n,k;
-#ifdef HAVE_CXX11
+#if __cplusplus >= 201103L
         std::ifstream ifs(path, std::ifstream::binary);
 #else
         std::ifstream ifs(path.c_str(), std::ifstream::binary);
index 489f8cff1f9658a43558b8f9059509171dacf827..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,81 +22,113 @@ 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) {
     for(TRouteList::iterator i = routes.begin(); i != routes.end(); i++) {
-#ifdef HAVE_CXX11
+#if __cplusplus >= 201103L
       std::streamsize ss = os.width();
       std::ios::fmtflags ff = os.setf(std::ios::left);
       os.width(10);
@@ -122,7 +152,7 @@ namespace YaHTTP {
 
     bool found = false;
     for(TRouteList::iterator i = routes.begin(); !found && i != routes.end(); i++) {
-#ifdef HAVE_CXX11
+#if __cplusplus >= 201103L
       if (std::get<3>(*i) == name) { mask = std::get<1>(*i); method = std::get<0>(*i); found = true; }
 #else
       if (i->get<3>() == name) { mask = i->get<1>(); method = i->get<0>(); found = true; }
index a0dbd1380e6384d1d0573b754f75dc35d3474ebe..3cb13ed98680a19f23b2a98b54d253848f03886c 100644 (file)
@@ -2,7 +2,7 @@
 /* @file 
  * @brief Defines router class and support structures
  */
-#ifdef HAVE_CXX11
+#if __cplusplus >= 201103L
 #include <functional>
 #include <tuple>
 #define HAVE_CPP_FUNC_PTR
@@ -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 b2ad4437c8926b7d4d877067c49ff4cd41581197..ca26a8b100336885e2859a477897683095fa1d1c 100644 (file)
@@ -4,35 +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) ;
-- 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
@@ -41,22 +45,24 @@ Corpus
 This directory contains a few files used for continuous fuzzing
 of the PowerDNS products.
 
-The 'corpus' directory contains three sub-directories:
-- proxy-protocol-raw-packets/ contains DNS queries prefixed with a Proxy
-  Protocol v2 header, used by fuzz_target_proxyprotocol ;
-- raw-dns-packets/ contains DNS queries and responses as captured on
-  the wire. These are used by the fuzz_target_dnsdistcache,
-  fuzz_target_moadnsparser and fuzz_target_packetcache targets ;
-- 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" \
@@ -67,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/http-raw-payloads/http0_get.raw b/fuzzing/corpus/http-raw-payloads/http0_get.raw
new file mode 100644 (file)
index 0000000..56d93d9
--- /dev/null
@@ -0,0 +1,2 @@
+GET /
+
diff --git a/fuzzing/corpus/http-raw-payloads/http10_nohost_get.raw b/fuzzing/corpus/http-raw-payloads/http10_nohost_get.raw
new file mode 100644 (file)
index 0000000..c1da607
--- /dev/null
@@ -0,0 +1,2 @@
+GET / HTTP/1.0
+
diff --git a/fuzzing/corpus/http-raw-payloads/http11_get.raw b/fuzzing/corpus/http-raw-payloads/http11_get.raw
new file mode 100644 (file)
index 0000000..b2be663
--- /dev/null
@@ -0,0 +1,10 @@
+GET /foo?param=42a HTTP/1.1
+Host: 127.0.0.1:8085
+User-Agent: HTTPie/2.3.0
+Accept-Encoding: gzip, deflate
+Accept: */*
+Connection: keep-alive
+X-API-Key: redacted
+customheader: foobar
+Authorization: Basic YTpzdXBlcnNlY3JldA==
+
diff --git a/fuzzing/corpus/http-raw-payloads/http11_put.raw b/fuzzing/corpus/http-raw-payloads/http11_put.raw
new file mode 100644 (file)
index 0000000..4c16819
--- /dev/null
@@ -0,0 +1,13 @@
+PUT /api/v1/servers/localhost/config/allow-from HTTP/1.1
+Host: 127.0.0.1:8085
+User-Agent: HTTPie/2.3.0
+Accept-Encoding: gzip, deflate
+Accept: application/json, */*;q=0.5
+Connection: keep-alive
+Content-Type: application/json
+X-API-Key: apikey
+Content-Length: 114
+
+{"name": "allow-from",
+ "type": "ConfigSetting",
+ "value": ["192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24"]}
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
index acc12624edcfd318b7a18b4bb2501bc42e9cb94f..d01258e851b42816e6c126ed2b6a3c85c4fde578 100644 (file)
@@ -43,7 +43,7 @@ AC_DEFUN([AX_CHECK_SIGN], [
  AC_CACHE_CHECK([whether $1 is signed], ax_cv_decl_${typename}_signed, [
    AC_COMPILE_IFELSE(
      [AC_LANG_PROGRAM([[$4]],
-       [[ int foo @<:@ 1 - 2 * !((($1) -1) < 0) @:>@ ]])],
+       [[ int foo @<:@ 1 - 2 * !((($1) -1) < 0) @:>@ ; (void)foo[0] ]])],
      [ eval "ax_cv_decl_${typename}_signed=\"yes\"" ],
      [ eval "ax_cv_decl_${typename}_signed=\"no\"" ])
  ])
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
index 43087b2e6889ec6f8ebd2f8ba77f4a9a716f8ac2..2aef96dd5b02486763199f44323eb001fd0f930e 100644 (file)
@@ -595,8 +595,7 @@ namespace cxx17
 
   namespace test_constexpr_lambdas
   {
-
-    constexpr int foo = [](){return 42;}();
+    [[maybe_unused]] constexpr int foo = [](){return 42;}();
 
   }
 
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 936ee28d252b4da7c8c8132b5f47bdfb7b1e6722..30a4d917b276748312f203a001247b6c4777923e 100644 (file)
@@ -22,7 +22,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 m4_define([_BOOST_SERIAL], [m4_translit([
-# serial 37
+# serial 38
 ], [#
 ], [])])
 
@@ -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__ == 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" \
     "defined __clang__ && __clang_major__ == 11 && __clang_minor__ == 1 @ clang111" \
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 68e9be5ef5066aca18aba03a3d245e35ecf7b308..ae4b5b7c60e6963081e28fcafac9e2616da643ee 100644 (file)
@@ -19,10 +19,45 @@ dnl <http://www.gnu.org/licenses/>.
 dnl
 
 AC_DEFUN([AC_CC_D_FORTIFY_SOURCE],[
-      OLD_CXXFLAGS="$CXXFLAGS"
-      CXXFLAGS="-Wall -W -Werror $CXXFLAGS"
+  AC_ARG_ENABLE([fortify-source],
+    AS_HELP_STRING([--enable-fortify-source], [enable FORTIFY_SOURCE support @<:@default=2@:>@]),
+    [enable_fortify_source=$enableval],
+    [enable_fortify_source=2]
+  )
+
+  AS_IF([test "x$enable_fortify_source" != "xno"], [
+
+    dnl Auto means the highest version we support, which is currently 3
+    AS_IF([test "x$enable_fortify_source" == "xauto"],
+      [enable_fortify_source=3],
+      []
+    )
+
+    dnl If 3 is not supported, we try to fallback to 2
+    AS_IF([test "x$enable_fortify_source" == "x3"], [
+      gl_COMPILER_OPTION_IF([-D_FORTIFY_SOURCE=3], [
+        CFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 $CFLAGS"
+        CXXFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 $CXXFLAGS"
+      ], [enable_fortify_source=2])
+    ])
+
+    dnl If 2 is not supported, we try to fallback to 1
+    AS_IF([test "x$enable_fortify_source" == "x2"], [
       gl_COMPILER_OPTION_IF([-D_FORTIFY_SOURCE=2], [
         CFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 $CFLAGS"
-        CXXFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 $OLD_CXXFLAGS"
-      ], [CXXFLAGS="$OLD_CXXFLAGS"], [AC_LANG_PROGRAM([[#include <stdio.h>]],[])])
-]) 
+        CXXFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 $CXXFLAGS"
+      ], [enable_fortify_source=1])
+    ])
+
+    AS_IF([test "x$enable_fortify_source" == "x1"], [
+      gl_COMPILER_OPTION_IF([-D_FORTIFY_SOURCE=1], [
+        CFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1 $CFLAGS"
+        CXXFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1 $CXXFLAGS"
+      ], [enable_fortify_source=no])
+    ])
+
+  ])
+
+  AC_MSG_CHECKING([whether FORTIFY_SOURCE is supported])
+  AC_MSG_RESULT([$enable_fortify_source])
+])
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])
     ])
diff --git a/m4/pdns_enable_gss_tsig.m4 b/m4/pdns_enable_gss_tsig.m4
new file mode 100644 (file)
index 0000000..d080c5e
--- /dev/null
@@ -0,0 +1,24 @@
+AC_DEFUN([PDNS_ENABLE_GSS_TSIG],[
+  AC_MSG_CHECKING([whether to enable experimental GSS-TSIG support])
+  AC_ARG_ENABLE([experimental_gss_tsig],
+    AS_HELP_STRING([--enable-experimental-gss-tsig],
+      [enable experimental GSS-TSIG support @<:@default=no@:>@]
+    ),
+    [enable_experimental_gss_tsig=$enableval],
+    [enable_experimental_gss_tsig=no]
+  )
+
+  AC_MSG_RESULT([$enable_experimental_gss_tsig])
+
+  AM_CONDITIONAL([GSS_TSIG],[test "x$enable_experimental_gss_tsig" != "xno"])
+  AC_SUBST(GSS_TSIG)
+  AS_IF([test "x$enable_experimental_gss_tsig" != "xno"],
+   [PKG_CHECK_MODULES([GSS], [krb5 krb5-gssapi],
+      [
+        AC_DEFINE([ENABLE_GSS_TSIG], [1], [Define to 1 if you want to enable GSS-TSIG support])
+        GSS_TSIG=yes
+      ],
+      [AC_MSG_ERROR([Required libraries for GSS-TSIG not found])]
+   )],
+    [GSS_TSIG=no])
+])
diff --git a/m4/pdns_enable_lto.m4 b/m4/pdns_enable_lto.m4
new file mode 100644 (file)
index 0000000..a464231
--- /dev/null
@@ -0,0 +1,39 @@
+AC_DEFUN([PDNS_ENABLE_LTO],[
+  AC_ARG_ENABLE([lto],
+    AS_HELP_STRING([--enable-lto], [enable Link-Time Optimizations (LTO) support @<:@default=no@:>@]),
+    [enable_lto=$enableval],
+    [enable_lto=no]
+  )
+
+  AS_IF([test "x$enable_lto" != "xno"], [
+
+    dnl If thin is not supported, we try to fallback to auto
+    AS_IF([test "x$enable_lto" == "xthin"], [
+      gl_COMPILER_OPTION_IF([-flto=thin], [
+        CFLAGS="-flto=thin $CFLAGS"
+        CXXFLAGS="-flto=thin $CXXFLAGS"
+        LDFLAGS="-flto=thin $LDFLAGS"
+      ], [enable_lto=auto])
+    ])
+
+    dnl If auto is not supported, we try to fallback -flto
+    AS_IF([test "x$enable_lto" == "xauto"], [
+      gl_COMPILER_OPTION_IF([-flto=auto], [
+        CFLAGS="-flto=auto $CFLAGS"
+        CXXFLAGS="-flto=auto $CXXFLAGS"
+        LDFLAGS="-flto=auto $LDFLAGS"
+      ], [enable_lto=yes])
+    ])
+
+    AS_IF([test "x$enable_lto" == "xyes"], [
+      gl_COMPILER_OPTION_IF([-flto], [
+        CFLAGS="-flto $CFLAGS"
+        CXXFLAGS="-flto $CXXFLAGS"
+        LDFLAGS="-flto $LDFLAGS"
+      ], [enable_lto=no])
+    ])
+  ])
+
+  AC_MSG_CHECKING([whether link-time optimization is supported])
+  AC_MSG_RESULT([$enable_lto])
+])
diff --git a/m4/pdns_init_auto_vars.m4 b/m4/pdns_init_auto_vars.m4
new file mode 100644 (file)
index 0000000..cf93ffd
--- /dev/null
@@ -0,0 +1,31 @@
+dnl
+dnl Check for support for enabling initialization of automatic variables
+dnl
+
+AC_DEFUN([PDNS_INIT_AUTO_VARS],[
+  AC_MSG_CHECKING([whether to enable initialization of automatic variables])
+  AC_ARG_ENABLE([auto-var-init],
+    AS_HELP_STRING([--enable-auto-var-init],[enable initialization of automatic variables (zero, pattern) @<:@default=no@:>@]),
+    [enable_initautovars=$enableval],
+    [enable_initautovars=no],
+  )
+  AC_MSG_RESULT([$enable_initautovars])
+
+  AS_IF([test "x$enable_initautovars" = "xyes"], [
+    [enable_initautovars=zero]
+  ])
+
+  AS_IF([test "x$enable_initautovars" = "xzero" ], [
+    gl_COMPILER_OPTION_IF([-ftrivial-auto-var-init=zero], [
+      CFLAGS="-ftrivial-auto-var-init=zero $CFLAGS"
+      CXXFLAGS="-ftrivial-auto-var-init=zero $CXXFLAGS"
+    ])
+  ])
+
+  AS_IF([test "x$enable_initautovars" = "xpattern" ], [
+    gl_COMPILER_OPTION_IF([-ftrivial-auto-var-init=pattern], [
+      CFLAGS="-ftrivial-auto-var-init=pattern $CFLAGS"
+      CXXFLAGS="-ftrivial-auto-var-init=pattern $CXXFLAGS"
+    ])
+  ])
+])
index 9710366021a852917bdd0140cd3bc875590c8286..0c1bef3c25ecbf3babb44ff2725c7cb2c6e99702 100644 (file)
@@ -19,5 +19,40 @@ AC_DEFUN([PDNS_WITH_LIBDECAF],[
         AC_MSG_ERROR([Could not find libdecaf])
     ])
     LIBS="$save_LIBS"
+
+    AS_IF([test "x$LIBDECAF_CFLAGS" = "x"],[
+      AC_MSG_CHECKING([for libdecaf headers])
+      libdecaf_header_dir=""
+
+      header_dirs="/usr /usr/local"
+      for header_dir in $header_dirs; do
+        if test -f "$header_dir/include/decaf.hxx"; then
+          libdecaf_header_dir="$header_dir/include"
+          break
+        fi
+
+        if test -f "$header_dir/include/decaf/decaf.hxx"; then
+          libdecaf_header_dir="$header_dir/include/decaf"
+          break
+        fi
+      done
+
+      AS_IF([test "x$libdecaf_header_dir" != "x"],[
+          AC_MSG_RESULT([$libdecaf_header_dir])
+          LIBDECAF_CFLAGS="-I$libdecaf_header_dir"
+        ],
+        [AC_MSG_RESULT([not found])])
+    ])
+
+    AC_SUBST([LIBDECAF_CFLAGS])
+
+    save_CXXFLAGS="$CXXFLAGS"
+    CXXFLAGS="$CXXFLAGS $LIBDECAF_CFLAGS"
+    AC_CHECK_HEADERS(
+      [decaf.hxx],
+      [],
+      [AC_MSG_ERROR([cannot find libdecaf headers])]
+    )
+    CXXFLAGS="$save_CXXFLAGS"
   ])
 ])
index d4cfcfac6881ce1bdb06fa48253981c0307fe9d3..19102b6cf19fcb971d8d5d71732283eba841d23d 100644 (file)
@@ -1,15 +1,14 @@
 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])
 
   AS_IF([test "x$with_net_snmp" != "xno"], [
     AS_IF([test "x$with_net_snmp" = "xyes" -o "x$with_net_snmp" = "xauto"], [
-      AC_CHECK_PROG([NET_SNMP_CFLAGS], [net-snmp-config], [`net-snmp-config --cflags`])
       AC_CHECK_PROG([NET_SNMP_LIBS], [net-snmp-config], [`net-snmp-config --netsnmp-agent-libs`])
       AC_CHECK_DECLS([snmp_select_info2], [
           AC_DEFINE([HAVE_SNMP_SELECT_INFO2], [1], [define to 1 if snmp_select_info2 is available.])
index 66dc62cdeb80c390083f8e8eb73d52e92e6b45c6..faa53589ca9d931f15609c3f9f20a65be1bb2f79 100644 (file)
@@ -193,6 +193,12 @@ AC_DEFUN([AX_CHECK_SYSTEMD_FEATURES], [
               if test $_systemd_version -ge 245; then
                  systemd_protect_clock=y
               fi
+              if test $_systemd_version -ge 247; then
+                 systemd_protect_proc=y
+              fi
+              if test $_systemd_version -ge 248; then
+                 systemd_private_ipc=y
+              fi
           ])
         ])
         AM_CONDITIONAL([HAVE_SYSTEMD_DYNAMIC_USER], [ test x"$systemd_dynamic_user" = "xy" ])
@@ -200,6 +206,7 @@ AC_DEFUN([AX_CHECK_SYSTEMD_FEATURES], [
         AM_CONDITIONAL([HAVE_SYSTEMD_MEMORY_DENY_WRITE_EXECUTE], [ test x"$systemd_memory_deny_write_execute" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_PERCENT_T], [ test x"$systemd_percent_t" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_PRIVATE_DEVICES], [ test x"$systemd_private_devices" = "xy" ])
+        AM_CONDITIONAL([HAVE_SYSTEMD_PRIVATE_IPC], [ test x"$systemd_private_ipc" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_PRIVATE_MOUNTS], [ test x"$systemd_private_mounts" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_PRIVATE_TMP], [ test x"$systemd_private_tmp" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_PRIVATE_USERS], [ test x"$systemd_private_users" = "xy" ])
@@ -210,6 +217,7 @@ AC_DEFUN([AX_CHECK_SYSTEMD_FEATURES], [
         AM_CONDITIONAL([HAVE_SYSTEMD_PROTECT_KERNEL_LOGS], [ test x"$systemd_protect_kernel_logs" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_PROTECT_KERNEL_MODULES], [ test x"$systemd_protect_kernel_modules" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_PROTECT_KERNEL_TUNABLES], [ test x"$systemd_protect_kernel_tunables" = "xy" ])
+        AM_CONDITIONAL([HAVE_SYSTEMD_PROTECT_PROC], [ test x"$systemd_protect_proc" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_PROTECT_SYSTEM], [ test x"$systemd_protect_system" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_PROTECT_SYSTEM_STRICT], [ test x"$systemd_protect_system_strict" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_REMOVE_IPC], [ test x"$systemd_remove_ipc" = "xy" ])
index 59916b05916ade216a4d676492f187fba339e56e..7906e2078cb90d8c163975cd67eee3a94725083e 100644 (file)
@@ -1,5 +1,5 @@
 pkglib_LTLIBRARIES = libbindbackend.la
-AM_CPPFLAGS += -I../../pdns
+AM_CPPFLAGS += -I../../pdns $(LIBCRYPTO_INCLUDES)
 
 AM_LFLAGS = -i
 AM_YFLAGS = -d --verbose --debug
@@ -19,7 +19,3 @@ libbindbackend_la_SOURCES = \
        binddnssec.cc
 
 libbindbackend_la_LDFLAGS = -module -avoid-version
-
-# for bindparser.h/hh
-.hh.h:
-       cp $< $@
index 43e2034999fd08ea258a0724a6c7c32fe0934c26..013c9eb8ac7e7fb953c956ad4ab0a51d6965ff21 100644 (file)
@@ -23,7 +23,7 @@
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
-#include <errno.h>
+#include <cerrno>
 #include <string>
 #include <set>
 #include <sys/types.h>
@@ -56,7 +56,7 @@
 #include "pdns/auth-zonecache.hh"
 #include "pdns/auth-caches.hh"
 
-/* 
+/*
    All instances of this backend share one s_state, which is indexed by zone name and zone id.
    The s_state is protected by a read/write lock, and the goal it to only interact with it briefly.
    When a query comes in, we take a read lock and COPY the best zone to answer from s_state (BB2DomainInfo object)
@@ -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;
 
@@ -145,7 +145,7 @@ bool Bind2Backend::safeGetBBDomainInfo(int id, BB2DomainInfo* bbd)
 bool Bind2Backend::safeGetBBDomainInfo(const DNSName& name, BB2DomainInfo* bbd)
 {
   auto state = s_state.read_lock();
-  auto& nameindex = boost::multi_index::get<NameTag>(*state);
+  const auto& nameindex = boost::multi_index::get<NameTag>(*state);
   auto iter = nameindex.find(name);
   if (iter == nameindex.end()) {
     return false;
@@ -157,7 +157,7 @@ bool Bind2Backend::safeGetBBDomainInfo(const DNSName& name, BB2DomainInfo* bbd)
 bool Bind2Backend::safeRemoveBBDomainInfo(const DNSName& name)
 {
   auto state = s_state.write_lock();
-  typedef state_t::index<NameTag>::type nameindex_t;
+  using nameindex_t = state_t::index<NameTag>::type;
   nameindex_t& nameindex = boost::multi_index::get<NameTag>(*state);
 
   nameindex_t::iterator iter = nameindex.find(name);
@@ -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;
   }
@@ -275,7 +275,7 @@ bool Bind2Backend::abortTransaction()
   return true;
 }
 
-bool Bind2Backend::feedRecord(const DNSResourceRecord& rr, const DNSName& ordername, bool ordernameIsNSEC3)
+bool Bind2Backend::feedRecord(const DNSResourceRecord& rr, const DNSName& /* ordername */, bool /* ordernameIsNSEC3 */)
 {
   if (d_transaction_id < 1) {
     throw DBException("Bind2Backend::feedRecord() called outside of transaction");
@@ -298,7 +298,7 @@ bool Bind2Backend::feedRecord(const DNSResourceRecord& rr, const DNSName& ordern
     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& ordern
   return true;
 }
 
-void Bind2Backend::getUpdatedMasters(vector<DomainInfo>* changedDomains)
+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)
       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));
     }
   }
@@ -356,13 +356,13 @@ void Bind2Backend::getUpdatedMasters(vector<DomainInfo>* changedDomains)
       }
       if (di.notified_serial) { // don't do notification storm on startup
         di.serial = soadata.serial;
-        changedDomains->push_back(std::move(di));
+        changedDomains.push_back(std::move(di));
       }
     }
   }
 }
 
-void Bind2Backend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool include_disabled)
+void Bind2Backend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool /* include_disabled */)
 {
   SOAData soadata;
 
@@ -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;
@@ -491,7 +492,7 @@ void Bind2Backend::alsoNotifies(const DNSName& domain, set<string>* ips)
 void Bind2Backend::parseZoneFile(BB2DomainInfo* bbd)
 {
   NSEC3PARAMRecordContent ns3pr;
-  bool nsec3zone;
+  bool nsec3zone = false;
   if (d_hybrid) {
     DNSSECKeeper dk;
     nsec3zone = dk.getNSEC3PARAM(bbd->d_name, &ns3pr);
@@ -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
@@ -553,7 +554,7 @@ void Bind2Backend::insertRecord(std::shared_ptr<recordstorage_t>& records, const
   bdr.content = content;
   bdr.nsec3hash = hashed;
 
-  if (auth) // Set auth on empty non-terminals
+  if (auth != nullptr) // Set auth on empty non-terminals
     bdr.auth = *auth;
   else
     bdr.auth = true;
@@ -562,11 +563,11 @@ void Bind2Backend::insertRecord(std::shared_ptr<recordstorage_t>& records, const
   records->insert(std::move(bdr));
 }
 
-string Bind2Backend::DLReloadNowHandler(const vector<string>& parts, Utility::pid_t ppid)
+string Bind2Backend::DLReloadNowHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
 {
   ostringstream ret;
 
-  for (vector<string>::const_iterator i = parts.begin() + 1; i < parts.end(); ++i) {
+  for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
     BB2DomainInfo bbd;
     DNSName zone(*i);
     if (safeGetBBDomainInfo(zone, &bbd)) {
@@ -587,12 +588,12 @@ string Bind2Backend::DLReloadNowHandler(const vector<string>& parts, Utility::pi
   return ret.str();
 }
 
-string Bind2Backend::DLDomStatusHandler(const vector<string>& parts, Utility::pid_t ppid)
+string Bind2Backend::DLDomStatusHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
 {
   ostringstream ret;
 
   if (parts.size() > 1) {
-    for (vector<string>::const_iterator i = parts.begin() + 1; i < parts.end(); ++i) {
+    for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
       BB2DomainInfo bbd;
       if (safeGetBBDomainInfo(DNSName(*i), &bbd)) {
         ret << *i << ": " << (bbd.d_loaded ? "" : "[rejected]") << "\t" << bbd.d_status << "\n";
@@ -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) {
@@ -649,12 +650,12 @@ static void printDomainExtendedStatus(ostringstream& ret, const BB2DomainInfo& i
   ret << "\t Last notified: " << info.d_lastnotified << std::endl;
 }
 
-string Bind2Backend::DLDomExtendedStatusHandler(const vector<string>& parts, Utility::pid_t ppid)
+string Bind2Backend::DLDomExtendedStatusHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
 {
   ostringstream ret;
 
   if (parts.size() > 1) {
-    for (vector<string>::const_iterator i = parts.begin() + 1; i < parts.end(); ++i) {
+    for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
       BB2DomainInfo bbd;
       if (safeGetBBDomainInfo(DNSName(*i), &bbd)) {
         printDomainExtendedStatus(ret, bbd);
@@ -678,7 +679,7 @@ string Bind2Backend::DLDomExtendedStatusHandler(const vector<string>& parts, Uti
   return ret.str();
 }
 
-string Bind2Backend::DLListRejectsHandler(const vector<string>& parts, Utility::pid_t ppid)
+string Bind2Backend::DLListRejectsHandler(const vector<string>& /* parts */, Utility::pid_t /* ppid */)
 {
   ostringstream ret;
   auto rstate = s_state.read_lock();
@@ -689,7 +690,7 @@ string Bind2Backend::DLListRejectsHandler(const vector<string>& parts, Utility::
   return ret.str();
 }
 
-string Bind2Backend::DLAddDomainHandler(const vector<string>& parts, Utility::pid_t ppid)
+string Bind2Backend::DLAddDomainHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
 {
   if (parts.size() < 3)
     return "ERROR: Domain name and zone filename are required";
@@ -758,7 +759,7 @@ Bind2Backend::Bind2Backend(const string& suffix, bool loadZones)
   std::lock_guard<std::mutex> l(s_startup_lock);
 
   setupDNSSEC();
-  if (!s_first) {
+  if (s_first == 0) {
     return;
   }
 
@@ -811,16 +812,16 @@ void Bind2Backend::fixupOrderAndAuth(std::shared_ptr<recordstorage_t>& records,
 
     if (!iter->qname.isRoot() && shorter.chopOff() && !iter->qname.isRoot()) {
       do {
-        if (nssets.count(shorter)) {
+        if (nssets.count(shorter) != 0u) {
           skip = true;
           break;
         }
       } while (shorter.chopOff() && !iter->qname.isRoot());
     }
 
-    iter->auth = (!skip && (iter->qtype == QType::DS || iter->qtype == QType::RRSIG || !nssets.count(iter->qname)));
+    iter->auth = (!skip && (iter->qtype == QType::DS || iter->qtype == QType::RRSIG || (nssets.count(iter->qname) == 0u)));
 
-    if (!skip && nsec3zone && iter->qtype != QType::RRSIG && (iter->auth || (iter->qtype == QType::NS && !ns3pr.d_flags) || dssets.count(iter->qname))) {
+    if (!skip && nsec3zone && iter->qtype != QType::RRSIG && (iter->auth || (iter->qtype == QType::NS && (ns3pr.d_flags == 0u)) || (dssets.count(iter->qname) != 0u))) {
       Bind2DNSRecord bdr = *iter;
       bdr.nsec3hash = toBase32Hex(hashQNameWithSalt(ns3pr, bdr.qname + zoneName));
       records->replace(iter, bdr);
@@ -832,7 +833,7 @@ void Bind2Backend::fixupOrderAndAuth(std::shared_ptr<recordstorage_t>& records,
 
 void Bind2Backend::doEmptyNonTerminals(std::shared_ptr<recordstorage_t>& records, const DNSName& zoneName, bool nsec3zone, const NSEC3PARAMRecordContent& ns3pr)
 {
-  bool auth;
+  bool auth = false;
   DNSName shorter;
   std::unordered_set<DNSName> qnames;
   std::unordered_map<DNSName, bool> nonterm;
@@ -845,19 +846,19 @@ void Bind2Backend::doEmptyNonTerminals(std::shared_ptr<recordstorage_t>& records
   for (const auto& bdr : *records) {
 
     if (!bdr.auth && bdr.qtype == QType::NS)
-      auth = (!nsec3zone || !ns3pr.d_flags);
+      auth = (!nsec3zone || (ns3pr.d_flags == 0u));
     else
       auth = bdr.auth;
 
     shorter = bdr.qname;
     while (shorter.chopOff()) {
-      if (!qnames.count(shorter)) {
+      if (qnames.count(shorter) == 0u) {
         if (!(maxent)) {
           g_log << Logger::Error << "Zone '" << zoneName << "' has too many empty non terminals." << endl;
           return;
         }
 
-        if (!nonterm.count(shorter)) {
+        if (nonterm.count(shorter) == 0u) {
           nonterm.emplace(shorter, auth);
           --maxent;
         }
@@ -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;
 
@@ -931,9 +932,10 @@ void Bind2Backend::loadConfig(string* status)
         continue;
       }
 
-      if (domain.type == "")
+      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 != "") {
+      }
+      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;
@@ -953,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;
@@ -978,7 +982,7 @@ void Bind2Backend::loadConfig(string* status)
           ostringstream msg;
           msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.reason;
 
-          if (status)
+          if (status != nullptr)
             *status += msg.str();
           bbd.d_status = msg.str();
 
@@ -988,11 +992,11 @@ 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();
 
-          if (status)
+          if (status != nullptr)
             *status += msg.str();
           bbd.d_status = msg.str();
           g_log << Logger::Warning << d_logprefix << msg.str() << endl;
@@ -1002,7 +1006,7 @@ void Bind2Backend::loadConfig(string* status)
           ostringstream msg;
           msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.what();
 
-          if (status)
+          if (status != nullptr)
             *status += msg.str();
           bbd.d_status = msg.str();
 
@@ -1031,7 +1035,7 @@ void Bind2Backend::loadConfig(string* status)
 
     ostringstream msg;
     msg << " Done parsing domains, " << rejected << " rejected, " << newdomains << " new, " << remdomains << " removed";
-    if (status)
+    if (status != nullptr)
       *status = msg.str();
 
     g_log << Logger::Error << d_logprefix << msg.str() << endl;
@@ -1074,7 +1078,7 @@ void Bind2Backend::queueReloadAndStore(unsigned int id)
   }
 }
 
-bool Bind2Backend::findBeforeAndAfterUnhashed(std::shared_ptr<const recordstorage_t>& records, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
+bool Bind2Backend::findBeforeAndAfterUnhashed(std::shared_ptr<const recordstorage_t>& records, const DNSName& qname, DNSName& /* unhashed */, DNSName& before, DNSName& after)
 {
   // for(const auto& record: *records)
   //   cerr<<record.qname<<"\t"<<makeHexDump(record.qname.toDNSString())<<endl;
@@ -1117,7 +1121,7 @@ bool Bind2Backend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qn
     return findBeforeAndAfterUnhashed(records, qname, unhashed, before, after);
   }
   else {
-    auto& hashindex = boost::multi_index::get<NSEC3Tag>(*records);
+    const auto& hashindex = boost::multi_index::get<NSEC3Tag>(*records);
 
     // for(auto iter = first; iter != hashindex.end(); iter++)
     //  cerr<<iter->nsec3hash<<endl;
@@ -1144,13 +1148,13 @@ bool Bind2Backend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qn
   }
 }
 
-void Bind2Backend::lookup(const QType& qtype, const DNSName& qname, int zoneId, DNSPacket* pkt_p)
+void Bind2Backend::lookup(const QType& qtype, const DNSName& qname, int zoneId, DNSPacket* /* pkt_p */)
 {
   d_handle.reset();
 
   static bool mustlog = ::arg().mustDo("query-logging");
 
-  bool found;
+  bool found = false;
   DNSName domain;
   BB2DomainInfo bbd;
 
@@ -1193,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();
@@ -1203,20 +1207,12 @@ void Bind2Backend::lookup(const QType& qtype, const DNSName& qname, int zoneId,
 
   d_handle.mustlog = mustlog;
 
-  auto& hashedidx = boost::multi_index::get<UnorderedNameTag>(*d_handle.d_records);
+  const auto& hashedidx = boost::multi_index::get<UnorderedNameTag>(*d_handle.d_records);
   auto range = hashedidx.equal_range(d_handle.qname);
 
-  if (range.first == range.second) {
-    d_handle.d_list = false;
-    d_handle.d_iter = d_handle.d_end_iter = range.first;
-    return;
-  }
-  else {
-    d_handle.d_iter = range.first;
-    d_handle.d_end_iter = range.second;
-  }
-
   d_handle.d_list = false;
+  d_handle.d_iter = range.first;
+  d_handle.d_end_iter = range.second;
 }
 
 Bind2Backend::handle::handle()
@@ -1294,7 +1290,7 @@ bool Bind2Backend::handle::get_normal(DNSResourceRecord& r)
   return true;
 }
 
-bool Bind2Backend::list(const DNSName& target, int id, bool include_disabled)
+bool Bind2Backend::list(const DNSName& /* target */, int id, bool /* include_disabled */)
 {
   BB2DomainInfo bbd;
 
@@ -1335,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;
   }
 
@@ -1348,7 +1344,7 @@ bool Bind2Backend::autoPrimariesList(std::vector<AutoPrimary>& primaries)
   while (getline(c_if, line)) {
     std::istringstream ii(line);
     ii >> sip;
-    if (sip.size() != 0) {
+    if (!sip.empty()) {
       ii >> saccount;
       primaries.emplace_back(sip, "", saccount);
     }
@@ -1358,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;
   }
 
@@ -1386,7 +1382,7 @@ bool Bind2Backend::superMasterBackend(const string& ip, const DNSName& domain, c
   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();
@@ -1417,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");
@@ -1475,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;
@@ -1505,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");
@@ -1528,7 +1524,7 @@ public:
 private:
   void assertEmptySuffix(const string& suffix)
   {
-    if (suffix.length())
+    if (!suffix.empty())
       throw PDNSException("launch= suffixes are not supported on the bindbackend");
   }
 };
index dc461e9a3be4dffb74d18c1148c9afec82a7cf5b..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) 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;
@@ -218,7 +216,7 @@ public:
   bool deactivateDomainKey(const DNSName& name, unsigned int id) override;
   bool publishDomainKey(const DNSName& name, unsigned int id) override;
   bool unpublishDomainKey(const DNSName& name, unsigned int id) override;
-  bool getTSIGKey(const DNSName& name, DNSName* algorithm, string* content) override;
+  bool getTSIGKey(const DNSName& name, DNSName& algorithm, string& content) override;
   bool setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content) override;
   bool deleteTSIGKey(const DNSName& name) override;
   bool getTSIGKeys(std::vector<struct TSIGKey>& keys) 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 8e56319538ef47f25ee3d2c19509b3fbdff756ce..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) {
@@ -209,7 +208,7 @@ bool Bind2Backend::getNSEC3PARAM(const DNSName& name, NSEC3PARAMRecordContent* n
   if (!safeGetBBDomainInfo(name, &bbd))
     return false;
 
-  if (ns3p) {
+  if (ns3p != nullptr) {
     *ns3p = bbd.d_nsec3param;
   }
 
@@ -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) {
@@ -440,7 +439,7 @@ bool Bind2Backend::unpublishDomainKey(const DNSName& name, unsigned int id)
   return true;
 }
 
-bool Bind2Backend::getTSIGKey(const DNSName& name, DNSName* algorithm, string* content)
+bool Bind2Backend::getTSIGKey(const DNSName& name, DNSName& algorithm, string& content)
 {
   if (!d_dnssecdb || d_hybrid)
     return false;
@@ -449,12 +448,11 @@ bool Bind2Backend::getTSIGKey(const DNSName& name, DNSName* algorithm, string* c
     d_getTSIGKeyQuery_stmt->bind("key_name", name)->execute();
 
     SSqlStatement::row_t row;
-    content->clear();
     while (d_getTSIGKeyQuery_stmt->hasNextRow()) {
       d_getTSIGKeyQuery_stmt->nextRow(row);
-      if (row.size() >= 2 && (algorithm->empty() || *algorithm == DNSName(row[0]))) {
-        *algorithm = DNSName(row[0]);
-        *content = row[1];
+      if (row.size() >= 2 && (algorithm.empty() || algorithm == DNSName(row[0]))) {
+        algorithm = DNSName(row[0]);
+        content = row[1];
       }
     }
 
@@ -463,7 +461,7 @@ bool Bind2Backend::getTSIGKey(const DNSName& name, DNSName* algorithm, string* c
   catch (SSqlException& e) {
     throw PDNSException("Error accessing DNSSEC database in BIND backend, getTSIGKey(): " + e.txtReason());
   }
-  return !content->empty();
+  return true;
 }
 
 bool Bind2Backend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content)
@@ -517,7 +515,7 @@ bool Bind2Backend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
   catch (SSqlException& e) {
     throw PDNSException("Error accessing DNSSEC database in BIND backend, getTSIGKeys(): " + e.txtReason());
   }
-  return !keys.empty();
+  return true;
 }
 
 #endif
index a11b31ab2460b5eb1969fa9a0151349e588d2632..f9622206ff86bf137e2ab0b07d9df1893e75d47d 100644 (file)
@@ -1,4 +1,4 @@
-AM_CPPFLAGS += $(YAML_CFLAGS) $(GEOIP_CFLAGS) $(MMDB_CFLAGS)
+AM_CPPFLAGS += $(YAML_CFLAGS) $(GEOIP_CFLAGS) $(MMDB_CFLAGS) $(LIBCRYPTO_INCLUDES)
 
 EXTRA_DIST = OBJECTFILES OBJECTLIBS
 
index 078e7a893d95e9d5cf167369bdbf70d2bfc69acf..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;
@@ -62,8 +68,8 @@ struct GeoIPDomain
 static vector<GeoIPDomain> s_domains;
 static int s_rc = 0; // refcount - always accessed under lock
 
-static string GeoIP_WEEKDAYS[] = {"mon", "tue", "wed", "thu", "fri", "sat", "sun"};
-static string GeoIP_MONTHS[] = {"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"};
+const static std::array<string, 7> GeoIP_WEEKDAYS = {"mon", "tue", "wed", "thu", "fri", "sat", "sun"};
+const static std::array<string, 12> GeoIP_MONTHS = {"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"};
 
 /* So how does it work - we have static records and services. Static records "win".
    We also insert empty non terminals for records and services.
@@ -76,16 +82,14 @@ static string GeoIP_MONTHS[] = {"jan", "feb", "mar", "apr", "may", "jun", "jul",
 
 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 == NULL) {
+  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();
@@ -454,7 +512,7 @@ void GeoIPBackend::lookup(const QType& qtype, const DNSName& qdomain, int zoneId
   }
 
   Netmask addr{"0.0.0.0/0"};
-  if (pkt_p != NULL)
+  if (pkt_p != nullptr)
     addr = Netmask(pkt_p->getRealRemote());
 
   gl.netmask = 0;
@@ -466,7 +524,7 @@ void GeoIPBackend::lookup(const QType& qtype, const DNSName& qdomain, int zoneId
     return; // no hit
 
   const NetmaskTree<vector<string>>::node_type* node = target->second.masks.lookup(addr);
-  if (node == NULL)
+  if (node == nullptr)
     return; // no hit, again.
 
   DNSName sformat;
@@ -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;
   }
@@ -649,13 +708,13 @@ string GeoIPBackend::format2str(string sformat, const Netmask& addr, GeoIPNetmas
   string::size_type cur, last;
   boost::optional<int> alt, prec;
   double lat, lon;
-  time_t t = time((time_t*)NULL);
+  time_t t = time(nullptr);
   GeoIPNetmask tmp_gl; // largest wins
   struct tm gtm;
   gmtime_r(&t, &gtm);
   last = 0;
 
-  while ((cur = sformat.find("%", last)) != string::npos) {
+  while ((cur = sformat.find('%', last)) != string::npos) {
     string rep;
     int nrep = 3;
     tmp_gl.netmask = 0;
@@ -757,12 +816,12 @@ string GeoIPBackend::format2str(string sformat, const Netmask& addr, GeoIPNetmas
     }
     else if (!sformat.compare(cur, 4, "%wds")) {
       nrep = 4;
-      rep = GeoIP_WEEKDAYS[gtm.tm_wday];
+      rep = GeoIP_WEEKDAYS.at(gtm.tm_wday);
       tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
     }
     else if (!sformat.compare(cur, 4, "%mos")) {
       nrep = 4;
-      rep = GeoIP_MONTHS[gtm.tm_mon];
+      rep = GeoIP_MONTHS.at(gtm.tm_mon);
       tmp_gl.netmask = (addr.isIPv6() ? 128 : 32);
     }
     else if (!sformat.compare(cur, 3, "%wd")) {
@@ -827,12 +886,12 @@ void GeoIPBackend::reload()
   }
 }
 
-void GeoIPBackend::rediscover(string* status)
+void GeoIPBackend::rediscover(string* /* status */)
 {
   reload();
 }
 
-bool GeoIPBackend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool getSerial)
+bool GeoIPBackend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool /* getSerial */)
 {
   ReadLock rl(&s_state_lock);
 
@@ -851,7 +910,7 @@ bool GeoIPBackend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool get
   return false;
 }
 
-void GeoIPBackend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool include_disabled)
+void GeoIPBackend::getAllDomains(vector<DomainInfo>* domains, bool /* getSerial */, bool /* include_disabled */)
 {
   ReadLock rl(&s_state_lock);
 
@@ -919,7 +978,7 @@ bool GeoIPBackend::getDomainKeys(const DNSName& name, std::vector<DNSBackend::Ke
       ostringstream pathname;
       pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
       glob_t glob_result;
-      if (glob(pathname.str().c_str(), GLOB_ERR, NULL, &glob_result) == 0) {
+      if (glob(pathname.str().c_str(), GLOB_ERR, nullptr, &glob_result) == 0) {
         for (size_t i = 0; i < glob_result.gl_pathc; i++) {
           if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
             DNSBackend::KeyData kd;
@@ -965,7 +1024,7 @@ bool GeoIPBackend::removeDomainKey(const DNSName& name, unsigned int id)
       ostringstream pathname;
       pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
       glob_t glob_result;
-      if (glob(pathname.str().c_str(), GLOB_ERR, NULL, &glob_result) == 0) {
+      if (glob(pathname.str().c_str(), GLOB_ERR, nullptr, &glob_result) == 0) {
         for (size_t i = 0; i < glob_result.gl_pathc; i++) {
           if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
             auto kid = pdns::checked_stoi<unsigned int>(glob_result.gl_pathv[i] + regm[3].rm_so);
@@ -1001,7 +1060,7 @@ bool GeoIPBackend::addDomainKey(const DNSName& name, const KeyData& key, int64_t
       ostringstream pathname;
       pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
       glob_t glob_result;
-      if (glob(pathname.str().c_str(), GLOB_ERR, NULL, &glob_result) == 0) {
+      if (glob(pathname.str().c_str(), GLOB_ERR, nullptr, &glob_result) == 0) {
         for (size_t i = 0; i < glob_result.gl_pathc; i++) {
           if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
             auto kid = pdns::checked_stoi<unsigned int>(glob_result.gl_pathv[i] + regm[3].rm_so);
@@ -1037,7 +1096,7 @@ bool GeoIPBackend::activateDomainKey(const DNSName& name, unsigned int id)
       ostringstream pathname;
       pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
       glob_t glob_result;
-      if (glob(pathname.str().c_str(), GLOB_ERR, NULL, &glob_result) == 0) {
+      if (glob(pathname.str().c_str(), GLOB_ERR, nullptr, &glob_result) == 0) {
         for (size_t i = 0; i < glob_result.gl_pathc; i++) {
           if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
             auto kid = pdns::checked_stoi<unsigned int>(glob_result.gl_pathv[i] + regm[3].rm_so);
@@ -1072,7 +1131,7 @@ bool GeoIPBackend::deactivateDomainKey(const DNSName& name, unsigned int id)
       ostringstream pathname;
       pathname << getArg("dnssec-keydir") << "/" << dom.domain.toStringNoDot() << "*.key";
       glob_t glob_result;
-      if (glob(pathname.str().c_str(), GLOB_ERR, NULL, &glob_result) == 0) {
+      if (glob(pathname.str().c_str(), GLOB_ERR, nullptr, &glob_result) == 0) {
         for (size_t i = 0; i < glob_result.gl_pathc; i++) {
           if (regexec(&reg, glob_result.gl_pathv[i], 5, regm, 0) == 0) {
             auto kid = pdns::checked_stoi<unsigned int>(glob_result.gl_pathv[i] + regm[3].rm_so);
@@ -1094,12 +1153,12 @@ bool GeoIPBackend::deactivateDomainKey(const DNSName& name, unsigned int id)
   return false;
 }
 
-bool GeoIPBackend::publishDomainKey(const DNSName& name, unsigned int id)
+bool GeoIPBackend::publishDomainKey(const DNSName& /* name */, unsigned int /* id */)
 {
   return false;
 }
 
-bool GeoIPBackend::unpublishDomainKey(const DNSName& name, unsigned int id)
+bool GeoIPBackend::unpublishDomainKey(const DNSName& /* name */, unsigned int /* id */)
 {
   return false;
 }
@@ -1109,7 +1168,7 @@ bool GeoIPBackend::hasDNSSECkey(const DNSName& name)
   ostringstream pathname;
   pathname << getArg("dnssec-keydir") << "/" << name.toStringNoDot() << "*.key";
   glob_t glob_result;
-  if (glob(pathname.str().c_str(), GLOB_ERR, NULL, &glob_result) == 0) {
+  if (glob(pathname.str().c_str(), GLOB_ERR, nullptr, &glob_result) == 0) {
     globfree(&glob_result);
     return true;
   }
index 470da9bdb03e033c2b94dbd0ff85b67c320ad7e8..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,13 +53,13 @@ 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
+  bool list(const DNSName& /* target */, int /* domain_id */, bool /* include_disabled */ = false) override { return false; } // not supported
   bool get(DNSResourceRecord& r) override;
   void reload() override;
-  void rediscover(string* status = 0) override;
+  void rediscover(string* status = nullptr) override;
   bool getDomainInfo(const DNSName& domain, DomainInfo& di, bool getSerial = true) override;
   void getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool include_disabled) override;
 
@@ -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 75b11ccc58f8f843933941bc77e878a323bd86e6..61c98a6bb249684fb444b954c5eca85803ad309c 100644 (file)
@@ -433,7 +433,7 @@ public:
 
   bool queryLocationV6(GeoIPNetmask& gl, const string& ip,
                        double& latitude, double& longitude,
-                       boost::optional<int>& alt, boost::optional<int>& prec) override
+                       boost::optional<int>& /* alt */, boost::optional<int>& /* prec */) override
   {
     if (d_db_type == GEOIP_REGION_EDITION_REV0 || d_db_type == GEOIP_REGION_EDITION_REV1 || d_db_type == GEOIP_CITY_EDITION_REV0_V6 || d_db_type == GEOIP_CITY_EDITION_REV1_V6) {
       std::unique_ptr<GeoIPRecord, geoiprecord_deleter> gir(GeoIP_record_by_addr_v6(d_gi.get(), ip.c_str()));
@@ -449,7 +449,7 @@ public:
 
   bool queryLocation(GeoIPNetmask& gl, const string& ip,
                      double& latitude, double& longitude,
-                     boost::optional<int>& alt, boost::optional<int>& prec) override
+                     boost::optional<int>& /* alt */, boost::optional<int>& /* prec */) override
   {
     if (d_db_type == GEOIP_REGION_EDITION_REV0 || d_db_type == GEOIP_REGION_EDITION_REV1 || d_db_type == GEOIP_CITY_EDITION_REV0 || d_db_type == GEOIP_CITY_EDITION_REV1) {
       std::unique_ptr<GeoIPRecord, geoiprecord_deleter> gir(GeoIP_record_by_addr(d_gi.get(), ip.c_str()));
@@ -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 969d5abc5293b4987cfed9d3b7bf1cbcc6eca885..b3f08fb1cc985cb177f9fdefa67599cb0ce1e119 100644 (file)
@@ -208,7 +208,7 @@ public:
 
   bool queryLocation(GeoIPNetmask& gl, const string& ip,
                      double& latitude, double& longitude,
-                     boost::optional<int>& alt, boost::optional<int>& prec) override
+                     boost::optional<int>& /* alt */, boost::optional<int>& prec) override
   {
     MMDB_entry_data_s data;
     MMDB_lookup_result_s res;
@@ -228,7 +228,7 @@ public:
 
   bool queryLocationV6(GeoIPNetmask& gl, const string& ip,
                        double& latitude, double& longitude,
-                       boost::optional<int>& alt, boost::optional<int>& prec) override
+                       boost::optional<int>& /* alt */, boost::optional<int>& prec) override
   {
     MMDB_entry_data_s data;
     MMDB_lookup_result_s res;
@@ -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 2ef58894c49f23805e26086c78c5da6b43d74736..fc6cc78260e5c1edf28e13347f532179178dd2f3 100644 (file)
Binary files a/modules/geoipbackend/regression-tests/GeoLiteCity.mmdb and b/modules/geoipbackend/regression-tests/GeoLiteCity.mmdb differ
index b1cfab9fdcccbe30f0e37f0bb40d180a3fa3aed9..fb11a75df3b51020178ac769738e4c8f5b3c433f 100644 (file)
@@ -1,3 +1,3 @@
-0      geo.example.com.        IN      A       30      127.0.0.1
+0      geo.example.com.        30      IN      A       127.0.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='geo.example.com.', qtype=A
index 21713ac15f4f7f2a499c4897db0ede77faa49f00..268c130c64dded907089d093b72d6e480637cd06 100644 (file)
@@ -1,5 +1,5 @@
-0      www.geo.example.com.    IN      A       30      127.0.0.1
-0      www.geo.example.com.    IN      RRSIG   30      A 13 4 30 [expiry] [inception] [keytag] geo.example.com. ...
-2      .       IN      OPT     32768   
+0      www.geo.example.com.    30      IN      A       127.0.0.1
+0      www.geo.example.com.    30      IN      RRSIG   A 13 4 30 [expiry] [inception] [keytag] geo.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='www.geo.example.com.', qtype=A
index 05838e43b4f26dde4e928ee3df804c5cd9307d04..533e26282d94ddfe0acd58415581f64da9a1edca 100644 (file)
@@ -1,3 +1,3 @@
-0      www.geo.example.com.    IN      A       30      127.0.0.1
+0      www.geo.example.com.    30      IN      A       127.0.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.geo.example.com.', qtype=A
index a6990b5a0b3045cf981a751fd7e1c956bdba2450..d92847daad9ae46061caa198eb6884f851cdee24 100644 (file)
@@ -1,12 +1,12 @@
-0      city.geo.example.com.   IN      A       30      127.0.1.1
-2      .       IN      OPT     0       AAgACAABICB/AAAB
+0      city.geo.example.com.   30      IN      A       127.0.1.1
+2      .       0       IN      OPT     AAgACAABICB/AAAB
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='city.geo.example.com.', qtype=A
-0      city.geo.example.com.   IN      A       30      127.0.2.1
-2      .       IN      OPT     0       AAgACAABICB/AAAC
+0      city.geo.example.com.   30      IN      A       127.0.2.1
+2      .       0       IN      OPT     AAgACAABICB/AAAC
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='city.geo.example.com.', qtype=A
-0      city.geo.example.com.   IN      A       30      127.0.3.1
-2      .       IN      OPT     0       AAgACAABICB/AAAD
+0      city.geo.example.com.   30      IN      A       127.0.3.1
+2      .       0       IN      OPT     AAgACAABICB/AAAD
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='city.geo.example.com.', qtype=A
index 2930214ef2789ed5377c7fbf5a8fbcd049da7504..3ab8eb0b651787f619f06385a40e84c2d8bd46bf 100644 (file)
@@ -1,8 +1,8 @@
-0      map.geo.example.com.    IN      TXT     30      "custom mapping"
-2      .       IN      OPT     0       AAgACAABIBgBAQEB
+0      map.geo.example.com.    30      IN      TXT     "custom mapping"
+2      .       0       IN      OPT     AAgACAABIBgBAQEB
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='map.geo.example.com.', qtype=TXT
-0      map.geo2.example.com.   IN      TXT     30      "overridden moon mapping"
-2      .       IN      OPT     0       AAgACAABIBgBAQEB
+0      map.geo2.example.com.   30      IN      TXT     "overridden moon mapping"
+2      .       0       IN      OPT     AAgACAABIBgBAQEB
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='map.geo2.example.com.', qtype=TXT
index f2bbe2acadde45b51f077daeff0467c0df0a50de..216de06f7b416c41c6a754c103539c2193085ecd 100644 (file)
@@ -1,8 +1,8 @@
-0      ip.geo.example.com.     IN      A       30      127.0.0.2
-2      .       IN      OPT     0       AAgACAABICB/AAAC
+0      ip.geo.example.com.     30      IN      A       127.0.0.2
+2      .       0       IN      OPT     AAgACAABICB/AAAC
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='ip.geo.example.com.', qtype=ANY
-0      ip.geo.example.com.     IN      AAAA    30      ::2
-2      .       IN      OPT     0       AAgAFAACgIAAAAAAAAAAAAAAAAAAAAAC
+0      ip.geo.example.com.     30      IN      AAAA    ::2
+2      .       0       IN      OPT     AAgAFAACgIAAAAAAAAAAAAAAAAAAAAAC
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='ip.geo.example.com.', qtype=ANY
index 1c333a888ee1de3577ad6574e77a5081e9ec3ab7..2e6f27135f84f450dfc5fe47bffa82685a0dd390 100644 (file)
@@ -1,3 +1,3 @@
-1      geo.example.com.        IN      SOA     30      ns1.example.com. hostmaster.example.com. 2014090125 7200 3600 1209600 3600
+1      geo.example.com.        30      IN      SOA     ns1.example.com. hostmaster.example.com. 2014090125 7200 3600 1209600 3600
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='o1.city.geo.example.com.', qtype=A
index df1335d2e2b5545673328c09b13d21927b9f0288..bb28a6cc50adbd0742c97dd94232da6626f0b170 100644 (file)
@@ -1,8 +1,8 @@
-0      loc.geo.example.com.    IN      LOC     30      1 0 0.000 N 1 0 0.000 E 0.00m 1.00m 10000.00m 10.00m
-2      .       IN      OPT     0       AAgACAABIBgBAQEB
+0      loc.geo.example.com.    30      IN      LOC     1 0 0.000 N 1 0 0.000 E 0.00m 1.00m 10000.00m 10.00m
+2      .       0       IN      OPT     AAgACAABIBgBAQEB
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='loc.geo.example.com.', qtype=LOC
-0      loc.geo.example.com.    IN      TXT     30      "1.000000 1.000000"
-2      .       IN      OPT     0       AAgACAABIBgBAQEB
+0      loc.geo.example.com.    30      IN      TXT     "1.000000 1.000000"
+2      .       0       IN      OPT     AAgACAABIBgBAQEB
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='loc.geo.example.com.', qtype=TXT
index bdd6e61dc9dac3728555c6bec71c502ec8481feb..4c48cb7500ff17a69a2125fcd4f05f72f597956f 100644 (file)
@@ -1,10 +1,10 @@
-0      mixed_weight.geo.example.com.   IN      A       30      127.0.0.1
+0      mixed_weight.geo.example.com.   30      IN      A       127.0.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='mixed_weight.geo.example.com.', qtype=A
-0      mixed_weight.geo.example.com.   IN      TXT     30      "text"
+0      mixed_weight.geo.example.com.   30      IN      TXT     "text"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='mixed_weight.geo.example.com.', qtype=TXT
-0      mixed_weight.geo.example.com.   IN      A       30      127.0.0.1
-0      mixed_weight.geo.example.com.   IN      TXT     30      "text"
+0      mixed_weight.geo.example.com.   30      IN      A       127.0.0.1
+0      mixed_weight.geo.example.com.   30      IN      TXT     "text"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='mixed_weight.geo.example.com.', qtype=ANY
index d7847b96428e0dbe950bf6c80356c42bf769c206..6570030066ae7b1ab903186a9ad22ab632778fe4 100644 (file)
@@ -1,7 +1,7 @@
-0      geo.example.com.        IN      A       30      127.0.0.1
-0      geo.example.com.        IN      MX      30      10 mx.example.com.
-0      geo.example.com.        IN      NS      30      ns1.example.com.
-0      geo.example.com.        IN      NS      30      ns2.example.com.
-0      geo.example.com.        IN      SOA     30      ns1.example.com. hostmaster.example.com. 2014090125 7200 3600 1209600 3600
+0      geo.example.com.        30      IN      A       127.0.0.1
+0      geo.example.com.        30      IN      MX      10 mx.example.com.
+0      geo.example.com.        30      IN      NS      ns1.example.com.
+0      geo.example.com.        30      IN      NS      ns2.example.com.
+0      geo.example.com.        30      IN      SOA     ns1.example.com. hostmaster.example.com. 2014090125 7200 3600 1209600 3600
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='geo.example.com.', qtype=ANY
index 0b90fbedc6c5983ab42bc755d630b50343c4d530..8a98d90d637ae6035e2ebb1365a96361afab7ea6 100644 (file)
@@ -49,6 +49,7 @@ $tree->insert_network(
       'location' => { "latitude" => 47.913000, "longitude" => -122.304200, accuracy_radius => 1 },
       'autonomous_system_number' => 3320,
       'autonomous_system_organization' => "Test Networks",
+         'subdivisions' => [{ "geoname_id" => 5332921, "iso_code" => "CA", "names" => { "en" => "California" } }]
     }
 );
 
diff --git a/modules/gmysqlbackend/4.3.0_to_4.7.0_schema.mysql.sql b/modules/gmysqlbackend/4.3.0_to_4.7.0_schema.mysql.sql
new file mode 100644 (file)
index 0000000..5daeca4
--- /dev/null
@@ -0,0 +1,5 @@
+ALTER TABLE domains ADD options VARCHAR(64000) DEFAULT NULL;
+ALTER TABLE domains ADD catalog VARCHAR(255) DEFAULT NULL;
+ALTER TABLE domains MODIFY type VARCHAR(8) NOT NULL;
+
+CREATE INDEX catalog_idx ON domains(catalog);
index 44f1b354edd0ef078706d36f31b98d355ea823b5..2fbbc5c4c88928ea5719a10f1a852707cee88388 100644 (file)
@@ -1,4 +1,4 @@
-AM_CPPFLAGS += $(MYSQL_CFLAGS)
+AM_CPPFLAGS += $(MYSQL_CFLAGS) $(LIBCRYPTO_INCLUDES)
 
 pkglib_LTLIBRARIES = libgmysqlbackend.la
 
@@ -13,11 +13,12 @@ dist_doc_DATA = \
        3.4.0_to_4.1.0_schema.mysql.sql \
        4.1.0_to_4.2.0_schema.mysql.sql \
        4.2.0_to_4.3.0_schema.mysql.sql \
+       4.3.0_to_4.7.0_schema.mysql.sql \
        schema.mysql.sql
 
 libgmysqlbackend_la_SOURCES = \
        gmysqlbackend.cc gmysqlbackend.hh \
        smysql.cc smysql.hh
 
-libgmysqlbackend_la_LDFLAGS = -module -avoid-version
+libgmysqlbackend_la_LDFLAGS = -module -avoid-version $(LIBCRYPTO_LDFLAGS) $(LIBSSL_LDFLAGS)
 libgmysqlbackend_la_LIBADD = $(MYSQL_LIBS)
index fcea02024c6467ccb6545849d946da5c84cffaca..869f819db2bb54f10fac6469042919c643eecb22 100644 (file)
@@ -101,12 +101,12 @@ public:
     declare(suffix, "remove-empty-non-terminals-from-zone-query", "remove all empty non-terminals from zone", "delete from records where domain_id=? and type is null");
     declare(suffix, "delete-empty-non-terminal-query", "delete empty non-terminal from zone", "delete from records where domain_id=? and name=? and type is null");
 
-    declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,account from domains where name=?");
+    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 id,name,master,last_check from domains where type='SLAVE'");
-    declare(suffix, "supermaster-query", "", "select account from supermasters where ip=? and nameserver=?");
-    declare(suffix, "supermaster-name-to-ips", "", "select ip,account from supermasters where nameserver=? and account=?");
-    declare(suffix, "supermaster-add", "", "insert into supermasters (ip, nameserver, account) values (?,?,?)");
+    declare(suffix, "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,12 +125,16 @@ 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.notified_serial, 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='MASTER'");
+    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=?");
     declare(suffix, "delete-zone-query", "", "delete from records where domain_id=?");
     declare(suffix, "delete-rrset-query", "", "delete from records where domain_id=? and name=? and type=?");
@@ -155,7 +159,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 c923035ba672389ee19a099156c20d5f967c70f8..0f3a6ccdfb6816332855589774890b38fadfdaf0 100644 (file)
@@ -3,13 +3,16 @@ CREATE TABLE domains (
   name                  VARCHAR(255) NOT NULL,
   master                VARCHAR(128) DEFAULT NULL,
   last_check            INT DEFAULT NULL,
-  type                  VARCHAR(6) NOT NULL,
+  type                  VARCHAR(8) NOT NULL,
   notified_serial       INT UNSIGNED DEFAULT NULL,
   account               VARCHAR(40) CHARACTER SET 'utf8' DEFAULT NULL,
+  options               VARCHAR(64000) DEFAULT NULL,
+  catalog               VARCHAR(255) DEFAULT NULL,
   PRIMARY KEY (id)
 ) Engine=InnoDB CHARACTER SET 'latin1';
 
 CREATE UNIQUE INDEX name_index ON domains(name);
+CREATE INDEX catalog_idx ON domains(catalog);
 
 
 CREATE TABLE records (
index 8b3c80bbc55623f92e413f6cd22a09e09e6593bd..efcbffd18e451a34fa035be683f1c5c0d38cd110 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();
 
@@ -262,20 +262,20 @@ public:
     }
 
     if (d_dolog)
-      g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " usec to execute" << endl;
+      g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us to execute" << endl;
 
     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() << " total usec to last row" << endl;
+      g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us total to last row" << endl;
     }
     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,11 +489,6 @@ 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);
@@ -562,7 +557,7 @@ void SMySQL::execute(const string& query)
 
   int err;
   if ((err = mysql_query(&d_db, query.c_str())))
-    throw sPerrorException("Failed to execute mysql_query '" + query + "' Err=" + itoa(err));
+    throw sPerrorException("Failed to execute mysql_query '" + query + "' Err=" + std::to_string(err));
 }
 
 void SMySQL::startTransaction()
index 255649ca1ab040e66a0f9a3fe539021f7e4f05cc..f39e5b942750d5b79016cbbccb9d50b940fd7871 100644 (file)
@@ -35,7 +35,7 @@ public:
          bool setIsolation = false, unsigned int timeout = 10,
          bool threadCleanup = false, bool clientSSL = false);
 
-  ~SMySQL();
+  ~SMySQL() override;
 
   SSqlException sPerrorException(const string& reason) override;
   void setLog(bool state) override;
diff --git a/modules/godbcbackend/4.3.0_to_4.7.0_schema.mssql.sql b/modules/godbcbackend/4.3.0_to_4.7.0_schema.mssql.sql
new file mode 100644 (file)
index 0000000..c0cd3d5
--- /dev/null
@@ -0,0 +1,5 @@
+ALTER TABLE domains ADD COLUMN options VARCHAR(MAX) DEFAULT NULL;
+ALTER TABLE domains ADD COLUMN catalog VARCHAR(255) DEFAULT NULL;
+ALTER TABLE domains ALTER COLUMN type VARCHAR(8) NOT NULL;
+
+CREATE INDEX catalog_idx ON domains(catalog);
index 9884e3df0f42272692e8d5e160427c2e81d8018b..3941d4e1979c77e172f66026d99dc35f57ca6f1f 100644 (file)
@@ -1,4 +1,4 @@
-AM_CPPFLAGS += $(UNIXODBC_CFLAGS)
+AM_CPPFLAGS += $(UNIXODBC_CFLAGS) $(LIBCRYPTO_INCLUDES)
 pkglib_LTLIBRARIES = libgodbcbackend.la
 
 EXTRA_DIST = \
@@ -8,7 +8,8 @@ EXTRA_DIST = \
 dist_doc_DATA = \
        schema.mssql.sql \
        4.0.0_to_4.2.0_schema.mssql.sql \
-       4.2.0_to_4.3.0_schema.mssql.sql
+       4.2.0_to_4.3.0_schema.mssql.sql \
+       4.3.0_to_4.7.0_schema.mssql.sql
 
 libgodbcbackend_la_SOURCES = \
        godbcbackend.cc godbcbackend.hh \
index 87e7d4d828ab72a42326a3ad64932651c138e03d..83123d8b03781069e94c3251e4217205c8d10b40 100644 (file)
@@ -81,12 +81,12 @@ public:
     declare(suffix, "remove-empty-non-terminals-from-zone-query", "remove all empty non-terminals from zone", "delete from records where domain_id=? and type is null");
     declare(suffix, "delete-empty-non-terminal-query", "delete empty non-terminal from zone", "delete from records where domain_id=? and name=? and type is null");
 
-    declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,account from domains where name=?");
+    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 id,name,master,last_check from domains where type='SLAVE'");
-    declare(suffix, "supermaster-query", "", "select account from supermasters where ip=? and nameserver=?");
-    declare(suffix, "supermaster-name-to-ips", "", "select ip,account from supermasters where nameserver=? and account=?");
-    declare(suffix, "supermaster-add", "", "insert into supermasters (ip, nameserver, account) values (?,?,?)");
+    declare(suffix, "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,12 +105,16 @@ 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.notified_serial, 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='MASTER'");
+    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=?");
     declare(suffix, "delete-zone-query", "", "delete from records where domain_id=?");
     declare(suffix, "delete-rrset-query", "", "delete from records where domain_id=? and name=? and type=?");
@@ -138,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 d5922dc85289c2d96619ce2d9af321707c3d505d..b7928b470825beb8d1b1b849781ba6f487e5c001 100644 (file)
@@ -3,13 +3,17 @@ CREATE TABLE domains (
   name                  VARCHAR(255) NOT NULL,
   master                VARCHAR(128) DEFAULT NULL,
   last_check            INT DEFAULT NULL,
-  type                  VARCHAR(6) NOT NULL,
+  type                  VARCHAR(8) NOT NULL,
   notified_serial       INT DEFAULT NULL,
   account               VARCHAR(40) DEFAULT NULL,
+  options               VARCHAR(MAX) DEFAULT NULL,
+  catalog               VARCHAR(255) DEFAULT NULL,
   PRIMARY KEY (id)
 );
 
 CREATE UNIQUE INDEX name_index ON domains(name);
+CREATE INDEX catalog_idx ON domains(catalog);
+
 
 CREATE TABLE records (
   id                    INT IDENTITY,
index 3db686fccaa139813bf7ed71478840e72f826813..c79ac2b26504bb4531be84cc0a5f6ee4de440fe8 100644 (file)
@@ -89,17 +89,18 @@ public:
 
   vector<ODBCParam> d_req_bind;
 
-  SSqlStatement* bind(const string& name, ODBCParam& p)
+  SSqlStatement* bind(const string& /* name */, ODBCParam& p)
   {
     prepareStatement();
     d_req_bind.push_back(p);
+    SQLLEN ColumnSize = (p.ParameterType == SQL_VARCHAR) ? *(p.LenPtr) : 0;
     SQLRETURN result = SQLBindParameter(
       d_statement, // StatementHandle,
       d_paridx + 1, // ParameterNumber,
       SQL_PARAM_INPUT, // InputOutputType,
       p.ValueType, // ValueType,
       p.ParameterType, // ParameterType,
-      0, // ColumnSize,
+      ColumnSize, // ColumnSize,
       0, // DecimalDigits,
       p.ParameterValuePtr, // ParameterValuePtr,
       0, // BufferLength,
@@ -111,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;
@@ -146,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;
@@ -157,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;
@@ -168,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;
@@ -189,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.");
@@ -206,7 +207,7 @@ public:
     return bind(name, p);
   }
 
-  SSqlStatement* execute()
+  SSqlStatement* execute() override
   {
     prepareStatement();
     SQLRETURN result;
@@ -237,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;
@@ -256,7 +257,7 @@ public:
     return this;
   }
 
-  SSqlStatement* reset()
+  SSqlStatement* reset() override
   {
     SQLCloseCursor(d_statement); // hack, this probably violates some state transitions
 
@@ -274,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();
   }
@@ -438,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;
diff --git a/modules/gpgsqlbackend/4.3.0_to_4.7.0_schema.pgsql.sql b/modules/gpgsqlbackend/4.3.0_to_4.7.0_schema.pgsql.sql
new file mode 100644 (file)
index 0000000..fdfd61d
--- /dev/null
@@ -0,0 +1,5 @@
+ALTER TABLE domains ALTER COLUMN type TYPE text;
+ALTER TABLE domains ADD COLUMN options TEXT DEFAULT NULL,
+  ADD COLUMN catalog TEXT DEFAULT NULL;
+
+CREATE INDEX catalog_idx ON domains(catalog);
index e1ca64df7fab3abd38552d28e2da5cbdc4db415f..b9663a530a0115f32918bd61fbba3f3e30a46a3a 100644 (file)
@@ -1,4 +1,5 @@
-AM_CPPFLAGS += $(PGSQL_CFLAGS)
+AM_CPPFLAGS += $(PGSQL_CFLAGS) \
+       $(LIBCRYPTO_INCLUDES)
 pkglib_LTLIBRARIES = libgpgsqlbackend.la
 
 EXTRA_DIST = \
@@ -11,7 +12,8 @@ dist_doc_DATA = \
        dnssec-3.x_to_3.4.0_schema.pgsql.sql \
        3.4.0_to_4.1.0_schema.pgsql.sql \
        4.1.0_to_4.2.0_schema.pgsql.sql \
-       4.2.0_to_4.3.0_schema.pgsql.sql
+       4.2.0_to_4.3.0_schema.pgsql.sql \
+       4.3.0_to_4.7.0_schema.pgsql.sql
 
 libgpgsqlbackend_la_SOURCES = \
        gpgsqlbackend.cc gpgsqlbackend.hh \
index 906fa8128225e29eb24fd182bef74e24954e3951..f69828aeaff1b47aa0ca243734f38fb16a7d8053 100644 (file)
@@ -108,12 +108,12 @@ public:
     declare(suffix, "remove-empty-non-terminals-from-zone-query", "remove all empty non-terminals from zone", "delete from records where domain_id=$1 and type is null");
     declare(suffix, "delete-empty-non-terminal-query", "delete empty non-terminal from zone", "delete from records where domain_id=$1 and name=$2 and type is null");
 
-    declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,account from domains where name=$1");
+    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 id,name,master,last_check from domains where type='SLAVE'");
-    declare(suffix, "supermaster-query", "", "select account from supermasters where ip=$1 and nameserver=$2");
-    declare(suffix, "supermaster-name-to-ips", "", "select ip,account from supermasters where nameserver=$1 and account=$2");
-    declare(suffix, "supermaster-add", "", "insert into supermasters (ip, nameserver, account) values ($1,$2,$3)");
+    declare(suffix, "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,12 +132,16 @@ 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.notified_serial, 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='MASTER'");
+    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");
     declare(suffix, "delete-zone-query", "", "delete from records where domain_id=$1");
     declare(suffix, "delete-rrset-query", "", "delete from records where domain_id=$1 and name=$2 and type=$3");
@@ -162,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 80735c2e6b41c396c11908da6686a30a89568bf7..10f542c9f3805fcbbb2c2293d15f697f803bb443 100644 (file)
@@ -3,13 +3,16 @@ CREATE TABLE domains (
   name                  VARCHAR(255) NOT NULL,
   master                VARCHAR(128) DEFAULT NULL,
   last_check            INT DEFAULT NULL,
-  type                  VARCHAR(6) NOT NULL,
+  type                  TEXT NOT NULL,
   notified_serial       BIGINT DEFAULT NULL,
   account               VARCHAR(40) DEFAULT NULL,
+  options               TEXT DEFAULT NULL,
+  catalog               TEXT DEFAULT NULL,
   CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
 );
 
 CREATE UNIQUE INDEX name_index ON domains(name);
+CREATE INDEX catalog_idx ON domains(catalog);
 
 
 CREATE TABLE records (
index 996d72dbc7aa4581eafecd5c4cacfdecc2c4c70a..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) {
@@ -85,7 +85,13 @@ public:
           if (i != 0) {
             log_message << ", ";
           }
-          log_message << "$" << (i + 1) << " = '" << paramValues[i] << "'";
+          log_message << "$" << (i + 1) << " = ";
+          if (paramValues[i] == nullptr) {
+            log_message << "NULL";
+          }
+          else {
+            log_message << "'" << paramValues[i] << "'";
+          }
         }
         g_log << Logger::Warning << log_message.str() << endl;
       }
@@ -106,7 +112,7 @@ public:
     d_cur_set = 0;
     if (d_dolog) {
       auto diff = d_dtime.udiffNoReset();
-      g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << diff << " usec to execute" << endl;
+      g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << diff << " us to execute" << endl;
     }
 
     nextResult();
@@ -134,16 +140,16 @@ public:
     }
   }
 
-  bool hasNextRow()
+  bool hasNextRow() override
   {
     if (d_dolog && d_residx == d_resnum) {
-      g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiff() << " total usec to last row" << endl;
+      g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiff() << " us total to last row" << endl;
     }
 
     return d_residx < d_resnum;
   }
 
-  SSqlStatement* nextRow(row_t& row)
+  SSqlStatement* nextRow(row_t& row) override
   {
     int i;
     row.clear();
@@ -171,7 +177,7 @@ public:
     return this;
   }
 
-  SSqlStatement* getResult(result_t& result)
+  SSqlStatement* getResult(result_t& result) override
   {
     result.clear();
     if (d_res == nullptr)
@@ -185,7 +191,7 @@ public:
     return this;
   }
 
-  SSqlStatement* reset()
+  SSqlStatement* reset() override
   {
     int i;
     if (d_res) {
@@ -211,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 2543c5537150a13139cac47a9145cbf03769c0f9..53ebcad2833af9f4950b022e82fa42b75b05345a 100644 (file)
@@ -1,3 +1,5 @@
+.bail on
+
 -- Disable foreign keys, if any
 PRAGMA foreign_keys = 0;
 
index 47c39addc3960dbbdbb0510846214c6c7bfd363b..b22f0534a8ded694357f91051d71d48a2ade78cd 100644 (file)
@@ -1,3 +1,5 @@
+.bail on
+
 BEGIN TRANSACTION;
     CREATE TEMPORARY TABLE records_backup (
       id                    INTEGER PRIMARY KEY,
index a6ca8fd3dcf12c3a106c021ffd5d6381e5e988a5..a6703c2d79cbe7ab02490dd15cebcd6699e088b4 100644 (file)
@@ -1,3 +1,5 @@
+.bail on
+
 BEGIN TRANSACTION;
   CREATE TABLE cryptokeys_temp (
     id                  INTEGER PRIMARY KEY,
index 26eb9972fcf915c789274729400b3bf902f475ae..37dc72fb01059ba3119c6c85a46a73f418477c48 100644 (file)
@@ -1,3 +1,5 @@
+.bail on
+
 CREATE INDEX records_lookup_idx ON records(name, type);
 CREATE INDEX records_lookup_id_idx ON records(domain_id, name, type);
 CREATE INDEX records_order_idx ON records(domain_id, ordername);
diff --git a/modules/gsqlite3backend/4.3.1_to_4.7.0_schema.sqlite3.sql b/modules/gsqlite3backend/4.3.1_to_4.7.0_schema.sqlite3.sql
new file mode 100644 (file)
index 0000000..e6774d5
--- /dev/null
@@ -0,0 +1,14 @@
+.bail on
+
+PRAGMA foreign_keys = 0;
+
+BEGIN TRANSACTION;
+  ALTER TABLE domains ADD options VARCHAR(65535) DEFAULT NULL;
+  ALTER TABLE domains ADD catalog VARCHAR(255) DEFAULT NULL;
+
+  CREATE INDEX catalog_idx ON domains(catalog);
+COMMIT;
+
+PRAGMA foreign_keys = 1;
+
+ANALYZE;
index 4c5de6dcbec7b59fa29107651500822d04668c31..4f2b46c58a1413619cd19abcb979484b05ccceba 100644 (file)
@@ -1,5 +1,7 @@
 pkglib_LTLIBRARIES = libgsqlite3backend.la
 
+AM_CPPFLAGS += $(LIBCRYPTO_INCLUDES)
+
 EXTRA_DIST = \
        OBJECTFILES \
        OBJECTLIBS
@@ -11,6 +13,7 @@ dist_doc_DATA = \
        4.0.0_to_4.2.0_schema.sqlite3.sql \
        4.2.0_to_4.3.0_schema.sqlite3.sql \
        4.3.0_to_4.3.1_schema.sqlite3.sql \
+       4.3.1_to_4.7.0_schema.sqlite3.sql \
        schema.sqlite3.sql
 
 libgsqlite3backend_la_SOURCES = gsqlite3backend.cc gsqlite3backend.hh
index dc2f6c4b654a07fa47c92d80b7a2fc7de23104cb..bfa924e22f7d7d3e4877644b9fcce6452986777d 100644 (file)
@@ -1,3 +1,5 @@
+.bail on
+
 CREATE TABLE comments (
   id                    INTEGER PRIMARY KEY,
   domain_id             INTEGER NOT NULL,
index fd50ddcb601487735c777a18d1ddbdf6b3fc5fea..829213d6c81b1d55739110e2f3fdea84ef93effc 100644 (file)
@@ -94,16 +94,16 @@ public:
     declare(suffix, "remove-empty-non-terminals-from-zone-query", "remove all empty non-terminals from zone", "delete from records where domain_id=:domain_id and type is null");
     declare(suffix, "delete-empty-non-terminal-query", "delete empty non-terminal from zone", "delete from records where domain_id=:domain_id and name=:qname and type is null");
 
-    declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,account from domains where name=:domain");
+    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 id,name,master,last_check from domains where type='SLAVE'");
-    declare(suffix, "supermaster-query", "", "select account from supermasters where ip=:ip and nameserver=:nameserver");
-    declare(suffix, "supermaster-name-to-ips", "", "select ip,account from supermasters where nameserver=:nameserver and account=:account");
-    declare(suffix, "supermaster-add", "", "insert into supermasters (ip, nameserver, account) values (:ip,:nameserver,:account)");
+    declare(suffix, "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,12 +118,16 @@ 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.notified_serial, 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='MASTER'");
+    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");
     declare(suffix, "delete-zone-query", "", "delete from records where domain_id=:domain_id");
     declare(suffix, "delete-rrset-query", "", "delete from records where domain_id=:domain_id and name=:qname and type=:qtype");
@@ -148,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 08755211855fe5be610fd3463a36fa81574445f3..b34e3a46d9885abf4071a9ea1aabb1f48a2cc5f6 100644 (file)
@@ -5,12 +5,15 @@ CREATE TABLE domains (
   name                  VARCHAR(255) NOT NULL COLLATE NOCASE,
   master                VARCHAR(128) DEFAULT NULL,
   last_check            INTEGER DEFAULT NULL,
-  type                  VARCHAR(6) NOT NULL,
+  type                  VARCHAR(8) NOT NULL,
   notified_serial       INTEGER DEFAULT NULL,
-  account               VARCHAR(40) DEFAULT NULL
+  account               VARCHAR(40) DEFAULT NULL,
+  options               VARCHAR(65535) DEFAULT NULL,
+  catalog               VARCHAR(255) DEFAULT NULL
 );
 
 CREATE UNIQUE INDEX name_index ON domains(name);
+CREATE INDEX catalog_idx ON domains(catalog);
 
 
 CREATE TABLE records (
index ff58faa1957dd09b03dd8ca84f80a0a726b0e7b6..ae2fb7a6b6977ba40a7e454d75492e3da8dcc724 100644 (file)
@@ -1,5 +1,7 @@
 pkglib_LTLIBRARIES = libldapbackend.la
 
+AM_CPPFLAGS += $(LIBCRYPTO_INCLUDES)
+
 EXTRA_DIST = \
     OBJECTFILES \
     OBJECTLIBS \
@@ -15,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 9a4ddb91bf766f6cf7f5f1844492f2609755eb5a..611b4391fb1ca4494d38c1582affe6084050fd9a 100644 (file)
@@ -22,9 +22,9 @@
 #include "ldaputils.hh"
 
 /*****************************
- * 
+ *
  * LdapSimpleAuthenticator
- * 
+ *
  ****************************/
 
 LdapSimpleAuthenticator::LdapSimpleAuthenticator(const std::string& dn, const std::string& pw, int tmout) :
@@ -69,17 +69,17 @@ void LdapSimpleAuthenticator::fillLastError(LDAP* conn, int code)
 }
 
 /*****************************
- * 
+ *
  * LdapGssapiAuthenticator
- * 
+ *
  ****************************/
 
-static int ldapGssapiAuthenticatorSaslInteractCallback(LDAP* conn, unsigned flags, void* defaults, void* in)
+static int ldapGssapiAuthenticatorSaslInteractCallback(LDAP* /* conn */, unsigned /* flags */, void* /* defaults */, void* /* in */)
 {
   return LDAP_SUCCESS;
 }
 
-LdapGssapiAuthenticator::LdapGssapiAuthenticator(const std::string& kt, const std::string& ccache, int tmout) :
+LdapGssapiAuthenticator::LdapGssapiAuthenticator(const std::string& kt, const std::string& ccache, int /* tmout */) :
   d_logPrefix("[LDAP GSSAPI] "), d_keytabFile(kt), d_cCacheFile(ccache)
 {
   krb5_error_code code;
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 35e5e6f2fa169597ea1403dacaa0390aefd14a5b..f400559e70668f1f1ffb6c3e1e66c585df5b00bc 100644 (file)
@@ -38,8 +38,8 @@ LdapBackend::LdapBackend(const string& suffix)
 
   try {
     d_qname.clear();
-    d_pldap = NULL;
-    d_authenticator = NULL;
+    d_pldap = nullptr;
+    d_authenticator = nullptr;
     d_qlog = arg().mustDo("query-logging");
     d_default_ttl = arg().asNum("default-ttl");
     d_myname = "[LdapBackend]";
@@ -97,7 +97,7 @@ LdapBackend::LdapBackend(const string& suffix)
     g_log << Logger::Error << d_myname << " Caught STL exception: " << e.what() << endl;
   }
 
-  if (d_pldap != NULL) {
+  if (d_pldap != nullptr) {
     delete (d_pldap);
   }
   throw PDNSException("Unable to connect to ldap server");
index 747c6c7e8e1b01b64f2f23c886347e659e5cf358..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) 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 51b2e044b6b1c23bde1e04bea60c40880d4934cd..48421ea99ce474353d753f31fd19a26d9809e64a 100644 (file)
@@ -24,7 +24,7 @@
 #include "ldapbackend.hh"
 #include <cstdlib>
 
-bool LdapBackend::list(const DNSName& target, int domain_id, bool include_disabled)
+bool LdapBackend::list(const DNSName& target, int domain_id, bool /* include_disabled */)
 {
   try {
     d_in_list = true;
@@ -57,7 +57,7 @@ bool LdapBackend::list(const DNSName& target, int domain_id, bool include_disabl
   return false;
 }
 
-bool LdapBackend::list_simple(const DNSName& target, int domain_id)
+bool LdapBackend::list_simple(const DNSName& target, int /* domain_id */)
 {
   string dn;
   string filter;
@@ -136,7 +136,7 @@ void LdapBackend::lookup(const QType& qtype, const DNSName& qname, int zoneid, D
   }
 }
 
-void LdapBackend::lookup_simple(const QType& qtype, const DNSName& qname, DNSPacket* dnspkt, int zoneid)
+void LdapBackend::lookup_simple(const QType& qtype, const DNSName& qname, DNSPacket* /* dnspkt */, int /* zoneid */)
 {
   string filter, attr, qesc;
   const char** attributes = ldap_attrany + 1; // skip associatedDomain
@@ -158,7 +158,7 @@ void LdapBackend::lookup_simple(const QType& qtype, const DNSName& qname, DNSPac
   d_search = d_pldap->search(getArg("basedn"), LDAP_SCOPE_SUBTREE, filter, attributes);
 }
 
-void LdapBackend::lookup_strict(const QType& qtype, const DNSName& qname, DNSPacket* dnspkt, int zoneid)
+void LdapBackend::lookup_strict(const QType& qtype, const DNSName& qname, DNSPacket* /* dnspkt */, int /* zoneid */)
 {
   int len;
   vector<string> parts;
@@ -200,7 +200,7 @@ void LdapBackend::lookup_strict(const QType& qtype, const DNSName& qname, DNSPac
   d_search = d_pldap->search(getArg("basedn"), LDAP_SCOPE_SUBTREE, filter, attributes);
 }
 
-void LdapBackend::lookup_tree(const QType& qtype, const DNSName& qname, DNSPacket* dnspkt, int zoneid)
+void LdapBackend::lookup_tree(const QType& qtype, const DNSName& qname, DNSPacket* /* dnspkt */, int /* zoneid */)
 {
   string filter, attr, qesc, dn;
   const char** attributes = ldap_attrany + 1; // skip associatedDomain
@@ -318,7 +318,7 @@ bool LdapBackend::get(DNSResourceRecord& rr)
   return true;
 }
 
-bool LdapBackend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool getSerial)
+bool LdapBackend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool /* getSerial */)
 {
   string filter;
   SOAData sd;
@@ -383,15 +383,15 @@ bool LdapBackend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool getS
 
     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 fc5af43c0caf86a1c5569314d8d1ff1041d956e2..fe5ce7f1bb00587df367affad71785d5e778d8a6 100644 (file)
@@ -42,7 +42,7 @@ PowerLDAP::SearchResult::~SearchResult()
       // not much we can do now
 }
 
-bool PowerLDAP::SearchResult::getNext(PowerLDAP::sentry_t& entry, bool dn, int timeout)
+bool PowerLDAP::SearchResult::getNext(PowerLDAP::sentry_t& entry, bool dn, int /* timeout */)
 {
   int i;
   char* attr;
@@ -214,7 +214,7 @@ void PowerLDAP::bind(LdapAuthenticator* authenticator)
     throw LDAPException("Failed to bind to LDAP server: " + authenticator->getError());
 }
 
-void PowerLDAP::bind(const string& ldapbinddn, const string& ldapsecret, int method)
+void PowerLDAP::bind(const string& ldapbinddn, const string& ldapsecret, int /* method */)
 {
   int msgid;
 
@@ -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 af970d0d43070ca4a3f5e95308e3583c57fba75f..e8b030d78fb5b9c12e3623cc8f8377941c2b189c 100644 (file)
@@ -29,7 +29,7 @@
 #include <vector>
 #include <stdexcept>
 #include <inttypes.h>
-#include <errno.h>
+#include <cerrno>
 #include <lber.h>
 #include <ldap.h>
 
@@ -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 9a4847803c2f4af27a2d23893789441b00d400ff..13c112d14fef73314428b4fbb44c6ca5c7617c02 100644 (file)
@@ -24,7 +24,7 @@
 #include "ldapbackend.hh"
 #include <cstdlib>
 
-void LdapBackend::getUpdatedMasters(vector<DomainInfo>* domains)
+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)
     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)
   catch (LDAPNoConnection& lnc) {
     g_log << Logger::Warning << d_myname << " Connection to LDAP lost, trying to reconnect" << endl;
     if (reconnect())
-      this->getUpdatedMasters(domains);
+      this->getUpdatedPrimaries(domains, catalogs, catalogHashes);
     else
       throw PDNSException("Failed to reconnect to LDAP server");
   }
@@ -66,7 +66,7 @@ void LdapBackend::getUpdatedMasters(vector<DomainInfo>* domains)
       continue;
 
     if (di.notified_serial < di.serial)
-      domains->push_back(di);
+      domains.push_back(di);
   }
 }
 
index 8ecb376cde15eefd3dc7c5c244eaed796ba045ef..d097aee8df7c13efcfd5bacb3f5e4375ee6db89a 100644 (file)
@@ -1,4 +1,4 @@
-AM_CPPFLAGS += $(LMDB_CFLAGS)
+AM_CPPFLAGS += $(LMDB_CFLAGS) $(LIBCRYPTO_INCLUDES)
 
 pkglib_LTLIBRARIES = liblmdbbackend.la
 
@@ -8,5 +8,5 @@ liblmdbbackend_la_SOURCES = \
        ../../ext/lmdb-safe/lmdb-safe.hh ../../ext/lmdb-safe/lmdb-safe.cc \
        ../../ext/lmdb-safe/lmdb-typed.hh ../../ext/lmdb-safe/lmdb-typed.cc \
        lmdbbackend.cc lmdbbackend.hh
-liblmdbbackend_la_LDFLAGS = -module -avoid-version
-liblmdbbackend_la_LIBADD = $(LMDB_LIBS) $(BOOST_SERIALIZATION_LIBS)
+liblmdbbackend_la_LDFLAGS = -module -avoid-version $(BOOST_SERIALIZATION_LDFLAGS)
+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 e287a0b892390c00068faaf319e76645b6fa7f07..e528d63295c2f35d61748ad0151de4031451b9a6 100644 (file)
  * 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"
 #endif
 
 #include <boost/iostreams/device/back_inserter.hpp>
 
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-daemon.h>
+#endif
+
 #include <stdio.h>
 #include <unistd.h>
 
 #include "lmdbbackend.hh"
 
-#define SCHEMAVERSION 3
+#define SCHEMAVERSION 5
 
 // List the class version here. Default is 0
 BOOST_CLASS_VERSION(LMDBBackend::KeyDataDB, 1)
+BOOST_CLASS_VERSION(DomainInfo, 1)
 
 static bool s_first = true;
 static int s_shards = 0;
 static std::mutex s_lmdbStartupLock;
 
+std::pair<uint32_t, uint32_t> LMDBBackend::getSchemaVersionAndShards(std::string& filename)
+{
+  // cerr << "getting schema version for path " << filename << endl;
+
+  uint32_t schemaversion;
+
+  int rc;
+  MDB_env* env = nullptr;
+
+  if ((rc = mdb_env_create(&env)) != 0) {
+    throw std::runtime_error("mdb_env_create failed");
+  }
+
+  if ((rc = mdb_env_set_mapsize(env, 0)) != 0) {
+    throw std::runtime_error("mdb_env_set_mapsize failed");
+  }
+
+  if ((rc = mdb_env_set_maxdbs(env, 20)) != 0) { // we need 17: 1 {"pdns"} + 4 {"domains", "keydata", "tsig", "metadata"} * 2 {v4, v5} * 2 {main, index in _0}
+    mdb_env_close(env);
+    throw std::runtime_error("mdb_env_set_maxdbs failed");
+  }
+
+  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 {0u, 0u};
+    }
+    mdb_env_close(env);
+    throw std::runtime_error("mdb_env_open failed");
+  }
+
+  MDB_txn* txn = nullptr;
+
+  if ((rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn)) != 0) {
+    mdb_env_close(env);
+    throw std::runtime_error("mdb_txn_begin failed");
+  }
+
+  MDB_dbi dbi;
+
+  if ((rc = mdb_dbi_open(txn, "pdns", 0, &dbi)) != 0) {
+    if (rc == MDB_NOTFOUND) {
+      // this means nothing has been inited yet
+      // we pretend this means 5
+      mdb_txn_abort(txn);
+      mdb_env_close(env);
+      return {5u, 0u};
+    }
+    mdb_txn_abort(txn);
+    mdb_env_close(env);
+    throw std::runtime_error("mdb_dbi_open failed");
+  }
+
+  MDB_val key, data;
+
+  key.mv_data = (char*)"schemaversion";
+  key.mv_size = strlen((char*)key.mv_data);
+
+  if ((rc = mdb_get(txn, dbi, &key, &data)) != 0) {
+    if (rc == MDB_NOTFOUND) {
+      // this means nothing has been inited yet
+      // we pretend this means 5
+      mdb_txn_abort(txn);
+      mdb_env_close(env);
+      return {5u, 0u};
+    }
+
+    throw std::runtime_error("mdb_get pdns.schemaversion failed");
+  }
+
+  if (data.mv_size == 4) {
+    // schemaversion is < 5 and is stored in 32 bits, in host order
+
+    memcpy(&schemaversion, data.mv_data, data.mv_size);
+  }
+  else if (data.mv_size >= LMDBLS::LS_MIN_HEADER_SIZE + sizeof(schemaversion)) {
+    // schemaversion presumably is 5, stored in 32 bits, network order, after the LS header
+
+    // FIXME: get actual header size (including extension blocks) instead of just reading from the back
+    // FIXME: add a test for reading schemaversion and shards (and actual data, later) when there are variably sized headers
+    memcpy(&schemaversion, (char*)data.mv_data + data.mv_size - sizeof(schemaversion), sizeof(schemaversion));
+    schemaversion = ntohl(schemaversion);
+  }
+  else {
+    throw std::runtime_error("pdns.schemaversion had unexpected size");
+  }
+
+  uint32_t shards;
+
+  key.mv_data = (char*)"shards";
+  key.mv_size = strlen((char*)key.mv_data);
+
+  if ((rc = mdb_get(txn, dbi, &key, &data)) != 0) {
+    if (rc == MDB_NOTFOUND) {
+      cerr << "schemaversion was set, but shards was not. Dazed and confused, trying to exit." << endl;
+      mdb_txn_abort(txn);
+      mdb_env_close(env);
+      exit(1);
+    }
+
+    throw std::runtime_error("mdb_get pdns.shards failed");
+  }
+
+  if (data.mv_size == 4) {
+    // 'shards' is stored in 32 bits, in host order
+
+    memcpy(&shards, data.mv_data, data.mv_size);
+  }
+  else if (data.mv_size >= LMDBLS::LS_MIN_HEADER_SIZE + sizeof(shards)) {
+    // FIXME: get actual header size (including extension blocks) instead of just reading from the back
+    memcpy(&shards, (char*)data.mv_data + data.mv_size - sizeof(shards), sizeof(shards));
+    shards = ntohl(shards);
+  }
+  else {
+    throw std::runtime_error("pdns.shards had unexpected size");
+  }
+
+  mdb_txn_abort(txn);
+  mdb_env_close(env);
+
+  return {schemaversion, shards};
+}
+
+namespace
+{
+// copy sdbi to tdbi, prepending an empty LS header (24 bytes of '\0') to all values
+void copyDBIAndAddLSHeader(MDB_txn* txn, MDB_dbi sdbi, MDB_dbi tdbi)
+{
+  // FIXME: clear out target dbi first
+
+  std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
+  int rc;
+
+  MDB_cursor* cur;
+
+  if ((rc = mdb_cursor_open(txn, sdbi, &cur)) != 0) {
+    throw std::runtime_error("mdb_cursur_open failed");
+  }
+
+  MDB_val key, data;
+
+  rc = mdb_cursor_get(cur, &key, &data, MDB_FIRST);
+
+  while (rc == 0) {
+    std::string skey(reinterpret_cast<const char*>(key.mv_data), key.mv_size);
+    std::string sdata(reinterpret_cast<const char*>(data.mv_data), data.mv_size);
+
+    std::string stdata = header + sdata;
+
+    // cerr<<"got key="<<makeHexDump(skey)<<", data="<<makeHexDump(sdata)<<", sdata="<<makeHexDump(stdata)<<endl;
+
+    MDB_val tkey;
+    MDB_val tdata;
+
+    tkey.mv_data = const_cast<char*>(skey.c_str());
+    tkey.mv_size = skey.size();
+    tdata.mv_data = const_cast<char*>(stdata.c_str());
+    tdata.mv_size = stdata.size();
+
+    if ((rc = mdb_put(txn, tdbi, &tkey, &tdata, 0)) != 0) {
+      throw std::runtime_error("mdb_put failed");
+    }
+
+    rc = mdb_cursor_get(cur, &key, &data, MDB_NEXT);
+  }
+  if (rc != MDB_NOTFOUND) {
+    cerr << "rc=" << rc << endl;
+    throw std::runtime_error("error while iterating dbi");
+  }
+}
+
+// migrated a typed DBI:
+// 1. change keys (uint32_t) from host to network order
+// 2. prepend empty LS header to values
+void copyTypedDBI(MDB_txn* txn, MDB_dbi sdbi, MDB_dbi tdbi)
+{
+  // FIXME: clear out target dbi first
+
+  std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
+  int rc;
+
+  MDB_cursor* cur;
+
+  if ((rc = mdb_cursor_open(txn, sdbi, &cur)) != 0) {
+    throw std::runtime_error("mdb_cursur_open failed");
+  }
+
+  MDB_val key, data;
+
+  rc = mdb_cursor_get(cur, &key, &data, MDB_FIRST);
+
+  while (rc == 0) {
+    // std::string skey((char*) key.mv_data, key.mv_size);
+    std::string sdata(reinterpret_cast<const char*>(data.mv_data), data.mv_size);
+
+    std::string stdata = header + sdata;
+
+    uint32_t id;
+
+    if (key.mv_size != sizeof(uint32_t)) {
+      throw std::runtime_error("got non-uint32_t key in TypedDBI");
+    }
+
+    memcpy(&id, key.mv_data, sizeof(uint32_t));
+
+    id = htonl(id);
+
+    // cerr<<"got key="<<makeHexDump(skey)<<", data="<<makeHexDump(sdata)<<", sdata="<<makeHexDump(stdata)<<endl;
+
+    MDB_val tkey;
+    MDB_val tdata;
+
+    tkey.mv_data = reinterpret_cast<char*>(&id);
+    tkey.mv_size = sizeof(uint32_t);
+    tdata.mv_data = const_cast<char*>(stdata.c_str());
+    tdata.mv_size = stdata.size();
+
+    if ((rc = mdb_put(txn, tdbi, &tkey, &tdata, 0)) != 0) {
+      throw std::runtime_error("mdb_put failed");
+    }
+
+    rc = mdb_cursor_get(cur, &key, &data, MDB_NEXT);
+  }
+  if (rc != MDB_NOTFOUND) {
+    cerr << "rc=" << rc << endl;
+    throw std::runtime_error("error while iterating dbi");
+  }
+}
+
+// migrating an index DBI:
+// newkey = oldkey.len(), oldkey, htonl(oldvalue)
+// newvalue = empty lsheader
+void copyIndexDBI(MDB_txn* txn, MDB_dbi sdbi, MDB_dbi tdbi)
+{
+  // FIXME: clear out target dbi first
+
+  std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
+  int rc;
+
+  MDB_cursor* cur;
+
+  if ((rc = mdb_cursor_open(txn, sdbi, &cur)) != 0) {
+    throw std::runtime_error("mdb_cursur_open failed");
+  }
+
+  MDB_val key, data;
+
+  rc = mdb_cursor_get(cur, &key, &data, MDB_FIRST);
+
+  while (rc == 0) {
+    std::string lenprefix(sizeof(uint16_t), '\0');
+    std::string skey((char*)key.mv_data, key.mv_size);
+
+    uint32_t id;
+
+    if (data.mv_size != sizeof(uint32_t)) {
+      throw std::runtime_error("got non-uint32_t ID value in IndexDBI");
+    }
+
+    memcpy((void*)&id, data.mv_data, sizeof(uint32_t));
+    id = htonl(id);
+
+    uint16_t len = htons(skey.size());
+    memcpy((void*)lenprefix.data(), &len, sizeof(len));
+    std::string stkey = lenprefix + skey + std::string((char*)&id, sizeof(uint32_t));
+
+    MDB_val tkey;
+    MDB_val tdata;
+
+    tkey.mv_data = (char*)stkey.c_str();
+    tkey.mv_size = stkey.size();
+    tdata.mv_data = (char*)header.c_str();
+    tdata.mv_size = header.size();
+
+    if ((rc = mdb_put(txn, tdbi, &tkey, &tdata, 0)) != 0) {
+      throw std::runtime_error("mdb_put failed");
+    }
+
+    rc = mdb_cursor_get(cur, &key, &data, MDB_NEXT);
+  }
+  if (rc != MDB_NOTFOUND) {
+    throw std::runtime_error("error while iterating dbi");
+  }
+}
+
+}
+
+bool LMDBBackend::upgradeToSchemav5(std::string& filename)
+{
+  int rc;
+
+  auto currentSchemaVersionAndShards = getSchemaVersionAndShards(filename);
+  uint32_t currentSchemaVersion = currentSchemaVersionAndShards.first;
+  uint32_t shards = currentSchemaVersionAndShards.second;
+
+  if (currentSchemaVersion != 3 && currentSchemaVersion != 4) {
+    throw std::runtime_error("upgrade to v5 requested but current schema is not v3 or v4, stopping");
+  }
+
+  MDB_env* env = nullptr;
+
+  if ((rc = mdb_env_create(&env)) != 0) {
+    throw std::runtime_error("mdb_env_create failed");
+  }
+
+  if ((rc = mdb_env_set_maxdbs(env, 20)) != 0) {
+    mdb_env_close(env);
+    throw std::runtime_error("mdb_env_set_maxdbs failed");
+  }
+
+  if ((rc = mdb_env_open(env, filename.c_str(), MDB_NOSUBDIR, 0600)) != 0) {
+    mdb_env_close(env);
+    throw std::runtime_error("mdb_env_open failed");
+  }
+
+  MDB_txn* txn = nullptr;
+
+  if ((rc = mdb_txn_begin(env, NULL, 0, &txn)) != 0) {
+    mdb_env_close(env);
+    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);
+    if (access(shardfile.c_str(), F_OK) < 0) {
+      if (errno == ENOENT) {
+        // apparently this shard doesn't exist yet, moving on
+        std::cerr << "shard " << shardfile << " not found, continuing" << std::endl;
+        continue;
+      }
+    }
+
+    std::cerr << "migrating shard " << shardfile << std::endl;
+    MDB_env* shenv = nullptr;
+
+    if ((rc = mdb_env_create(&shenv)) != 0) {
+      throw std::runtime_error("mdb_env_create failed");
+    }
+
+    if ((rc = mdb_env_set_maxdbs(shenv, 8)) != 0) {
+      mdb_env_close(env);
+      throw std::runtime_error("mdb_env_set_maxdbs failed");
+    }
+
+    if ((rc = mdb_env_open(shenv, shardfile.c_str(), MDB_NOSUBDIR, 0600)) != 0) {
+      mdb_env_close(env);
+      throw std::runtime_error("mdb_env_open failed");
+    }
+
+    MDB_txn* shtxn = nullptr;
+
+    if ((rc = mdb_txn_begin(shenv, NULL, 0, &shtxn)) != 0) {
+      mdb_env_close(env);
+      throw std::runtime_error("mdb_txn_begin failed");
+    }
+
+    MDB_dbi shdbi;
+
+    if ((rc = mdb_dbi_open(shtxn, "records", 0, &shdbi)) != 0) {
+      if (rc == MDB_NOTFOUND) {
+        mdb_txn_abort(shtxn);
+        mdb_env_close(shenv);
+        continue;
+      }
+      mdb_txn_abort(shtxn);
+      mdb_env_close(shenv);
+      throw std::runtime_error("mdb_dbi_open shard records failed");
+    }
+
+    MDB_dbi shdbi2;
+
+    if ((rc = mdb_dbi_open(shtxn, "records_v5", MDB_CREATE, &shdbi2)) != 0) {
+      mdb_dbi_close(shenv, shdbi);
+      mdb_txn_abort(shtxn);
+      mdb_env_close(shenv);
+      throw std::runtime_error("mdb_dbi_open shard records_v5 failed");
+    }
+
+    try {
+      copyDBIAndAddLSHeader(shtxn, shdbi, shdbi2);
+    }
+    catch (std::exception& e) {
+      mdb_dbi_close(shenv, shdbi2);
+      mdb_dbi_close(shenv, shdbi);
+      mdb_txn_abort(shtxn);
+      mdb_env_close(shenv);
+      throw std::runtime_error("copyDBIAndAddLSHeader failed");
+    }
+
+    cerr << "shard mbd_drop=" << mdb_drop(shtxn, shdbi, 1) << endl;
+    mdb_txn_commit(shtxn);
+    mdb_dbi_close(shenv, shdbi2);
+    mdb_env_close(shenv);
+  }
+
+  std::array<MDB_dbi, 4> fromtypeddbi;
+  std::array<MDB_dbi, 4> totypeddbi;
+
+  int index = 0;
+
+  for (const std::string dbname : {"domains", "keydata", "tsig", "metadata"}) {
+    std::cerr << "migrating " << dbname << std::endl;
+    std::string tdbname = dbname + "_v5";
+
+    if ((rc = mdb_dbi_open(txn, dbname.c_str(), 0, &fromtypeddbi[index])) != 0) {
+      mdb_txn_abort(txn);
+      mdb_env_close(env);
+      throw std::runtime_error("mdb_dbi_open typeddbi failed");
+    }
+
+    if ((rc = mdb_dbi_open(txn, tdbname.c_str(), MDB_CREATE, &totypeddbi[index])) != 0) {
+      mdb_dbi_close(env, fromtypeddbi[index]);
+      mdb_txn_abort(txn);
+      mdb_env_close(env);
+      throw std::runtime_error("mdb_dbi_open typeddbi target failed");
+    }
+
+    try {
+      copyTypedDBI(txn, fromtypeddbi[index], totypeddbi[index]);
+    }
+    catch (std::exception& e) {
+      mdb_dbi_close(env, totypeddbi[index]);
+      mdb_dbi_close(env, fromtypeddbi[index]);
+      mdb_txn_abort(txn);
+      mdb_env_close(env);
+      throw std::runtime_error("copyTypedDBI failed");
+    }
+
+    // mdb_dbi_close(env, dbi2);
+    // mdb_dbi_close(env, dbi);
+    std::cerr << "migrated " << dbname << std::endl;
+
+    index++;
+  }
+
+  std::array<MDB_dbi, 4> fromindexdbi;
+  std::array<MDB_dbi, 4> toindexdbi;
+
+  index = 0;
+
+  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";
+
+    if ((rc = mdb_dbi_open(txn, fdbname.c_str(), 0, &fromindexdbi[index])) != 0) {
+      mdb_txn_abort(txn);
+      mdb_env_close(env);
+      throw std::runtime_error("mdb_dbi_open indexdbi failed");
+    }
+
+    if ((rc = mdb_dbi_open(txn, tdbname.c_str(), MDB_CREATE, &toindexdbi[index])) != 0) {
+      mdb_dbi_close(env, fromindexdbi[index]);
+      mdb_txn_abort(txn);
+      mdb_env_close(env);
+      throw std::runtime_error("mdb_dbi_open indexdbi target failed");
+    }
+
+    try {
+      copyIndexDBI(txn, fromindexdbi[index], toindexdbi[index]);
+    }
+    catch (std::exception& e) {
+      mdb_dbi_close(env, toindexdbi[index]);
+      mdb_dbi_close(env, fromindexdbi[index]);
+      mdb_txn_abort(txn);
+      mdb_env_close(env);
+      throw std::runtime_error("copyIndexDBI failed");
+    }
+
+    // mdb_dbi_close(env, dbi2);
+    // mdb_dbi_close(env, dbi);
+    std::cerr << "migrated " << dbname << std::endl;
+
+    index++;
+  }
+
+  MDB_dbi dbi;
+
+  // finally, migrate the pdns db
+  if ((rc = mdb_dbi_open(txn, "pdns", 0, &dbi)) != 0) {
+    mdb_txn_abort(txn);
+    mdb_env_close(env);
+    throw std::runtime_error("mdb_dbi_open pdns failed");
+  }
+
+  MDB_val key, data;
+
+  std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
+
+  for (const std::string keyname : {"schemaversion", "shards"}) {
+    cerr << "migrating pdns." << keyname << endl;
+
+    key.mv_data = (char*)keyname.c_str();
+    key.mv_size = keyname.size();
+
+    if ((rc = mdb_get(txn, dbi, &key, &data))) {
+      throw std::runtime_error("mdb_get pdns.shards failed");
+    }
+
+    uint32_t value;
+
+    if (data.mv_size != sizeof(uint32_t)) {
+      throw std::runtime_error("got non-uint32_t key");
+    }
+
+    memcpy((void*)&value, data.mv_data, sizeof(uint32_t));
+
+    value = htonl(value);
+    if (keyname == "schemaversion") {
+      value = htonl(5);
+    }
+
+    std::string sdata((char*)data.mv_data, data.mv_size);
+
+    std::string stdata = header + std::string((char*)&value, sizeof(uint32_t));
+    ;
+
+    MDB_val tdata;
+
+    tdata.mv_data = (char*)stdata.c_str();
+    tdata.mv_size = stdata.size();
+
+    if ((rc = mdb_put(txn, dbi, &key, &tdata, 0)) != 0) {
+      throw std::runtime_error("mdb_put failed");
+    }
+  }
+
+  for (const std::string keyname : {"uuid"}) {
+    cerr << "migrating pdns." << keyname << endl;
+
+    key.mv_data = (char*)keyname.c_str();
+    key.mv_size = keyname.size();
+
+    if ((rc = mdb_get(txn, dbi, &key, &data))) {
+      throw std::runtime_error("mdb_get pdns.shards failed");
+    }
+
+    std::string sdata((char*)data.mv_data, data.mv_size);
+
+    std::string stdata = header + sdata;
+
+    MDB_val tdata;
+
+    tdata.mv_data = (char*)stdata.c_str();
+    tdata.mv_size = stdata.size();
+
+    if ((rc = mdb_put(txn, dbi, &key, &tdata, 0)) != 0) {
+      throw std::runtime_error("mdb_put failed");
+    }
+  }
+
+  for (int i = 0; i < 4; i++) {
+    mdb_drop(txn, fromtypeddbi[i], 1);
+    mdb_drop(txn, fromindexdbi[i], 1);
+  }
+
+  cerr << "txn commit=" << mdb_txn_commit(txn) << endl;
+
+  for (int i = 0; i < 4; i++) {
+    mdb_dbi_close(env, totypeddbi[i]);
+    mdb_dbi_close(env, toindexdbi[i]);
+  }
+  mdb_env_close(env);
+
+  // throw std::runtime_error("migration done");
+  cerr << "migration done" << endl;
+  // exit(1);
+  return true;
+}
+
 LMDBBackend::LMDBBackend(const std::string& suffix)
 {
   // overlapping domain ids in combination with relative names are a recipe for disaster
@@ -89,34 +676,74 @@ LMDBBackend::LMDBBackend(const std::string& suffix)
   catch (const std::exception& e) {
     throw std::runtime_error(std::string("Unable to parse the 'map-size' LMDB value: ") + e.what());
   }
-  d_tdomains = std::make_shared<tdomains_t>(getMDBEnv(getArg("filename").c_str(), MDB_NOSUBDIR | d_asyncFlag, 0600, mapSize), "domains");
-  d_tmeta = std::make_shared<tmeta_t>(d_tdomains->getEnv(), "metadata");
-  d_tkdb = std::make_shared<tkdb_t>(d_tdomains->getEnv(), "keydata");
-  d_ttsig = std::make_shared<ttsig_t>(d_tdomains->getEnv(), "tsig");
 
-  auto pdnsdbi = d_tdomains->getEnv()->openDB("pdns", MDB_CREATE);
+  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;
 
   if (s_first) {
     std::lock_guard<std::mutex> l(s_lmdbStartupLock);
     if (s_first) {
-      auto txn = d_tdomains->getEnv()->getRWTransaction();
+      auto filename = getArg("filename");
 
-      uint32_t schemaversion = 1;
-      MDBOutVal _schemaversion;
-      if (!txn->get(pdnsdbi, "schemaversion", _schemaversion)) {
-        schemaversion = _schemaversion.get<uint32_t>();
+      auto currentSchemaVersionAndShards = getSchemaVersionAndShards(filename);
+      uint32_t currentSchemaVersion = currentSchemaVersionAndShards.first;
+      // std::cerr<<"current schema version: "<<currentSchemaVersion<<", shards="<<currentSchemaVersionAndShards.second<<std::endl;
+
+      if (getArgAsNum("schema-version") != SCHEMAVERSION) {
+        throw std::runtime_error("This version of the lmdbbackend only supports schema version 5. Configuration demands a lower version. Not starting up.");
       }
 
-      if (schemaversion != SCHEMAVERSION) {
-        if (getArgAsNum("schema-version") != SCHEMAVERSION) {
-          throw std::runtime_error("Expected LMDB schema version " + std::to_string(SCHEMAVERSION) + " but got " + std::to_string(schemaversion));
+      if (currentSchemaVersion > 0 && currentSchemaVersion < 3) {
+        throw std::runtime_error("this version of the lmdbbackend can only upgrade from schema v3/v4 to v5. Upgrading from older schemas is not yet supported.");
+      }
+
+      if (currentSchemaVersion == 0) {
+        // no database is present yet, we can just create them
+        currentSchemaVersion = 5;
+      }
+
+      if (currentSchemaVersion == 3 || currentSchemaVersion == 4) {
+        if (!upgradeToSchemav5(filename)) {
+          throw std::runtime_error("Failed to perform LMDB schema version upgrade from v4 to v5");
         }
-        txn->put(pdnsdbi, "schemaversion", SCHEMAVERSION);
+        currentSchemaVersion = 5;
       }
 
+      if (currentSchemaVersion != 5) {
+        throw std::runtime_error("Somehow, we are not at schema version 5. Giving up");
+      }
+
+      d_tdomains = std::make_shared<tdomains_t>(getMDBEnv(getArg("filename").c_str(), MDB_NOSUBDIR | d_asyncFlag, 0600, mapSize), "domains_v5");
+      d_tmeta = std::make_shared<tmeta_t>(d_tdomains->getEnv(), "metadata_v5");
+      d_tkdb = std::make_shared<tkdb_t>(d_tdomains->getEnv(), "keydata_v5");
+      d_ttsig = std::make_shared<ttsig_t>(d_tdomains->getEnv(), "tsig_v5");
+
+      auto pdnsdbi = d_tdomains->getEnv()->openDB("pdns", MDB_CREATE);
+
+      opened = true;
+
+      auto txn = d_tdomains->getEnv()->getRWTransaction();
+
       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;
         }
@@ -133,17 +760,23 @@ LMDBBackend::LMDBBackend(const std::string& suffix)
         txn->put(pdnsdbi, "uuid", uuids);
       }
 
+      MDBOutVal _schemaversion;
+      if (txn->get(pdnsdbi, "schemaversion", _schemaversion)) {
+        // our DB is entirely new, so we need to write the schemaversion
+        txn->put(pdnsdbi, "schemaversion", currentSchemaVersion);
+      }
       txn->commit();
 
-      if (schemaversion < 3) {
-        if (!upgradeToSchemav3()) {
-          throw std::runtime_error("Failed to perform LMDB schema version upgrade to " + std::to_string(SCHEMAVERSION) + " from " + std::to_string(schemaversion));
-        }
-      }
       s_first = false;
     }
   }
 
+  if (!opened) {
+    d_tdomains = std::make_shared<tdomains_t>(getMDBEnv(getArg("filename").c_str(), MDB_NOSUBDIR | d_asyncFlag, 0600, mapSize), "domains_v5");
+    d_tmeta = std::make_shared<tmeta_t>(d_tdomains->getEnv(), "metadata_v5");
+    d_tkdb = std::make_shared<tkdb_t>(d_tdomains->getEnv(), "keydata_v5");
+    d_ttsig = std::make_shared<ttsig_t>(d_tdomains->getEnv(), "tsig_v5");
+  }
   d_trecords.resize(s_shards);
   d_dolog = ::arg().mustDo("query-logging");
 }
@@ -154,36 +787,37 @@ namespace serialization
 {
 
   template <class Archive>
-  void save(Archive& ar, const DNSName& g, const unsigned int version)
+  void save(Archive& ar, const DNSName& g, const unsigned int /* version */)
   {
-    if (!g.empty()) {
-      std::string tmp = g.toDNSStringLC(); // g++ 4.8 woes
-      ar& tmp;
+    if (g.empty()) {
+      ar& std::string();
+    }
+    else {
+      ar& g.toDNSStringLC();
     }
-    else
-      ar & "";
   }
 
   template <class Archive>
-  void load(Archive& ar, DNSName& g, const unsigned int version)
+  void load(Archive& ar, DNSName& g, const unsigned int /* version */)
   {
     string tmp;
     ar& tmp;
-    if (tmp.empty())
+    if (tmp.empty()) {
       g = DNSName();
-    else
+    }
+    else {
       g = DNSName(tmp.c_str(), tmp.size(), 0, false);
+    }
   }
 
   template <class Archive>
-  void save(Archive& ar, const QType& g, const unsigned int version)
+  void save(Archive& ar, const QType& g, const unsigned int /* version */)
   {
-    uint16_t tmp = g.getCode(); // g++ 4.8 woes
-    ar& tmp;
+    ar& g.getCode();
   }
 
   template <class Archive>
-  void load(Archive& ar, QType& g, const unsigned int version)
+  void load(Archive& ar, QType& g, const unsigned int /* version */)
   {
     uint16_t tmp;
     ar& tmp;
@@ -191,25 +825,47 @@ namespace serialization
   }
 
   template <class Archive>
-  void serialize(Archive& ar, DomainInfo& g, const unsigned int version)
+  void save(Archive& ar, const DomainInfo& g, const unsigned int /* version */)
+  {
+    ar& g.zone;
+    ar& g.last_check;
+    ar& g.account;
+    ar& g.primaries;
+    ar& g.id;
+    ar& g.notified_serial;
+    ar& g.kind;
+    ar& g.options;
+    ar& g.catalog;
+  }
+
+  template <class Archive>
+  void load(Archive& ar, DomainInfo& g, const unsigned int version)
   {
     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;
+    if (version >= 1) {
+      ar& g.options;
+      ar& g.catalog;
+    }
+    else {
+      g.options.clear();
+      g.catalog.clear();
+    }
   }
 
   template <class Archive>
-  void serialize(Archive& ar, LMDBBackend::DomainMeta& g, const unsigned int version)
+  void serialize(Archive& ar, LMDBBackend::DomainMeta& g, const unsigned int /* version */)
   {
     ar& g.domain& g.key& g.value;
   }
 
   template <class Archive>
-  void save(Archive& ar, const LMDBBackend::KeyDataDB& g, const unsigned int version)
+  void save(Archive& ar, const LMDBBackend::KeyDataDB& g, const unsigned int /* version */)
   {
     ar& g.domain& g.content& g.flags& g.active& g.published;
   }
@@ -227,7 +883,7 @@ namespace serialization
   }
 
   template <class Archive>
-  void serialize(Archive& ar, TSIGKey& g, const unsigned int version)
+  void serialize(Archive& ar, TSIGKey& g, const unsigned int /* version */)
   {
     ar& g.name;
     ar& g.algorithm; // this is the ordername
@@ -240,6 +896,7 @@ namespace serialization
 BOOST_SERIALIZATION_SPLIT_FREE(DNSName);
 BOOST_SERIALIZATION_SPLIT_FREE(QType);
 BOOST_SERIALIZATION_SPLIT_FREE(LMDBBackend::KeyDataDB);
+BOOST_SERIALIZATION_SPLIT_FREE(DomainInfo);
 BOOST_IS_BITWISE_SERIALIZABLE(ComboAddress);
 
 template <>
@@ -302,7 +959,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);
 }
 
@@ -338,8 +995,8 @@ void LMDBBackend::deleteDomainRecords(RecordsRWTransaction& txn, uint32_t domain
   MDBOutVal key, val;
   //  cout<<"Match: "<<makeHexDump(match);
   if (!cursor.lower_bound(match, key, val)) {
-    while (key.get<StringView>().rfind(match, 0) == 0) {
-      if (qtype == QType::ANY || co.getQType(key.get<StringView>()) == qtype)
+    while (key.getNoStripHeader<StringView>().rfind(match, 0) == 0) {
+      if (qtype == QType::ANY || co.getQType(key.getNoStripHeader<StringView>()) == qtype)
         cursor.del();
       if (cursor.next(key, val))
         break;
@@ -348,8 +1005,8 @@ void LMDBBackend::deleteDomainRecords(RecordsRWTransaction& txn, uint32_t domain
 }
 
 /* Here's the complicated story. Other backends have just one transaction, which is either
-   on or not. 
-   
+   on or not.
+
    You can't call feedRecord without a transaction started with startTransaction.
 
    However, other functions can be called after startTransaction() or without startTransaction()
@@ -527,7 +1184,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);
@@ -543,6 +1200,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)
 {
@@ -550,7 +1214,7 @@ std::shared_ptr<LMDBBackend::RecordsRWTransaction> LMDBBackend::getRecordsRWTran
   if (!shard.env) {
     shard.env = getMDBEnv((getArg("filename") + "-" + std::to_string(id % s_shards)).c_str(),
                           MDB_NOSUBDIR | d_asyncFlag, 0600);
-    shard.dbi = shard.env->openDB("records", MDB_CREATE);
+    shard.dbi = shard.env->openDB("records_v5", MDB_CREATE);
   }
   auto ret = std::make_shared<RecordsRWTransaction>(shard.env->getRWTransaction());
   ret->db = std::make_shared<RecordsDB>(shard);
@@ -558,7 +1222,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) {
@@ -567,7 +1231,7 @@ std::shared_ptr<LMDBBackend::RecordsROTransaction> LMDBBackend::getRecordsROTran
     }
     shard.env = getMDBEnv((getArg("filename") + "-" + std::to_string(id % s_shards)).c_str(),
                           MDB_NOSUBDIR | d_asyncFlag, 0600);
-    shard.dbi = shard.env->openDB("records", MDB_CREATE);
+    shard.dbi = shard.env->openDB("records_v5", MDB_CREATE);
   }
 
   if (rwtxn) {
@@ -582,6 +1246,8 @@ std::shared_ptr<LMDBBackend::RecordsROTransaction> LMDBBackend::getRecordsROTran
   }
 }
 
+#if 0
+// FIXME reinstate soon
 bool LMDBBackend::upgradeToSchemav3()
 {
   g_log << Logger::Warning << "Upgrading LMDB schema" << endl;
@@ -619,7 +1285,7 @@ bool LMDBBackend::upgradeToSchemav3()
     string_view currentKey;
     string value;
     for (;;) {
-      auto newKey = key.get<string_view>();
+      auto newKey = key.getNoStripHeader<string_view>();
       if (currentKey.compare(newKey) != 0) {
         if (value.size() > 0) {
           newTxn->put(newShard.dbi, currentKey, value);
@@ -643,50 +1309,93 @@ bool LMDBBackend::upgradeToSchemav3()
 
   return true;
 }
+#endif
 
 bool LMDBBackend::deleteDomain(const DNSName& domain)
 {
-  auto doms = d_tdomains->getRWTransaction();
+  if (!d_rwtxn) {
+    throw DBException(std::string(__PRETTY_FUNCTION__) + " called without a transaction");
+  }
 
-  DomainInfo di;
-  auto id = doms.get<0>(domain, di);
-  if (!id)
-    return false;
+  int transactionDomainId = d_transactiondomainid;
+  DNSName transactionDomain = d_transactiondomain;
 
-  shared_ptr<RecordsRWTransaction> txn;
-  bool needCommit = false;
-  if (d_rwtxn && d_transactiondomainid == id) {
-    txn = d_rwtxn;
-    //    cout<<"Reusing open transaction"<<endl;
+  abortTransaction();
+
+  LMDBIDvec idvec;
+
+  if (!d_handle_dups) {
+    // get domain id
+    auto txn = d_tdomains->getROTransaction();
+
+    DomainInfo di;
+    idvec.push_back(txn.get<0>(domain, di));
   }
   else {
-    //    cout<<"Making a new RW txn for delete domain"<<endl;
-    txn = getRecordsRWTransaction(id);
-    needCommit = true;
+    // 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);
   }
 
-  doms.del(id);
-  deleteDomainRecords(*txn, id);
+  for (auto id : idvec) {
 
-  if (needCommit)
-    txn->txn->commit();
+    startTransaction(domain, id);
+
+    { // Remove metadata
+      auto txn = d_tmeta->getRWTransaction();
+      LMDBIDvec ids;
+
+      txn.get_multi<0>(domain, ids);
+
+      for (auto& _id : ids) {
+        txn.del(_id);
+      }
+
+      txn.commit();
+    }
+
+    { // Remove cryptokeys
+      auto txn = d_tkdb->getRWTransaction();
+      LMDBIDvec ids;
+      txn.get_multi<0>(domain, ids);
+
+      for (auto _id : ids) {
+        txn.del(_id);
+      }
+
+      txn.commit();
+    }
+
+    // Remove records
+    commitTransaction();
+
+    // Remove zone
+    auto txn = d_tdomains->getRWTransaction();
+    txn.del(id);
+    txn.commit();
+  }
 
-  doms.commit();
+  startTransaction(transactionDomain, transactionDomainId);
 
   return true;
 }
 
-bool LMDBBackend::list(const DNSName& target, int id, bool include_disabled)
+bool LMDBBackend::list(const DNSName& target, int /* id */, bool include_disabled)
 {
   d_includedisabled = include_disabled;
 
   DomainInfo di;
   {
     auto dtxn = d_tdomains->getROTransaction();
-    if ((di.id = dtxn.get<0>(target, di)))
-      ; //      cout<<"Found domain "<<target<<" on domain_id "<<di.id <<", list requested "<<id<<endl;
+    if ((di.id = dtxn.get<0>(target, di))) {
+      // cerr << "Found domain " << target << " on domain_id " << di.id << ", list requested " << id << endl;
+    }
     else {
-      // cout<<"Did not find "<<target<<endl;
+      // cerr << "Did not find " << target << endl;
       return false;
     }
   }
@@ -698,8 +1407,10 @@ bool LMDBBackend::list(const DNSName& target, int id, bool include_disabled)
   d_matchkey = co(di.id);
 
   MDBOutVal key, val;
-  if (d_getcursor->lower_bound(d_matchkey, key, val) || key.get<StringView>().rfind(d_matchkey, 0) != 0) {
-    // cout<<"Found nothing for list"<<endl;
+  auto a = d_getcursor->lower_bound(d_matchkey, key, val);
+  auto b0 = key.getNoStripHeader<StringView>();
+  auto b = b0.rfind(d_matchkey, 0);
+  if (a || b != 0) {
     d_getcursor.reset();
   }
 
@@ -712,7 +1423,7 @@ bool LMDBBackend::list(const DNSName& target, int id, bool include_disabled)
   return true;
 }
 
-void LMDBBackend::lookup(const QType& type, const DNSName& qdomain, int zoneId, DNSPacket* p)
+void LMDBBackend::lookup(const QType& type, const DNSName& qdomain, int zoneId, DNSPacket* /* p */)
 {
   if (d_dolog) {
     g_log << Logger::Warning << "Got lookup for " << qdomain << "|" << type.toString() << " in zone " << zoneId << endl;
@@ -761,16 +1472,16 @@ void LMDBBackend::lookup(const QType& type, const DNSName& qdomain, int zoneId,
     d_matchkey = co(zoneId, relqname, type.getCode());
   }
 
-  if (d_getcursor->lower_bound(d_matchkey, key, val) || key.get<StringView>().rfind(d_matchkey, 0) != 0) {
+  if (d_getcursor->lower_bound(d_matchkey, key, val) || key.getNoStripHeader<StringView>().rfind(d_matchkey, 0) != 0) {
     d_getcursor.reset();
     if (d_dolog) {
-      g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " usec to execute (found nothing)" << endl;
+      g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us to execute (found nothing)" << endl;
     }
     return;
   }
 
   if (d_dolog) {
-    g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " usec to execute" << endl;
+    g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us to execute" << endl;
   }
 
   d_lookupdomain = hunt;
@@ -783,6 +1494,7 @@ void LMDBBackend::lookup(const QType& type, const DNSName& qdomain, int zoneId,
 bool LMDBBackend::get(DNSZoneRecord& zr)
 {
   for (;;) {
+    // std::cerr<<"d_getcursor="<<d_getcursor<<std::endl;
     if (!d_getcursor) {
       d_rotxn.reset();
       return false;
@@ -793,22 +1505,23 @@ bool LMDBBackend::get(DNSZoneRecord& zr)
     if (d_currentrrset.empty()) {
       d_getcursor->current(d_currentKey, d_currentVal);
 
-      key = d_currentKey.get<string_view>();
+      key = d_currentKey.getNoStripHeader<string_view>();
       zr.dr.d_type = compoundOrdername::getQType(key).getCode();
 
       if (zr.dr.d_type == QType::NSEC3) {
         // Hit a magic NSEC3 skipping
-        if (d_getcursor->next(d_currentKey, d_currentVal) || d_currentKey.get<StringView>().rfind(d_matchkey, 0) != 0) {
+        if (d_getcursor->next(d_currentKey, d_currentVal) || d_currentKey.getNoStripHeader<StringView>().rfind(d_matchkey, 0) != 0) {
+          // cerr<<"resetting d_getcursor 1"<<endl;
           d_getcursor.reset();
         }
         continue;
       }
 
-      serFromString(d_currentVal.get<string>(), d_currentrrset);
+      serFromString(d_currentVal.get<string_view>(), d_currentrrset);
       d_currentrrsetpos = 0;
     }
     else {
-      key = d_currentKey.get<string_view>();
+      key = d_currentKey.getNoStripHeader<string_view>();
     }
     try {
       const auto& lrr = d_currentrrset.at(d_currentrrsetpos++);
@@ -819,13 +1532,14 @@ bool LMDBBackend::get(DNSZoneRecord& zr)
         zr.domain_id = compoundOrdername::getDomainID(key);
         zr.dr.d_type = compoundOrdername::getQType(key).getCode();
         zr.dr.d_ttl = lrr.ttl;
-        zr.dr.d_content = deserializeContentZR(zr.dr.d_type, zr.dr.d_name, lrr.content);
+        zr.dr.setContent(deserializeContentZR(zr.dr.d_type, zr.dr.d_name, lrr.content));
         zr.auth = lrr.auth;
       }
 
       if (d_currentrrsetpos >= d_currentrrset.size()) {
         d_currentrrset.clear(); // will invalidate lrr
-        if (d_getcursor->next(d_currentKey, d_currentVal) || d_currentKey.get<StringView>().rfind(d_matchkey, 0) != 0) {
+        if (d_getcursor->next(d_currentKey, d_currentVal) || d_currentKey.getNoStripHeader<StringView>().rfind(d_matchkey, 0) != 0) {
+          // cerr<<"resetting d_getcursor 2"<<endl;
           d_getcursor.reset();
         }
       }
@@ -854,7 +1568,7 @@ bool LMDBBackend::get(DNSResourceRecord& rr)
   rr.qname = zr.dr.d_name;
   rr.ttl = zr.dr.d_ttl;
   rr.qtype = zr.dr.d_type;
-  rr.content = zr.dr.d_content->getZoneRepresentation(true);
+  rr.content = zr.dr.getContent()->getZoneRepresentation(true);
   rr.domain_id = zr.domain_id;
   rr.auth = zr.auth;
   rr.disabled = zr.disabled;
@@ -886,9 +1600,23 @@ bool LMDBBackend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool gets
 {
   {
     auto txn = d_tdomains->getROTransaction();
+    // auto range = txn.prefix_range<0>(domain);
+
+    // bool found = false;
+
+    // for (auto& iter = range.first ; iter != range.second; ++iter) {
+    //   found = true;
+    //   di.id = iter.getID();
+    //   di.backend = this;
+    // }
 
-    if (!(di.id = txn.get<0>(domain, di)))
+    // if (!found) {
+    //   return false;
+    // }
+    if (!(di.id = txn.get<0>(domain, di))) {
       return false;
+    }
+
     di.backend = this;
   }
 
@@ -899,7 +1627,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();
 
@@ -913,7 +1641,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;
 
@@ -944,35 +1672,14 @@ bool LMDBBackend::setAccount(const DNSName& domain, const std::string& account)
   });
 }
 
-void LMDBBackend::setStale(uint32_t domain_id)
-{
-  genChangeDomain(domain_id, [](DomainInfo& di) {
-    di.last_check = 0;
-  });
-}
-
-void LMDBBackend::setFresh(uint32_t domain_id)
-{
-  genChangeDomain(domain_id, [](DomainInfo& di) {
-    di.last_check = time(0);
-  });
-}
-
-void LMDBBackend::setNotified(uint32_t domain_id, uint32_t serial)
-{
-  genChangeDomain(domain_id, [serial](DomainInfo& di) {
-    di.serial = serial;
-  });
-}
-
-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;
 
@@ -984,7 +1691,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);
@@ -994,73 +1701,218 @@ bool LMDBBackend::createDomain(const DNSName& domain, const DomainInfo::DomainKi
   return true;
 }
 
-void LMDBBackend::getAllDomains(vector<DomainInfo>* domains, bool doSerial, bool include_disabled)
+void LMDBBackend::getAllDomainsFiltered(vector<DomainInfo>* domains, const std::function<bool(DomainInfo&)>& allow)
 {
-  domains->clear();
   auto txn = d_tdomains->getROTransaction();
-  for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
-    DomainInfo di = *iter;
-    di.id = iter.getID();
+  if (d_handle_dups) {
+    map<DNSName, DomainInfo> zonemap;
+    set<DNSName> dups;
 
-    if (!getSerial(di) && !include_disabled) {
-      continue;
+    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);
+      }
     }
 
-    di.backend = this;
-    domains->push_back(di);
+    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::getUnfreshSlaveInfos(vector<DomainInfo>* domains)
+void LMDBBackend::getAllDomains(vector<DomainInfo>* domains, bool /* doSerial */, bool include_disabled)
 {
-  //  cout<<"Start of getUnfreshSlaveInfos"<<endl;
   domains->clear();
-  auto txn = d_tdomains->getROTransaction();
 
+  getAllDomainsFiltered(domains, [this, include_disabled](DomainInfo& di) {
+    if (!getSerial(di) && !include_disabled) {
+      return false;
+    }
+
+    return true;
+  });
+}
+
+void LMDBBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
+{
+  uint32_t serial;
   time_t now = time(0);
-  for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
-    if (iter->kind != DomainInfo::Slave)
-      continue;
+  LMDBResourceRecord lrr;
+  soatimes st;
+
+  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;
-    uint32_t serial = 0;
-    if (!txn2->txn->get(txn2->db->dbi, co(iter.getID(), g_rootdnsname, QType::SOA), val)) {
-      LMDBResourceRecord lrr;
+    if (!txn2->txn->get(txn2->db->dbi, co(di.id, g_rootdnsname, QType::SOA), val)) {
       serFromString(val.get<string_view>(), lrr);
-      struct soatimes st;
-
       memcpy(&st, &lrr.content[lrr.content.size() - sizeof(soatimes)], sizeof(soatimes));
-
-      if ((time_t)(iter->last_check + ntohl(st.refresh)) >= now) { // still fresh
-        continue; // try next domain
+      if ((time_t)(di.last_check + ntohl(st.refresh)) > now) { // still fresh
+        return false;
       }
-      //      cout << di.last_check <<" + " <<sdata.refresh<<" > = " << now << "\n";
       serial = ntohl(st.serial);
     }
     else {
-      //      cout << "Could not find SOA for "<<iter->zone<<" with id "<<iter.getID()<<endl;
       serial = 0;
     }
-    DomainInfo di = *iter;
-    di.id = iter.getID();
-    di.serial = serial;
-    di.backend = this;
 
-    domains->push_back(di);
+    return true;
+  });
+}
+
+void LMDBBackend::setStale(uint32_t domain_id)
+{
+  genChangeDomain(domain_id, [](DomainInfo& di) {
+    di.last_check = 0;
+  });
+}
+
+void LMDBBackend::setFresh(uint32_t domain_id)
+{
+  genChangeDomain(domain_id, [](DomainInfo& di) {
+    di.last_check = time(nullptr);
+  });
+}
+
+void LMDBBackend::getUpdatedPrimaries(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
+{
+  CatalogInfo ci;
+
+  getAllDomainsFiltered(&(updatedDomains), [this, &catalogs, &catalogHashes, &ci](DomainInfo& di) {
+    if (!di.isPrimaryType()) {
+      return false;
+    }
+
+    if (di.kind == DomainInfo::Producer) {
+      catalogs.insert(di.zone);
+      catalogHashes[di.zone].process("\0");
+      return false; // Producer fresness check is performed elsewhere
+    }
+
+    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;
+      return true;
+    }
+
+    return false;
+  });
+}
+
+void LMDBBackend::setNotified(uint32_t domain_id, uint32_t serial)
+{
+  genChangeDomain(domain_id, [serial](DomainInfo& di) {
+    di.notified_serial = 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)
+{
+  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);
+
+      return false;
+    });
   }
-  //  cout<<"END of getUnfreshSlaveInfos"<<endl;
+  catch (const getCatalogMembersReturnFalseException& e) {
+    return false;
+  }
+  return true;
+}
+
+bool LMDBBackend::setOptions(const DNSName& domain, const std::string& options)
+{
+  return genChangeDomain(domain, [options](DomainInfo& di) {
+    di.options = options;
+  });
+}
+
+bool LMDBBackend::setCatalog(const DNSName& domain, const DNSName& catalog)
+{
+  return genChangeDomain(domain, [catalog](DomainInfo& di) {
+    di.catalog = catalog;
+  });
 }
 
 bool LMDBBackend::getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string>>& meta)
 {
   meta.clear();
   auto txn = d_tmeta->getROTransaction();
-  auto range = txn.equal_range<0>(name);
-
-  for (auto& iter = range.first; iter != range.second; ++iter) {
-    meta[iter->key].push_back(iter->value);
+  LMDBIDvec ids;
+  txn.get_multi<0>(name, ids);
+
+  DomainMeta dm;
+  // cerr<<"getAllDomainMetadata start"<<endl;
+  for (auto id : ids) {
+    if (txn.get(id, dm)) {
+      meta[dm.key].push_back(dm.value);
+    }
   }
   return true;
 }
@@ -1069,11 +1921,17 @@ bool LMDBBackend::setDomainMetadata(const DNSName& name, const std::string& kind
 {
   auto txn = d_tmeta->getRWTransaction();
 
-  auto range = txn.equal_range<0>(name);
+  LMDBIDvec ids;
+  txn.get_multi<0>(name, ids);
 
-  for (auto& iter = range.first; iter != range.second; ++iter) {
-    if (iter->key == kind)
-      iter.del();
+  DomainMeta dmeta;
+  for (auto id : ids) {
+    if (txn.get(id, dmeta)) {
+      if (dmeta.key == kind) {
+        // cerr<<"delete"<<endl;
+        txn.del(id);
+      }
+    }
   }
 
   for (const auto& m : meta) {
@@ -1087,10 +1945,16 @@ bool LMDBBackend::setDomainMetadata(const DNSName& name, const std::string& kind
 bool LMDBBackend::getDomainKeys(const DNSName& name, std::vector<KeyData>& keys)
 {
   auto txn = d_tkdb->getROTransaction();
-  auto range = txn.equal_range<0>(name);
-  for (auto& iter = range.first; iter != range.second; ++iter) {
-    KeyData kd{iter->content, iter.getID(), iter->flags, iter->active, iter->published};
-    keys.push_back(kd);
+  LMDBIDvec ids;
+  txn.get_multi<0>(name, ids);
+
+  KeyDataDB key;
+
+  for (auto id : ids) {
+    if (txn.get(id, key)) {
+      KeyData kd{key.content, id, key.flags, key.active, key.published};
+      keys.push_back(kd);
+    }
   }
 
   return true;
@@ -1217,13 +2081,13 @@ bool LMDBBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
     cursor.last(key, val);
 
     for (;;) {
-      if (co.getDomainID(key.get<StringView>()) != id) {
+      if (co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
         //cout<<"Last record also not part of this zone!"<<endl;
         // this implies something is wrong in the database, nothing we can do
         return false;
       }
 
-      if (co.getQType(key.get<StringView>()) == QType::NSEC3) {
+      if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
         serFromString(val.get<StringView>(), lrr);
         if (!lrr.ttl) // the kind of NSEC3 we need
           break;
@@ -1233,7 +2097,7 @@ bool LMDBBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
         return false;
       }
     }
-    before = co.getQName(key.get<StringView>());
+    before = co.getQName(key.getNoStripHeader<StringView>());
     unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + di.zone;
 
     // now to find after .. at the beginning of the zone
@@ -1242,28 +2106,28 @@ bool LMDBBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
       return false;
     }
     for (;;) {
-      if (co.getQType(key.get<StringView>()) == QType::NSEC3) {
+      if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
         serFromString(val.get<StringView>(), lrr);
         if (!lrr.ttl)
           break;
       }
 
-      if (cursor.next(key, val) || co.getDomainID(key.get<StringView>()) != id) {
+      if (cursor.next(key, val) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
         // cout<<"hit end of zone or database when we shouldn't"<<endl;
         return false;
       }
     }
-    after = co.getQName(key.get<StringView>());
+    after = co.getQName(key.getNoStripHeader<StringView>());
     // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
     return true;
   }
 
   // cout<<"Ended up at "<<co.getQName(key.get<StringView>()) <<endl;
 
-  before = co.getQName(key.get<StringView>());
+  before = co.getQName(key.getNoStripHeader<StringView>());
   if (before == qname) {
     // cout << "Ended up on exact right node" << endl;
-    before = co.getQName(key.get<StringView>());
+    before = co.getQName(key.getNoStripHeader<StringView>());
     // unhashed should be correct now, maybe check?
     if (cursor.next(key, val)) {
       // xxx should find first hash now
@@ -1273,18 +2137,18 @@ bool LMDBBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
         return false;
       }
       for (;;) {
-        if (co.getQType(key.get<StringView>()) == QType::NSEC3) {
+        if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
           serFromString(val.get<StringView>(), lrr);
           if (!lrr.ttl)
             break;
         }
 
-        if (cursor.next(key, val) || co.getDomainID(key.get<StringView>()) != id) {
+        if (cursor.next(key, val) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
           // cout<<"hit end of zone or database when we shouldn't" << __LINE__<<endl;
           return false;
         }
       }
-      after = co.getQName(key.get<StringView>());
+      after = co.getQName(key.getNoStripHeader<StringView>());
       // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
       return true;
     }
@@ -1293,7 +2157,7 @@ bool LMDBBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
     // cout <<"Going backwards to find 'before'"<<endl;
     int count = 0;
     for (;;) {
-      if (co.getQName(key.get<StringView>()).canonCompare(qname) && co.getQType(key.get<StringView>()) == QType::NSEC3) {
+      if (co.getQName(key.getNoStripHeader<StringView>()).canonCompare(qname) && co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
         // cout<<"Potentially stopping traverse at "<< co.getQName(key.get<StringView>()) <<", " << (co.getQName(key.get<StringView>()).canonCompare(qname))<<endl;
         // cout<<"qname = "<<qname<<endl;
         // cout<<"here  = "<<co.getQName(key.get<StringView>())<<endl;
@@ -1302,7 +2166,7 @@ bool LMDBBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
           break;
       }
 
-      if (cursor.prev(key, val) || co.getDomainID(key.get<StringView>()) != id) {
+      if (cursor.prev(key, val) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
         // cout <<"XXX Hit *beginning* of zone or database"<<endl;
         // this can happen, must deal with it
         // should now find the last hash of the zone
@@ -1315,13 +2179,13 @@ bool LMDBBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
           cursor.prev(key, val);
 
         for (;;) {
-          if (co.getDomainID(key.get<StringView>()) != id) {
+          if (co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
             //cout<<"Last record also not part of this zone!"<<endl;
             // this implies something is wrong in the database, nothing we can do
             return false;
           }
 
-          if (co.getQType(key.get<StringView>()) == QType::NSEC3) {
+          if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
             serFromString(val.get<StringView>(), lrr);
             if (!lrr.ttl) // the kind of NSEC3 we need
               break;
@@ -1331,7 +2195,7 @@ bool LMDBBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
             return false;
           }
         }
-        before = co.getQName(key.get<StringView>());
+        before = co.getQName(key.getNoStripHeader<StringView>());
         unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + di.zone;
         // cout <<"Should still find 'after'!"<<endl;
         // for 'after', we need to find the first hash of this zone
@@ -1342,7 +2206,7 @@ bool LMDBBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
           return false;
         }
         for (;;) {
-          if (co.getQType(key.get<StringView>()) == QType::NSEC3) {
+          if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
             serFromString(val.get<StringView>(), lrr);
             if (!lrr.ttl)
               break;
@@ -1354,14 +2218,14 @@ bool LMDBBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
             return false;
           }
         }
-        after = co.getQName(key.get<StringView>());
+        after = co.getQName(key.getNoStripHeader<StringView>());
 
         // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
         return true;
       }
       ++count;
     }
-    before = co.getQName(key.get<StringView>());
+    before = co.getQName(key.getNoStripHeader<StringView>());
     unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + di.zone;
     // cout<<"Went backwards, found "<<before<<endl;
     // return us to starting point
@@ -1370,7 +2234,7 @@ bool LMDBBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
   }
   //  cout<<"Now going forward"<<endl;
   for (int count = 0;; ++count) {
-    if ((count && cursor.next(key, val)) || co.getDomainID(key.get<StringView>()) != id) {
+    if ((count && cursor.next(key, val)) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
       // cout <<"Hit end of database or zone, finding first hash then in zone "<<id<<endl;
       if (cursor.lower_bound(co(id), key, val)) {
         // cout<<"hit end of zone find when we shouldn't"<<endl;
@@ -1378,7 +2242,7 @@ bool LMDBBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
         return false;
       }
       for (;;) {
-        if (co.getQType(key.get<StringView>()) == QType::NSEC3) {
+        if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
           serFromString(val.get<StringView>(), lrr);
           if (!lrr.ttl)
             break;
@@ -1391,21 +2255,21 @@ bool LMDBBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
         }
         // cout << "Next.. "<<endl;
       }
-      after = co.getQName(key.get<StringView>());
+      after = co.getQName(key.getNoStripHeader<StringView>());
 
       // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
       return true;
     }
 
     // cout<<"After "<<co.getQName(key.get<StringView>()) <<endl;
-    if (co.getQType(key.get<StringView>()) == QType::NSEC3) {
+    if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
       serFromString(val.get<StringView>(), lrr);
       if (!lrr.ttl) {
         break;
       }
     }
   }
-  after = co.getQName(key.get<StringView>());
+  after = co.getQName(key.getNoStripHeader<StringView>());
   // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
   return true;
 }
@@ -1425,8 +2289,8 @@ bool LMDBBackend::getBeforeAndAfterNames(uint32_t id, const DNSName& zonenameU,
   if (cursor.lower_bound(matchkey, key, val)) {
     // cout << "Hit end of database, bummer"<<endl;
     cursor.last(key, val);
-    if (co.getDomainID(key.get<string_view>()) == id) {
-      before = co.getQName(key.get<string_view>()) + zonename;
+    if (co.getDomainID(key.getNoStripHeader<string_view>()) == id) {
+      before = co.getQName(key.getNoStripHeader<string_view>()) + zonename;
       after = zonename;
     }
     // else
@@ -1435,7 +2299,7 @@ bool LMDBBackend::getBeforeAndAfterNames(uint32_t id, const DNSName& zonenameU,
   }
   // cout<<"Cursor is at "<<co.getQName(key.get<string_view>()) <<", in zone id "<<co.getDomainID(key.get<string_view>())<< endl;
 
-  if (co.getQType(key.get<string_view>()).getCode() && co.getDomainID(key.get<string_view>()) == id && co.getQName(key.get<string_view>()) == qname2) { // don't match ENTs
+  if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && co.getDomainID(key.getNoStripHeader<string_view>()) == id && co.getQName(key.getNoStripHeader<string_view>()) == qname2) { // don't match ENTs
     // cout << "Had an exact match!"<<endl;
     before = qname2 + zonename;
     int rc;
@@ -1444,23 +2308,23 @@ bool LMDBBackend::getBeforeAndAfterNames(uint32_t id, const DNSName& zonenameU,
       if (rc)
         break;
 
-      if (co.getDomainID(key.get<string_view>()) == id && key.get<StringView>().rfind(matchkey, 0) == 0)
+      if (co.getDomainID(key.getNoStripHeader<string_view>()) == id && key.getNoStripHeader<StringView>().rfind(matchkey, 0) == 0)
         continue;
       LMDBResourceRecord lrr;
       serFromString(val.get<StringView>(), lrr);
-      if (co.getQType(key.get<string_view>()).getCode() && (lrr.auth || co.getQType(key.get<string_view>()).getCode() == QType::NS))
+      if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()).getCode() == QType::NS))
         break;
     }
-    if (rc || co.getDomainID(key.get<string_view>()) != id) {
+    if (rc || co.getDomainID(key.getNoStripHeader<string_view>()) != id) {
       // cout << "We hit the end of the zone or database. 'after' is apex" << endl;
       after = zonename;
       return false;
     }
-    after = co.getQName(key.get<string_view>()) + zonename;
+    after = co.getQName(key.getNoStripHeader<string_view>()) + zonename;
     return true;
   }
 
-  if (co.getDomainID(key.get<string_view>()) != id) {
+  if (co.getDomainID(key.getNoStripHeader<string_view>()) != id) {
     // cout << "Ended up in next zone, 'after' is zonename" <<endl;
     after = zonename;
     // cout << "Now hunting for previous" << endl;
@@ -1472,39 +2336,39 @@ bool LMDBBackend::getBeforeAndAfterNames(uint32_t id, const DNSName& zonenameU,
         return false;
       }
 
-      if (co.getDomainID(key.get<string_view>()) != id) {
-        // cout<<"Reversed into zone, but found wrong zone id " << co.getDomainID(key.get<string_view>()) << " != "<<id<<endl;
+      if (co.getDomainID(key.getNoStripHeader<string_view>()) != id) {
+        // cout<<"Reversed into zone, but found wrong zone id " << co.getDomainID(key.getNoStripHeader<string_view>()) << " != "<<id<<endl;
         // "this can't happen"
         return false;
       }
       LMDBResourceRecord lrr;
       serFromString(val.get<StringView>(), lrr);
-      if (co.getQType(key.get<string_view>()).getCode() && (lrr.auth || co.getQType(key.get<string_view>()).getCode() == QType::NS))
+      if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()).getCode() == QType::NS))
         break;
     }
 
-    before = co.getQName(key.get<string_view>()) + zonename;
+    before = co.getQName(key.getNoStripHeader<string_view>()) + zonename;
     // cout<<"Found: "<< before<<endl;
     return true;
   }
 
-  // cout <<"We ended up after "<<qname<<", on "<<co.getQName(key.get<string_view>())<<endl;
+  // cout <<"We ended up after "<<qname<<", on "<<co.getQName(key.getNoStripHeader<string_view>())<<endl;
 
   int skips = 0;
   for (;;) {
     LMDBResourceRecord lrr;
     serFromString(val.get<StringView>(), lrr);
-    if (co.getQType(key.get<string_view>()).getCode() && (lrr.auth || co.getQType(key.get<string_view>()).getCode() == QType::NS)) {
-      after = co.getQName(key.get<string_view>()) + zonename;
-      // cout <<"Found auth ("<<lrr.auth<<") or an NS record "<<after<<", type: "<<co.getQType(key.get<string_view>()).toString()<<", ttl = "<<lrr.ttl<<endl;
+    if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()).getCode() == QType::NS)) {
+      after = co.getQName(key.getNoStripHeader<string_view>()) + zonename;
+      // cout <<"Found auth ("<<lrr.auth<<") or an NS record "<<after<<", type: "<<co.getQType(key.getNoStripHeader<string_view>()).toString()<<", ttl = "<<lrr.ttl<<endl;
       // cout << makeHexDump(val.get<string>()) << endl;
       break;
     }
-    // cout <<"  oops, " << co.getQName(key.get<string_view>()) << " was not auth "<<lrr.auth<< " type=" << lrr.qtype.toString()<<" or NS, so need to skip ahead a bit more" << endl;
+    // cout <<"  oops, " << co.getQName(key.getNoStripHeader<string_view>()) << " was not auth "<<lrr.auth<< " type=" << lrr.qtype.toString()<<" or NS, so need to skip ahead a bit more" << endl;
     int rc = cursor.next(key, val);
     if (!rc)
       ++skips;
-    if (rc || co.getDomainID(key.get<string_view>()) != id) {
+    if (rc || co.getDomainID(key.getNoStripHeader<string_view>()) != id) {
       // cout << "  oops, hit end of database or zone. This means after is apex" <<endl;
       after = zonename;
       break;
@@ -1516,16 +2380,16 @@ bool LMDBBackend::getBeforeAndAfterNames(uint32_t id, const DNSName& zonenameU,
 
   for (;;) {
     int rc = cursor.prev(key, val);
-    if (rc || co.getDomainID(key.get<string_view>()) != id) {
+    if (rc || co.getDomainID(key.getNoStripHeader<string_view>()) != id) {
       // XX I don't think this case can happen
       // cout << "We hit the beginning of the zone or database.. now what" << endl;
       return false;
     }
-    before = co.getQName(key.get<string_view>()) + zonename;
+    before = co.getQName(key.getNoStripHeader<string_view>()) + zonename;
     LMDBResourceRecord lrr;
     serFromString(val.get<string_view>(), lrr);
     // cout<<"And before to "<<before<<", auth = "<<rr.auth<<endl;
-    if (co.getQType(key.get<string_view>()).getCode() && (lrr.auth || co.getQType(key.get<string_view>()) == QType::NS))
+    if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()) == QType::NS))
       break;
     // cout << "Oops, that was wrong, go back one more"<<endl;
   }
@@ -1569,15 +2433,15 @@ bool LMDBBackend::updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName
   bool hasOrderName = !ordername.empty();
   bool needNSEC3 = hasOrderName;
 
-  for (; key.get<StringView>().rfind(matchkey, 0) == 0;) {
+  for (; key.getNoStripHeader<StringView>().rfind(matchkey, 0) == 0;) {
     vector<LMDBResourceRecord> lrrs;
 
-    if (co.getQType(key.get<StringView>()) != QType::NSEC3) {
+    if (co.getQType(key.getNoStripHeader<StringView>()) != QType::NSEC3) {
       serFromString(val.get<StringView>(), lrrs);
       bool changed = false;
       vector<LMDBResourceRecord> newRRs;
-      for (auto lrr : lrrs) {
-        lrr.qtype = co.getQType(key.get<StringView>());
+      for (auto& lrr : lrrs) {
+        lrr.qtype = co.getQType(key.getNoStripHeader<StringView>());
         if (!needNSEC3 && qtype != QType::ANY) {
           needNSEC3 = (lrr.ordername && QType(qtype) != lrr.qtype);
         }
@@ -1587,7 +2451,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));
@@ -1601,7 +2465,9 @@ bool LMDBBackend::updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName
   bool del = false;
   LMDBResourceRecord lrr;
   matchkey = co(domain_id, rel, QType::NSEC3);
-  if (!txn->txn->get(txn->db->dbi, matchkey, val)) {
+  // cerr<<"here qname="<<qname<<" ordername="<<ordername<<" qtype="<<qtype<<" matchkey="<<makeHexDump(matchkey)<<endl;
+  int txngetrc;
+  if (!(txngetrc = txn->txn->get(txn->db->dbi, matchkey, val))) {
     serFromString(val.get<string_view>(), lrr);
 
     if (needNSEC3) {
@@ -1693,27 +2559,40 @@ bool LMDBBackend::updateEmptyNonTerminals(uint32_t domain_id, set<DNSName>& inse
 }
 
 /* TSIG */
-bool LMDBBackend::getTSIGKey(const DNSName& name, DNSName* algorithm, string* content)
+bool LMDBBackend::getTSIGKey(const DNSName& name, DNSName& algorithm, string& content)
 {
   auto txn = d_ttsig->getROTransaction();
+  LMDBIDvec ids;
+  txn.get_multi<0>(name, ids);
+
+  TSIGKey key;
+  for (auto id : ids) {
+    if (txn.get(id, key)) {
+      if (algorithm.empty() || algorithm == DNSName(key.algorithm)) {
+        algorithm = DNSName(key.algorithm);
+        content = key.key;
+      }
+    }
+  }
 
-  TSIGKey tk;
-  if (!txn.get<0>(name, tk))
-    return false;
-  if (algorithm)
-    *algorithm = tk.algorithm;
-  if (content)
-    *content = tk.key;
   return true;
 }
+
 // this deletes an old key if it has the same algorithm
 bool LMDBBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content)
 {
   auto txn = d_ttsig->getRWTransaction();
 
-  for (auto range = txn.equal_range<0>(name); range.first != range.second; ++range.first) {
-    if (range.first->algorithm == algorithm)
-      range.first.del();
+  LMDBIDvec ids;
+  txn.get_multi<0>(name, ids);
+
+  TSIGKey key;
+  for (auto id : ids) {
+    if (txn.get(id, key)) {
+      if (key.algorithm == algorithm) {
+        txn.del(id);
+      }
+    }
   }
 
   TSIGKey tk;
@@ -1729,10 +2608,16 @@ bool LMDBBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, cons
 bool LMDBBackend::deleteTSIGKey(const DNSName& name)
 {
   auto txn = d_ttsig->getRWTransaction();
-  TSIGKey tk;
 
-  for (auto range = txn.equal_range<0>(name); range.first != range.second; ++range.first) {
-    range.first.del();
+  LMDBIDvec ids;
+  txn.get_multi<0>(name, ids);
+
+  TSIGKey key;
+
+  for (auto id : ids) {
+    if (txn.get(id, key)) {
+      txn.del(id);
+    }
   }
   txn.commit();
   return true;
@@ -1745,7 +2630,138 @@ bool LMDBBackend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
   for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
     keys.push_back(*iter);
   }
-  return false;
+  return true;
+}
+
+string LMDBBackend::directBackendCmd(const string& query)
+{
+  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();
+  }
+
+  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
@@ -1762,6 +2778,8 @@ public:
     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 99244a9ef86f626ed5e5dede5f1b9e6a8bc63573..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,15 +74,28 @@ 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;
   bool get(DNSResourceRecord& rr) override;
   bool get(DNSZoneRecord& dzr) override;
 
-  void getUnfreshSlaveInfos(vector<DomainInfo>* domains) override;
+  // secondary support
+  void getUnfreshSecondaryInfos(vector<DomainInfo>* domains) override;
+  void setStale(uint32_t domain_id) override;
+  void setFresh(uint32_t domain_id) override;
+
+  // primary support
+  void getUpdatedPrimaries(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
+  void setNotified(uint32_t id, uint32_t serial) override;
+
+  // catalog zones
+  bool getCatalogMembers(const DNSName& catalog, vector<CatalogInfo>& members, CatalogInfo::CatalogType type) override;
+  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
@@ -103,9 +116,6 @@ public:
   }
 
   bool setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta) override;
-  void setStale(uint32_t domain_id) override;
-  void setFresh(uint32_t domain_id) override;
-  void setNotified(uint32_t id, uint32_t serial) override;
   bool setAccount(const DNSName& domain, const std::string& account) override;
   bool deleteDomain(const DNSName& domain) override;
 
@@ -118,7 +128,7 @@ public:
   bool unpublishDomainKey(const DNSName& name, unsigned int id) override;
 
   // TSIG
-  bool getTSIGKey(const DNSName& name, DNSName* algorithm, string* content) override;
+  bool getTSIGKey(const DNSName& name, DNSName& algorithm, string& content) override;
   bool setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content) override;
   bool deleteTSIGKey(const DNSName& name) override;
   bool getTSIGKeys(std::vector<struct TSIGKey>& keys) override;
@@ -127,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;
 
@@ -138,6 +148,13 @@ public:
     return true;
   }
 
+  // other
+  string directBackendCmd(const string& query) override;
+
+  // functions to use without constructing a backend object
+  static std::pair<uint32_t, uint32_t> getSchemaVersionAndShards(std::string& filename);
+  static bool upgradeToSchemav5(std::string& filename);
+
 private:
   struct compoundOrdername
   {
@@ -226,7 +243,7 @@ public:
   class LMDBResourceRecord : public DNSResourceRecord
   {
   public:
-    LMDBResourceRecord() {}
+    LMDBResourceRecord() = default;
     LMDBResourceRecord(const DNSResourceRecord& rr) :
       DNSResourceRecord(rr), ordername(false) {}
 
@@ -288,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();
@@ -312,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 4e99b01eb9cf1f55b10dd528783e0cdf29ac7b5c..b91e63ba63c826344c3ff0a0efac268773d6cad1 100644 (file)
@@ -1,2 +1,3 @@
 1:4.2.2
-2:4.3.0
\ No newline at end of file
+2:4.3.0
+3:4.4.0
diff --git a/modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb-0 b/modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb-0
deleted file mode 100644 (file)
index 4bc4471..0000000
Binary files a/modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb-0 and /dev/null differ
diff --git a/modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb-1 b/modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb-1
deleted file mode 100644 (file)
index 65c700b..0000000
Binary files a/modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb-1 and /dev/null differ
diff --git a/modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb-0 b/modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb-0
deleted file mode 100644 (file)
index 4bc4471..0000000
Binary files a/modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb-0 and /dev/null differ
diff --git a/modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb-1 b/modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb-1
deleted file mode 100644 (file)
index 65c700b..0000000
Binary files a/modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb-1 and /dev/null differ
similarity index 95%
rename from modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb
rename to modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb
index d9b1f630e9e5f9281d55663e66a0f976646439e2..856f85242659bbca5e9f0f140010128ffbd61b3b 100644 (file)
Binary files a/modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb and b/modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb differ
diff --git a/modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb-0 b/modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb-0
new file mode 100644 (file)
index 0000000..9bed565
Binary files /dev/null and b/modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb-0 differ
similarity index 97%
rename from modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb-0-lock
rename to modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb-0-lock
index 26ca8a5f365fd503e3e11bc90fa6454543a92044..59479a6fb7dadb307e78cf5a0f69b35e42c3740b 100644 (file)
Binary files a/modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb-0-lock and b/modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb-0-lock differ
diff --git a/modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb-1 b/modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb-1
new file mode 100644 (file)
index 0000000..fa74288
Binary files /dev/null and b/modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb-1 differ
similarity index 97%
rename from modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb-1-lock
rename to modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb-1-lock
index ba97d65258bc0fee208828391896fb23a5b99da6..8d710b8cb3c63f33724a8fa7c96138df15c71b1d 100644 (file)
Binary files a/modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb-1-lock and b/modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb-1-lock differ
similarity index 97%
rename from modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb-lock
rename to modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb-lock
index a833f3b30a2fc4e92725e270956d7af2594432e6..f82fccb7791bf92010e6391df89fdd0dcc511de6 100644 (file)
Binary files a/modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb-lock and b/modules/lmdbbackend/test-assets/lmdb-v3-x86_64/pdns.lmdb-lock differ
similarity index 95%
rename from modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb
rename to modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb
index e3080c130fc14c94a04cc91845a80620ea9b3f4d..0d56ee3f5dd028dbeb12e206b88cd1d1d10f035f 100644 (file)
Binary files a/modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb and b/modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb differ
diff --git a/modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb-0 b/modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb-0
new file mode 100644 (file)
index 0000000..9bed565
Binary files /dev/null and b/modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb-0 differ
similarity index 97%
rename from modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb-0-lock
rename to modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb-0-lock
index eaa314cbde70d4bb48f4112c14aa5506e66c398d..cb698cc0d9519e7f7abbf70f3b941777437e2e42 100644 (file)
Binary files a/modules/lmdbbackend/test-assets/lmdb-v2-x86_64/pdns.lmdb-0-lock and b/modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb-0-lock differ
diff --git a/modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb-1 b/modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb-1
new file mode 100644 (file)
index 0000000..fa74288
Binary files /dev/null and b/modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb-1 differ
similarity index 97%
rename from modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb-1-lock
rename to modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb-1-lock
index 3858eb5a6dd3a436086ee27349d3ce3e944d79df..4cf2b295911bc2b7c44df330f0a231adee07a2b3 100644 (file)
Binary files a/modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb-1-lock and b/modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb-1-lock differ
similarity index 97%
rename from modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb-lock
rename to modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb-lock
index b3399636ce6af381310c0bec4c214b5393d9f3b7..62c3165d538baad93fb8410092d2db067600e415 100644 (file)
Binary files a/modules/lmdbbackend/test-assets/lmdb-v1-x86_64/pdns.lmdb-lock and b/modules/lmdbbackend/test-assets/lmdb-v4-x86_64/pdns.lmdb-lock differ
index 3b2477a83efc34df4f9c145eec45f2d797b98608..0a0d6c7b8f686618dafb18d411108ecefb726f98 100644 (file)
@@ -1,5 +1,6 @@
 AM_CPPFLAGS += $(LUA_CFLAGS) \
-       -I$(top_srcdir)/ext/luawrapper/include
+       -I$(top_srcdir)/ext/luawrapper/include \
+       $(LIBCRYPTO_INCLUDES)
 
 EXTRA_DIST = OBJECTFILES OBJECTLIBS
 
index aaedf1a9d666ea4d17ab8eb95925eba7f9997186..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);
@@ -176,7 +176,7 @@ public:
       g_log << Logger::Debug << "[" << getPrefix() << "] Got empty result" << endl;
   }
 
-  bool list(const DNSName& target, int domain_id, bool include_disabled = false) override
+  bool list(const DNSName& target, int domain_id, bool /* include_disabled */ = false) override
   {
     if (f_list == nullptr) {
       g_log << Logger::Error << "[" << getPrefix() << "] dns_list missing - cannot do AXFR" << endl;
@@ -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")
@@ -272,7 +272,7 @@ public:
     logResult("zone=" << di.zone << ",serial=" << di.serial << ",kind=" << di.getKindString());
   }
 
-  bool getDomainInfo(const DNSName& domain, DomainInfo& di, bool getSerial = true) override
+  bool getDomainInfo(const DNSName& domain, DomainInfo& di, bool /* getSerial */ = true) override
   {
     if (f_get_domaininfo == nullptr) {
       // use getAuth instead
@@ -298,7 +298,7 @@ public:
     return true;
   }
 
-  void getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool include_disabled) override
+  void getAllDomains(vector<DomainInfo>* domains, bool /* getSerial */, bool /* include_disabled */) override
   {
     if (f_get_all_domains == nullptr)
       return;
@@ -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 dad80045c5bb81b9cb77de0225c4ecd9591eb280..e22d3a940ff6b4d7a7c7e6a9f331537ba163b80e 100644 (file)
@@ -1,5 +1,5 @@
-0      www.test.invalid.       IN      A       60      127.0.0.3
-0      www.test.invalid.       IN      RRSIG   60      A 13 3 60 [expiry] [inception] [keytag] test.invalid. ...
-2      .       IN      OPT     32768   
+0      www.test.invalid.       60      IN      A       127.0.0.3
+0      www.test.invalid.       60      IN      RRSIG   A 13 3 60 [expiry] [inception] [keytag] test.invalid. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.test.invalid.', qtype=A
index c6d0549c1bf711af153a25f3c134b05bab9a315c..69e60ab3c2f593d5a2379bd5c0355f12f73b3476 100644 (file)
@@ -1,3 +1,3 @@
-0      www.test.invalid.       IN      A       60      127.0.0.3
+0      www.test.invalid.       60      IN      A       127.0.0.3
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.test.invalid.', qtype=A
index 2e4ec3c238cc168ab6087592195c483b03236fce..96c7639233cb725a5c1b554c56347d780cccf095 100644 (file)
@@ -1,3 +1,3 @@
-0      www.test.invalid.       IN      AAAA    60      fe80::3
+0      www.test.invalid.       60      IN      AAAA    fe80::3
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.test.invalid.', qtype=AAAA
index 4be5b12351309ea84152f306decd3492311ef847..93c37ef9b766cde2a9979663fa8664b5f0b03810 100644 (file)
@@ -1,9 +1,9 @@
-1      ns1.test.invalid.       IN      NSEC    4       www.test.invalid. A AAAA RRSIG NSEC
-1      ns1.test.invalid.       IN      RRSIG   4       NSEC 13 3 4 [expiry] [inception] [keytag] test.invalid. ...
-1      test.invalid.   IN      NSEC    4       ns1.test.invalid. NS SOA TXT RRSIG NSEC DNSKEY
-1      test.invalid.   IN      RRSIG   4       NSEC 13 2 4 [expiry] [inception] [keytag] test.invalid. ...
-1      test.invalid.   IN      RRSIG   4       SOA 13 2 60 [expiry] [inception] [keytag] test.invalid. ...
-1      test.invalid.   IN      SOA     4       ns1.test.invalid. root.test.invalid. 20180115 1 2 3 4
-2      .       IN      OPT     32768   
+1      ns1.test.invalid.       4       IN      NSEC    www.test.invalid. A AAAA RRSIG NSEC
+1      ns1.test.invalid.       4       IN      RRSIG   NSEC 13 3 4 [expiry] [inception] [keytag] test.invalid. ...
+1      test.invalid.   4       IN      NSEC    ns1.test.invalid. NS SOA TXT RRSIG NSEC DNSKEY
+1      test.invalid.   4       IN      RRSIG   NSEC 13 2 4 [expiry] [inception] [keytag] test.invalid. ...
+1      test.invalid.   4       IN      RRSIG   SOA 13 2 60 [expiry] [inception] [keytag] test.invalid. ...
+1      test.invalid.   4       IN      SOA     ns1.test.invalid. root.test.invalid. 20180115 1 2 3 4
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='wap.test.invalid.', qtype=A
index 1a93837e55d1cc7add07dbfd203769069c095e6e..955e72c1f30d367575667d7f16ad8aa8f75a4363 100644 (file)
@@ -1,7 +1,7 @@
-1      test.invalid.   IN      NSEC    4       ns1.test.invalid. NS SOA TXT RRSIG NSEC DNSKEY
-1      test.invalid.   IN      RRSIG   4       NSEC 13 2 4 [expiry] [inception] [keytag] test.invalid. ...
-1      test.invalid.   IN      RRSIG   4       SOA 13 2 60 [expiry] [inception] [keytag] test.invalid. ...
-1      test.invalid.   IN      SOA     4       ns1.test.invalid. root.test.invalid. 20180115 1 2 3 4
-2      .       IN      OPT     32768   
+1      test.invalid.   4       IN      NSEC    ns1.test.invalid. NS SOA TXT RRSIG NSEC DNSKEY
+1      test.invalid.   4       IN      RRSIG   NSEC 13 2 4 [expiry] [inception] [keytag] test.invalid. ...
+1      test.invalid.   4       IN      RRSIG   SOA 13 2 60 [expiry] [inception] [keytag] test.invalid. ...
+1      test.invalid.   4       IN      SOA     ns1.test.invalid. root.test.invalid. 20180115 1 2 3 4
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='middle.test.invalid.', qtype=A
index b91553dbc5fa431809b1d3cfd8c484a1014dc26a..8dbf30dbb8dda26652b1322b8fb7f4a3aff48893 100644 (file)
@@ -1,5 +1,7 @@
 pkglib_LTLIBRARIES = libpipebackend.la
 
+AM_CPPFLAGS += $(LIBCRYPTO_INCLUDES)
+
 EXTRA_DIST = \
        OBJECTFILES \
        OBJECTLIBS \
index 92c15f23e90c7f6766d13d8c5b086e6b586592ed..632e50c663a41f7324a400a704d8d82c4d38d67a 100644 (file)
@@ -26,8 +26,8 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <string>
-#include <errno.h>
-#include <signal.h>
+#include <cerrno>
+#include <csignal>
 #include <string.h>
 #include <sys/types.h>
 #include <sys/wait.h>
@@ -121,15 +121,15 @@ void CoProcess::checkStatus()
   int status;
   int ret = waitpid(d_pid, &status, WNOHANG);
   if (ret < 0)
-    throw PDNSException("Unable to ascertain status of coprocess " + itoa(d_pid) + " from " + itoa(getpid()) + ": " + string(strerror(errno)));
+    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 (WIFEXITED(status)) {
       int exitStatus = WEXITSTATUS(status);
-      throw PDNSException("Coprocess exited with code " + itoa(exitStatus));
+      throw PDNSException("Coprocess exited with code " + std::to_string(exitStatus));
     }
     if (WIFSIGNALED(status)) {
       int sig = WTERMSIG(status);
-      string reason = "CoProcess died on receiving signal " + itoa(sig);
+      string reason = "CoProcess died on receiving signal " + std::to_string(sig);
 #ifdef WCOREDUMP
       if (WCOREDUMP(status))
         reason += ". Dumped core";
@@ -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);
@@ -218,7 +218,7 @@ void CoProcess::sendReceive(const string& snd, string& rcv)
   receive(rcv);
 }
 
-UnixRemote::UnixRemote(const string& path, int timeout)
+UnixRemote::UnixRemote(const string& path)
 {
   d_fd = socket(AF_UNIX, SOCK_STREAM, 0);
   if (d_fd < 0)
index 033d8129dfb1d7c1f400efaad4614107e8926498..c9185132abf4ba4e25c6a112887829a693d6dead 100644 (file)
@@ -29,7 +29,7 @@
 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 +39,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;
@@ -60,7 +60,7 @@ private:
 class UnixRemote : public CoRemote
 {
 public:
-  UnixRemote(const string& path, int timeout = 0);
+  UnixRemote(const string& path);
   void sendReceive(const string& send, string& receive) override;
   void receive(string& rcv) override;
   void send(const string& send) override;
index 0f3dcbf5a97e1fac4736e565ddc5be20a416763b..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()
 {
@@ -66,7 +64,7 @@ void CoWrapper::launch()
     throw ArgException("pipe-command is not specified");
 
   if (isUnixSocket(d_command)) {
-    d_cp = std::make_unique<UnixRemote>(d_command, d_timeout);
+    d_cp = std::make_unique<UnixRemote>(d_command);
   }
   else {
     auto coprocess = std::make_unique<CoProcess>(d_command, d_timeout);
@@ -197,7 +195,7 @@ void PipeBackend::lookup(const QType& qtype, const DNSName& qname, int zoneId, D
   d_qname = qname;
 }
 
-bool PipeBackend::list(const DNSName& target, int inZoneId, bool include_disabled)
+bool PipeBackend::list(const DNSName& target, int inZoneId, bool /* include_disabled */)
 {
   try {
     launch();
@@ -216,7 +214,7 @@ bool PipeBackend::list(const DNSName& target, int inZoneId, bool include_disable
   catch (PDNSException& ae) {
     g_log << Logger::Error << kBackendId << " Error from coprocess: " << ae.reason << endl;
   }
-  d_qname = DNSName(itoa(inZoneId)); // why do we store a number here??
+  d_qname = DNSName(std::to_string(inZoneId)); // why do we store a number here??
   return true;
 }
 
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 1b5fc20025856dc2b86aabad2d5f2dc1ff836151..1e2032a59d36fd46fa44f4a286474234cb420b63 100644 (file)
@@ -2,6 +2,7 @@ AM_CPPFLAGS += \
        -I$(top_srcdir)/ext/json11 \
        $(YAHTTP_CFLAGS) \
        $(LIBCRYPTO_CFLAGS) \
+       $(LIBCRYPTO_INCLUDES) \
        $(LIBZMQ_CFLAGS)
 
 if LUA
@@ -15,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 \
@@ -109,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 \
@@ -121,6 +122,7 @@ libtestremotebackend_la_SOURCES = \
        ../../pdns/ednscookies.cc \
        ../../pdns/ednsoptions.cc ../../pdns/ednsoptions.hh \
        ../../pdns/ednssubnet.cc \
+       ../../pdns/gss_context.cc ../../pdns/gss_context.hh \
        ../../pdns/iputils.cc \
        ../../pdns/json.hh ../../pdns/json.cc \
        ../../pdns/logger.cc \
@@ -175,44 +177,51 @@ libtestremotebackend_la_CPPFLAGS += \
        $(P11KIT1_CFLAGS)
 endif
 
+if GSS_TSIG
+libtestremotebackend_la_LIBADD += \
+       $(GSS_LIBS)
+libtestremotebackend_la_CPPFLAGS+= \
+       $(GSS_CFLAGS)
+endif
+
 remotebackend_http_test_SOURCES = \
        test-remotebackend-http.cc \
        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 5d563acc0adccc29677f9b8ca8e8345383e676f9..cc90a441978fc7bc9e93adf9bc72707515bd06a1 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 = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(d_fd2[0], "r"), fclose))) {
       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,43 +172,49 @@ 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)
-    throw PDNSException("Unable to ascertain status of coprocess " + itoa(d_pid) + " from " + itoa(getpid()) + ": " + string(strerror(errno)));
-  else if (ret) {
+  if (ret < 0) {
+    throw PDNSException("Unable to ascertain status of coprocess " + std::to_string(d_pid) + " from " + std::to_string(getpid()) + ": " + string(strerror(errno)));
+  }
+  if (ret != 0) {
     if (WIFEXITED(status)) {
       int exitStatus = WEXITSTATUS(status);
-      throw PDNSException("Coprocess exited with code " + itoa(exitStatus));
+      throw PDNSException("Coprocess exited with code " + std::to_string(exitStatus));
     }
     if (WIFSIGNALED(status)) {
       int sig = WTERMSIG(status);
-      string reason = "CoProcess died on receiving signal " + itoa(sig);
+      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 fad201873389c4a5d23e719b720c67f878d77d86..6ae8a5c268b0d3ea63a7c454722a47a3e1b9eb4d 100644 (file)
@@ -1,4 +1,4 @@
-0      up.example.com. IN      NS      120     ns1.example.com.
-0      up.example.com. IN      NS      120     ns2.example.com.
+0      up.example.com. 120     IN      NS      ns1.example.com.
+0      up.example.com. 120     IN      NS      ns2.example.com.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='up.example.com.', qtype=NS
index 56c44b5317e6edaf90dd782c10b2ebfa35061af0..0e6c2c99ae902b929efa35cfe35604b42598d9b6 100644 (file)
@@ -1,5 +1,5 @@
-0      outpost.example.com.    IN      A       120     192.168.2.1
-0      outpost.example.com.    IN      RRSIG   120     A 13 3 120 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+0      outpost.example.com.    120     IN      A       192.168.2.1
+0      outpost.example.com.    120     IN      RRSIG   A 13 3 120 [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='outpost.example.com.', qtype=A
index b7318a1274eaaaa25fb05c426eed9934aafeeadb..abe4a12b3f86cd7c75a9fca131ba438cd86ec4e9 100644 (file)
@@ -1,3 +1,3 @@
-0      outpost.example.com.    IN      A       120     192.168.2.1
+0      outpost.example.com.    120     IN      A       192.168.2.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='outpost.example.com.', qtype=A
index fcac5c604325564d3da0218e522fbef599ab44e3..c3bfa1e4fbd6dd9ef06fb6551dcd907029ac4bea 100644 (file)
@@ -1,3 +1,3 @@
-0      outpost.example.com.    IN      AAAA    120     fe80::1
+0      outpost.example.com.    120     IN      AAAA    fe80::1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='outpost.example.com.', qtype=AAAA
index bcf7d1d55d2a8330cdacac62ece1346082b47cef..4d66160c0331afa8969f5aebad1504cf570832ed 100644 (file)
@@ -1,3 +1,3 @@
-0      example.com.    IN      DNSKEY  120     257 3 13 ...
+0      example.com.    120     IN      DNSKEY  257 3 13 ...
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='example.com.', qtype=DNSKEY
index 76b71f08b5a511b5436d14fc54e705fbf5937826..2a5d99167f1029ee38c924d4dd6fa320c428b11a 100644 (file)
@@ -1,3 +1,3 @@
-0      jump.up.example.com.    IN      TXT     120     "a very very long indeed text string that should pass out clean and proper thru the entire chain of powerdns processing"
+0      jump.up.example.com.    120     IN      TXT     "a very very long indeed text string that should pass out clean and proper thru the entire chain of powerdns processing"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='jump.up.example.com.', qtype=TXT
index fad201873389c4a5d23e719b720c67f878d77d86..6ae8a5c268b0d3ea63a7c454722a47a3e1b9eb4d 100644 (file)
@@ -1,4 +1,4 @@
-0      up.example.com. IN      NS      120     ns1.example.com.
-0      up.example.com. IN      NS      120     ns2.example.com.
+0      up.example.com. 120     IN      NS      ns1.example.com.
+0      up.example.com. 120     IN      NS      ns2.example.com.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='up.example.com.', qtype=NS
index 3e0527692ed02b486404b81b74ebeb890d292085..81eb87b0829729607f54b45bb0451408bd62c008 100644 (file)
@@ -1,9 +1,9 @@
-1      example.com.    IN      NSEC    120     ns1.example.com. NS SOA RRSIG NSEC DNSKEY
-1      example.com.    IN      RRSIG   120     NSEC 13 2 120 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   120     SOA 13 2 120 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     120     ns1.example.com. hostmaster.example.com. 2000010101 28800 7200 1209600 120
-1      ns2.example.com.        IN      NSEC    120     outpost.example.com. A RRSIG NSEC
-1      ns2.example.com.        IN      RRSIG   120     NSEC 13 3 120 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      example.com.    120     IN      NSEC    ns1.example.com. NS SOA RRSIG NSEC DNSKEY
+1      example.com.    120     IN      RRSIG   NSEC 13 2 120 [expiry] [inception] [keytag] example.com. ...
+1      example.com.    120     IN      RRSIG   SOA 13 2 120 [expiry] [inception] [keytag] example.com. ...
+1      example.com.    120     IN      SOA     ns1.example.com. hostmaster.example.com. 2000010101 28800 7200 1209600 120
+1      ns2.example.com.        120     IN      NSEC    outpost.example.com. A RRSIG NSEC
+1      ns2.example.com.        120     IN      RRSIG   NSEC 13 3 120 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='outerpost.example.com.', qtype=A
index db99bf313ab69d7bb119ead29ff962b188d36a26..09ab427e40fe15ee75a41adcdc9fb47b27970b25 100644 (file)
@@ -1,7 +1,7 @@
-1      example.com.    IN      NSEC    120     outpost.example.com. NS SOA RRSIG NSEC DNSKEY
-1      example.com.    IN      RRSIG   120     NSEC 13 2 120 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   120     SOA 13 2 120 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     120     ns1.example.com. hostmaster.example.com. 2000010101 28800 7200 1209600 120
-2      .       IN      OPT     32768   
+1      example.com.    120     IN      NSEC    outpost.example.com. NS SOA RRSIG NSEC DNSKEY
+1      example.com.    120     IN      RRSIG   NSEC 13 2 120 [expiry] [inception] [keytag] example.com. ...
+1      example.com.    120     IN      RRSIG   SOA 13 2 120 [expiry] [inception] [keytag] example.com. ...
+1      example.com.    120     IN      SOA     ns1.example.com. hostmaster.example.com. 2000010101 28800 7200 1209600 120
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='outerpost.example.com.', qtype=A
index db99bf313ab69d7bb119ead29ff962b188d36a26..09ab427e40fe15ee75a41adcdc9fb47b27970b25 100644 (file)
@@ -1,7 +1,7 @@
-1      example.com.    IN      NSEC    120     outpost.example.com. NS SOA RRSIG NSEC DNSKEY
-1      example.com.    IN      RRSIG   120     NSEC 13 2 120 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   120     SOA 13 2 120 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     120     ns1.example.com. hostmaster.example.com. 2000010101 28800 7200 1209600 120
-2      .       IN      OPT     32768   
+1      example.com.    120     IN      NSEC    outpost.example.com. NS SOA RRSIG NSEC DNSKEY
+1      example.com.    120     IN      RRSIG   NSEC 13 2 120 [expiry] [inception] [keytag] example.com. ...
+1      example.com.    120     IN      RRSIG   SOA 13 2 120 [expiry] [inception] [keytag] example.com. ...
+1      example.com.    120     IN      SOA     ns1.example.com. hostmaster.example.com. 2000010101 28800 7200 1209600 120
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='outerpost.example.com.', qtype=A
index 19362ecfbf18b1ec08abef9409ee269a6e6f2231..2ed671beba9d3132dd3814eaa8b06220c5fd619d 100644 (file)
@@ -1,5 +1,5 @@
-0      jump.up.example.com.    IN      A       120     192.168.3.1
-0      jump.up.example.com.    IN      RRSIG   120     A 13 4 120 [expiry] [inception] [keytag] up.example.com. ...
-2      .       IN      OPT     32768   
+0      jump.up.example.com.    120     IN      A       192.168.3.1
+0      jump.up.example.com.    120     IN      RRSIG   A 13 4 120 [expiry] [inception] [keytag] up.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='jump.up.example.com.', qtype=A
index e648b1c521845aafbd97b773e322da7711733029..ddf2119715af4a109d3cc72688f6851e9e35fab0 100644 (file)
@@ -1,6 +1,6 @@
-0      jump.up.example.com.    IN      A       120     192.168.3.1
-0      jump.up.example.com.    IN      RRSIG   120     A 13 4 120 [expiry] [inception] [keytag] up.example.com. ...
-0      jump.up.example.com.    IN      RRSIG   120     A 13 4 120 [expiry] [inception] [keytag] up.example.com. ...
-2      .       IN      OPT     32768   
+0      jump.up.example.com.    120     IN      A       192.168.3.1
+0      jump.up.example.com.    120     IN      RRSIG   A 13 4 120 [expiry] [inception] [keytag] up.example.com. ...
+0      jump.up.example.com.    120     IN      RRSIG   A 13 4 120 [expiry] [inception] [keytag] up.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='jump.up.example.com.', qtype=A
index e648b1c521845aafbd97b773e322da7711733029..ddf2119715af4a109d3cc72688f6851e9e35fab0 100644 (file)
@@ -1,6 +1,6 @@
-0      jump.up.example.com.    IN      A       120     192.168.3.1
-0      jump.up.example.com.    IN      RRSIG   120     A 13 4 120 [expiry] [inception] [keytag] up.example.com. ...
-0      jump.up.example.com.    IN      RRSIG   120     A 13 4 120 [expiry] [inception] [keytag] up.example.com. ...
-2      .       IN      OPT     32768   
+0      jump.up.example.com.    120     IN      A       192.168.3.1
+0      jump.up.example.com.    120     IN      RRSIG   A 13 4 120 [expiry] [inception] [keytag] up.example.com. ...
+0      jump.up.example.com.    120     IN      RRSIG   A 13 4 120 [expiry] [inception] [keytag] up.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='jump.up.example.com.', qtype=A
index 0ec92452332918c6d2cb2100052c07ac716bbc6e..8f8eb15c7991428bc7c7b21032ce2df761d02ff8 100644 (file)
@@ -1,7 +1,7 @@
-1      example.com.    IN      NSEC    120     ns1.example.com. NS SOA RRSIG NSEC DNSKEY
-1      example.com.    IN      RRSIG   120     NSEC 13 2 120 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   120     SOA 13 2 120 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     120     ns1.example.com. hostmaster.example.com. 2000010101 28800 7200 1209600 120
-2      .       IN      OPT     32768   
+1      example.com.    120     IN      NSEC    ns1.example.com. NS SOA RRSIG NSEC DNSKEY
+1      example.com.    120     IN      RRSIG   NSEC 13 2 120 [expiry] [inception] [keytag] example.com. ...
+1      example.com.    120     IN      RRSIG   SOA 13 2 120 [expiry] [inception] [keytag] example.com. ...
+1      example.com.    120     IN      SOA     ns1.example.com. hostmaster.example.com. 2000010101 28800 7200 1209600 120
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.example.com.', qtype=A
index 7f7f1abe95eb146c0297bde7d53c1157012c806e..accfad51db3ce325c4bc863fa99e1a35b2b9f9c9 100644 (file)
@@ -1,11 +1,11 @@
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      NSEC3   86400   1 1 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2000081501 28800 7200 604800 86400
-1      gnk5kv3h2h1h8ge405j6093608ukp3i5.example.com.   IN      NSEC3   86400   1 1 1 abcd GNO4LESKG6U7HKEJ9UL71SF1HD7F1P96 A RRSIG
-1      gnk5kv3h2h1h8ge405j6093608ukp3i5.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 1 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   86400   IN      NSEC3   1 1 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.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. 2000081501 28800 7200 604800 86400
+1      gnk5kv3h2h1h8ge405j6093608ukp3i5.example.com.   86400   IN      NSEC3   1 1 1 abcd GNO4LESKG6U7HKEJ9UL71SF1HD7F1P96 A RRSIG
+1      gnk5kv3h2h1h8ge405j6093608ukp3i5.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 1 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='zzz.example.com.', qtype=A
index 124cc56e92123f782fb1b21efe0f4f9897023572..67b2c1a789920c1c852e8154b9048ece84a8a923 100644 (file)
@@ -1,9 +1,9 @@
-1      example.com.    IN      NSEC    120     ns1.example.com. NS SOA RRSIG NSEC DNSKEY
-1      example.com.    IN      RRSIG   120     NSEC 13 2 120 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   120     SOA 13 2 120 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     120     ns1.example.com. hostmaster.example.com. 2000010101 28800 7200 1209600 120
-1      www.example.com.        IN      NSEC    120     example.com. A RRSIG NSEC
-1      www.example.com.        IN      RRSIG   120     NSEC 13 3 120 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      example.com.    120     IN      NSEC    ns1.example.com. NS SOA RRSIG NSEC DNSKEY
+1      example.com.    120     IN      RRSIG   NSEC 13 2 120 [expiry] [inception] [keytag] example.com. ...
+1      example.com.    120     IN      RRSIG   SOA 13 2 120 [expiry] [inception] [keytag] example.com. ...
+1      example.com.    120     IN      SOA     ns1.example.com. hostmaster.example.com. 2000010101 28800 7200 1209600 120
+1      www.example.com.        120     IN      NSEC    example.com. A RRSIG NSEC
+1      www.example.com.        120     IN      RRSIG   NSEC 13 3 120 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='zzz.example.com.', qtype=A
index 7f7f1abe95eb146c0297bde7d53c1157012c806e..accfad51db3ce325c4bc863fa99e1a35b2b9f9c9 100644 (file)
@@ -1,11 +1,11 @@
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      NSEC3   86400   1 1 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2000081501 28800 7200 604800 86400
-1      gnk5kv3h2h1h8ge405j6093608ukp3i5.example.com.   IN      NSEC3   86400   1 1 1 abcd GNO4LESKG6U7HKEJ9UL71SF1HD7F1P96 A RRSIG
-1      gnk5kv3h2h1h8ge405j6093608ukp3i5.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 1 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   86400   IN      NSEC3   1 1 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.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. 2000081501 28800 7200 604800 86400
+1      gnk5kv3h2h1h8ge405j6093608ukp3i5.example.com.   86400   IN      NSEC3   1 1 1 abcd GNO4LESKG6U7HKEJ9UL71SF1HD7F1P96 A RRSIG
+1      gnk5kv3h2h1h8ge405j6093608ukp3i5.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 1 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='zzz.example.com.', qtype=A
index 380d956442de3440fe63523461c1f04e1199b1df..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()
@@ -496,22 +511,24 @@ bool RemoteBackend::doesDNSSEC()
   return d_dnssec;
 }
 
-bool RemoteBackend::getTSIGKey(const DNSName& name, DNSName* algorithm, std::string* content)
+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");
+  algorithm = DNSName(stringFromJson(answer["result"], "algorithm"));
+  content = stringFromJson(answer["result"], "content");
 
   return true;
 }
@@ -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;
@@ -600,17 +616,20 @@ void RemoteBackend::parseDomainInfo(const Json& obj, DomainInfo& di)
   di.backend = this;
 }
 
-bool RemoteBackend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool getSerial)
+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,13 +717,10 @@ 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)
+bool RemoteBackend::feedRecord(const DNSResourceRecord& rr, const DNSName& ordername, bool /* ordernameIsNSEC3 */)
 {
   Json query = Json::object{
     {"method", "feedRecord"},
@@ -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,24 +872,26 @@ 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;
 }
 
-void RemoteBackend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool include_disabled)
+void RemoteBackend::getAllDomains(vector<DomainInfo>* domains, bool /* getSerial */, bool include_disabled)
 {
   Json query = Json::object{
     {"method", "getAllDomains"},
     {"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,14 +900,7 @@ void RemoteBackend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, b
   }
 }
 
-void RemoteBackend::alsoNotifies(const DNSName& domain, set<string>* ips)
-{
-  std::vector<std::string> meta;
-  getDomainMetadata(domain, "ALSO-NOTIFY", meta);
-  ips->insert(meta.begin(), meta.end());
-}
-
-void RemoteBackend::getUpdatedMasters(vector<DomainInfo>* domains)
+void RemoteBackend::getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
 {
   Json query = Json::object{
     {"method", "getUpdatedMasters"},
@@ -893,20 +908,22 @@ void RemoteBackend::getUpdatedMasters(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;
     this->parseDomainInfo(row, di);
-    domains->push_back(di);
+    domains.push_back(di);
   }
 }
 
-void RemoteBackend::getUnfreshSlaveInfos(vector<DomainInfo>* domains)
+void RemoteBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
 {
   Json query = Json::object{
     {"method", "getUnfreshSlaveInfos"},
@@ -914,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;
@@ -934,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;
   }
 }
@@ -946,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;
   }
 }
@@ -958,7 +977,7 @@ DNSBackend* RemoteBackend::maker()
   }
   catch (...) {
     g_log << Logger::Error << kBackendId << " Unable to instantiate a remotebackend!" << endl;
-    return 0;
+    return nullptr;
   };
 }
 
index 08811fe078ea7e6036272d93ed1242fd1a857b1f..a435465173944cbd901be27a0971567e22f3443a 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,19 +143,19 @@ 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};
@@ -162,7 +165,7 @@ 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;
@@ -171,7 +174,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 getDomainKeys(const DNSName& name, std::vector<DNSBackend::KeyData>& keys) override;
-  bool getTSIGKey(const DNSName& name, DNSName* algorithm, std::string* content) override;
+  bool getTSIGKey(const DNSName& name, DNSName& algorithm, std::string& content) override;
   bool getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after) override;
   bool setDomainMetadata(const DNSName& name, const string& kind, const std::vector<std::basic_string<char>>& meta) override;
   bool removeDomainKey(const DNSName& name, unsigned int id) 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,12 +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) override;
-  void alsoNotifies(const DNSName& domain, set<string>* ips) 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;
 
@@ -212,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 06ebeb596eb207a205b178e8cddb6d36ab759209..ebbb636cc8af5c3da509ceeafd9b7e1548246b59 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,156 +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);
 
-  di = result[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);
+  BOOST_REQUIRE(!result.empty());
+  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);
+  backendUnderTest->getUpdatedPrimaries(result, catalogs, hashes);
 
-  BOOST_CHECK(result.size() > 0);
+  BOOST_REQUIRE(!result.empty());
 
-  di = result[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 9fd071528bf6fae586d66858f4ef8eb3360b7d29..82447c617264edc6d7c2e85d50a2f34b85fa2733 100644 (file)
@@ -62,42 +62,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 +117,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 +146,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 +157,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 +183,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 +206,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 f8adb95c3d3b6ed08ee236b9a791cea39d69ec6a..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
@@ -20167,6 +20169,9 @@ Zexample.com:ns1.example.com.:ahu.example.com.:2847484148:28800:7200:604800:8640
 &sub.test.test.com::ns-test.example.net.test.com.:3600
 &test.com::ns1.test.com.:3600
 &test.com::ns2.test.com.:3600
++10.order.test.com:192.168.0.1:3600
++100.order.test.com:192.168.0.1:3600
++15.order.test.com:192.168.0.1:3600
 +\052.a.b.c.test.com:8.7.6.5:3600
 +b.c.test.com:5.6.7.8:3600
 +blah.test.com:192.168.6.1:3600
@@ -20219,6 +20224,7 @@ Ztest.com:ns1.test.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
 +ttl.test.dyndns:127.0.0.1:3600
 :delete-add.test.dyndns:16:\034Should\040be\040gone\040after\040a\040while:3600
 :txt.test.dyndns:16:\021This\040is\040some\040text:3600
+:xautosplit.test.dyndns:16:\377They\040fixed\040up\040the\040corner\040store\040like\040it\040was\040a\040nightclub\040-\040It\047s\040permanently\040disco\040-\040Everyone\040is\040dressed\040so\040oddly\040I\040can\047t\040recognize\040them\040-\040I\040can\047t\040tell\040the\040staff\040from\040the\040customers\040-\040Baby\040check\040this\040out\054\040I\047ve\040got\040something\040to\040say\040-\040Man\054\040it\047s\040so\040loud\040in\040here\040S-\040When\040they\040stop\040the\040drum\040machine\040and\040I\040can\040think\040again\040-\040I\047ll\040remember\040what\040it\040was:3600
 @test.dyndns::host-1.test.dyndns.:10:3600
 @test.dyndns::host-2.test.dyndns.:20:3600
 Ccname1.test.dyndns:host-1.test.dyndns.:3600
@@ -20267,6 +20273,7 @@ Znztest.com:ns1.nztest.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:
 +ns2.secure-delegated.dnssec-parent.com:5.6.7.8:3600
 +something1.auth-ent.dnssec-parent.com:1.1.2.3:3600
 :secure-delegated.dnssec-parent.com:43:\324\057\010\002\240\271\303\214\323\044\030\052\360\357f\203\015\012\016\205\241\325\211y\311\203N\030\310qw\236\004\010W\267:3600
+C\052.dnssec-parent.com:secure-delegated.dnssec-parent.com.:3600
 Cwww.dnssec-parent.com:www.insecure.dnssec-parent.com.:3600
 Zdnssec-parent.com:ns1.dnssec-parent.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
 #2000081501 auto axfr-get
index 5460277209efc0e6299c0e6564158ef252e271ba..ecb1694777a9a64bfd3e358e69d2ccd098119c27 100644 (file)
Binary files a/modules/tinydnsbackend/data.cdb and b/modules/tinydnsbackend/data.cdb differ
index ae35c0feaaad9acb75f15962f635e6889c1606de..6ba897fb48f94b53d60e5050e56f045ab3e511ae 100644 (file)
@@ -88,7 +88,7 @@ TinyDNSBackend::TinyDNSBackend(const string& suffix)
   d_isWildcardQuery = false;
 }
 
-void TinyDNSBackend::getUpdatedMasters(vector<DomainInfo>* retDomains)
+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)) {
@@ -120,13 +120,13 @@ void TinyDNSBackend::getUpdatedMasters(vector<DomainInfo>* retDomains)
 
       di->id = s_lastId;
       if (di->notified_serial > 0) {
-        retDomains->push_back(*di);
+        retDomains.push_back(*di);
       }
     }
     else {
       if (itByZone->notified_serial < di->serial) {
         di->id = itByZone->id;
-        retDomains->push_back(*di);
+        retDomains.push_back(*di);
       }
     }
   }
@@ -151,7 +151,7 @@ void TinyDNSBackend::setNotified(uint32_t id, uint32_t serial)
   (*domainInfo)[d_suffix] = *domains;
 }
 
-void TinyDNSBackend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool include_disabled)
+void TinyDNSBackend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool /* include_disabled */)
 {
   d_isAxfr = true;
   d_isGetDomains = true;
@@ -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) {
@@ -195,7 +195,7 @@ void TinyDNSBackend::getAllDomains(vector<DomainInfo>* domains, bool getSerial,
   }
 }
 
-bool TinyDNSBackend::list(const DNSName& target, int domain_id, bool include_disabled)
+bool TinyDNSBackend::list(const DNSName& target, int /* domain_id */, bool /* include_disabled */)
 {
   d_isAxfr = true;
   d_isGetDomains = false;
@@ -211,7 +211,7 @@ bool TinyDNSBackend::list(const DNSName& target, int domain_id, bool include_dis
   return d_cdbReader->searchSuffix(key);
 }
 
-void TinyDNSBackend::lookup(const QType& qtype, const DNSName& qdomain, int zoneId, DNSPacket* pkt_p)
+void TinyDNSBackend::lookup(const QType& qtype, const DNSName& qdomain, int /* zoneId */, DNSPacket* pkt_p)
 {
   d_isAxfr = false;
   d_isGetDomains = false;
@@ -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 10ee5527825c8e7564989af73b1ffa51d47c05f8..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) 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 4d24f952bf23ea28343ece94c7a715d713466459..b7e90bd0cc1d11663686a59e17a4bebdac56dc5f 100644 (file)
@@ -4,7 +4,6 @@
 /comfun
 /config.h
 /mkbindist
-/pdns.init
 /showvar
 /stamp-h
 /pdns_control
@@ -37,7 +36,6 @@
 /nproxy
 /speedtest
 /tcpbench
-/toysdig
 /tsig-tests
 version_generated.h
 /zone2ldap
@@ -69,5 +67,7 @@ effective_tld_names.dat
 /fuzz_target_moadnsparser
 /fuzz_target_packetcache
 /fuzz_target_proxyprotocol
+/fuzz_target_yahttp
 /fuzz_target_zoneparsertng
 /fuzz_target_dnslabeltext_parseRFC1035CharString
+/.cache
index 43656a7d414a86b01876cd7e6b0f96ccfbd167d6..263bf3cd01867378de6ee64ece90125da43f8ff0 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
@@ -35,17 +37,24 @@ if LUA
 AM_CPPFLAGS +=$(LUA_CFLAGS)
 endif
 
+if GSS_TSIG
+AM_CPPFLAGS +=$(GSS_CFLAGS)
+endif
+
 if LIBSODIUM
 AM_CPPFLAGS +=$(LIBSODIUM_CFLAGS)
 endif
 
+if LIBDECAF
+AM_CPPFLAGS += $(LIBDECAF_CFLAGS)
+endif
+
 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 \
@@ -53,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 \
@@ -61,7 +71,7 @@ EXTRA_DIST = \
 
 BUILT_SOURCES = \
        bind-dnssec.schema.sqlite3.sql.h \
-       bindparser.h \
+       bindparser.hh \
        dnslabeltext.cc \
        apidocfiles.h
 
@@ -85,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:
@@ -103,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
@@ -176,17 +186,19 @@ EXTRA_PROGRAMS = \
        sdig \
        speedtest \
        testrunner \
-       toysdig \
        tsig-tests \
        zone2ldap
 
 pdns_server_SOURCES = \
        arguments.cc arguments.hh \
-       ascii.hh \
        auth-caches.cc auth-caches.hh \
        auth-carbon.cc \
+       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 \
@@ -196,17 +208,18 @@ pdns_server_SOURCES = \
        bind-dnssec.schema.sqlite3.sql.h \
        bindlexer.l \
        bindparser.cc \
+       burtle.hh \
        cachecleaner.hh \
        circular_buffer.hh \
        comment.hh \
-       common_startup.cc common_startup.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 \
@@ -224,15 +237,17 @@ pdns_server_SOURCES = \
        ednscookies.cc ednscookies.hh \
        ednsoptions.cc ednsoptions.hh \
        ednssubnet.cc ednssubnet.hh \
+       gettime.cc gettime.hh \
+       gss_context.cc gss_context.hh \
        histogram.hh \
        iputils.cc iputils.hh \
        ixfr.cc ixfr.hh \
        json.cc json.hh \
        lock.hh \
        logger.cc logger.hh \
+       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 \
@@ -246,7 +261,6 @@ pdns_server_SOURCES = \
        qtype.cc qtype.hh \
        query-local-address.hh query-local-address.cc \
        rcpgenerator.cc \
-       receiver.cc \
        resolver.cc resolver.hh \
        responsestats.cc responsestats.hh responsestats-auth.cc \
        rfc2136handler.cc \
@@ -257,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 \
@@ -323,9 +336,14 @@ if LUA
 pdns_server_LDADD += $(LUA_LIBS)
 endif
 
+if GSS_TSIG
+pdns_server_LDADD += $(GSS_LIBS)
+endif
+
 pdnsutil_SOURCES = \
        arguments.cc \
        auth-caches.cc auth-caches.hh \
+       auth-catalogzone.cc auth-catalogzone.hh \
        auth-packetcache.cc auth-packetcache.hh \
        auth-querycache.cc auth-querycache.hh \
        auth-zonecache.cc auth-zonecache.hh \
@@ -340,7 +358,6 @@ pdnsutil_SOURCES = \
        credentials.cc credentials.hh \
        dbdnsseckeeper.cc \
        dns.cc \
-       dns_random.cc \
        dnsbackend.cc \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
@@ -354,6 +371,8 @@ pdnsutil_SOURCES = \
        ednscookies.cc ednscookies.hh \
        ednsoptions.cc ednsoptions.hh \
        ednssubnet.cc \
+       gettime.cc gettime.hh \
+       gss_context.cc gss_context.hh \
        ipcipher.cc ipcipher.hh \
        iputils.cc iputils.hh \
        json.cc \
@@ -379,6 +398,7 @@ pdnsutil_SOURCES = \
        ueberbackend.cc \
        unix_utility.cc \
        uuid-utils.hh uuid-utils.cc \
+       validate.hh \
        zonemd.hh zonemd.cc \
        zoneparser-tng.cc
 
@@ -423,6 +443,10 @@ if LUA
 pdnsutil_LDADD += $(LUA_LIBS)
 endif
 
+if GSS_TSIG
+pdnsutil_LDADD += $(GSS_LIBS)
+endif
+
 zone2sql_SOURCES = \
        arguments.cc \
        base32.cc \
@@ -432,7 +456,6 @@ zone2sql_SOURCES = \
        bindparser.yy \
        bindparserclasses.hh \
        dns.cc \
-       dns_random_urandom.cc \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
        dnsparser.cc \
@@ -498,7 +521,6 @@ zone2ldap_SOURCES = \
        bindlexer.l \
        bindparser.yy \
        bindparserclasses.hh \
-       dns_random_urandom.cc \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
        dnsparser.cc \
@@ -519,6 +541,10 @@ zone2ldap_SOURCES = \
 zone2ldap_LDADD = $(LIBCRYPTO_LIBS)
 zone2ldap_LDFLAGS = $(AM_LDFLAGS) $(LIBCRYPTO_LDFLAGS)
 
+if GSS_TSIG
+zone2ldap_LDADD += $(GSS_LIBS)
+endif
+
 sdig_SOURCES = \
        base32.cc \
        base64.cc base64.hh \
@@ -529,6 +555,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 \
@@ -577,7 +604,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 \
@@ -613,6 +640,8 @@ dumresp_SOURCES = \
        statbag.cc \
        unix_utility.cc
 
+dumresp_LDFLAGS = $(AM_LDFLAGS) $(LIBCRYPTO_LDFLAGS)
+
 kvresp_SOURCES = \
        dnslabeltext.cc dnsname.cc dnsname.hh \
        kvresp.cc \
@@ -626,12 +655,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 \
@@ -651,13 +681,14 @@ 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 \
        dnsrecords.cc \
        dnssecinfra.cc \
        dnswriter.cc dnswriter.hh \
+       gss_context.cc gss_context.hh \
        iputils.cc \
        logger.cc \
        misc.cc misc.hh \
@@ -679,20 +710,35 @@ saxfr_SOURCES += pkcs11signers.cc pkcs11signers.hh
 saxfr_LDADD += $(P11KIT1_LIBS)
 endif
 
+if GSS_TSIG
+saxfr_LDADD += $(GSS_LIBS)
+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 \
        ixfrdist-stats.hh ixfrdist-stats.cc \
@@ -708,12 +754,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 \
@@ -722,6 +770,7 @@ ixfrdist_SOURCES = \
 ixfrdist_LDADD = \
        $(BOOST_PROGRAM_OPTIONS_LIBS) \
        $(JSON11_LIBS) \
+       $(LIBDL) \
        $(LIBCRYPTO_LIBS) \
        $(YAHTTP_LIBS) \
        $(YAML_LIBS)
@@ -740,19 +789,27 @@ ixfrdist_SOURCES += pkcs11signers.cc pkcs11signers.hh
 ixfrdist_LDADD += $(P11KIT1_LIBS)
 endif
 
+if GSS_TSIG
+ixfrdist_LDADD += $(GSS_LIBS)
+endif
+
+
 ixplore_SOURCES = \
        arguments.cc \
        axfr-retriever.cc \
        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 \
+       ednsoptions.cc ednsoptions.hh \
+       ednssubnet.cc ednssubnet.hh \
+       gss_context.cc gss_context.hh  \
        iputils.cc \
        ixfr.cc ixfr.hh \
        ixfrutils.cc ixfrutils.hh \
@@ -773,6 +830,9 @@ ixplore_SOURCES = \
 
 ixplore_LDADD = $(LIBCRYPTO_LIBS)
 ixplore_LDFLAGS = $(AM_LDFLAGS) $(LIBCRYPTO_LDFLAGS)
+if GSS_TSIG
+ixplore_LDADD += $(GSS_LIBS)
+endif
 
 if PKCS11
 ixplore_SOURCES += pkcs11signers.cc pkcs11signers.hh
@@ -819,6 +879,7 @@ nsec3dig_SOURCES = \
        dnsrecords.cc \
        dnssecinfra.cc \
        dnswriter.cc dnswriter.hh \
+       gss_context.cc gss_context.hh \
        iputils.cc \
        logger.cc \
        misc.cc misc.hh \
@@ -832,54 +893,17 @@ nsec3dig_SOURCES = \
        svc-records.cc svc-records.hh \
        unix_utility.cc
 
+
 nsec3dig_LDADD = $(LIBCRYPTO_LIBS)
 nsec3dig_LDFLAGS = $(AM_LDFLAGS) $(LIBCRYPTO_LDFLAGS)
 
-if PKCS11
-nsec3dig_SOURCES += pkcs11signers.cc pkcs11signers.hh
-nsec3dig_LDADD += $(P11KIT1_LIBS)
+if GSS_TSIG
+nsec3dig_LDADD += $(GSS_LIBS)
 endif
 
-toysdig_SOURCES = \
-       base32.cc \
-       base64.cc base64.hh \
-       dns_random_urandom.cc \
-       dnslabeltext.cc \
-       dnsname.cc dnsname.hh \
-       dnsparser.cc dnsparser.hh \
-       dnsrecords.cc \
-       dnssecinfra.cc \
-       dnswriter.cc dnswriter.hh \
-       ednssubnet.cc ednssubnet.hh \
-       filterpo.hh \
-       iputils.cc \
-       logger.cc \
-       misc.cc misc.hh \
-       nsecrecords.cc \
-       opensslsigners.cc opensslsigners.hh \
-       qtype.cc \
-       rcpgenerator.cc rcpgenerator.hh \
-       rec-lua-conf.hh \
-       recursor_cache.hh \
-       root-dnssec.hh \
-       sholder.hh \
-       sillyrecords.cc \
-       sortlist.hh \
-       sstuff.hh \
-       statbag.cc \
-       svc-records.cc svc-records.hh \
-       toysdig.cc \
-       unix_utility.cc \
-       validate.cc validate.hh
-
-
-toysdig_LDFLAGS = $(AM_LDFLAGS) \
-       $(LIBCRYPTO_LDFLAGS)
-toysdig_LDADD = $(LIBCRYPTO_LIBS)
-
 if PKCS11
-toysdig_SOURCES += pkcs11signers.cc pkcs11signers.hh
-toysdig_LDADD += $(P11KIT1_LIBS)
+nsec3dig_SOURCES += pkcs11signers.cc pkcs11signers.hh
+nsec3dig_LDADD += $(P11KIT1_LIBS)
 endif
 
 tsig_tests_SOURCES = \
@@ -889,13 +913,14 @@ 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 \
        dnsrecords.cc \
        dnssecinfra.cc \
        dnswriter.cc dnswriter.hh \
+       gss_context.cc gss_context.hh \
        iputils.cc \
        logger.cc \
        misc.cc misc.hh \
@@ -925,13 +950,14 @@ 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 \
        dnsrecords.cc \
        dnssecinfra.cc dnssecinfra.hh \
        dnswriter.cc dnswriter.hh \
+       gss_context.cc gss_context.hh \
        iputils.cc \
        logger.cc \
        misc.cc misc.hh \
@@ -942,8 +968,8 @@ speedtest_SOURCES = \
        speedtest.cc \
        statbag.cc \
        svc-records.cc svc-records.hh \
-        unix_utility.cc \
-        uuid-utils.cc
+       unix_utility.cc \
+       uuid-utils.cc
 
 speedtest_LDFLAGS = $(AM_LDFLAGS) $(LIBCRYPTO_LDFLAGS)
 speedtest_LDADD = $(LIBCRYPTO_LIBS) \
@@ -964,20 +990,21 @@ dnswasher_SOURCES = \
        statbag.cc \
        unix_utility.cc
 
-dnswasher_LDFLAGS =    $(AM_LDFLAGS) $(BOOST_PROGRAM_OPTIONS_LDFLAGS) $(LIBCRYPTO_LDFLAGS)
-dnswasher_LDADD =      $(BOOST_PROGRAM_OPTIONS_LIBS) $(LIBCRYPTO_LIBS) $(IPCRYPT_LIBS)
+dnswasher_LDFLAGS =    $(AM_LDFLAGS) $(BOOST_PROGRAM_OPTIONS_LDFLAGS) $(LIBCRYPTO_LDFLAGS)
+dnswasher_LDADD =      $(BOOST_PROGRAM_OPTIONS_LIBS) $(LIBCRYPTO_LIBS) $(IPCRYPT_LIBS)
 
 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 \
@@ -1125,7 +1152,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 \
@@ -1328,10 +1354,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 \
@@ -1346,6 +1372,7 @@ testrunner_SOURCES = \
        ednsoptions.cc ednsoptions.hh \
        ednssubnet.cc \
        gettime.cc gettime.hh \
+       gss_context.cc gss_context.hh \
        histogram.hh \
        ipcipher.cc ipcipher.hh \
        iputils.cc \
@@ -1374,6 +1401,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 \
@@ -1407,6 +1435,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 \
@@ -1415,13 +1444,16 @@ 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
 
 testrunner_LDFLAGS = \
        $(AM_LDFLAGS) \
        $(LIBCRYPTO_LDFLAGS) \
-       $(BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS) 
+       $(BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS)
 
 testrunner_LDADD = \
        $(LIBCRYPTO_LIBS) \
@@ -1429,7 +1461,14 @@ 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
 testrunner_SOURCES += pkcs11signers.cc pkcs11signers.hh
@@ -1489,10 +1528,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:
@@ -1512,14 +1553,14 @@ 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 \
-       fuzz_target_zoneparsertng \
-       fuzz_target_dnslabeltext_parseRFC1035CharString
+       fuzz_target_dnslabeltext_parseRFC1035CharString \
+       fuzz_target_yahttp \
+       fuzz_target_zoneparsertng
 
-fuzz_targets: $(fuzz_targets_programs)
+fuzz_targets: $(ARC4RANDOM_LIBS) $(fuzz_targets_programs)
 
 bin_PROGRAMS += \
        $(fuzz_targets_programs)
@@ -1585,29 +1626,13 @@ 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_yahttp_SOURCES = \
+       fuzz_yahttp.cc
 
-fuzz_target_dnsdistcache_DEPENDENCIES = $(fuzz_targets_deps)
-fuzz_target_dnsdistcache_LDFLAGS = $(fuzz_targets_ldflags)
-fuzz_target_dnsdistcache_LDADD = $(fuzz_targets_libs)
+fuzz_target_yahttp_DEPENDENCIES = $(fuzz_targets_deps)
+fuzz_target_yahttp_LDFLAGS = $(fuzz_targets_ldflags)
+fuzz_target_yahttp_LDADD = $(fuzz_targets_libs) \
+       $(YAHTTP_LIBS)
 
 fuzz_target_zoneparsertng_SOURCES = \
        base32.cc base32.hh \
@@ -1650,11 +1675,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"
@@ -1726,6 +1747,15 @@ endif
 if !HAVE_SYSTEMD_SYSTEM_CALL_FILTER
        $(AM_V_GEN)perl -ni -e 'print unless /^SystemCallFilter/' $@
 endif
+if !HAVE_SYSTEMD_PROTECT_PROC
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectProc/' $@
+endif
+if !HAVE_SYSTEMD_PRIVATE_IPC
+       $(AM_V_GEN)perl -ni -e 'print unless /^PrivateIPC/' $@
+endif
+if !HAVE_SYSTEMD_REMOVE_IPC
+       $(AM_V_GEN)perl -ni -e 'print unless /^RemoveIPC/' $@
+endif
 
 pdns@.service: pdns.service
        $(AM_V_GEN)sed -e 's!/pdns_server!& --config-name=%i!' \
@@ -1806,6 +1836,18 @@ endif
 if !HAVE_SYSTEMD_SYSTEM_CALL_FILTER
        $(AM_V_GEN)perl -ni -e 'print unless /^SystemCallFilter/' $@
 endif
+if !HAVE_SYSTEMD_PROTECT_PROC
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectProc/' $@
+endif
+if !HAVE_SYSTEMD_MEMORY_DENY_WRITE_EXECUTE
+       $(AM_V_GEN)perl -ni -e 'print unless /^MemoryDenyWriteExecute/' $@
+endif
+if !HAVE_SYSTEMD_PRIVATE_IPC
+       $(AM_V_GEN)perl -ni -e 'print unless /^PrivateIPC/' $@
+endif
+if !HAVE_SYSTEMD_REMOVE_IPC
+       $(AM_V_GEN)perl -ni -e 'print unless /^RemoveIPC/' $@
+endif
 
 ixfrdist@.service: ixfrdist.service
        $(AM_V_GEN)sed -e 's!/ixfrdist!& --config $(sysconfdir)/ixfrdist-%i.yml!' \
index 8c2e168890bb2e48758c907c4a5887086f73d0c7..ec028ea8c361c198a9d26435724b09dd28f915f0 100644 (file)
@@ -29,8 +29,7 @@
 
 struct QuestionIdentifier
 {
-  QuestionIdentifier() 
-  {}
+  QuestionIdentifier() = default;
 
   bool operator<(const QuestionIdentifier& rhs) const
   {
@@ -69,8 +68,8 @@ struct QuestionIdentifier
   ComboAddress d_source, d_dest;
 
   DNSName d_qname;
-  uint16_t d_qtype;
-  uint16_t d_id;
+  uint16_t d_qtype{0};
+  uint16_t d_id{0};
 };
 
 inline ostream& operator<<(ostream &s, const QuestionIdentifier& qi) 
index 4ece63184dbd5e6583786c8e9c37a30e03414b79..f87fa39b1d23458862148a68c424f40da560605d 100644 (file)
 #include <dirent.h>
 #include <sys/stat.h>
 #include <unistd.h>
-#include <limits.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)
-{
-  return helpmap[item];
-}
-
-string & ArgvMap::set(const string &var, const string &help)
+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;
+  stringtok(parts, param->second, ", \t");
+  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,351 +187,378 @@ 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 string& arg)
 {
-  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 string& arg)
 {
-   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;
+  const auto* cptr_orig = d_params[arg].c_str();
+  char* cptr_ret = nullptr;
+  auto retval = strtod(cptr_orig, &cptr_ret);
 
-  cptr_orig = d_params[arg].c_str();
-  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" }
+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"},
 };
 
-static void 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()) {
-    g_log << Logger::Warning << "'" << var << "' is deprecated and will be removed in a future release, use '" << msg->second << "' instead" << endl;
+    SLOG(g_log << Logger::Warning << "'" << var << "' is deprecated and will be removed in a future release, use '" << msg->second << "' instead" << endl,
+         d_log->info(Logr::Warning, "Option is deprecated and will be removed in a future release", "deprecatedName", Logging::Loggable(var), "alternative", Logging::Loggable(msg->second)));
   }
 }
 
-void ArgvMap::parseOne(const string &arg, const string &parseOnly, bool lax)
+string ArgvMap::isDeprecated(const string& var)
+{
+  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, val;
-  string::size_type pos;
+  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)) {
-    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");
+  if (!var.empty() && (parseOnly.empty() || var == parseOnly)) {
+    if (!lax) {
+      warnIfDeprecated(var);
+    }
+    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;
-        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;
-  string::size_type pos;
 
-  ifstream f(fname);
-  if(!f)
+  std::ifstream configFileStream(fname);
+  if (!configFileStream) {
     return false;
+  }
 
-  while(getline(f,pline)) {
+  while (getline(configFileStream, pline)) {
     boost::trim_right(pline);
-    
-    if(!pline.empty() && pline[pline.size()-1]=='\\') {
-      line+=pline.substr(0,pline.length()-1);
+
+    if (!pline.empty() && pline[pline.size() - 1] == '\\') {
+      line += pline.substr(0, pline.length() - 1);
       continue;
     }
-    else
-      line+=pline;
+
+    line += pline;
 
     // strip everything after a #
-    if((pos=line.find('#'))!=string::npos) {
+    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]))
-        line=line.substr(0,pos);
+      if (pos == 0 || (std::isspace(line[pos - 1]) != 0)) {
+        line = line.substr(0, pos);
+      }
     }
 
     // strip trailing spaces
     boost::trim_right(line);
 
     // strip leading spaces
-    if((pos=line.find_first_not_of(" \t\r\n"))!=string::npos)
-      line=line.substr(pos);
+    pos = line.find_first_not_of(" \t\r\n");
+    if (pos != string::npos) {
+      line = line.substr(pos);
+    }
 
     // gpgsql-basic-query=sdfsdfs dfsdfsdf sdfsdfsfd
 
-    parseOne( string("--") + line, arg, lax );
-    line="";
+    parseOne(string("--") + line, arg, lax);
+    line = "";
   }
 
   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);
+  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");
+  if (!parmIsset("include-dir")) { // inject include-dir
+    set("include-dir", "Directory to include configuration files from");
+  }
 
-  if(!parseFile(fname, "", lax)) {
-    g_log << Logger::Warning << "Unable to open " << fname << std::endl;
+  if (!parseFile(fname, "", lax)) {
+    SLOG(g_log << Logger::Warning << "Unable to open " << fname << std::endl,
+         d_log->error(Logr::Warning, "Unable to open file", "name", Logging::Loggable(fname)));
     return false;
   }
 
   // handle include here (avoid re-include)
   if (!included && !d_params["include-dir"].empty()) {
     std::vector<std::string> extraConfigs;
-    gatherIncludes(extraConfigs); 
-    for(const std::string& fn :  extraConfigs) {
-      if (!file(fn.c_str(), lax, true)) {
-        g_log << Logger::Error << fn << " could not be parsed" << std::endl;
-        throw ArgException(fn + " could not be parsed");
+    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,
+             d_log->info(Logr::Error, "Unable to parse config file", "name", Logging::Loggable(filename)));
+        throw ArgException(filename + " could not be parsed");
       }
     }
   }
@@ -544,37 +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);
-    g_log << Logger::Error << msg << std::endl;
-    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";
-        g_log << Logger::Error << msg << std::endl;
-        closedir(dir);
+      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(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 696f3ffa9257a0108038a06dfa647658fa040579..cbc834be74b508b30c9f004f138360af7b4c0367 100644 (file)
@@ -33,8 +33,9 @@
 #include <grp.h>
 
 #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:
 
@@ -54,15 +55,15 @@ typedef PDNSException ArgException;
     \code
 
     ArgvMap R;
-  
+
     R.set("port")="25";  // use this to specify default parameters
     R.file("./default.conf"); // parse configuration file
-    
+
     R.parse(argc, argv); // read the arguments from main()
-    
+
     cout<<"Will we be a daemon?: "<<R.isset("daemon")<<endl;
     cout<<"Our port will be "<<R["port"]<<endl;
-    
+
     map<string,string>::const_iterator i;
     cout<<"via iterator"<<endl;
     for(i=R.begin();i!=R.end();i++)
@@ -70,64 +71,82 @@ 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)
+  {
+    d_log = log;
+  }
+#endif
 private:
-  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
+  std::shared_ptr<Logr::Logger> d_log;
+#endif
 };
 
-extern ArgvMap &arg();
+extern ArgvMaparg();
index 41767bc7bfd4139f906c18efac5c81901f49a811..e46ff8d0ed92001f93cd9b09e750ae0446239675 100644 (file)
@@ -28,7 +28,7 @@
 #include "iputils.hh"
 #include "sstuff.hh"
 #include "arguments.hh"
-#include "common_startup.hh"
+#include "auth-main.hh"
 
 #include "namespaces.hh"
 
diff --git a/pdns/auth-catalogzone.cc b/pdns/auth-catalogzone.cc
new file mode 100644 (file)
index 0000000..b8d825d
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * 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 "dnsbackend.hh"
+
+void CatalogInfo::fromJson(const std::string& json, CatalogType type)
+{
+  d_type = type;
+  if (d_type == CatalogType::None) {
+    throw std::runtime_error("CatalogType is set to None");
+  }
+  if (json.empty()) {
+    return;
+  }
+  std::string err;
+  d_doc = json11::Json::parse(json, err);
+  if (!d_doc.is_null()) {
+    if (!d_doc[getTypeString(d_type)].is_null()) {
+      auto items = d_doc[getTypeString(type)].object_items();
+      if (!items["coo"].is_null()) {
+        if (items["coo"].is_string()) {
+          if (!items["coo"].string_value().empty()) {
+            d_coo = DNSName(items["coo"].string_value());
+          }
+        }
+        else {
+          throw std::out_of_range("Key 'coo' is not a string");
+        }
+      }
+      if (!items["unique"].is_null()) {
+        if (items["unique"].is_string()) {
+          if (!items["unique"].string_value().empty()) {
+            d_unique = DNSName(items["unique"].string_value());
+          }
+        }
+        else {
+          throw std::out_of_range("Key 'unique' is not a string");
+        }
+      }
+      if (!items["group"].is_null()) {
+        if (items["group"].is_array()) {
+          for (const auto& value : items["group"].array_items()) {
+            d_group.insert(value.string_value());
+          }
+        }
+        else {
+          throw std::out_of_range("Key 'group' is not an array");
+        }
+      }
+    }
+  }
+  else {
+    throw std::runtime_error("Parsing of JSON options failed: " + err);
+  }
+}
+
+std::string CatalogInfo::toJson() const
+{
+  if (d_type == CatalogType::None) {
+    throw std::runtime_error("CatalogType is set to None");
+  }
+  json11::Json::object object;
+  if (!d_coo.empty()) {
+    object["coo"] = d_coo.toString();
+  }
+  if (!d_unique.empty()) {
+    if (d_unique.countLabels() > 1) {
+      throw std::out_of_range("Multiple labels in a unique value are not allowed");
+    }
+    object["unique"] = d_unique.toString();
+  }
+  if (!d_group.empty()) {
+    json11::Json::array entries;
+    for (const string& group : d_group) {
+      entries.push_back(group);
+    }
+    object["group"] = entries;
+  }
+  auto tmp = d_doc.object_items();
+  tmp[getTypeString(d_type)] = object;
+  const json11::Json ret = tmp;
+  return ret.dump();
+}
+
+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());
+}
+
+DNSZoneRecord CatalogInfo::getCatalogVersionRecord(const DNSName& zone)
+{
+  DNSZoneRecord dzr;
+  dzr.dr.d_name = DNSName("version") + zone;
+  dzr.dr.d_ttl = 0;
+  dzr.dr.d_type = QType::TXT;
+  dzr.dr.setContent(std::make_shared<TXTRecordContent>("2"));
+  return dzr;
+}
+
+void CatalogInfo::toDNSZoneRecords(const DNSName& zone, vector<DNSZoneRecord>& dzrs) const
+{
+  DNSName prefix;
+  if (d_unique.empty()) {
+    prefix = getUnique();
+  }
+  else {
+    prefix = d_unique;
+  }
+  prefix += DNSName("zones") + zone;
+
+  DNSZoneRecord dzr;
+  dzr.dr.d_name = prefix;
+  dzr.dr.d_ttl = 0;
+  dzr.dr.d_type = QType::PTR;
+  dzr.dr.setContent(std::make_shared<PTRRecordContent>(d_zone.toString()));
+  dzrs.emplace_back(dzr);
+
+  if (!d_coo.empty()) {
+    dzr.dr.d_name = DNSName("coo") + prefix;
+    dzr.dr.d_ttl = 0;
+    dzr.dr.d_type = QType::PTR;
+    dzr.dr.setContent(std::make_shared<PTRRecordContent>(d_coo));
+    dzrs.emplace_back(dzr);
+  }
+
+  for (const auto& group : d_group) {
+    dzr.dr.d_name = DNSName("group") + prefix;
+    dzr.dr.d_ttl = 0;
+    dzr.dr.d_type = QType::TXT;
+    dzr.dr.setContent(std::make_shared<TXTRecordContent>("\"" + group + "\""));
+    dzrs.emplace_back(dzr);
+  }
+}
diff --git a/pdns/auth-catalogzone.hh b/pdns/auth-catalogzone.hh
new file mode 100644 (file)
index 0000000..d8a4fe9
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * 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 "ext/json11/json11.hpp"
+#include "base32.hh"
+#include "dnssecinfra.hh"
+
+struct DomainInfo;
+
+typedef map<DNSName, pdns::SHADigest> CatalogHashMap;
+
+class CatalogInfo
+{
+public:
+  enum CatalogType : uint8_t
+  {
+    None,
+    Producer,
+    Consumer
+  };
+
+  static const string& getTypeString(enum CatalogType type)
+  {
+    static const std::array<const string, 3> types = {"none", "producer", "consumer"};
+    return types.at(type);
+  }
+
+  CatalogInfo() :
+    d_id(0), d_type(CatalogType::None) {}
+  CatalogInfo(uint32_t id, const DNSName& zone, const std::string& options, CatalogType type)
+  {
+    d_id = id;
+    d_zone = zone;
+    fromJson(options, type);
+  }
+
+  void fromJson(const std::string& json, CatalogType type);
+  std::string toJson() const;
+  void setType(CatalogType type) { d_type = type; }
+
+  void updateHash(CatalogHashMap& hashes, const DomainInfo& di) const;
+  DNSName getUnique() const { return DNSName(toBase32Hex(hashQNameWithSalt(std::to_string(d_id), 0, d_zone))); } // salt with domain id to detect recreated zones
+  static DNSZoneRecord getCatalogVersionRecord(const DNSName& zone);
+  void toDNSZoneRecords(const DNSName& zone, vector<DNSZoneRecord>& dzrs) const;
+
+  bool operator<(const CatalogInfo& rhs) const
+  {
+    return d_zone < rhs.d_zone;
+  }
+
+  uint32_t d_id;
+  DNSName d_zone, d_coo, d_unique;
+  std::set<std::string> d_group;
+  vector<ComboAddress> d_primaries;
+
+private:
+  CatalogType d_type;
+  json11::Json d_doc;
+};
diff --git a/pdns/auth-main.cc b/pdns/auth-main.cc
new file mode 100644 (file)
index 0000000..4155b74
--- /dev/null
@@ -0,0 +1,1529 @@
+/*
+ * 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 <cstdio>
+#include <csignal>
+#include <cstring>
+#include <cstdlib>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <iostream>
+#include <string>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <cerrno>
+#include <pthread.h>
+#include <thread>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <fstream>
+#include <boost/algorithm/string.hpp>
+#ifdef HAVE_LIBSODIUM
+#include <sodium.h>
+#endif
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-daemon.h>
+#endif
+
+#include "auth-main.hh"
+#include "coverage.hh"
+#include "secpoll-auth.hh"
+#include "dynhandler.hh"
+#include "dnsseckeeper.hh"
+#include "threadname.hh"
+#include "misc.hh"
+#include "query-local-address.hh"
+#include "trusted-notification-proxy.hh"
+#include "packetcache.hh"
+#include "packethandler.hh"
+#include "opensslsigners.hh"
+#include "dns.hh"
+#include "dnsbackend.hh"
+#include "ueberbackend.hh"
+#include "dnspacket.hh"
+#include "nameserver.hh"
+#include "distributor.hh"
+#include "logger.hh"
+#include "arguments.hh"
+#include "packethandler.hh"
+#include "statbag.hh"
+#include "tcpreceiver.hh"
+#include "misc.hh"
+#include "dynlistener.hh"
+#include "dynhandler.hh"
+#include "communicator.hh"
+#include "dnsproxy.hh"
+#include "utility.hh"
+#include "dnsrecords.hh"
+#include "version.hh"
+#include "ws-auth.hh"
+
+#ifdef HAVE_LUA_RECORDS
+#include "minicurl.hh"
+#endif /* HAVE_LUA_RECORDS */
+
+time_t g_starttime;
+
+string g_programname = "pdns"; // used in packethandler.cc
+
+const char* funnytext = "*****************************************************************************\n"
+                        "Ok, you just ran pdns_server through 'strings' hoping to find funny messages.\n"
+                        "Well, you found one. \n"
+                        "Two ions are flying through their particle accelerator, says the one to the\n"
+                        "other 'I think I've lost an electron!' \n"
+                        "So the other one says, 'Are you sure?'. 'YEAH! I'M POSITIVE!'\n"
+                        "                                            the pdns crew - pdns@powerdns.com\n"
+                        "*****************************************************************************\n";
+
+bool g_anyToTcp;
+bool g_8bitDNS;
+#ifdef HAVE_LUA_RECORDS
+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;
+#endif
+typedef Distributor<DNSPacket, DNSPacket, PacketHandler> DNSDistributor;
+
+ArgvMap theArg;
+StatBag S; //!< Statistics are gathered across PDNS via the StatBag class S
+AuthPacketCache PC; //!< This is the main PacketCache, shared across all threads
+AuthQueryCache QC;
+AuthZoneCache g_zoneCache;
+std::unique_ptr<DNSProxy> DP{nullptr};
+static std::unique_ptr<DynListener> s_dynListener{nullptr};
+CommunicatorClass Communicator;
+static double avg_latency{0.0}, receive_latency{0.0}, cache_latency{0.0}, backend_latency{0.0}, send_latency{0.0};
+static unique_ptr<TCPNameserver> s_tcpNameserver{nullptr};
+static vector<DNSDistributor*> s_distributors;
+static shared_ptr<UDPNameserver> s_udpNameserver{nullptr};
+static vector<std::shared_ptr<UDPNameserver>> s_udpReceivers;
+NetmaskGroup g_proxyProtocolACL;
+size_t g_proxyProtocolMaximumSize;
+
+ArgvMap& arg()
+{
+  return theArg;
+}
+
+static void declareArguments()
+{
+  ::arg().set("config-dir", "Location of configuration directory (pdns.conf)") = SYSCONFDIR;
+  ::arg().set("config-name", "Name of this virtual configuration - will rename the binary image") = "";
+  ::arg().set("socket-dir", string("Where the controlsocket will live, ") + LOCALSTATEDIR + "/pdns when unset and not chrooted"
+#ifdef HAVE_SYSTEMD
+                + ". Set to the RUNTIME_DIRECTORY environment variable when that variable has a value (e.g. under systemd).")
+    = "";
+  auto runtimeDir = getenv("RUNTIME_DIRECTORY");
+  if (runtimeDir != nullptr) {
+    ::arg().set("socket-dir") = runtimeDir;
+  }
+#else
+              )
+    = "";
+#endif
+  ::arg().set("module-dir", "Default directory for modules") = PKGLIBDIR;
+  ::arg().set("chroot", "If set, chroot to this directory for more security") = "";
+  ::arg().set("logging-facility", "Log under a specific facility") = "";
+  ::arg().set("daemon", "Operate as a daemon") = "no";
+
+  ::arg().set("local-port", "The port on which we listen") = "53";
+  ::arg().setSwitch("dnsupdate", "Enable/Disable DNS update (RFC2136) support. Default is no.") = "no";
+  ::arg().setSwitch("write-pid", "Write a PID file") = "yes";
+  ::arg().set("allow-dnsupdate-from", "A global setting to allow DNS updates from these IP ranges.") = "127.0.0.0/8,::1";
+  ::arg().set("proxy-protocol-from", "A Proxy Protocol header is only allowed from these subnets, and is mandatory then too.") = "";
+  ::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-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 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, ::";
+  ::arg().setSwitch("local-address-nonexist-fail", "Fail to start if one or more of the local-address's do not exist on this server") = "yes";
+  ::arg().setSwitch("non-local-bind", "Enable binding to non-local addresses by using FREEBIND / BINDANY socket options") = "no";
+  ::arg().setSwitch("reuseport", "Enable higher performance on compliant kernels by using SO_REUSEPORT allowing each receiver thread to open its own socket") = "no";
+  ::arg().set("query-local-address", "Source IP addresses for sending queries") = "0.0.0.0 ::";
+  ::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 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";
+  ::arg().setSwitch("dname-processing", "If we should support DNAME records") = "no";
+
+  ::arg().setCmd("help", "Provide a helpful message");
+  ::arg().setCmd("version", "Output version and compilation date");
+  ::arg().setCmd("config", "Provide configuration file on standard output");
+  ::arg().setCmd("list-modules", "Lists all modules available");
+  ::arg().setCmd("no-config", "Don't parse configuration file");
+
+  ::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().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 stdout") = "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";
+  ::arg().set("udp-truncation-threshold", "Maximum UDP response size before we truncate") = "1232";
+
+  ::arg().set("config-name", "Name of this virtual configuration - will rename the binary image") = "";
+
+  ::arg().set("load-modules", "Load this module - supply absolute or relative path") = "";
+  ::arg().set("launch", "Which backends to launch and order to query them in") = "";
+  ::arg().setSwitch("disable-axfr", "Disable zonetransfers but do allow TCP queries") = "no";
+  ::arg().set("allow-axfr-ips", "Allow zonetransfers only to these subnets") = "127.0.0.0/8,::1";
+  ::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("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";
+
+  ::arg().set("tcp-control-address", "If set, PowerDNS can be controlled over TCP on this address") = "";
+  ::arg().set("tcp-control-port", "If set, PowerDNS can be controlled over TCP on this address") = "53000";
+  ::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("secondary", "Act as a secondary") = "no";
+  ::arg().setSwitch("primary", "Act as a primary") = "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)") = "";
+
+  ::arg().setSwitch("webserver", "Start a webserver for monitoring (api=yes also enables the HTTP listener)") = "no";
+  ::arg().setSwitch("webserver-print-arguments", "If the webserver should print arguments") = "no";
+  ::arg().set("webserver-address", "IP Address of webserver/API to listen on") = "127.0.0.1";
+  ::arg().set("webserver-port", "Port of webserver/API to listen on") = "8081";
+  ::arg().set("webserver-password", "Password required for accessing the webserver") = "";
+  ::arg().set("webserver-allow-from", "Webserver/API 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().set("webserver-max-bodysize", "Webserver/API maximum request/response body size in megabytes") = "2";
+  ::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().setSwitch("query-logging", "Hint backends that queries should be logged") = "no";
+
+  ::arg().set("carbon-namespace", "If set overwrites the first part of the carbon string") = "pdns";
+  ::arg().set("carbon-ourname", "If set, overrides our reported hostname for carbon stats") = "";
+  ::arg().set("carbon-instance", "If set overwrites the instance name default") = "auth";
+  ::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("cache-ttl", "Seconds to store packets in the PacketCache") = "20";
+  ::arg().set("negquery-cache-ttl", "Seconds to store negative query results in the QueryCache") = "60";
+  ::arg().set("query-cache-ttl", "Seconds to store query results in the QueryCache") = "20";
+  ::arg().set("zone-cache-refresh-interval", "Seconds to cache list of known zones") = "300";
+  ::arg().set("server-id", "Returned when queried for 'id.server' TXT or NSID, defaults to hostname - disabled or custom") = "";
+  ::arg().set("default-soa-content", "Default SOA content") = "a.misconfigured.dns.server.invalid hostmaster.@ 0 10800 3600 604800 3600";
+  ::arg().set("default-soa-edit", "Default SOA-EDIT value") = "";
+  ::arg().set("default-soa-edit-signed", "Default SOA-EDIT value for signed zones") = "";
+  ::arg().set("dnssec-key-cache-ttl", "Seconds to cache DNSSEC keys from the database") = "30";
+  ::arg().set("domain-metadata-cache-ttl", "Seconds to cache zone metadata from the database") = "";
+  ::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("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 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";
+  ::arg().set("max-tcp-connections-per-client", "Maximum number of simultaneous TCP connections per client") = "0";
+  ::arg().set("max-tcp-transactions-per-conn", "Maximum number of subsequent queries per TCP connection") = "0";
+  ::arg().set("max-tcp-connection-duration", "Maximum time in seconds that a TCP DNS connection is allowed to stay open.") = "0";
+  ::arg().set("tcp-idle-timeout", "Maximum time in seconds that a TCP DNS connection is allowed to stay open while being idle") = "5";
+
+  ::arg().setSwitch("no-shuffle", "Set this to prevent random shuffling of answers - for regression testing") = "off";
+
+  ::arg().set("setuid", "If set, change user id to this uid for more security") = "";
+  ::arg().set("setgid", "If set, change group id to this gid for more security") = "";
+
+  ::arg().set("max-cache-entries", "Maximum number of entries in the query cache") = "1000000";
+  ::arg().set("max-packet-cache-entries", "Maximum number of entries in the packet cache") = "1000000";
+  ::arg().set("max-signature-cache-entries", "Maximum number of signatures cache entries") = "";
+  ::arg().set("max-ent-entries", "Maximum number of empty non-terminals in a zone") = "100000";
+  ::arg().set("entropy-source", "If set, read entropy from this file") = "/dev/urandom";
+
+  ::arg().set("lua-prequery-script", "Lua script with prequery handler (DO NOT USE)") = "";
+  ::arg().set("lua-dnsupdate-policy-script", "Lua script with DNS update policy handler") = "";
+
+  ::arg().setSwitch("traceback-handler", "Enable the traceback handler (Linux only)") = "yes";
+  ::arg().setSwitch("direct-dnskey", "Fetch DNSKEY, CDS and CDNSKEY RRs from backend during DNSKEY or CDS/CDNSKEY synthesis") = "no";
+  ::arg().set("default-ksk-algorithm", "Default KSK algorithm") = "ecdsa256";
+  ::arg().set("default-ksk-size", "Default KSK size (0 means default)") = "0";
+  ::arg().set("default-zsk-algorithm", "Default ZSK algorithm") = "";
+  ::arg().set("default-zsk-size", "Default ZSK size (0 means default)") = "0";
+  ::arg().set("max-nsec3-iterations", "Limit the number of NSEC3 hash iterations") = "100";
+  ::arg().set("default-publish-cdnskey", "Default value for PUBLISH-CDNSKEY") = "";
+  ::arg().set("default-publish-cds", "Default value for PUBLISH-CDS") = "";
+
+  ::arg().set("include-dir", "Include *.conf files from this directory");
+  ::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().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 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";
+  ::arg().set("axfr-fetch-timeout", "Maximum time in seconds for inbound AXFR to start or be idle after starting") = "10";
+
+  ::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("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file") = "0";
+  ::arg().set("max-include-depth", "Maximum number of nested $INCLUDE directives while processing a zone file") = "20";
+  ::arg().setSwitch("upgrade-unknown-types", "Transparently upgrade known TYPExxx records. Recommended to keep off, except for PowerDNS upgrades until data sources are cleaned up") = "no";
+  ::arg().setSwitch("svc-autohints", "Transparently fill ipv6hint=auto ipv4hint=auto SVC params with AAAA/A records for the target name of the record (if within the same zone)") = "no";
+
+  ::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
+  ::arg().setDefaults();
+}
+
+static time_t s_start = time(nullptr);
+static uint64_t uptimeOfProcess(const std::string& /* str */)
+{
+  return time(nullptr) - s_start;
+}
+
+static uint64_t getSysUserTimeMsec(const std::string& str)
+{
+  struct rusage ru;
+  getrusage(RUSAGE_SELF, &ru);
+
+  if (str == "sys-msec") {
+    return (ru.ru_stime.tv_sec * 1000ULL + ru.ru_stime.tv_usec / 1000);
+  }
+  else
+    return (ru.ru_utime.tv_sec * 1000ULL + ru.ru_utime.tv_usec / 1000);
+}
+
+static uint64_t getTCPConnectionCount(const std::string& /* str */)
+{
+  return s_tcpNameserver->numTCPConnections();
+}
+
+static uint64_t getQCount(const std::string& /* str */)
+try {
+  int totcount = 0;
+  for (const auto& d : s_distributors) {
+    if (!d)
+      continue;
+    totcount += d->getQueueSize(); // this does locking and other things, so don't get smart
+  }
+  return totcount;
+}
+catch (std::exception& e) {
+  g_log << Logger::Error << "Had error retrieving queue sizes: " << e.what() << endl;
+  return 0;
+}
+catch (PDNSException& e) {
+  g_log << Logger::Error << "Had error retrieving queue sizes: " << e.reason << endl;
+  return 0;
+}
+
+static uint64_t getLatency(const std::string& /* str */)
+{
+  return round(avg_latency);
+}
+
+static uint64_t getReceiveLatency(const std::string& /* str */)
+{
+  return round(receive_latency);
+}
+
+static uint64_t getCacheLatency(const std::string& /* str */)
+{
+  return round(cache_latency);
+}
+
+static uint64_t getBackendLatency(const std::string& /* str */)
+{
+  return round(backend_latency);
+}
+
+static uint64_t getSendLatency(const std::string& /* str */)
+{
+  return round(send_latency);
+}
+
+static void declareStats()
+{
+  S.declare("udp-queries", "Number of UDP queries received");
+  S.declare("udp-do-queries", "Number of UDP queries received with DO bit");
+  S.declare("udp-cookie-queries", "Number of UDP queries received with the COOKIE EDNS option");
+  S.declare("udp-answers", "Number of answers sent out over UDP");
+  S.declare("udp-answers-bytes", "Total size of answers sent out over UDP");
+  S.declare("udp4-answers-bytes", "Total size of answers sent out over UDPv4");
+  S.declare("udp6-answers-bytes", "Total size of answers sent out over UDPv6");
+
+  S.declare("udp4-answers", "Number of IPv4 answers sent out over UDP");
+  S.declare("udp4-queries", "Number of IPv4 UDP queries received");
+  S.declare("udp6-answers", "Number of IPv6 answers sent out over UDP");
+  S.declare("udp6-queries", "Number of IPv6 UDP queries received");
+  S.declare("overload-drops", "Queries dropped because backends overloaded");
+
+  S.declare("rd-queries", "Number of recursion desired questions");
+  S.declare("recursion-unanswered", "Number of packets unanswered by configured recursor");
+  S.declare("recursing-answers", "Number of recursive answers sent out");
+  S.declare("recursing-questions", "Number of questions sent to recursor");
+  S.declare("corrupt-packets", "Number of corrupt packets received");
+  S.declare("signatures", "Number of DNSSEC signatures made");
+  S.declare("tcp-queries", "Number of TCP queries received");
+  S.declare("tcp-cookie-queries", "Number of TCP queries received with the COOKIE option");
+  S.declare("tcp-answers", "Number of answers sent out over TCP");
+  S.declare("tcp-answers-bytes", "Total size of answers sent out over TCP");
+  S.declare("tcp4-answers-bytes", "Total size of answers sent out over TCPv4");
+  S.declare("tcp6-answers-bytes", "Total size of answers sent out over TCPv6");
+
+  S.declare("tcp4-queries", "Number of IPv4 TCP queries received");
+  S.declare("tcp4-answers", "Number of IPv4 answers sent out over TCP");
+
+  S.declare("tcp6-queries", "Number of IPv6 TCP queries received");
+  S.declare("tcp6-answers", "Number of IPv6 answers sent out over TCP");
+
+  S.declare("open-tcp-connections", "Number of currently open TCP connections", getTCPConnectionCount, StatType::gauge);
+
+  S.declare("qsize-q", "Number of questions waiting for database attention", getQCount, StatType::gauge);
+
+  S.declare("dnsupdate-queries", "DNS update packets received.");
+  S.declare("dnsupdate-answers", "DNS update packets successfully answered.");
+  S.declare("dnsupdate-refused", "DNS update packets that are refused.");
+  S.declare("dnsupdate-changes", "DNS update changes to records in total.");
+
+  S.declare("incoming-notifications", "NOTIFY packets received.");
+
+  S.declare("uptime", "Uptime of process in seconds", uptimeOfProcess, StatType::counter);
+  S.declare("real-memory-usage", "Actual unique use of memory in bytes (approx)", getRealMemoryUsage, StatType::gauge);
+  S.declare("special-memory-usage", "Actual unique use of memory in bytes (approx)", getSpecialMemoryUsage, StatType::gauge);
+  S.declare("fd-usage", "Number of open filedescriptors", getOpenFileDescriptors, StatType::gauge);
+#ifdef __linux__
+  S.declare("udp-recvbuf-errors", "UDP 'recvbuf' errors", udpErrorStats, StatType::counter);
+  S.declare("udp-sndbuf-errors", "UDP 'sndbuf' errors", udpErrorStats, StatType::counter);
+  S.declare("udp-noport-errors", "UDP 'noport' errors", udpErrorStats, StatType::counter);
+  S.declare("udp-in-errors", "UDP 'in' errors", udpErrorStats, StatType::counter);
+  S.declare("udp-in-csum-errors", "UDP 'in checksum' errors", udpErrorStats, StatType::counter);
+  S.declare("udp6-in-errors", "UDP 'in' errors over IPv6", udp6ErrorStats, StatType::counter);
+  S.declare("udp6-recvbuf-errors", "UDP 'recvbuf' errors over IPv6", udp6ErrorStats, StatType::counter);
+  S.declare("udp6-sndbuf-errors", "UDP 'sndbuf' errors over IPv6", udp6ErrorStats, StatType::counter);
+  S.declare("udp6-noport-errors", "UDP 'noport' errors over IPv6", udp6ErrorStats, StatType::counter);
+  S.declare("udp6-in-csum-errors", "UDP 'in checksum' errors over IPv6", udp6ErrorStats, StatType::counter);
+#endif
+
+  S.declare("sys-msec", "Number of msec spent in system time", getSysUserTimeMsec, StatType::counter);
+  S.declare("user-msec", "Number of msec spent in user time", getSysUserTimeMsec, StatType::counter);
+
+#ifdef __linux__
+  S.declare("cpu-iowait", "Time spent waiting for I/O to complete by the whole system, in units of USER_HZ", getCPUIOWait, StatType::counter);
+  S.declare("cpu-steal", "Stolen time, which is the time spent by the whole system in other operating systems when running in a virtualized environment, in units of USER_HZ", getCPUSteal, StatType::counter);
+#endif
+
+  S.declare("meta-cache-size", "Number of entries in the metadata cache", DNSSECKeeper::dbdnssecCacheSizes, StatType::gauge);
+  S.declare("key-cache-size", "Number of entries in the key cache", DNSSECKeeper::dbdnssecCacheSizes, StatType::gauge);
+  S.declare("signature-cache-size", "Number of entries in the signature cache", signatureCacheSize, StatType::gauge);
+
+  S.declare("nxdomain-packets", "Number of times an NXDOMAIN packet was sent out");
+  S.declare("noerror-packets", "Number of times a NOERROR packet was sent out");
+  S.declare("servfail-packets", "Number of times a server-failed packet was sent out");
+  S.declare("unauth-packets", "Number of times a zone we are not auth for was queried");
+  S.declare("latency", "Average number of microseconds needed to answer a question", getLatency, StatType::gauge);
+  S.declare("receive-latency", "Average number of microseconds needed to receive a query", getReceiveLatency, StatType::gauge);
+  S.declare("cache-latency", "Average number of microseconds needed for a packet cache lookup", getCacheLatency, StatType::gauge);
+  S.declare("backend-latency", "Average number of microseconds needed for a backend lookup", getBackendLatency, StatType::gauge);
+  S.declare("send-latency", "Average number of microseconds needed to send the answer", getSendLatency, StatType::gauge);
+  S.declare("timedout-packets", "Number of packets which weren't answered within timeout set");
+  S.declare("security-status", "Security status based on regular polling", StatType::gauge);
+  S.declare(
+    "xfr-queue", "Size of the queue of zones to be XFRd", [](const string&) { return Communicator.getSuckRequestsWaiting(); }, StatType::gauge);
+  S.declareDNSNameQTypeRing("queries", "UDP Queries Received");
+  S.declareDNSNameQTypeRing("nxdomain-queries", "Queries for nonexistent records within existent zones");
+  S.declareDNSNameQTypeRing("noerror-queries", "Queries for existing records, but for type we don't have");
+  S.declareDNSNameQTypeRing("servfail-queries", "Queries that could not be answered due to backend errors");
+  S.declareDNSNameQTypeRing("unauth-queries", "Queries for zones that we are not authoritative for");
+  S.declareRing("logmessages", "Log Messages");
+  S.declareComboRing("remotes", "Remote server IP addresses");
+  S.declareComboRing("remotes-unauth", "Remote hosts querying zones for which we are not auth");
+  S.declareComboRing("remotes-corrupt", "Remote hosts sending corrupt packets");
+}
+
+static int isGuarded(char** argv)
+{
+  char* p = strstr(argv[0], "-instance");
+
+  return !!p;
+}
+
+static void sendout(std::unique_ptr<DNSPacket>& a, int start)
+{
+  if (!a)
+    return;
+
+  try {
+    int diff = a->d_dt.udiffNoReset();
+    backend_latency = 0.999 * backend_latency + 0.001 * std::max(diff - start, 0);
+    start = diff;
+
+    s_udpNameserver->send(*a);
+
+    diff = a->d_dt.udiff();
+    send_latency = 0.999 * send_latency + 0.001 * std::max(diff - start, 0);
+
+    avg_latency = 0.999 * avg_latency + 0.001 * std::max(diff, 0);
+  }
+  catch (const std::exception& e) {
+    g_log << Logger::Error << "Caught unhandled exception while sending a response: " << e.what() << endl;
+  }
+}
+
+//! The qthread receives questions over the internet via the Nameserver class, and hands them to the Distributor for further processing
+static void qthread(unsigned int num)
+try {
+  setThreadName("pdns/receiver");
+
+  s_distributors[num] = DNSDistributor::Create(::arg().asNum("distributor-threads", 1));
+  DNSDistributor* distributor = s_distributors[num]; // the big dispatcher!
+  DNSPacket question(true);
+  DNSPacket cached(false);
+
+  AtomicCounter& numreceived = *S.getPointer("udp-queries");
+  AtomicCounter& numreceiveddo = *S.getPointer("udp-do-queries");
+  AtomicCounter& numreceivedcookie = *S.getPointer("udp-cookie-queries");
+
+  AtomicCounter& numreceived4 = *S.getPointer("udp4-queries");
+
+  AtomicCounter& numreceived6 = *S.getPointer("udp6-queries");
+  AtomicCounter& overloadDrops = *S.getPointer("overload-drops");
+
+  int diff, start;
+  bool logDNSQueries = ::arg().mustDo("log-dns-queries");
+  shared_ptr<UDPNameserver> NS;
+  std::string buffer;
+  ComboAddress accountremote;
+
+  // If we have SO_REUSEPORT then create a new port for all receiver threads
+  // other than the first one.
+  if (s_udpNameserver->canReusePort()) {
+    NS = s_udpReceivers[num];
+    if (NS == nullptr) {
+      NS = s_udpNameserver;
+    }
+  }
+  else {
+    NS = s_udpNameserver;
+  }
+
+  for (;;) {
+    try {
+      if (g_proxyProtocolACL.empty()) {
+        buffer.resize(DNSPacket::s_udpTruncationThreshold);
+      }
+      else {
+        buffer.resize(DNSPacket::s_udpTruncationThreshold + g_proxyProtocolMaximumSize);
+      }
+
+      if (!NS->receive(question, buffer)) { // receive a packet         inline
+        continue; // packet was broken, try again
+      }
+
+      diff = question.d_dt.udiffNoReset();
+      receive_latency = 0.999 * receive_latency + 0.001 * std::max(diff, 0);
+
+      numreceived++;
+
+      accountremote = question.d_remote;
+      if (question.d_inner_remote)
+        accountremote = *question.d_inner_remote;
+
+      if (accountremote.sin4.sin_family == AF_INET)
+        numreceived4++;
+      else
+        numreceived6++;
+
+      if (question.d_dnssecOk)
+        numreceiveddo++;
+
+      if (question.hasEDNSCookie())
+        numreceivedcookie++;
+
+      if (question.d.qr)
+        continue;
+
+      S.ringAccount("queries", question.qdomain, question.qtype);
+      S.ringAccount("remotes", question.getInnerRemote());
+      if (logDNSQueries) {
+        g_log << Logger::Notice << "Remote " << question.getRemoteString() << " wants '" << question.qdomain << "|" << question.qtype << "', do = " << question.d_dnssecOk << ", bufsize = " << question.getMaxReplyLen();
+        if (question.d_ednsRawPacketSizeLimit > 0 && question.getMaxReplyLen() != (unsigned int)question.d_ednsRawPacketSizeLimit)
+          g_log << " (" << question.d_ednsRawPacketSizeLimit << ")";
+      }
+
+      if (PC.enabled() && (question.d.opcode != Opcode::Notify && question.d.opcode != Opcode::Update) && question.couldBeCached()) {
+        start = diff;
+        bool haveSomething = PC.get(question, cached); // does the PacketCache recognize this question?
+        if (haveSomething) {
+          if (logDNSQueries)
+            g_log << ": packetcache HIT" << endl;
+          cached.setRemote(&question.d_remote); // inlined
+          cached.d_inner_remote = question.d_inner_remote;
+          cached.setSocket(question.getSocket()); // inlined
+          cached.d_anyLocal = question.d_anyLocal;
+          cached.setMaxReplyLen(question.getMaxReplyLen());
+          cached.d.rd = question.d.rd; // copy in recursion desired bit
+          cached.d.id = question.d.id;
+          cached.commitD(); // commit d to the packet                        inlined
+
+          diff = question.d_dt.udiffNoReset();
+          cache_latency = 0.999 * cache_latency + 0.001 * std::max(diff - start, 0);
+          start = diff;
+
+          NS->send(cached); // answer it then                              inlined
+
+          diff = question.d_dt.udiff();
+          send_latency = 0.999 * send_latency + 0.001 * std::max(diff - start, 0);
+          avg_latency = 0.999 * avg_latency + 0.001 * std::max(diff, 0); // 'EWMA'
+          continue;
+        }
+        diff = question.d_dt.udiffNoReset();
+        cache_latency = 0.999 * cache_latency + 0.001 * std::max(diff - start, 0);
+      }
+
+      if (distributor->isOverloaded()) {
+        if (logDNSQueries)
+          g_log << ": Dropped query, backends are overloaded" << endl;
+        overloadDrops++;
+        continue;
+      }
+
+      if (logDNSQueries) {
+        if (PC.enabled()) {
+          g_log << ": packetcache MISS" << endl;
+        }
+        else {
+          g_log << endl;
+        }
+      }
+
+      try {
+        distributor->question(question, &sendout); // otherwise, give to the distributor
+      }
+      catch (DistributorFatal& df) { // when this happens, we have leaked loads of memory. Bailing out time.
+        _exit(1);
+      }
+    }
+    catch (const std::exception& e) {
+      g_log << Logger::Error << "Caught unhandled exception in question thread: " << e.what() << endl;
+    }
+  }
+}
+catch (PDNSException& pe) {
+  g_log << Logger::Error << "Fatal error in question thread: " << pe.reason << endl;
+  _exit(1);
+}
+
+static void dummyThread()
+{
+}
+
+static void triggerLoadOfLibraries()
+{
+  std::thread dummy(dummyThread);
+  dummy.join();
+}
+
+static void mainthread()
+{
+  gid_t newgid = 0;
+  if (!::arg()["setgid"].empty())
+    newgid = strToGID(::arg()["setgid"]);
+  uid_t newuid = 0;
+  if (!::arg()["setuid"].empty())
+    newuid = strToUID(::arg()["setuid"]);
+
+  g_anyToTcp = ::arg().mustDo("any-to-tcp");
+  g_8bitDNS = ::arg().mustDo("8bit-dns");
+#ifdef HAVE_LUA_RECORDS
+  g_doLuaRecord = ::arg().mustDo("enable-lua-records");
+  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
+  g_doGssTSIG = ::arg().mustDo("enable-gss-tsig");
+#endif
+
+  DNSPacket::s_udpTruncationThreshold = std::max(512, ::arg().asNum("udp-truncation-threshold"));
+  DNSPacket::s_doEDNSSubnetProcessing = ::arg().mustDo("edns-subnet-processing");
+  PacketHandler::s_SVCAutohints = ::arg().mustDo("svc-autohints");
+
+  g_proxyProtocolACL.toMasks(::arg()["proxy-protocol-from"]);
+  g_proxyProtocolMaximumSize = ::arg().asNum("proxy-protocol-maximum-size");
+
+  if (::arg()["edns-cookie-secret"].size() != 0) {
+    // User wants cookie processing
+#ifdef HAVE_CRYPTO_SHORTHASH // we can do siphash-based cookies
+    DNSPacket::s_doEDNSCookieProcessing = true;
+    try {
+      if (::arg()["edns-cookie-secret"].size() != EDNSCookiesOpt::EDNSCookieSecretSize) {
+        throw std::range_error("wrong size (" + std::to_string(::arg()["edns-cookie-secret"].size()) + "), must be " + std::to_string(EDNSCookiesOpt::EDNSCookieSecretSize));
+      }
+      DNSPacket::s_EDNSCookieKey = makeBytesFromHex(::arg()["edns-cookie-secret"]);
+    }
+    catch (const std::range_error& e) {
+      g_log << Logger::Error << "edns-cookie-secret invalid: " << e.what() << endl;
+      exit(1);
+    }
+#else
+    g_log << Logger::Error << "Support for EDNS Cookies is not available because of missing cryptographic functions (libsodium support should be enabled, with the crypto_shorthash() function available)" << endl;
+    exit(1);
+#endif
+  }
+
+  PC.setTTL(::arg().asNum("cache-ttl"));
+  PC.setMaxEntries(::arg().asNum("max-packet-cache-entries"));
+  QC.setMaxEntries(::arg().asNum("max-cache-entries"));
+  DNSSECKeeper::setMaxEntries(::arg().asNum("max-cache-entries"));
+
+  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();
+
+  if (!::arg()["chroot"].empty()) {
+#ifdef HAVE_SYSTEMD
+    char* ns;
+    ns = getenv("NOTIFY_SOCKET");
+    if (ns != nullptr) {
+      g_log << Logger::Error << "Unable to chroot when running from systemd. Please disable chroot= or set the 'Type' for this service to 'simple'" << endl;
+      exit(1);
+    }
+#endif
+    triggerLoadOfLibraries();
+    if (::arg().mustDo("primary") || ::arg().mustDo("secondary"))
+      gethostbyname("a.root-servers.net"); // this forces all lookup libraries to be loaded
+    Utility::dropGroupPrivs(newuid, newgid);
+    if (chroot(::arg()["chroot"].c_str()) < 0 || chdir("/") < 0) {
+      g_log << Logger::Error << "Unable to chroot to '" + ::arg()["chroot"] + "': " << stringerror() << ", exiting" << endl;
+      exit(1);
+    }
+    else
+      g_log << Logger::Error << "Chrooted to '" << ::arg()["chroot"] << "'" << endl;
+  }
+  else {
+    Utility::dropGroupPrivs(newuid, newgid);
+  }
+
+  AuthWebServer webserver;
+  Utility::dropUserPrivs(newuid);
+
+  if (::arg().mustDo("resolver")) {
+    DP = std::make_unique<DNSProxy>(::arg()["resolver"]);
+    DP->go();
+  }
+
+  try {
+    doSecPoll(true);
+  }
+  catch (...) {
+  }
+
+  {
+    // Some sanity checking on default key settings
+    bool hadKeyError = false;
+    int kskAlgo{0}, zskAlgo{0};
+    for (const string algotype : {"ksk", "zsk"}) {
+      int algo, size;
+      if (::arg()["default-" + algotype + "-algorithm"].empty())
+        continue;
+      algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-" + algotype + "-algorithm"]);
+      size = ::arg().asNum("default-" + algotype + "-size");
+      if (algo == -1) {
+        g_log << Logger::Error << "Error: default-" << algotype << "-algorithm set to unknown algorithm: " << ::arg()["default-" + algotype + "-algorithm"] << endl;
+        hadKeyError = true;
+      }
+      else if (algo <= 10 && size == 0) {
+        g_log << Logger::Error << "Error: default-" << algotype << "-algorithm is set to an algorithm (" << ::arg()["default-" + algotype + "-algorithm"] << ") that requires a non-zero default-" << algotype << "-size!" << endl;
+        hadKeyError = true;
+      }
+      if (algotype == "ksk") {
+        kskAlgo = algo;
+      }
+      else {
+        zskAlgo = algo;
+      }
+    }
+    if (hadKeyError) {
+      exit(1);
+    }
+    if (kskAlgo == 0 && zskAlgo != 0) {
+      g_log << Logger::Error << "Error: default-zsk-algorithm is set, but default-ksk-algorithm is not set." << endl;
+      exit(1);
+    }
+    if (zskAlgo != 0 && zskAlgo != kskAlgo) {
+      g_log << Logger::Error << "Error: default-zsk-algorithm (" << ::arg()["default-zsk-algorithm"] << "), when set, can not be different from default-ksk-algorithm (" << ::arg()["default-ksk-algorithm"] << ")." << endl;
+      exit(1);
+    }
+  }
+
+  pdns::parseQueryLocalAddress(::arg()["query-local-address"]);
+
+  pdns::parseTrustedNotificationProxy(::arg()["trusted-notification-proxy"]);
+
+  UeberBackend::go();
+
+  // Setup the zone cache
+  g_zoneCache.setRefreshInterval(::arg().asNum("zone-cache-refresh-interval"));
+  try {
+    UeberBackend B;
+    B.updateZoneCache();
+  }
+  catch (PDNSException& e) {
+    g_log << Logger::Error << "PDNSException while filling the zone cache: " << e.reason << endl;
+    exit(1);
+  }
+  catch (std::exception& e) {
+    g_log << Logger::Error << "STL Exception while filling the zone cache: " << e.what() << endl;
+    exit(1);
+  }
+
+  // NOW SAFE TO CREATE THREADS!
+  s_dynListener->go();
+
+  if (::arg().mustDo("webserver") || ::arg().mustDo("api")) {
+    webserver.go(S);
+  }
+
+  if (::arg().mustDo("primary") || ::arg().mustDo("secondary") || !::arg()["forward-notify"].empty())
+    Communicator.go();
+
+  s_tcpNameserver->go(); // tcp nameserver launch
+
+  unsigned int max_rthreads = ::arg().asNum("receiver-threads", 1);
+  s_distributors.resize(max_rthreads);
+  for (unsigned int n = 0; n < max_rthreads; ++n) {
+    std::thread t(qthread, n);
+    t.detach();
+  }
+
+  std::thread carbonThread(carbonDumpThread); // runs even w/o carbon, might change @ runtime
+
+#ifdef HAVE_SYSTEMD
+  /* If we are here, notify systemd that we are ay-ok! This might have some
+   * timing issues with the backend-threads. e.g. if the initial MySQL connection
+   * is slow and times out (leading to process termination through the backend)
+   * We probably have told systemd already that we have started correctly.
+   */
+  sd_notify(0, "READY=1");
+#endif
+
+  const uint32_t secpollInterval = 1800;
+  uint32_t secpollSince = 0;
+  uint32_t zoneCacheUpdateSince = 0;
+  for (;;) {
+    const uint32_t sleeptime = g_zoneCache.getRefreshInterval() == 0 ? secpollInterval : std::min(secpollInterval, g_zoneCache.getRefreshInterval());
+    sleep(sleeptime); // if any signals arrive, we might run more often than expected.
+
+    zoneCacheUpdateSince += sleeptime;
+    if (zoneCacheUpdateSince >= g_zoneCache.getRefreshInterval()) {
+      try {
+        UeberBackend B;
+        B.updateZoneCache();
+        zoneCacheUpdateSince = 0;
+      }
+      catch (PDNSException& e) {
+        g_log << Logger::Error << "PDNSException while updating zone cache: " << e.reason << endl;
+      }
+      catch (std::exception& e) {
+        g_log << Logger::Error << "STL Exception while updating zone cache: " << e.what() << endl;
+      }
+    }
+
+    secpollSince += sleeptime;
+    if (secpollSince >= secpollInterval) {
+      secpollSince = 0;
+      try {
+        doSecPoll(false);
+      }
+      catch (...) {
+      }
+    }
+  }
+
+  g_log << Logger::Error << "Mainthread exiting - should never happen" << endl;
+}
+
+static void daemonize()
+{
+  if (fork())
+    exit(0); // bye bye
+
+  setsid();
+
+  int i = open("/dev/null", O_RDWR); /* open stdin */
+  if (i < 0)
+    g_log << Logger::Critical << "Unable to open /dev/null: " << stringerror() << endl;
+  else {
+    dup2(i, 0); /* stdin */
+    dup2(i, 1); /* stderr */
+    dup2(i, 2); /* stderr */
+    close(i);
+  }
+}
+
+static int cpid;
+static void takedown(int /* i */)
+{
+  if (cpid) {
+    g_log << Logger::Error << "Guardian is killed, taking down children with us" << endl;
+    kill(cpid, SIGKILL);
+    exit(0);
+  }
+}
+
+static void writePid()
+{
+  if (!::arg().mustDo("write-pid"))
+    return;
+
+  string fname = ::arg()["socket-dir"];
+  if (::arg()["socket-dir"].empty()) {
+    if (::arg()["chroot"].empty())
+      fname = std::string(LOCALSTATEDIR) + "/pdns";
+    else
+      fname = ::arg()["chroot"] + "/";
+  }
+  else if (!::arg()["socket-dir"].empty() && !::arg()["chroot"].empty()) {
+    fname = ::arg()["chroot"] + ::arg()["socket-dir"];
+  }
+
+  fname += +"/" + g_programname + ".pid";
+  ofstream of(fname.c_str());
+  if (of)
+    of << getpid() << endl;
+  else
+    g_log << Logger::Error << "Writing pid for " << getpid() << " to " << fname << " failed: " << stringerror() << endl;
+}
+
+static int g_fd1[2], g_fd2[2];
+static FILE* g_fp;
+static std::mutex g_guardian_lock;
+
+// The next two methods are not in dynhandler.cc because they use a few items declared in this file.
+static string DLCycleHandler(const vector<string>& /* parts */, pid_t /* ppid */)
+{
+  kill(cpid, SIGKILL); // why?
+  kill(cpid, SIGKILL); // why?
+  sleep(1);
+  return "ok";
+}
+
+static string DLRestHandler(const vector<string>& parts, pid_t /* ppid */)
+{
+  string line;
+
+  for (vector<string>::const_iterator i = parts.begin(); i != parts.end(); ++i) {
+    if (i != parts.begin())
+      line.append(1, ' ');
+    line.append(*i);
+  }
+  line.append(1, '\n');
+
+  std::lock_guard<std::mutex> l(g_guardian_lock);
+
+  try {
+    writen2(g_fd1[1], line.c_str(), line.size() + 1);
+  }
+  catch (PDNSException& ae) {
+    return "Error communicating with instance: " + ae.reason;
+  }
+  char mesg[512];
+  string response;
+  while (fgets(mesg, sizeof(mesg), g_fp)) {
+    if (*mesg == '\0')
+      break;
+    response += mesg;
+  }
+  boost::trim_right(response);
+  return response;
+}
+
+static int guardian(int argc, char** argv)
+{
+  if (isGuarded(argv))
+    return 0;
+
+  int infd = 0, outfd = 1;
+
+  DynListener dlg(g_programname);
+  dlg.registerFunc("QUIT", &DLQuitHandler, "quit daemon");
+  dlg.registerFunc("CYCLE", &DLCycleHandler, "restart instance");
+  dlg.registerFunc("PING", &DLPingHandler, "ping guardian");
+  dlg.registerFunc("STATUS", &DLStatusHandler, "get instance status from guardian");
+  dlg.registerRestFunc(&DLRestHandler);
+  dlg.go();
+  string progname = argv[0];
+
+  bool first = true;
+  cpid = 0;
+
+  g_guardian_lock.lock();
+
+  for (;;) {
+    int pid;
+    setStatus("Launching child");
+
+    if (pipe(g_fd1) < 0 || pipe(g_fd2) < 0) {
+      g_log << Logger::Critical << "Unable to open pipe for coprocess: " << stringerror() << endl;
+      exit(1);
+    }
+
+    if (!(g_fp = fdopen(g_fd2[0], "r"))) {
+      g_log << Logger::Critical << "Unable to associate a file pointer with pipe: " << stringerror() << endl;
+      exit(1);
+    }
+    setbuf(g_fp, nullptr); // no buffering please, confuses select
+
+    if (!(pid = fork())) { // child
+      signal(SIGTERM, SIG_DFL);
+
+      signal(SIGHUP, SIG_DFL);
+      signal(SIGUSR1, SIG_DFL);
+      signal(SIGUSR2, SIG_DFL);
+
+      char** const newargv = new char*[argc + 2];
+      int n;
+
+      if (::arg()["config-name"] != "") {
+        progname += "-" + ::arg()["config-name"];
+        g_log << Logger::Error << "Virtual configuration name: " << ::arg()["config-name"] << endl;
+      }
+
+      newargv[0] = strdup(const_cast<char*>((progname + "-instance").c_str()));
+      for (n = 1; n < argc; n++) {
+        newargv[n] = argv[n];
+      }
+      newargv[n] = nullptr;
+
+      g_log << Logger::Error << "Guardian is launching an instance" << endl;
+      close(g_fd1[1]);
+      fclose(g_fp); // this closes g_fd2[0] for us
+
+      if (g_fd1[0] != infd) {
+        dup2(g_fd1[0], infd);
+        close(g_fd1[0]);
+      }
+
+      if (g_fd2[1] != outfd) {
+        dup2(g_fd2[1], outfd);
+        close(g_fd2[1]);
+      }
+      if (execvp(argv[0], newargv) < 0) {
+        g_log << Logger::Error << "Unable to execvp '" << argv[0] << "': " << stringerror() << endl;
+        char** p = newargv;
+        while (*p)
+          g_log << Logger::Error << *p++ << endl;
+
+        exit(1);
+      }
+      g_log << Logger::Error << "execvp returned!!" << endl;
+      // never reached
+    }
+    else if (pid > 0) { // parent
+      close(g_fd1[0]);
+      close(g_fd2[1]);
+
+      if (first) {
+        first = false;
+        signal(SIGTERM, takedown);
+
+        signal(SIGHUP, SIG_IGN);
+        signal(SIGUSR1, SIG_IGN);
+        signal(SIGUSR2, SIG_IGN);
+
+        writePid();
+      }
+      g_guardian_lock.unlock();
+      int status;
+      cpid = pid;
+      for (;;) {
+        int ret = waitpid(pid, &status, WNOHANG);
+
+        if (ret < 0) {
+          g_log << Logger::Error << "In guardian loop, waitpid returned error: " << stringerror() << endl;
+          g_log << Logger::Error << "Dying" << endl;
+          exit(1);
+        }
+        else if (ret) // something exited
+          break;
+        else { // child is alive
+          // execute some kind of ping here
+          if (DLQuitPlease())
+            takedown(1); // needs a parameter..
+          setStatus("Child running on pid " + std::to_string(pid));
+          sleep(1);
+        }
+      }
+
+      g_guardian_lock.lock();
+      close(g_fd1[1]);
+      fclose(g_fp);
+      g_fp = nullptr;
+
+      if (WIFEXITED(status)) {
+        int ret = WEXITSTATUS(status);
+
+        if (ret == 99) {
+          g_log << Logger::Error << "Child requested a stop, exiting" << endl;
+          exit(1);
+        }
+        setStatus("Child died with code " + std::to_string(ret));
+        g_log << Logger::Error << "Our pdns instance exited with code " << ret << ", respawning" << endl;
+
+        sleep(1);
+        continue;
+      }
+      if (WIFSIGNALED(status)) {
+        int sig = WTERMSIG(status);
+        setStatus("Child died because of signal " + std::to_string(sig));
+        g_log << Logger::Error << "Our pdns instance (" << pid << ") exited after signal " << sig << endl;
+#ifdef WCOREDUMP
+        if (WCOREDUMP(status))
+          g_log << Logger::Error << "Dumped core" << endl;
+#endif
+
+        g_log << Logger::Error << "Respawning" << endl;
+        sleep(1);
+        continue;
+      }
+      g_log << Logger::Error << "No clue what happened! Respawning" << endl;
+    }
+    else {
+      g_log << Logger::Error << "Unable to fork: " << stringerror() << endl;
+      exit(1);
+    }
+  }
+}
+
+#if defined(__GLIBC__) && !defined(__UCLIBC__)
+#include <execinfo.h>
+static void tbhandler(int num)
+{
+  g_log << Logger::Critical << "Got a signal " << num << ", attempting to print trace: " << endl;
+  void* array[20]; // only care about last 17 functions (3 taken with tracing support)
+  size_t size;
+  char** strings;
+  size_t i;
+
+  size = backtrace(array, 20);
+  strings = backtrace_symbols(array, size); // Need -rdynamic gcc (linker) flag for this to work
+
+  for (i = 0; i < size; i++) // skip useless functions
+    g_log << Logger::Error << strings[i] << endl;
+
+  signal(SIGABRT, SIG_DFL);
+  abort(); // hopefully will give core
+}
+#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)
+{
+  versionSetProduct(ProductAuthoritative);
+  reportAllTypes(); // init MOADNSParser
+
+  g_programname = "pdns";
+  g_starttime = time(nullptr);
+
+#if defined(__GLIBC__) && !defined(__UCLIBC__)
+  signal(SIGSEGV, tbhandler);
+  signal(SIGFPE, tbhandler);
+  signal(SIGABRT, tbhandler);
+  signal(SIGILL, tbhandler);
+#endif
+
+  std::ios_base::sync_with_stdio(false);
+
+  g_log.toConsole(Logger::Warning);
+  try {
+    declareArguments();
+
+    ::arg().laxParse(argc, argv); // do a lax parse
+
+    if (::arg().mustDo("version")) {
+      showProductVersion();
+      showBuildConfiguration();
+      return 0;
+    }
+
+    if (::arg()["config-name"] != "")
+      g_programname += "-" + ::arg()["config-name"];
+
+    g_log.setName(g_programname);
+
+    string configname = ::arg()["config-dir"] + "/" + g_programname + ".conf";
+    cleanSlashes(configname);
+
+    if (::arg()["config"] != "default" && !::arg().mustDo("no-config")) // "config" == print a configuration file
+      ::arg().laxFile(configname.c_str());
+
+    ::arg().laxParse(argc, argv); // reparse so the commandline still wins
+    if (!::arg()["logging-facility"].empty()) {
+      int val = logFacilityToLOG(::arg().asNum("logging-facility"));
+      if (val >= 0)
+        g_log.setFacility(val);
+      else
+        g_log << Logger::Error << "Unknown logging facility " << ::arg().asNum("logging-facility") << endl;
+    }
+
+    if (!::arg().isEmpty("domain-metadata-cache-ttl"))
+      ::arg().set("zone-metadata-cache-ttl") = ::arg()["domain-metadata-cache-ttl"];
+
+    // this mirroring back is on purpose, so that config dumps reflect the actual setting on both names
+    ::arg().set("domain-metadata-cache-ttl") = ::arg()["zone-metadata-cache-ttl"];
+
+    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")));
+
+    if (::arg().mustDo("help") || ::arg().mustDo("config")) {
+      ::arg().set("daemon") = "no";
+      ::arg().set("guardian") = "no";
+    }
+
+    if (::arg().mustDo("guardian") && !isGuarded(argv)) {
+      if (::arg().mustDo("daemon")) {
+        g_log.toConsole(Logger::Critical);
+        daemonize();
+      }
+      guardian(argc, argv);
+      // never get here, guardian will reinvoke process
+      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__)
+    if (!::arg().mustDo("traceback-handler")) {
+      g_log << Logger::Warning << "Disabling traceback handler" << endl;
+      signal(SIGSEGV, SIG_DFL);
+      signal(SIGFPE, SIG_DFL);
+      signal(SIGABRT, SIG_DFL);
+      signal(SIGILL, SIG_DFL);
+    }
+#endif
+
+#ifdef HAVE_LIBSODIUM
+    if (sodium_init() == -1) {
+      cerr << "Unable to initialize sodium crypto library" << endl;
+      exit(99);
+    }
+#endif
+
+    openssl_thread_setup();
+    openssl_seed();
+
+#ifdef HAVE_LUA_RECORDS
+    MiniCurl::init();
+#endif /* HAVE_LUA_RECORDS */
+
+    if (!::arg()["load-modules"].empty()) {
+      vector<string> modules;
+
+      stringtok(modules, ::arg()["load-modules"], ", ");
+      if (!UeberBackend::loadModules(modules, ::arg()["module-dir"])) {
+        exit(1);
+      }
+    }
+
+    BackendMakers().launch(::arg()["launch"]); // vrooooom!
+
+    if (!::arg().getCommands().empty()) {
+      cerr << "Fatal: non-option";
+      if (::arg().getCommands().size() > 1) {
+        cerr << "s";
+      }
+      cerr << " (";
+      bool first = true;
+      for (const auto& c : ::arg().getCommands()) {
+        if (!first) {
+          cerr << ", ";
+        }
+        first = false;
+        cerr << c;
+      }
+      cerr << ") on the command line, perhaps a '--setting=123' statement missed the '='?" << endl;
+      exit(99);
+    }
+
+    if (::arg().mustDo("help")) {
+      cout << "syntax:" << endl
+           << endl;
+      cout << ::arg().helpstring(::arg()["help"]) << endl;
+      exit(0);
+    }
+
+    if (::arg().mustDo("config")) {
+      string config = ::arg()["config"];
+      if (config == "default") {
+        cout << ::arg().configstring(false, true);
+      }
+      else if (config == "diff") {
+        cout << ::arg().configstring(true, false);
+      }
+      else if (config == "check") {
+        try {
+          if (!::arg().mustDo("no-config"))
+            ::arg().file(configname.c_str());
+          ::arg().parse(argc, argv);
+          exit(0);
+        }
+        catch (const ArgException& A) {
+          cerr << "Fatal error: " << A.reason << endl;
+          exit(1);
+        }
+      }
+      else {
+        cout << ::arg().configstring(true, true);
+      }
+      exit(0);
+    }
+
+    if (::arg().mustDo("list-modules")) {
+      auto modules = BackendMakers().getModules();
+      cout << "Modules available:" << endl;
+      for (const auto& m : modules)
+        cout << m << endl;
+
+      _exit(99);
+    }
+
+    if (!::arg().asNum("local-port")) {
+      g_log << Logger::Error << "Unable to launch, binding to no port or port 0 makes no sense" << endl;
+      exit(99); // this isn't going to fix itself either
+    }
+    if (!BackendMakers().numLauncheable()) {
+      g_log << Logger::Error << "Unable to launch, no backends configured for querying" << endl;
+      exit(99); // this isn't going to fix itself either
+    }
+    if (::arg().mustDo("daemon")) {
+      g_log.toConsole(Logger::None);
+      if (!isGuarded(argv))
+        daemonize();
+    }
+
+    if (isGuarded(argv)) {
+      g_log << Logger::Warning << "This is a guarded instance of pdns" << endl;
+      s_dynListener = std::make_unique<DynListener>(); // listens on stdin
+    }
+    else {
+      g_log << Logger::Warning << "This is a standalone pdns" << endl;
+
+      if (::arg().mustDo("control-console"))
+        s_dynListener = std::make_unique<DynListener>();
+      else
+        s_dynListener = std::make_unique<DynListener>(g_programname);
+
+      writePid();
+    }
+    DynListener::registerFunc("SHOW", &DLShowHandler, "show a specific statistic or * to get a list", "<statistic>");
+    DynListener::registerFunc("RPING", &DLPingHandler, "ping instance");
+    DynListener::registerFunc("QUIT", &DLRQuitHandler, "quit daemon");
+    DynListener::registerFunc("UPTIME", &DLUptimeHandler, "get instance uptime");
+    DynListener::registerFunc("NOTIFY-HOST", &DLNotifyHostHandler, "notify host for specific zone", "<zone> <host>");
+    DynListener::registerFunc("NOTIFY", &DLNotifyHandler, "queue a notification", "<zone>");
+    DynListener::registerFunc("RELOAD", &DLReloadHandler, "reload all zones");
+    DynListener::registerFunc("REDISCOVER", &DLRediscoverHandler, "discover any new zones");
+    DynListener::registerFunc("VERSION", &DLVersionHandler, "get instance version");
+    DynListener::registerFunc("PURGE", &DLPurgeHandler, "purge entries from packet cache", "[<record>]");
+    DynListener::registerFunc("CCOUNTS", &DLCCHandler, "get cache statistics");
+    DynListener::registerFunc("QTYPES", &DLQTypesHandler, "get QType statistics");
+    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 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>");
+    DynListener::registerFunc("XFR-QUEUE", &DLSuckRequests, "Get all requests for XFR in queue");
+
+    if (!::arg()["tcp-control-address"].empty()) {
+      DynListener* dlTCP = new DynListener(ComboAddress(::arg()["tcp-control-address"], ::arg().asNum("tcp-control-port")));
+      dlTCP->go();
+    }
+
+    // reparse, with error checking
+    if (!::arg().mustDo("no-config"))
+      ::arg().file(configname.c_str());
+    ::arg().parse(argc, argv);
+
+    if (::arg()["server-id"].empty()) {
+      char tmp[128];
+      if (gethostname(tmp, sizeof(tmp) - 1) == 0) {
+        ::arg().set("server-id") = tmp;
+      }
+      else {
+        g_log << Logger::Warning << "Unable to get the hostname, NSID and id.server values will be empty: " << stringerror() << endl;
+      }
+    }
+
+    s_udpNameserver = std::make_shared<UDPNameserver>(); // this fails when we are not root, throws exception
+    s_udpReceivers.push_back(s_udpNameserver);
+
+    size_t rthreads = ::arg().asNum("receiver-threads", 1);
+    if (rthreads > 1 && s_udpNameserver->canReusePort()) {
+      s_udpReceivers.resize(rthreads);
+
+      for (size_t idx = 1; idx < rthreads; idx++) {
+        try {
+          s_udpReceivers[idx] = std::make_shared<UDPNameserver>(true);
+        }
+        catch (const PDNSException& e) {
+          g_log << Logger::Error << "Unable to reuse port, falling back to original bind" << endl;
+          break;
+        }
+      }
+    }
+
+    s_tcpNameserver = make_unique<TCPNameserver>();
+  }
+  catch (const ArgException& A) {
+    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 (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);
+
+  showProductVersion();
+
+  try {
+    mainthread();
+  }
+  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 (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 (...) {
+    cerr << "Uncaught exception of unknown type - sorry" << endl;
+  }
+
+  exit(1);
+}
similarity index 80%
rename from pdns/common_startup.hh
rename to pdns/auth-main.hh
index 849bf243579f6960d9f006c4acd31b49ae1787db..b96a61c681870a6060b20544d8c64d9d927d2636 100644 (file)
 #include "tcpreceiver.hh"
 #include "dnsseckeeper.hh"
 
+extern time_t g_starttime;
 extern ArgvMap theArg;
-extern StatBag S;  //!< Statistics are gathered across PDNS via the StatBag class S
+extern StatBag S; //!< Statistics are gathered across PDNS via the StatBag class S
 extern AuthPacketCache PC; //!< This is the main PacketCache, shared across all threads
 extern AuthQueryCache QC;
 extern std::unique_ptr<DNSProxy> DP;
-extern std::unique_ptr<DynListener> dl;
 extern CommunicatorClass Communicator;
-extern std::shared_ptr<UDPNameserver> N;
-extern vector<std::shared_ptr<UDPNameserver> > g_udpReceivers;
-extern double avg_latency;
-extern std::unique_ptr<TCPNameserver> TN;
-extern void declareArguments();
-extern void declareStats();
-extern void mainthread();
-extern int isGuarded( char ** );
-void carbonDumpThread();
+void carbonDumpThread(); // Implemented in auth-carbon.cc. Avoids having an auth-carbon.hh declaring exactly one function.
 extern bool g_anyToTcp;
 extern bool g_8bitDNS;
 extern NetmaskGroup g_proxyProtocolACL;
@@ -60,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;
 
diff --git a/pdns/auth-primarycommunicator.cc b/pdns/auth-primarycommunicator.cc
new file mode 100644 (file)
index 0000000..1b9ff3b
--- /dev/null
@@ -0,0 +1,368 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "auth-caches.hh"
+#include "auth-zonecache.hh"
+#include "utility.hh"
+#include <cerrno>
+#include "communicator.hh"
+#include <set>
+#include <boost/utility.hpp>
+
+#include "dnsbackend.hh"
+#include "ueberbackend.hh"
+#include "packethandler.hh"
+#include "nameserver.hh"
+#include "resolver.hh"
+#include "logger.hh"
+#include "dns.hh"
+#include "arguments.hh"
+#include "packetcache.hh"
+#include "base64.hh"
+#include "namespaces.hh"
+#include "query-local-address.hh"
+
+void CommunicatorClass::queueNotifyDomain(const DomainInfo& di, UeberBackend* B)
+{
+  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());
+            }
+          }
+      }
+
+      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) {
+    g_log << Logger::Error << "Error looking up name servers for " << di.zone << ", cannot notify: " << ae.reason << endl;
+    return;
+  }
+  catch (std::exception& e) {
+    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) {
+    try {
+      const ComboAddress caIp(j, 53);
+      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_delayNotifications);
+      }
+      hasQueuedItem = true;
+    }
+    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;
+}
+
+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;
+    return false;
+  }
+  queueNotifyDomain(di, B);
+  // call backend and tell them we sent out the notification - even though that is premature
+  if (di.serial != di.notified_serial)
+    di.backend->setNotified(di.id, di.serial);
+
+  return true;
+}
+
+void NotificationQueue::dump()
+{
+  cerr << "Waiting for notification responses: " << endl;
+  for (NotificationRequest& nr : d_nqueue) {
+    cerr << nr.domain << ", " << nr.ip << endl;
+  }
+}
+
+void CommunicatorClass::getUpdatedProducers(UeberBackend* B, vector<DomainInfo>& domains, const std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
+{
+  std::string metaHash;
+  std::string mapHash;
+  for (auto& ch : catalogHashes) {
+    if (!catalogs.count(ch.first)) {
+      g_log << Logger::Warning << "orphaned member zones found with catalog '" << ch.first << "'" << endl;
+      continue;
+    }
+
+    if (!B->getDomainMetadata(ch.first, "CATALOG-HASH", metaHash)) {
+      metaHash.clear();
+    }
+
+    mapHash = Base64Encode(ch.second.digest());
+    if (mapHash != metaHash) {
+      DomainInfo di;
+      if (B->getDomainInfo(ch.first, di)) {
+        if (di.kind != DomainInfo::Producer) {
+          g_log << Logger::Warning << "zone '" << di.zone << "' is no producer zone" << endl;
+          continue;
+        }
+
+        B->setDomainMetadata(di.zone, "CATALOG-HASH", mapHash);
+
+        g_log << Logger::Warning << "new CATALOG-HASH '" << mapHash << "' for zone '" << di.zone << "'" << endl;
+
+        SOAData sd;
+        if (!B->getSOAUncached(di.zone, sd)) {
+          g_log << Logger::Warning << "SOA lookup failed for producer zone '" << di.zone << "'" << endl;
+          continue;
+        }
+
+        DNSResourceRecord rr;
+        makeIncreasedSOARecord(sd, "EPOCH", "", rr);
+        di.backend->startTransaction(sd.qname, -1);
+        if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
+          di.backend->abortTransaction();
+          throw PDNSException("backend hosting producer zone '" + sd.qname.toLogString() + "' does not support editing records");
+        }
+        di.backend->commitTransaction();
+
+        domains.emplace_back(di);
+      }
+    }
+  }
+}
+
+void CommunicatorClass::primaryUpdateCheck(PacketHandler* P)
+{
+  if (!::arg().mustDo("primary"))
+    return;
+
+  UeberBackend* B = P->getBackend();
+  vector<DomainInfo> cmdomains;
+  std::unordered_set<DNSName> catalogs;
+  CatalogHashMap catalogHashes;
+  B->getUpdatedPrimaries(cmdomains, catalogs, catalogHashes);
+  getUpdatedProducers(B, cmdomains, catalogs, catalogHashes);
+
+  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) {
+    purgeAuthCachesExact(di.zone);
+    g_zoneCache.add(di.zone, di.id);
+    queueNotifyDomain(di, B);
+    di.backend->setNotified(di.id, di.serial);
+  }
+}
+
+time_t CommunicatorClass::doNotifications(PacketHandler* P)
+{
+  UeberBackend* B = P->getBackend();
+  ComboAddress from;
+  char buffer[1500];
+  int sock;
+  set<int> fds = {d_nsock4, d_nsock6};
+
+  // receive incoming notifications on the nonblocking socket and take them off the list
+  while (waitForMultiData(fds, 0, 0, &sock) > 0) {
+    Utility::socklen_t fromlen = sizeof(from);
+    const auto size = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&from, &fromlen);
+    if (size < 0) {
+      break;
+    }
+    DNSPacket p(true);
+
+    p.setRemote(&from);
+
+    if (p.parse(buffer, (size_t)size) < 0) {
+      g_log << Logger::Warning << "Unable to parse SOA notification answer from " << p.getRemote() << endl;
+      continue;
+    }
+
+    if (p.d.rcode) {
+      g_log << Logger::Warning << "Received unsuccessful notification report for '" << p.qdomain << "' from " << from.toStringWithPort() << ", error: " << RCode::to_s(p.d.rcode) << endl;
+    }
+
+    if (d_nq.removeIf(from, p.d.id, p.qdomain)) {
+      g_log << Logger::Notice << "Removed from notification list: '" << p.qdomain << "' to " << from.toStringWithPort() << " " << (p.d.rcode ? RCode::to_s(p.d.rcode) : "(was acknowledged)") << endl;
+    }
+    else {
+      g_log << Logger::Warning << "Received spurious notify answer for '" << p.qdomain << "' from " << from.toStringWithPort() << endl;
+      // d_nq.dump();
+    }
+  }
+
+  // send out possible new notifications
+  DNSName domain;
+  string ip;
+  uint16_t id = 0;
+
+  bool purged;
+  while (d_nq.getOne(domain, ip, &id, purged)) {
+    if (!purged) {
+      try {
+        ComboAddress remote(ip, 53); // default to 53
+        if ((d_nsock6 < 0 && remote.sin4.sin_family == AF_INET6) || (d_nsock4 < 0 && remote.sin4.sin_family == AF_INET)) {
+          g_log << Logger::Warning << "Unable to notify " << remote.toStringWithPort() << " for domain '" << domain << "', address family is disabled. Is an IPv" << (remote.sin4.sin_family == AF_INET ? "4" : "6") << " address set in query-local-address?" << endl;
+          d_nq.removeIf(remote, id, domain); // Remove, we'll never be able to notify
+          continue; // don't try to notify what we can't!
+        }
+        if (d_preventSelfNotification && AddressIsUs(remote)) {
+          continue;
+        }
+
+        sendNotification(remote.sin4.sin_family == AF_INET ? d_nsock4 : d_nsock6, domain, remote, id, B);
+        drillHole(domain, ip);
+      }
+      catch (ResolverException& re) {
+        g_log << Logger::Warning << "Error trying to resolve '" << ip << "' for notifying '" << domain << "' to server: " << re.reason << endl;
+      }
+    }
+    else {
+      g_log << Logger::Warning << "Notification for " << domain << " to " << ip << " failed after retries" << endl;
+    }
+  }
+
+  return d_nq.earliest();
+}
+
+void CommunicatorClass::sendNotification(int sock, const DNSName& domain, const ComboAddress& remote, uint16_t id, UeberBackend* B)
+{
+  vector<string> meta;
+  DNSName tsigkeyname;
+  DNSName tsigalgorithm;
+  string tsigsecret64;
+  string tsigsecret;
+
+  if (::arg().mustDo("send-signed-notify") && B->getDomainMetadata(domain, "TSIG-ALLOW-AXFR", meta) && meta.size() > 0) {
+    tsigkeyname = DNSName(meta[0]);
+  }
+
+  vector<uint8_t> packet;
+  DNSPacketWriter pw(packet, domain, QType::SOA, 1, Opcode::Notify);
+  pw.getHeader()->id = id;
+  pw.getHeader()->aa = true;
+
+  if (tsigkeyname.empty() == false) {
+    if (!B->getTSIGKey(tsigkeyname, tsigalgorithm, tsigsecret64)) {
+      g_log << Logger::Error << "TSIG key '" << tsigkeyname << "' for domain '" << domain << "' not found" << endl;
+      return;
+    }
+    TSIGRecordContent trc;
+    if (tsigalgorithm.toStringNoDot() == "hmac-md5")
+      trc.d_algoName = DNSName(tsigalgorithm.toStringNoDot() + ".sig-alg.reg.int.");
+    else
+      trc.d_algoName = tsigalgorithm;
+    trc.d_time = time(nullptr);
+    trc.d_fudge = 300;
+    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;
+      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());
+  }
+}
+
+void CommunicatorClass::drillHole(const DNSName& domain, const string& ip)
+{
+  (*d_holes.lock())[pair(domain, ip)] = time(nullptr);
+}
+
+bool CommunicatorClass::justNotified(const DNSName& domain, const string& ip)
+{
+  auto holes = d_holes.lock();
+  auto it = holes->find(pair(domain, ip));
+  if (it == holes->end()) {
+    // no hole
+    return false;
+  }
+
+  if (it->second > time(nullptr) - 900) {
+    // recent hole
+    return true;
+  }
+
+  // do we want to purge this? XXX FIXME
+  return false;
+}
+
+void CommunicatorClass::makeNotifySockets()
+{
+  if (pdns::isQueryLocalAddressFamilyEnabled(AF_INET)) {
+    d_nsock4 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET, 0), true, ::arg().mustDo("non-local-bind"));
+  }
+  else {
+    d_nsock4 = -1;
+  }
+  if (pdns::isQueryLocalAddressFamilyEnabled(AF_INET6)) {
+    d_nsock6 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET6, 0), true, ::arg().mustDo("non-local-bind"));
+  }
+  else {
+    d_nsock6 = -1;
+  }
+}
+
+void CommunicatorClass::notify(const DNSName& domain, const string& ip)
+{
+  d_nq.add(domain, ip);
+}
index dea4583e9f3f1e239fea58f9e0c3d8b2e8ec8bb6..b397c1e830454aae92ee33792885d248afdcb41e 100644 (file)
@@ -76,10 +76,9 @@ void AuthQueryCache::insert(const DNSName &qname, const QType& qtype, vector<DNS
 
   if(!ttl)
     return;
-  
+
   time_t now = time(nullptr);
   CacheEntry val;
-  val.created = now;
   val.ttd = now + ttl;
   val.qname = qname;
   val.qtype = qtype.getCode();
index 20c8ffb4d4bcbd46008a25da799024e85eeba321..b144b366700d42fa6caa0ccdf440e25ee1ac2bc7 100644 (file)
@@ -67,7 +67,6 @@ private:
   {
     DNSName qname;
     mutable vector<DNSZoneRecord> drs;
-    mutable time_t created{0};
     mutable time_t ttd{0};
     uint16_t qtype{0};
     int zoneID{-1};
@@ -93,10 +92,8 @@ private:
 
   struct MapCombo
   {
-    MapCombo() {
-    }
-    ~MapCombo() {
-    }
+    MapCombo() = default;
+    ~MapCombo() = default;
     MapCombo(const MapCombo &) = delete; 
     MapCombo & operator=(const MapCombo &) = delete;
 
diff --git a/pdns/auth-secondarycommunicator.cc b/pdns/auth-secondarycommunicator.cc
new file mode 100644 (file)
index 0000000..fb0e73c
--- /dev/null
@@ -0,0 +1,1408 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "utility.hh"
+#include "dnssecinfra.hh"
+#include "dnsseckeeper.hh"
+#include "base32.hh"
+#include <cerrno>
+#include "communicator.hh"
+#include <set>
+#include <boost/utility.hpp>
+#include "dnsbackend.hh"
+#include "ueberbackend.hh"
+#include "packethandler.hh"
+#include "axfr-retriever.hh"
+#include "logger.hh"
+#include "dns.hh"
+#include "arguments.hh"
+#include "auth-caches.hh"
+
+#include "base64.hh"
+#include "inflighter.cc"
+#include "namespaces.hh"
+#include "auth-main.hh"
+#include "query-local-address.hh"
+
+#include "ixfr.hh"
+
+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.primary = primary;
+  sr.force = force;
+  sr.priorityAndOrder.first = priority;
+  sr.priorityAndOrder.second = data->d_sorthelper++;
+  pair<UniQueue::iterator, bool> res;
+
+  res = data->d_suckdomains.insert(sr);
+  if (res.second) {
+    d_suck_sem.post();
+  }
+  else {
+    data->d_suckdomains.modify(res.first, [priorityAndOrder = sr.priorityAndOrder](SuckRequest& so) {
+      if (priorityAndOrder.first < so.priorityAndOrder.first) {
+        so.priorityAndOrder = priorityAndOrder;
+      }
+    });
+  }
+}
+
+struct ZoneStatus
+{
+  bool isDnssecZone{false};
+  bool isPresigned{false};
+  bool isNSEC3{false};
+  bool optOutFlag{false};
+  NSEC3PARAMRecordContent ns3pr;
+
+  bool isNarrow{false};
+  unsigned int soa_serial{0};
+  set<DNSName> nsset, qnames, secured;
+  uint32_t domain_id;
+  int numDeltas{0};
+};
+
+static bool catalogDiff(const DomainInfo& di, vector<CatalogInfo>& fromXFR, vector<CatalogInfo>& fromDB, const string& logPrefix)
+{
+  extern CommunicatorClass Communicator;
+
+  bool doTransaction{true};
+  bool inTransaction{false};
+  CatalogInfo ciCreate, ciRemove;
+  std::unordered_map<DNSName, bool> clearCache;
+  vector<CatalogInfo> retrieve;
+
+  try {
+    sort(fromXFR.begin(), fromXFR.end());
+    sort(fromDB.begin(), fromDB.end());
+
+    auto xfr = fromXFR.cbegin();
+    auto db = fromDB.cbegin();
+
+    while (xfr != fromXFR.end() || db != fromDB.end()) {
+      bool create{false};
+      bool remove{false};
+
+      if (xfr != fromXFR.end() && (db == fromDB.end() || *xfr < *db)) { // create
+        ciCreate = *xfr;
+        create = true;
+        ++xfr;
+      }
+      else if (db != fromDB.end() && (xfr == fromXFR.end() || *db < *xfr)) { // remove
+        ciRemove = *db;
+        remove = true;
+        ++db;
+      }
+      else {
+        CatalogInfo ciXFR = *xfr;
+        CatalogInfo ciDB = *db;
+        if (ciDB.d_unique.empty() || ciXFR.d_unique == ciDB.d_unique) { // update
+          bool doOptions{false};
+
+          if (ciDB.d_unique.empty()) { // set unique
+            g_log << Logger::Warning << logPrefix << "set unique, zone '" << ciXFR.d_zone << "' is now a member" << endl;
+            ciDB.d_unique = ciXFR.d_unique;
+            doOptions = true;
+          }
+
+          if (ciXFR.d_coo != ciDB.d_coo) { // update coo
+            g_log << Logger::Warning << logPrefix << "update coo for zone '" << ciXFR.d_zone << "' to '" << ciXFR.d_coo << "'" << endl;
+            ciDB.d_coo = ciXFR.d_coo;
+            doOptions = true;
+          }
+
+          if (ciXFR.d_group != ciDB.d_group) { // update group
+            g_log << Logger::Warning << logPrefix << "update group for zone '" << ciXFR.d_zone << "' to '" << boost::join(ciXFR.d_group, ", ") << "'" << endl;
+            ciDB.d_group = ciXFR.d_group;
+            doOptions = true;
+          }
+
+          if (doOptions) { // update zone options
+            if (doTransaction && (inTransaction = di.backend->startTransaction(di.zone))) {
+              g_log << Logger::Warning << logPrefix << "backend transaction started" << endl;
+              doTransaction = false;
+            }
+
+            g_log << Logger::Warning << logPrefix << "update options for zone '" << ciXFR.d_zone << "'" << endl;
+            di.backend->setOptions(ciXFR.d_zone, ciDB.toJson());
+          }
+
+          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.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->setPrimaries(ciXFR.d_zone, di.primaries);
+
+            retrieve.emplace_back(ciXFR);
+          }
+        }
+        else { // reset
+          ciCreate = *xfr;
+          ciRemove = *db;
+          create = true;
+          remove = true;
+        }
+        ++xfr;
+        ++db;
+      }
+
+      DomainInfo d;
+      if (create && remove) {
+        g_log << Logger::Warning << logPrefix << "zone '" << ciCreate.d_zone << "' state reset" << endl;
+      }
+      else if (create && di.backend->getDomainInfo(ciCreate.d_zone, d)) { // detect clash
+        CatalogInfo ci;
+        ci.fromJson(d.options, CatalogInfo::CatalogType::Consumer);
+
+        if (di.zone != d.catalog && di.zone == ci.d_coo) {
+          if (ciCreate.d_unique == ci.d_unique) {
+            g_log << Logger::Warning << logPrefix << "zone '" << d.zone << "' owner change without state reset, old catalog '" << d.catalog << "', new catalog '" << di.zone << "'" << endl;
+
+            if (doTransaction && (inTransaction = di.backend->startTransaction(di.zone))) {
+              g_log << Logger::Warning << logPrefix << "backend transaction started" << endl;
+              doTransaction = false;
+            }
+
+            di.backend->setPrimaries(ciCreate.d_zone, di.primaries);
+            di.backend->setOptions(ciCreate.d_zone, ciCreate.toJson());
+            di.backend->setCatalog(ciCreate.d_zone, di.zone);
+
+            retrieve.emplace_back(ciCreate);
+            continue;
+          }
+          g_log << Logger::Warning << logPrefix << "zone '" << d.zone << "' owner change with state reset, old catalog '" << d.catalog << "', new catalog '" << di.zone << "'" << endl;
+
+          ciRemove.d_zone = d.zone;
+          remove = true;
+        }
+        else {
+          g_log << Logger::Warning << logPrefix << "zone '" << d.zone << "' already exists";
+          if (!d.catalog.empty()) {
+            g_log << " in catalog '" << d.catalog;
+          }
+          g_log << "', create skipped" << endl;
+          continue;
+        }
+      }
+
+      if (remove) { // delete zone
+        if (doTransaction && (inTransaction = di.backend->startTransaction(di.zone))) {
+          g_log << Logger::Warning << logPrefix << "backend transaction started" << endl;
+          doTransaction = false;
+        }
+
+        g_log << Logger::Warning << logPrefix << "delete zone '" << ciRemove.d_zone << "'" << endl;
+        di.backend->deleteDomain(ciRemove.d_zone);
+
+        if (!create) {
+          clearCache[ciRemove.d_zone] = false;
+        }
+      }
+
+      if (create) { // create zone
+        if (doTransaction && (inTransaction = di.backend->startTransaction(di.zone))) {
+          g_log << Logger::Warning << logPrefix << "backend transaction started" << endl;
+          doTransaction = false;
+        }
+
+        g_log << Logger::Warning << logPrefix << "create zone '" << ciCreate.d_zone << "'" << endl;
+        di.backend->createDomain(ciCreate.d_zone, DomainInfo::Secondary, ciCreate.d_primaries, "");
+
+        di.backend->setPrimaries(ciCreate.d_zone, di.primaries);
+        di.backend->setOptions(ciCreate.d_zone, ciCreate.toJson());
+        di.backend->setCatalog(ciCreate.d_zone, di.zone);
+
+        clearCache[ciCreate.d_zone] = true;
+        retrieve.emplace_back(ciCreate);
+      }
+    }
+
+    if (inTransaction && di.backend->commitTransaction()) {
+      g_log << Logger::Warning << logPrefix << "backend transaction committed" << endl;
+    }
+
+    // Update zonecache and clear all caches
+    DomainInfo d;
+    for (const auto& zone : clearCache) {
+      if (g_zoneCache.isEnabled()) {
+        if (zone.second) {
+          if (di.backend->getDomainInfo(zone.first, d)) {
+            g_zoneCache.add(zone.first, d.id);
+          }
+          else {
+            g_log << Logger::Error << logPrefix << "new zone '" << zone.first << "' does not exists and was not inserted in the zone-cache" << endl;
+          }
+        }
+        else {
+          g_zoneCache.remove(zone.first);
+        }
+      }
+
+      DNSSECKeeper::clearCaches(zone.first);
+      purgeAuthCaches(zone.first.toString() + "$");
+    }
+
+    // retrieve new and updated zones with new primaries
+    auto primaries = di.primaries;
+    if (!primaries.empty()) {
+      for (auto& ret : retrieve) {
+        shuffle(primaries.begin(), primaries.end(), pdns::dns_random_engine());
+        const auto& primary = primaries.front();
+        Communicator.addSuckRequest(ret.d_zone, primary, SuckRequest::Notify);
+      }
+    }
+
+    return true;
+  }
+  catch (DBException& re) {
+    g_log << Logger::Error << logPrefix << "DBException " << re.reason << endl;
+  }
+  catch (PDNSException& pe) {
+    g_log << Logger::Error << logPrefix << "PDNSException " << pe.reason << endl;
+  }
+  catch (std::exception& re) {
+    g_log << Logger::Error << logPrefix << "std::exception " << re.what() << endl;
+  }
+
+  if (di.backend && inTransaction) {
+    g_log << Logger::Info << logPrefix << "aborting possible open transaction" << endl;
+    di.backend->abortTransaction();
+  }
+
+  return false;
+}
+
+static bool catalogProcess(const DomainInfo& di, vector<DNSResourceRecord>& rrs, string logPrefix)
+{
+  logPrefix += "Catalog-Zone ";
+
+  vector<CatalogInfo> fromXFR, fromDB;
+  std::unordered_set<DNSName> dupcheck;
+
+  // From XFR
+  bool hasSOA{false};
+  bool zoneInvalid{false};
+  int hasVersion{0};
+
+  CatalogInfo ci;
+
+  vector<DNSResourceRecord> ret;
+
+  const auto compare = [](const DNSResourceRecord& a, const DNSResourceRecord& b) { return a.qname == b.qname ? a.qtype < b.qtype : a.qname.canonCompare(b.qname); };
+  sort(rrs.begin(), rrs.end(), compare);
+
+  DNSName rel;
+  DNSName unique;
+  for (auto& rr : rrs) {
+    if (di.zone == rr.qname) {
+      if (rr.qtype == QType::SOA) {
+        hasSOA = true;
+        continue;
+      }
+    }
+
+    else if (rr.qname == DNSName("version") + di.zone && rr.qtype == QType::TXT) {
+      if (hasVersion) {
+        g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "', multiple version records found, aborting" << endl;
+        return false;
+      }
+
+      if (rr.content == "\"1\"") {
+        hasVersion = 1;
+      }
+      else if (rr.content == "\"2\"") {
+        hasVersion = 2;
+      }
+      else {
+        g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "', unsupported catalog zone schema version " << rr.content << ", aborting" << endl;
+        return false;
+      }
+    }
+
+    else if (rr.qname.isPartOf(DNSName("zones") + di.zone)) {
+      if (rel.empty() && !hasVersion) {
+        g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "', catalog zone schema version missing, aborting" << endl;
+        return false;
+      }
+
+      rel = rr.qname.makeRelative(DNSName("zones") + di.zone);
+
+      if (rel.countLabels() == 1 && rr.qtype == QType::PTR) {
+        if (!unique.empty()) {
+          if (rel != unique) {
+            fromXFR.emplace_back(ci);
+          }
+          else {
+            g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "', duplicate unique '" << unique << "'" << endl;
+            zoneInvalid = true;
+          }
+        }
+
+        unique = rel;
+
+        ci = {};
+        ci.setType(CatalogInfo::CatalogType::Consumer);
+        ci.d_zone = DNSName(rr.content);
+        ci.d_unique = unique;
+
+        if (!dupcheck.insert(ci.d_zone).second) {
+          g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "', duplicate member zone'" << ci.d_zone << "'" << endl;
+          zoneInvalid = true;
+        }
+      }
+
+      else if (hasVersion == 2) {
+        if (rel == (DNSName("coo") + unique) && rr.qtype == QType::PTR) {
+          if (!ci.d_coo.empty()) {
+            g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "', duplicate COO for unique '" << unique << "'" << endl;
+            zoneInvalid = true;
+          }
+          else {
+            ci.d_coo = DNSName(rr.content);
+          }
+        }
+        else if (rel == (DNSName("group") + unique) && rr.qtype == QType::TXT) {
+          std::string content = rr.content;
+          if (content.length() >= 2 && content.at(0) == '\"' && content.at(content.length() - 1) == '\"') { // TXT pain
+            content = content.substr(1, content.length() - 2);
+          }
+          ci.d_group.insert(content);
+        }
+      }
+    }
+    rr.disabled = true;
+  }
+  if (!ci.d_zone.empty()) {
+    fromXFR.emplace_back(ci);
+  }
+
+  if (!hasSOA || !hasVersion || zoneInvalid) {
+    g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "' is invalid, skip updates" << endl;
+    return false;
+  }
+
+  // Get catalog ifo from db
+  if (!di.backend->getCatalogMembers(di.zone, fromDB, CatalogInfo::CatalogType::Consumer)) {
+    return false;
+  }
+
+  // Process
+  return catalogDiff(di, fromXFR, fromDB, logPrefix);
+}
+
+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() + "', ";
+
+  UeberBackend B; // fresh UeberBackend
+
+  DomainInfo di;
+  di.backend = nullptr;
+  //  bool transaction=false;
+  try {
+    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::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;
+      return;
+    }
+
+    uint16_t xfrTimeout = ::arg().asNum("axfr-fetch-timeout");
+    soatimes st;
+    memset(&st, 0, sizeof(st));
+    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();
+    //    cout<<"Got "<<deltas.size()<<" deltas from serial "<<di.serial<<", applying.."<<endl;
+
+    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!
+        *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;
+
+      for (const auto& x : remove)
+        grouped[{x.d_name, x.d_type}].first.push_back(x);
+      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) {
+        vector<DNSRecord> rrset;
+        {
+          DNSZoneRecord 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);
+          }
+        }
+        // O(N^2)!
+        rrset.erase(remove_if(rrset.begin(), rrset.end(),
+                              [&g](const DNSRecord& dr) {
+                                return count(g.second.first.cbegin(),
+                                             g.second.first.cend(), dr);
+                              }),
+                    rrset.end());
+        // the DNSRecord== operator compares on name, type, class and lowercase content representation
+
+        for (const auto& x : g.second.second) {
+          rrset.push_back(x);
+        }
+
+        vector<DNSResourceRecord> replacement;
+        for (const auto& dr : rrset) {
+          auto rr = DNSResourceRecord::fromWire(dr);
+          rr.qname += domain;
+          rr.domain_id = di.id;
+          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;
+          }
+
+          replacement.push_back(rr);
+        }
+
+        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;
+    throw;
+  }
+  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()) {
+  case QType::NSEC3PARAM:
+    zs.ns3pr = NSEC3PARAMRecordContent(rr.content);
+    zs.isDnssecZone = zs.isNSEC3 = true;
+    zs.isNarrow = false;
+    return false;
+  case QType::NSEC3: {
+    NSEC3RecordContent ns3rc(rr.content);
+    if (firstNSEC3) {
+      zs.isDnssecZone = zs.isPresigned = true;
+      firstNSEC3 = false;
+    }
+    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)) {
+      DNSName hashPart = rr.qname.makeRelative(domain);
+      zs.secured.insert(hashPart);
+    }
+    return false;
+  }
+
+  case QType::NSEC:
+    zs.isDnssecZone = zs.isPresigned = true;
+    return false;
+
+  case QType::NS:
+    if (rr.qname != domain)
+      zs.nsset.insert(rr.qname);
+    break;
+  }
+
+  zs.qnames.insert(rr.qname);
+
+  rr.domain_id = zs.domain_id;
+  return true;
+}
+
+/* So this code does a number of things.
+   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
+   3) The code walks through the zone records do determine DNSSEC status (secured, nsec/nsec3, optout)
+   4) It inserts the zone into the database
+      With the right 'ordername' fields
+   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)
+{
+  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);
+  Resolver::res_t recs;
+  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;
+    }
+
+    for (auto& rec : recs) {
+      rec.qname.makeUsLowerCase();
+      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;
+        continue;
+      }
+
+      vector<DNSResourceRecord> 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;
+          continue;
+        }
+        if (!processRecordForZS(domain, firstNSEC3, rr, zs))
+          continue;
+        if (rr.qtype.getCode() == QType::SOA) {
+          if (soa_received)
+            continue; // skip the last SOA
+          SOAData 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)
+{
+  {
+    auto data = d_data.lock();
+    if (data->d_inprogress.count(domain)) {
+      return;
+    }
+    data->d_inprogress.insert(domain);
+  }
+  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() + "', ";
+
+  g_log << Logger::Notice << logPrefix << "initiating transfer" << endl;
+  UeberBackend B; // fresh UeberBackend
+
+  DomainInfo di;
+  di.backend = nullptr;
+  bool transaction = false;
+  try {
+    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)
+        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;
+      return;
+    }
+    ZoneStatus zs;
+    zs.domain_id = di.id;
+
+    TSIGTriplet tt;
+    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;
+          return;
+        }
+      }
+      else {
+        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()) {
+      if (pdns_iequals(scripts[0], "NONE")) {
+        script.clear();
+      }
+      else {
+        script = scripts[0];
+      }
+    }
+    if (!script.empty()) {
+      try {
+        pdl = make_unique<AuthLua4>();
+        pdl->loadFile(script);
+        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;
+        return;
+      }
+    }
+
+    vector<string> localaddr;
+    ComboAddress laddr;
+
+    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;
+      }
+      catch (std::exception& e) {
+        g_log << Logger::Error << logPrefix << "failed to set xfr source '" << localaddr[0] << "': " << e.what() << endl;
+        return;
+      }
+    }
+    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;
+        return;
+      }
+      laddr = pdns::getQueryLocalAddress(remote.sin4.sin_family, 0);
+    }
+
+    bool hadDnssecZone = false;
+    bool hadPresigned = false;
+    bool hadNSEC3 = false;
+    NSEC3PARAMRecordContent hadNs3pr;
+    bool hadNarrow = false;
+
+    vector<DNSResourceRecord> rrs;
+    if (dk.isSecuredZone(domain, false)) {
+      hadDnssecZone = true;
+      hadPresigned = dk.isPresigned(domain, false);
+      if (dk.getNSEC3PARAM(domain, &zs.ns3pr, &zs.isNarrow, false)) {
+        hadNSEC3 = true;
+        hadNs3pr = zs.ns3pr;
+        hadNarrow = zs.isNarrow;
+      }
+    }
+    else if (di.serial) {
+      vector<string> meta;
+      B.getDomainMetadata(domain, "IXFR", meta);
+      if (!meta.empty() && meta[0] == "1") {
+        logPrefix = "I" + logPrefix; // XFR -> IXFR
+        vector<DNSRecord> axfr;
+        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;
+          rrs.reserve(axfr.size());
+          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))
+              continue;
+            if (dr.d_type == QType::SOA) {
+              auto sd = getRR<SOARecordContent>(dr);
+              zs.soa_serial = sd->d_st.serial;
+            }
+            rrs.push_back(rr);
+          }
+        }
+        else {
+          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;
+      rrs = doAxfr(remote, domain, tt, laddr, pdl, zs);
+      logPrefix = "A" + logPrefix; // XFR -> AXFR
+      g_log << Logger::Notice << logPrefix << "retrieval finished" << endl;
+    }
+
+    if (di.kind == DomainInfo::Consumer) {
+      if (!catalogProcess(di, rrs, logPrefix)) {
+        g_log << Logger::Warning << logPrefix << "Catalog-Zone update failed, only import records" << endl;
+      }
+    }
+
+    if (zs.isNSEC3) {
+      zs.ns3pr.d_flags = zs.optOutFlag ? 1 : 0;
+    }
+
+    if (!zs.isPresigned) {
+      DNSSECKeeper::keyset_t keys = dk.getKeys(domain, false);
+      if (!keys.empty()) {
+        zs.isDnssecZone = true;
+        zs.isNSEC3 = hadNSEC3;
+        zs.ns3pr = hadNs3pr;
+        zs.optOutFlag = (hadNs3pr.d_flags & 1);
+        zs.isNarrow = hadNarrow;
+      }
+    }
+
+    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;
+    }
+
+    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) {
+      // update presigned if there was a change
+      if (zs.isPresigned && !hadPresigned) {
+        // zone is now presigned
+        dk.setPresigned(domain);
+      }
+      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)) {
+          dk.setNSEC3PARAM(domain, zs.ns3pr, zs.isNarrow);
+        }
+      }
+      else if (hadNSEC3) {
+        // zone is no longer NSEC3
+        dk.unsetNSEC3PARAM(domain);
+      }
+    }
+    else if (hadDnssecZone) {
+      // zone is no longer signed
+      if (hadPresigned) {
+        // remove presigned
+        dk.unsetPresigned(domain);
+      }
+      if (hadNSEC3) {
+        // unset NSEC3PARAM
+        dk.unsetNSEC3PARAM(domain);
+      }
+    }
+
+    bool doent = true;
+    uint32_t maxent = ::arg().asNum("max-ent-entries");
+    DNSName shorter, ordername;
+    set<DNSName> rrterm;
+    map<DNSName, bool> nonterm;
+
+    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"))
+          continue;
+      }
+
+      // Figure out auth and ents
+      rr.auth = true;
+      shorter = rr.qname;
+      rrterm.clear();
+      do {
+        if (doent) {
+          if (!zs.qnames.count(shorter))
+            rrterm.insert(shorter);
+        }
+        if (zs.nsset.count(shorter) && rr.qtype.getCode() != QType::DS)
+          rr.auth = false;
+
+        if (shorter == domain) // stop at apex
+          break;
+      } while (shorter.chopOff());
+
+      // Insert ents
+      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;
+
+        for (const auto& nt : rrterm) {
+          if (!nonterm.count(nt))
+            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;
+          nonterm.clear();
+          doent = false;
+        }
+      }
+
+      // RRSIG is always auth, even inside a delegation
+      if (rr.qtype.getCode() == QType::RRSIG)
+        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))))) {
+            di.backend->feedRecord(rr, ordername, true);
+          }
+          else
+            di.backend->feedRecord(rr, DNSName());
+        }
+        else {
+          // NSEC
+          if (rr.auth || rr.qtype.getCode() == QType::NS) {
+            ordername = rr.qname.makeRelative(domain);
+            di.backend->feedRecord(rr, ordername);
+          }
+          else
+            di.backend->feedRecord(rr, DNSName());
+        }
+      }
+      else
+        di.backend->feedRecord(rr, DNSName());
+    }
+
+    // Insert empty non-terminals
+    if (doent && !nonterm.empty()) {
+      if (zs.isNSEC3) {
+        di.backend->feedEnts3(zs.domain_id, domain, nonterm, zs.ns3pr, zs.isNarrow);
+      }
+      else
+        di.backend->feedEnts(zs.domain_id, nonterm);
+    }
+
+    di.backend->commitTransaction();
+    transaction = false;
+    di.backend->setFresh(zs.domain_id);
+    purgeAuthCaches(domain.toString() + "$");
+
+    g_log << Logger::Warning << logPrefix << "zone committed with serial " << zs.soa_serial << endl;
+
+    // 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("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;
+      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;
+      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;
+      di.backend->abortTransaction();
+    }
+  }
+  catch (ResolverException& re) {
+    {
+      auto data = d_data.lock();
+      // 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 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_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_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;
+      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;
+      di.backend->abortTransaction();
+    }
+  }
+}
+namespace
+{
+struct DomainNotificationInfo
+{
+  DomainInfo di;
+  bool dnssecOk;
+  ComboAddress localaddr;
+  DNSName tsigkeyname, tsigalgname;
+  string tsigsecret;
+};
+}
+
+struct SecondarySenderReceiver
+{
+  typedef std::tuple<DNSName, ComboAddress, uint16_t> Identifier;
+
+  struct Answer
+  {
+    uint32_t theirSerial;
+    uint32_t theirInception;
+    uint32_t theirExpire;
+  };
+
+  map<uint32_t, Answer> d_freshness;
+
+  void deliverTimeout(const Identifier& /* i */)
+  {
+  }
+
+  Identifier send(DomainNotificationInfo& dni)
+  {
+    shuffle(dni.di.primaries.begin(), dni.di.primaries.end(), pdns::dns_random_engine());
+    try {
+      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);
+    }
+  }
+
+  bool receive(Identifier& id, Answer& a)
+  {
+    return d_resolver.tryGetSOASerial(&(std::get<0>(id)), &(std::get<1>(id)), &a.theirSerial, &a.theirInception, &a.theirExpire, &(std::get<2>(id)));
+  }
+
+  void deliverAnswer(const DomainNotificationInfo& dni, const Answer& a, unsigned int /* usec */)
+  {
+    d_freshness[dni.di.id] = a;
+  }
+
+  Resolver d_resolver;
+};
+
+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 primary, clear all other primaries so we can be sure the
+  // query goes to that one.
+  for (const auto& primary : di.primaries) {
+    if (ComboAddress::addressOnlyEqual()(remote, primary)) {
+      ours.primaries.clear();
+      ours.primaries.push_back(primary);
+      break;
+    }
+  }
+  data->d_tocheck.erase(di);
+  data->d_tocheck.insert(ours);
+  d_any_sem.post(); // kick the loop!
+}
+
+void CommunicatorClass::addTryAutoPrimaryRequest(const DNSPacket& p)
+{
+  const DNSPacket& ours = p;
+  auto data = d_data.lock();
+  if (data->d_potentialautoprimaries.insert(ours).second) {
+    d_any_sem.post(); // kick the loop!
+  }
+}
+
+void CommunicatorClass::secondaryRefresh(PacketHandler* P)
+{
+  // not unless we are secondary
+  if (!::arg().mustDo("secondary"))
+    return;
+
+  UeberBackend* B = P->getBackend();
+  vector<DomainInfo> rdomains;
+  vector<DomainNotificationInfo> sdomains;
+  set<DNSPacket, Data::cmp> trysuperdomains;
+  {
+    auto data = d_data.lock();
+    set<DomainInfo> requeue;
+    rdomains.reserve(data->d_tocheck.size());
+    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;
+        requeue.insert(di);
+      }
+      else {
+        // 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_potentialautoprimaries);
+    data->d_potentialautoprimaries.clear();
+  }
+
+  for (const DNSPacket& dp : trysuperdomains) {
+    // get the TSIG key name
+    TSIGRecordContent trc;
+    DNSName tsigkeyname;
+    dp.getTSIGDetails(&trc, &tsigkeyname);
+    P->tryAutoPrimarySynchronous(dp, tsigkeyname); // FIXME could use some error logging
+  }
+  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);
+    time_t now = time(nullptr);
+
+    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;
+        continue;
+      }
+      std::vector<std::string> localaddr;
+      SuckRequest sr;
+      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.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
+        continue;
+      }
+
+      DomainNotificationInfo dni;
+      dni.di = di;
+      dni.dnssecOk = checkSignatures;
+
+      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;
+          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;
+          continue;
+        }
+      }
+
+      localaddr.clear();
+      // check for AXFR-SOURCE
+      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;
+        }
+        catch (std::exception& e) {
+          g_log << Logger::Error << "Failed to load freshness check source '" << localaddr[0] << "' for '" << di.zone << "': " << e.what() << endl;
+          return;
+        }
+      }
+      else {
+        dni.localaddr.sin4.sin_family = 0;
+      }
+
+      sdomains.push_back(std::move(dni));
+    }
+  }
+  if (sdomains.empty()) {
+    if (d_secondarieschanged) {
+      auto data = d_data.lock();
+      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_secondarieschanged = !rdomains.empty();
+    return;
+  }
+  else {
+    auto data = d_data.lock();
+    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;
+  }
+
+  SecondarySenderReceiver ssr;
+
+  Inflighter<vector<DomainNotificationInfo>, SecondarySenderReceiver> ifl(sdomains, ssr);
+
+  ifl.d_maxInFlight = 200;
+
+  for (;;) {
+    try {
+      ifl.run();
+      break;
+    }
+    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;
+    }
+  }
+
+  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;
+  }
+
+  time_t now = time(nullptr);
+  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 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.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;
+        continue;
+      }
+      // Backend for di still doesn't exist and this might cause us to
+      // SEGFAULT on the setFresh command later on
+      di.backend = tempdi.backend;
+    }
+
+    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_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_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;
+      }
+      // Make sure we recheck SOA for notifies
+      if (di.receivedNotify) {
+        di.backend->setStale(di.id);
+      }
+      continue;
+    }
+
+    {
+      auto data = d_data.lock();
+      const auto wasFailedDomain = data->d_failedSecondaryRefresh.find(di.zone);
+      if (wasFailedDomain != data->d_failedSecondaryRefresh.end())
+        data->d_failedSecondaryRefresh.erase(di.zone);
+    }
+
+    bool hasSOA = false;
+    SOAData sd;
+    try {
+      // Use UeberBackend cache for SOA. Cache gets cleared after AXFR/IXFR.
+      B->lookup(QType(QType::SOA), di.zone, di.id, nullptr);
+      DNSZoneRecord zr;
+      hasSOA = B->get(zr);
+      if (hasSOA) {
+        fillSOAData(zr, sd);
+        while (B->get(zr))
+          ;
+      }
+    }
+    catch (...) {
+    }
+
+    uint32_t theirserial = ssr.d_freshness[di.id].theirSerial;
+    uint32_t ourserial = sd.serial;
+    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 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)) {
+        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)) {
+          auto rrsig = getRR<RRSIGRecordContent>(zr.dr);
+          if (rrsig->d_type == QType::SOA) {
+            maxInception = std::max(maxInception, rrsig->d_siginception);
+            maxExpire = std::max(maxExpire, rrsig->d_sigexpire);
+          }
+        }
+      }
+
+      SuckRequest::RequestPriority prio = SuckRequest::SignaturesRefresh;
+      if (di.receivedNotify) {
+        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 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 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, 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, 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, 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 primary " << remote.toStringWithPortExcept(53) << ", so DNSSEC is stale, serial is " << ourserial << endl;
+        addSuckRequest(di.zone, remote, prio);
+      }
+    }
+    else {
+      SuckRequest::RequestPriority prio = SuckRequest::SerialRefresh;
+      if (di.receivedNotify) {
+        prio = SuckRequest::Notify;
+      }
+
+      if (hasSOA) {
+        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, primary " << remote.toStringWithPortExcept(53) << " serial " << theirserial << endl;
+      }
+      addSuckRequest(di.zone, remote, prio);
+    }
+  }
+}
+
+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.primary);
+  }
+  return ret;
+}
+
+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 c206306eeb05ad32f0b7da97c77901bff734fc3f..f2630c9aebf47139e20e92ef42c533c21c84229b 100644 (file)
@@ -133,6 +133,10 @@ int AXFRRetriever::getChunk(Resolver::res_t &res, vector<DNSRecord>* records, ui
     throw ResolverException("AXFR chunk error: " + RCode::to_s(err));
   }
 
+  if(mdp.d_header.tc) {
+    throw ResolverException("AXFR chunk had TC bit set");
+  }
+
   try {
     d_tsigVerifier.check(std::string(d_buf.data(), len), mdp);
   }
@@ -171,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 dc9a31947848428a011797fe2396179f1b107597..8ebb3c8acff797991236a304dff4dec666b8dec7 100644 (file)
@@ -67,20 +67,24 @@ 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");
   d_DeleteZoneQuery=getArg("delete-zone-query");
   d_DeleteRRSetQuery=getArg("delete-rrset-query");
@@ -139,21 +143,25 @@ 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;
   d_DeleteZoneQuery_stmt = nullptr;
   d_DeleteRRSetQuery_stmt = nullptr;
@@ -200,14 +208,16 @@ 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 "+itoa(domain_id)+": "+e.txtReason());
+    throw PDNSException("GSQLBackend unable to refresh domain_id "+std::to_string(domain_id)+": "+e.txtReason());
   }
 }
 
@@ -216,10 +226,15 @@ 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 " + itoa(domain_id) + ": " + e.txtReason());
+    throw PDNSException("GSQLBackend unable to update last_check for domain_id " + std::to_string(domain_id) + ": " + e.txtReason());
   }
 }
 
@@ -233,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;
 }
@@ -263,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());
@@ -275,16 +294,56 @@ bool GSQLBackend::setKind(const DNSName &domain, const DomainInfo::DomainKind ki
   return true;
 }
 
+bool GSQLBackend::setOptions(const DNSName& domain, const string& options)
+{
+  try {
+    reconnectIfNeeded();
+
+    // clang-format off
+    d_UpdateOptionsOfZoneQuery_stmt->
+      bind("options", options)->
+      bind("domain", domain)->
+      execute()->
+      reset();
+    // clang-format on
+  }
+  catch (SSqlException& e) {
+    throw PDNSException("GSQLBackend unable to set options of domain '" + domain.toLogString() + "' to '" + options + "': " + e.txtReason());
+  }
+  return true;
+}
+
+bool GSQLBackend::setCatalog(const DNSName& domain, const DNSName& catalog)
+{
+  try {
+    reconnectIfNeeded();
+
+    // clang-format off
+    d_UpdateCatalogOfZoneQuery_stmt->
+      bind("catalog", catalog)->
+      bind("domain", domain)->
+      execute()->
+      reset();
+    // clang-format on
+  }
+  catch (SSqlException& e) {
+    throw PDNSException("GSQLBackend unable to set catalog of domain '" + domain.toLogString() + "' to '" + catalog.toLogString() + "': " + e.txtReason());
+  }
+  return true;
+}
+
 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());
@@ -299,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());
@@ -313,22 +374,24 @@ bool GSQLBackend::getDomainInfo(const DNSName &domain, DomainInfo &di, bool getS
   if(!numanswers)
     return false;
 
-  ASSERT_ROW_COLUMNS("info-zone-query", d_result[0], 7);
+  ASSERT_ROW_COLUMNS("info-zone-query", d_result[0], 9);
 
   pdns::checked_stoi_into(di.id, d_result[0][0]);
   try {
     di.zone=DNSName(d_result[0][1]);
+    di.catalog = (!d_result[0][7].empty() ? DNSName(d_result[0][7]) : DNSName());
   } catch (...) {
     return false;
   }
   string type=d_result[0][5];
-  di.account=d_result[0][6];
+  di.options = d_result[0][6];
+  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;
@@ -350,144 +413,317 @@ 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 slave, and insert into SlaveDomain:
-     id,name,master IP,serial */
+  /*
+    list all domains that need refreshing for which we are secondary, and insert into
+    unfreshDomains: id, name, master, serial
+  */
+
   try {
     reconnectIfNeeded();
 
-    d_InfoOfAllSlaveDomainsQuery_stmt->
+    // clang-format off
+    d_InfoOfAllSecondaryDomainsQuery_stmt->
       execute()->
       getResult(d_result)->
       reset();
+    // clang-format on
   }
   catch (SSqlException &e) {
-    throw PDNSException("GSQLBackend unable to retrieve list of slave domains: "+e.txtReason());
+    throw PDNSException(std::string(__PRETTY_FUNCTION__) + " unable to retrieve list of secondary domains: " + e.txtReason());
   }
 
-  vector<DomainInfo> allSlaves;
+  SOAData sd;
+  DomainInfo di;
+  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-secondaries-query", row, 6);
 
-  bool loggedAssertRowColumns = false;
-  for(const auto& row : d_result) { // id,name,master,last_check
-    DomainInfo sd;
     try {
-      ASSERT_ROW_COLUMNS("info-all-slaves-query", row, 4);
-    } catch(const PDNSException &e) {
-      if (!loggedAssertRowColumns) {
-        g_log<<Logger::Warning<<e.reason<<endl;
-      }
-      loggedAssertRowColumns = true;
+      di.zone = DNSName(row[1]);
+    }
+    catch (const std::runtime_error& e) {
+      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " zone name '" << row[1] << "' is not a valid DNS name: " << e.what() << endl;
       continue;
     }
-
-    try {
-      sd.zone = DNSName(row[1]);
-    } catch(const std::runtime_error &e) {
-      g_log<<Logger::Warning<<"Domain name '"<<row[1]<<"' is not a valid DNS name: "<<e.what()<<endl;
+    catch (PDNSException& ae) {
+      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " zone name '" << row[1] << "' is not a valid DNS name: " << ae.reason << endl;
       continue;
     }
 
+    if (!row[5].empty()) {
+      try {
+        fillSOAData(row[5], sd);
+      }
+      catch (const std::exception& exp) {
+        g_log << Logger::Warning << __PRETTY_FUNCTION__ << " error while parsing SOA data for zone '" << di.zone << "': " << exp.what() << endl;
+        continue;
+      }
+      catch (...) {
+        g_log << Logger::Warning << __PRETTY_FUNCTION__ << " error while parsing SOA data for zone '" << di.zone << endl;
+        continue;
+      }
+
+      uint32_t last_check;
+      try {
+        pdns::checked_stoi_into(last_check, row[4]);
+      }
+      catch (const std::exception& e) {
+        g_log << Logger::Warning << __PRETTY_FUNCTION__ << " could not convert last_check '" << row[4] << "' for zone '" << di.zone << "' into an integer: " << e.what() << endl;
+        continue;
+      }
+
+      if (static_cast<time_t>(last_check + sd.refresh) > time(nullptr)) { // still fresh
+        continue;
+      }
+      di.serial = sd.serial;
+    }
+
     try {
-      pdns::checked_stoi_into(sd.id, row[0]);
+      pdns::checked_stoi_into(di.id, row[0]);
     } catch (const std::exception &e) {
-      g_log<<Logger::Warning<<"Could not convert id ("<<row[0]<<") for domain '"<<sd.zone<<"' into an integer: "<<e.what()<<endl;
+      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " could not convert id '" << row[0] << "' for zone '" << di.zone << "' into an integer: " << e.what() << endl;
       continue;
     }
 
-    vector<string> masters;
-    stringtok(masters, row[2], ", \t");
-    for(const auto& m : masters) {
+    di.primaries.clear();
+    primaries.clear();
+    stringtok(primaries, row[3], ", \t");
+    for (const auto& m : primaries) {
       try {
-        sd.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 '"<<sd.zone<<"': "<<e.reason<<endl;
+        g_log << Logger::Warning << __PRETTY_FUNCTION__ << " could not parse primary address '" << m << "' for zone '" << di.zone << "': " << e.reason << endl;
       }
     }
-    if (sd.masters.empty()) {
-      g_log<<Logger::Warning<<"No masters for slave zone '"<<sd.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::Secondary;
+    }
+    else if (pdns_iequals(row[2], "CONSUMER")) {
+      di.kind = DomainInfo::Consumer;
+    }
+    else {
+      g_log << Logger::Warning << __PRETTY_FUNCTION__ << "type '" << row[2] << "' for zone '" << di.zone << "' is no secondary type" << endl;
+    }
+
+    di.backend = this;
+    unfreshDomains->emplace_back(di);
+  }
+}
+
+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
+    updatedDomains: id, name, notified_serial, serial
+  */
+
+  try {
+    reconnectIfNeeded();
+
+    // clang-format off
+    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 primary domains: " + e.txtReason());
+  }
+
+  SOAData sd;
+  DomainInfo di;
+  CatalogInfo ci;
+
+  updatedDomains.reserve(d_result.size());
+  for (const auto& row : d_result) { // id, name, type, notified_serial, options, catalog, content
+    ASSERT_ROW_COLUMNS("info-all-primary-query", row, 7);
+
+    di.backend = this;
+
     try {
-      pdns::checked_stoi_into(sd.last_check, row[3]);
-    } catch (const std::exception &e) {
-      g_log<<Logger::Warning<<"Could not convert last_check ("<<row[3]<<") for domain '"<<sd.zone<<"' into an integer: "<<e.what()<<endl;
+      pdns::checked_stoi_into(di.id, row[0]);
+    }
+    catch (const std::exception& e) {
+      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " could not convert id '" << row[0] << "' for zone '" << di.zone << "' into an integer: " << e.what() << endl;
       continue;
     }
 
-    sd.backend=this;
-    sd.kind=DomainInfo::Slave;
-    allSlaves.push_back(sd);
-  }
+    try {
+      di.zone = DNSName(row[1]);
+    }
+    catch (const std::runtime_error& e) {
+      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " zone name '" << row[1] << "' is not a valid DNS name: " << e.what() << endl;
+      continue;
+    }
+    catch (PDNSException& ae) {
+      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " zone name '" << row[1] << "' is not a valid DNS name: " << ae.reason << endl;
+      continue;
+    }
+
+    try {
+      di.catalog = DNSName(row[5]);
+    }
+    catch (const std::runtime_error& e) {
+      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " zone name '" << row[5] << "' is not a valid DNS name: " << e.what() << endl;
+      continue;
+    }
+    catch (PDNSException& ae) {
+      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " zone name '" << row[5] << "' is not a valid DNS name: " << ae.reason << endl;
+      continue;
+    }
+
+    if (pdns_iequals(row[2], "PRODUCER")) {
+      catalogs.insert(di.zone);
+      catalogHashes[di.zone].process("\0");
+      continue; // Producer fresness check is performed elsewhere
+    }
+    else if (!pdns_iequals(row[2], "MASTER")) {
+      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " type '" << row[2] << "' for zone '" << di.zone << "' is no primary type" << endl;
+    }
 
-  for (auto& slave : allSlaves) {
     try {
-      SOAData sdata;
-      sdata.serial=0;
-      sdata.refresh=0;
-      getSOA(slave.zone, sdata);
-      if(static_cast<time_t>(slave.last_check + sdata.refresh) < time(nullptr)) {
-        slave.serial=sdata.serial;
-        unfreshDomains->push_back(slave);
+      if (!row[5].empty()) {
+        ci.fromJson(row[4], CatalogInfo::CatalogType::Producer);
+        ci.updateHash(catalogHashes, di);
       }
     }
-    catch(const std::exception& exp) {
-      g_log<<Logger::Warning<<"Error while parsing SOA data for slave zone '"<<slave.zone<<"': "<<exp.what()<<endl;
+    catch (const std::exception& e) {
+      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " catalog hash update failed'" << row[4] << "' for zone '" << di.zone << "' member of '" << di.catalog << "': " << e.what() << endl;
+      continue;
+    }
+
+    try {
+      pdns::checked_stoi_into(di.notified_serial, row[3]);
+    }
+    catch (const std::exception& e) {
+      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " could not convert notified_serial '" << row[4] << "' for zone '" << di.zone << "' into an integer: " << e.what() << endl;
+      continue;
+    }
+
+    try {
+      fillSOAData(row[6], sd);
+    }
+    catch (const std::exception& exp) {
+      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " error while parsing SOA content '" << row[6] << "' for zone '" << di.zone << "': " << exp.what() << endl;
       continue;
     }
-    catch(...) {
-      g_log<<Logger::Warning<<"Error while parsing SOA data for slave zone '"<<slave.zone<<"', skipping"<<endl;
+    catch (...) {
+      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " error while parsing SOA content '" << row[6] << "' for zone '" << di.zone << endl;
       continue;
     }
+
+    if (di.notified_serial != sd.serial) {
+      di.kind = DomainInfo::Primary;
+      di.serial = sd.serial;
+      di.catalog.clear();
+
+      updatedDomains.emplace_back(di);
+    }
   }
 }
 
-void GSQLBackend::getUpdatedMasters(vector<DomainInfo> *updatedDomains)
+bool GSQLBackend::getCatalogMembers(const DNSName& catalog, vector<CatalogInfo>& members, CatalogInfo::CatalogType type)
 {
-  /* list all domains that need notifications for which we are master, and insert into updatedDomains
-     id, name, notified_serial, serial */
   try {
     reconnectIfNeeded();
 
-    d_InfoOfAllMasterDomainsQuery_stmt->
-      execute()->
-      getResult(d_result)->
-      reset();
+    if (type == CatalogInfo::CatalogType::Producer) {
+      // clang-format off
+      d_InfoProducerMembersQuery_stmt->
+        bind("catalog", catalog)->
+        execute()->
+        getResult(d_result)->
+        reset();
+      // clang-format on
+    }
+    else if (type == CatalogInfo::CatalogType::Consumer) {
+      // clang-format off
+      d_InfoConsumerMembersQuery_stmt->
+        bind("catalog", catalog)->
+        execute()->
+        getResult(d_result)->
+        reset();
+      // clang-format on
+    }
+    else {
+      PDNSException(std::string(__PRETTY_FUNCTION__) + " unknown type '" + CatalogInfo::getTypeString(type) + "'");
+    }
   }
-  catch(SSqlException &e) {
-    throw PDNSException("GSQLBackend unable to retrieve list of master domains: "+e.txtReason());
+  catch (SSqlException& e) {
+    throw PDNSException(std::string(__PRETTY_FUNCTION__) + " unable to retrieve list of member zones: " + e.txtReason());
   }
 
-  size_t numanswers=d_result.size();
-  vector<string>parts;
-  DomainInfo di;
-
-  di.backend = this;
-  di.kind = DomainInfo::Master;
+  members.reserve(d_result.size());
+  for (const auto& row : d_result) { // id, zone, options, [master]
+    if (type == CatalogInfo::CatalogType::Producer) {
+      ASSERT_ROW_COLUMNS("info-producer/consumer-members-query", row, 3);
+    }
+    else {
+      ASSERT_ROW_COLUMNS("info-producer/consumer-members-query", row, 4);
+    }
 
-  for( size_t n = 0; n < numanswers; ++n ) { // id, name, notified_serial, content
-    ASSERT_ROW_COLUMNS( "info-all-master-query", d_result[n], 4 );
+    CatalogInfo ci;
 
-    parts.clear();
-    stringtok( parts, d_result[n][3] );
+    try {
+      ci.d_zone = DNSName(row[1]);
+    }
+    catch (const std::runtime_error& e) {
+      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " zone name '" << row[1] << "' is not a valid DNS name: " << e.what() << endl;
+      members.clear();
+      return false;
+    }
+    catch (PDNSException& ae) {
+      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " zone name '" << row[1] << "' is not a valid DNS name: " << ae.reason << endl;
+      members.clear();
+      return false;
+    }
 
     try {
-      uint32_t serial = parts.size() > 2 ? pdns::checked_stoi<uint32_t>(parts[2]) : 0;
-      auto notified_serial = pdns::checked_stoi<uint32_t>(d_result[n][2]);
+      pdns::checked_stoi_into(ci.d_id, row[0]);
+    }
+    catch (const std::exception& e) {
+      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " could not convert id '" << row[0] << "' for zone '" << ci.d_zone << "' into an integer: " << e.what() << endl;
+      members.clear();
+      return false;
+    }
 
-      if( serial != notified_serial ) {
-        pdns::checked_stoi_into(di.id, d_result[n][0]);
-        di.zone = DNSName( d_result[n][1] );
-        di.serial = serial;
-        di.notified_serial = notified_serial;
+    try {
+      ci.fromJson(row[2], type);
+    }
+    catch (const std::runtime_error& e) {
+      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " options '" << row[2] << "' for zone '" << ci.d_zone << "' is no valid JSON: " << e.what() << endl;
+      members.clear();
+      return false;
+    }
 
-        updatedDomains->emplace_back(di);
+    if (row.size() >= 4) { // Consumer only
+      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 primary address '" << m << "' for zone '" << ci.d_zone << "': " << e.reason << endl;
+          members.clear();
+          return false;
+        }
       }
-    } catch ( ... ) {
-      continue;
     }
+
+    members.emplace_back(ci);
   }
+  return true;
 }
 
 bool GSQLBackend::updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName& qname, const DNSName& ordername, bool auth, const uint16_t qtype)
@@ -500,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)->
@@ -507,14 +744,16 @@ 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 "+itoa(domain_id)+", domain name '" + qname.toLogString() + "': "+e.txtReason());
+        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());
       }
     } else {
       try {
         reconnectIfNeeded();
 
+        // clang-format off
         d_updateOrderNameAndAuthTypeQuery_stmt->
           bind("ordername", ordername.labelReverse().toString(" ", false))->
           bind("auth", auth)->
@@ -523,9 +762,10 @@ 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 "+itoa(domain_id)+": "+e.txtReason());
+        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());
       }
     }
   } else {
@@ -533,20 +773,23 @@ 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 "+itoa(domain_id)+": "+e.txtReason());
+        throw PDNSException("GSQLBackend unable to nullify ordername and update auth for " + qname.toLogString() + " for domain_id "+std::to_string(domain_id)+": "+e.txtReason());
       }
     } else {
       try {
         reconnectIfNeeded();
 
+        // clang-format off
         d_nullifyOrderNameAndUpdateAuthTypeQuery_stmt->
           bind("auth", auth)->
           bind("domain_id", domain_id)->
@@ -554,9 +797,10 @@ 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 "+itoa(domain_id)+": "+e.txtReason());
+        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());
       }
     }
   }
@@ -569,13 +813,15 @@ 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 "+itoa(domain_id)+": "+e.txtReason());
+      throw PDNSException("GSQLBackend unable to delete empty non-terminal records from domain_id "+std::to_string(domain_id)+": "+e.txtReason());
     }
   }
   else
@@ -584,14 +830,16 @@ 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 "+itoa(domain_id)+": "+e.txtReason());
+        throw PDNSException("GSQLBackend unable to delete empty non-terminal rr '"+qname.toLogString()+"' from domain_id "+std::to_string(domain_id)+": "+e.txtReason());
       }
     }
   }
@@ -600,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)->
@@ -607,9 +856,10 @@ 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 "+itoa(domain_id)+": "+e.txtReason());
+      throw PDNSException("GSQLBackend unable to insert empty non-terminal rr '"+qname.toLogString()+"' in domain_id "+std::to_string(domain_id)+": "+e.txtReason());
     }
   }
 
@@ -631,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);
@@ -645,16 +897,18 @@ bool GSQLBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
     d_afterOrderQuery_stmt->reset();
   }
   catch(SSqlException &e) {
-    throw PDNSException("GSQLBackend unable to find before/after (after) for domain_id "+itoa(id)+" and qname '"+ qname.toLogString() +"': "+e.txtReason());
+    throw PDNSException("GSQLBackend unable to find before/after (after) for domain_id "+std::to_string(id)+" and qname '"+ qname.toLogString() +"': "+e.txtReason());
   }
 
   if(after.empty()) {
     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);
@@ -663,7 +917,7 @@ bool GSQLBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
       d_firstOrderQuery_stmt->reset();
     }
     catch(SSqlException &e) {
-      throw PDNSException("GSQLBackend unable to find before/after (first) for domain_id "+itoa(id)+" and qname '"+ qname.toLogString() + "': "+e.txtReason());
+      throw PDNSException("GSQLBackend unable to find before/after (first) for domain_id "+std::to_string(id)+" and qname '"+ qname.toLogString() + "': "+e.txtReason());
     }
   }
 
@@ -673,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);
@@ -690,7 +946,7 @@ bool GSQLBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
       d_beforeOrderQuery_stmt->reset();
     }
     catch(SSqlException &e) {
-      throw PDNSException("GSQLBackend unable to find before/after (before) for domain_id "+itoa(id)+" and qname '"+ qname.toLogString() + ": "+e.txtReason());
+      throw PDNSException("GSQLBackend unable to find before/after (before) for domain_id "+std::to_string(id)+" and qname '"+ qname.toLogString() + ": "+e.txtReason());
     }
 
     if(! unhashed.empty())
@@ -702,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);
@@ -718,7 +976,7 @@ bool GSQLBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
       d_lastOrderQuery_stmt->reset();
     }
     catch(SSqlException &e) {
-      throw PDNSException("GSQLBackend unable to find before/after (last) for domain_id "+itoa(id)+" and qname '"+ qname.toLogString() + ": "+e.txtReason());
+      throw PDNSException("GSQLBackend unable to find before/after (last) for domain_id "+std::to_string(id)+" and qname '"+ qname.toLogString() + ": "+e.txtReason());
     }
   } else {
     before=qname;
@@ -735,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)->
@@ -742,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;
@@ -786,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());
@@ -806,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());
@@ -826,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());
@@ -846,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());
@@ -868,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());
@@ -880,25 +1150,26 @@ bool GSQLBackend::removeDomainKey(const DNSName& name, unsigned int id)
   return true;
 }
 
-bool GSQLBackend::getTSIGKey(const DNSName& name, DNSName* algorithm, string* content)
+bool GSQLBackend::getTSIGKey(const DNSName& name, DNSName& algorithm, string& content)
 {
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_getTSIGKeyQuery_stmt->
       bind("key_name", name)->
       execute();
+    // clang-format on
 
     SSqlStatement::row_t row;
 
-    content->clear();
     while(d_getTSIGKeyQuery_stmt->hasNextRow()) {
       d_getTSIGKeyQuery_stmt->nextRow(row);
       ASSERT_ROW_COLUMNS("get-tsig-key-query", row, 2);
       try{
-        if(algorithm->empty() || *algorithm==DNSName(row[0])) {
-          *algorithm = DNSName(row[0]);
-          *content = row[1];
+        if (algorithm.empty() || algorithm == DNSName(row[0])) {
+          algorithm = DNSName(row[0]);
+          content = row[1];
         }
       } catch (...) {}
     }
@@ -909,7 +1180,7 @@ bool GSQLBackend::getTSIGKey(const DNSName& name, DNSName* algorithm, string* co
     throw PDNSException("GSQLBackend unable to retrieve TSIG key with name '" + name.toLogString() + "': "+e.txtReason());
   }
 
-  return !content->empty();
+  return true;
 }
 
 bool GSQLBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content)
@@ -917,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());
@@ -935,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());
@@ -951,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;
 
@@ -976,7 +1253,7 @@ bool GSQLBackend::getTSIGKeys(std::vector< struct TSIGKey > &keys)
     throw PDNSException("GSQLBackend unable to retrieve TSIG keys: "+e.txtReason());
   }
 
-  return keys.empty();
+  return true;
 }
 
 bool GSQLBackend::getDomainKeys(const DNSName& name, std::vector<KeyData>& keys)
@@ -987,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;
@@ -1016,23 +1295,16 @@ bool GSQLBackend::getDomainKeys(const DNSName& name, std::vector<KeyData>& keys)
   return true;
 }
 
-void GSQLBackend::alsoNotifies(const DNSName &domain, set<string> *ips)
-{
-  vector<string> meta;
-  getDomainMetadata(domain, "ALSO-NOTIFY", meta);
-  for(const auto& str: meta) {
-    ips->insert(str);
-  }
-}
-
 bool GSQLBackend::getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string> >& meta)
 {
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_GetAllDomainMetadataQuery_stmt->
       bind("domain", name)->
       execute();
+    // clang-format on
 
     SSqlStatement::row_t row;
 
@@ -1062,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;
 
@@ -1092,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
       }
     }
   }
@@ -1115,7 +1393,7 @@ bool GSQLBackend::setDomainMetadata(const DNSName& name, const std::string& kind
   return true;
 }
 
-void GSQLBackend::lookup(const QType &qtype,const DNSName &qname, int domain_id, DNSPacket *pkt_p)
+void GSQLBackend::lookup(const QType& qtype, const DNSName& qname, int domain_id, DNSPacket* /* pkt_p */)
 {
   try {
     reconnectIfNeeded();
@@ -1124,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
       }
     }
 
@@ -1171,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());
@@ -1195,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());
@@ -1246,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());
@@ -1271,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());
@@ -1290,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());
@@ -1307,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;
@@ -1335,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 {
@@ -1350,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();
@@ -1362,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;
 }
@@ -1410,6 +1709,7 @@ bool GSQLBackend::deleteDomain(const DNSName &domain)
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_DeleteZoneQuery_stmt->
       bind("domain_id", di.id)->
       execute()->
@@ -1430,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());
@@ -1444,41 +1745,53 @@ 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;
-      } else {
+      }
+      else if (pdns_iequals(row[3], "PRODUCER")) {
+        di.kind = DomainInfo::Producer;
+      }
+      else if (pdns_iequals(row[3], "CONSUMER")) {
+        di.kind = DomainInfo::Consumer;
+      }
+      else {
         g_log<<Logger::Warning<<"Could not parse domain kind '"<<row[3]<<"' as one of 'MASTER', 'SLAVE' or 'NATIVE'. Setting zone kind to 'NATIVE'"<<endl;
         di.kind = DomainInfo::Native;
       }
 
       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;
           }
         }
       }
@@ -1525,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"+itoa(qt.getCode()))->
+          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) {
@@ -1554,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());
@@ -1572,7 +1893,7 @@ bool GSQLBackend::replaceRRSet(uint32_t domain_id, const DNSName& qname, const Q
   return true;
 }
 
-bool GSQLBackend::feedRecord(const DNSResourceRecord &r, const DNSName &ordername, bool ordernameIsNSEC3)
+bool GSQLBackend::feedRecord(const DNSResourceRecord& r, const DNSName& ordername, bool /* ordernameIsNSEC3 */)
 {
   int prio=0;
   string content(r.content);
@@ -1588,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));
@@ -1623,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());
@@ -1638,7 +1963,7 @@ bool GSQLBackend::feedEnts(int domain_id, map<DNSName,bool>& nonterm)
   return true;
 }
 
-bool GSQLBackend::feedEnts3(int domain_id, const DNSName &domain, map<DNSName,bool> &nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow)
+bool GSQLBackend::feedEnts3(int domain_id, const DNSName& /* domain */, map<DNSName, bool>& nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow)
 {
   if(!d_dnssecQueries)
       return false;
@@ -1649,21 +1974,29 @@ bool GSQLBackend::feedEnts3(int domain_id, const DNSName &domain, map<DNSName,bo
     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());
@@ -1683,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) {
@@ -1730,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());
@@ -1771,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)
@@ -1800,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());
@@ -1855,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())
     {
@@ -1891,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;
@@ -1938,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) {
@@ -1983,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 365fd69a8a5e4b086b75eaad1213e7bbd4b238a6..8f92d5fb8c46fa77617541a5a9c7041b7570c416 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)
   {
     freeStatements();
@@ -61,23 +61,27 @@ 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);
       d_DeleteZoneQuery_stmt = d_db->prepare(d_DeleteZoneQuery, 1);
       d_DeleteRRSetQuery_stmt = d_db->prepare(d_DeleteRRSetQuery, 3);
@@ -127,23 +131,27 @@ 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();
     d_DeleteZoneQuery_stmt.reset();
     d_DeleteRRSetQuery_stmt.reset();
@@ -190,28 +198,30 @@ public:
   bool list(const DNSName &target, int domain_id, bool include_disabled=false) override;
   bool get(DNSResourceRecord &r) override;
   void getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool include_disabled) override;
-  void alsoNotifies(const DNSName &domain, set<string> *ips) override;
   bool startTransaction(const DNSName &domain, int domain_id=-1) override;
   bool commitTransaction() override;
   bool abortTransaction() override;
   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) 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;
   bool setAccount(const DNSName &domain, const string &account) override;
 
   bool getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after) override;
@@ -227,25 +237,25 @@ 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;
   bool publishDomainKey(const DNSName& name, unsigned int id) override;
   bool unpublishDomainKey(const DNSName& name, unsigned int id) override;
-  
-  bool getTSIGKey(const DNSName& name, DNSName* algorithm, string* content) override;
+
+  bool getTSIGKey(const DNSName& name, DNSName& algorithm, string& content) override;
   bool setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content) override;
   bool deleteTSIGKey(const DNSName& name) override;
   bool getTSIGKeys(std::vector< struct TSIGKey > &keys) override;
 
   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);
@@ -267,7 +277,7 @@ protected:
     reconnect();
   }
   virtual void reconnect() { }
-  virtual bool inTransaction() override
+  bool inTransaction() override
   {
     return d_inTransaction;
   }
@@ -288,25 +298,29 @@ 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;
   string d_DeleteZoneQuery;
   string d_DeleteRRSetQuery;
@@ -363,23 +377,27 @@ 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;
   unique_ptr<SSqlStatement> d_DeleteZoneQuery_stmt;
   unique_ptr<SSqlStatement> d_DeleteRRSetQuery_stmt;
index 7007d5fe9042835c1d006d11b9d006e1c4e9383d..dbc46a32a5773dc0784b592612645cac275d91fd 100644 (file)
  */
 #pragma once
 #include <string>
+#include <utility>
 #include <vector>
-#include <inttypes.h>
+#include <cinttypes>
 #include "../../dnsname.hh"
 #include "../../namespaces.hh"
 #include "../../misc.hh"
 
-class SSqlException 
+class SSqlException
 {
-public: 
-  SSqlException(const string &reason) : d_reason(reason)
+public:
+  SSqlException(string reason) :
+    d_reason(std::move(reason))
   {
   }
-  
+
   string txtReason()
   {
     return d_reason;
   }
+
 private:
   string d_reason;
 };
+
 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) {
-    return bind(name, value.makeLowerCase().toStringRootDot());
+  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 9f8243fd9eb4cf7299fb85901432108ff0f0f047..179ca457617a39bbab6ea3aa1640ad5ac08a14ff 100644 (file)
@@ -36,7 +36,7 @@ extern int linenumber;
 static void yyerror(const char *str)
 {
   extern char *current_filename;       
-  throw PDNSException("Error in bind configuration '"+string(current_filename)+"' on line "+itoa(linenumber)+": "+str);
+  throw PDNSException("Error in bind configuration '"+string(current_filename)+"' on line "+std::to_string(linenumber)+": "+str);
 }
 
 extern FILE *yyin;
@@ -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 d0ec0f617225f58cae9989bb2354fc18470fcf7f..4a12d9d94e615cecbae56729f1cee5fe88e7f54c 100644 (file)
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #include "bpf-filter.hh"
+#include "iputils.hh"
+#include "dolog.hh"
 
 #ifdef HAVE_EBPF
 
 #include <sys/syscall.h>
+#include <sys/resource.h>
 #include <linux/bpf.h>
 
 #include "ext/libbpf/libbpf.h"
 
 #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));
 }
 
@@ -49,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);
@@ -61,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);
 
@@ -80,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)
+// 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));
@@ -89,59 +93,60 @@ int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size,
   attr.key_size = key_size;
   attr.value_size = value_size;
   attr.max_entries = max_entries;
+  attr.map_flags = map_flags;
   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);
@@ -190,17 +195,12 @@ struct QNameValue
   uint16_t qtype{0};
 };
 
-struct CounterAndActionValue
-{
-  uint64_t counter{0};
-  BPFFilter::MatchAction action{BPFFilter::MatchAction::Pass};
-};
 
 BPFFilter::Map::Map(const BPFFilter::MapConfiguration& config, BPFFilter::MapFormat format): d_config(config)
 {
   if (d_config.d_type == BPFFilter::MapType::Filters) {
     /* special case, this is a map of eBPF programs */
-    d_fd = FDWrapper(bpf_create_map(BPF_MAP_TYPE_PROG_ARRAY, sizeof(uint32_t), sizeof(uint32_t), d_config.d_maxItems));
+    d_fd = FDWrapper(bpf_create_map(BPF_MAP_TYPE_PROG_ARRAY, sizeof(uint32_t), sizeof(uint32_t), d_config.d_maxItems, 0));
     if (d_fd.getHandle() == -1) {
       throw std::runtime_error("Error creating a BPF program map of size " + std::to_string(d_config.d_maxItems) + ": " + stringerror());
     }
@@ -208,6 +208,8 @@ BPFFilter::Map::Map(const BPFFilter::MapConfiguration& config, BPFFilter::MapFor
   else {
     int keySize = 0;
     int valueSize = 0;
+    int flags = 0;
+    bpf_map_type type = BPF_MAP_TYPE_HASH;
     if (format == MapFormat::Legacy) {
       switch (d_config.d_type) {
       case MapType::IPv4:
@@ -223,7 +225,7 @@ BPFFilter::Map::Map(const BPFFilter::MapConfiguration& config, BPFFilter::MapFor
         valueSize = sizeof(QNameValue);
         break;
       default:
-        throw std::runtime_error("Unsupported eBPF map type: " + std::to_string(static_cast<uint8_t>(d_config.d_type)));
+        throw std::runtime_error("Unsupported eBPF map type: " + std::to_string(static_cast<uint8_t>(d_config.d_type)) + " for legacy eBPF, perhaps you are trying to use an external program instead?");
       }
     }
     else {
@@ -236,6 +238,18 @@ BPFFilter::Map::Map(const BPFFilter::MapConfiguration& config, BPFFilter::MapFor
         keySize = sizeof(KeyV6);
         valueSize = sizeof(CounterAndActionValue);
         break;
+      case MapType::CIDR4:
+        keySize = sizeof(CIDR4);
+        valueSize = sizeof(CounterAndActionValue);
+        flags = BPF_F_NO_PREALLOC;
+        type = BPF_MAP_TYPE_LPM_TRIE;
+        break;
+      case MapType::CIDR6:
+        keySize = sizeof(CIDR6);
+        valueSize = sizeof(CounterAndActionValue);
+        flags = BPF_F_NO_PREALLOC;
+        type = BPF_MAP_TYPE_LPM_TRIE;
+        break;
       case MapType::QNames:
         keySize = sizeof(QNameAndQTypeKey);
         valueSize = sizeof(CounterAndActionValue);
@@ -251,21 +265,39 @@ BPFFilter::Map::Map(const BPFFilter::MapConfiguration& config, BPFFilter::MapFor
       if (d_fd.getHandle() != -1) {
         /* sanity checks: key and value size */
         bpf_check_map_sizes(d_fd.getHandle(), keySize, valueSize);
-
-        if (d_config.d_type == MapType::IPv4) {
+        switch (d_config.d_type) {
+        case MapType::IPv4: {
           uint32_t key = 0;
           while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
             ++d_count;
           }
+          break;
         }
-        else if (d_config.d_type == MapType::IPv6) {
+        case MapType::IPv6: {
           KeyV6 key;
           memset(&key, 0, sizeof(key));
           while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
             ++d_count;
           }
+          break;
+        }
+        case MapType::CIDR4: {
+          CIDR4 key;
+          memset(&key, 0, sizeof(key));
+          while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
+            ++d_count;
+          }
+          break;
+        }
+        case MapType::CIDR6: {
+          CIDR6 key;
+          memset(&key, 0, sizeof(key));
+          while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
+            ++d_count;
+          }
+          break;
         }
-        else if (d_config.d_type == MapType::QNames) {
+        case MapType::QNames: {
           if (format == MapFormat::Legacy) {
             QNameKey key;
             memset(&key, 0, sizeof(key));
@@ -280,12 +312,17 @@ BPFFilter::Map::Map(const BPFFilter::MapConfiguration& config, BPFFilter::MapFor
               ++d_count;
             }
           }
+          break;
+        }
+
+        default:
+          throw std::runtime_error("Unsupported eBPF map type: " + std::to_string(static_cast<uint8_t>(d_config.d_type)));
         }
       }
     }
 
     if (d_fd.getHandle() == -1) {
-      d_fd = FDWrapper(bpf_create_map(BPF_MAP_TYPE_HASH, keySize, valueSize, static_cast<int>(d_config.d_maxItems)));
+      d_fd = FDWrapper(bpf_create_map(type, keySize, valueSize, static_cast<int>(d_config.d_maxItems), flags));
       if (d_fd.getHandle() == -1) {
         throw std::runtime_error("Error creating a BPF map of size " + std::to_string(d_config.d_maxItems) + ": " + stringerror());
       }
@@ -301,29 +338,56 @@ 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;
 }
 
 
-BPFFilter::BPFFilter(const BPFFilter::MapConfiguration& v4, const BPFFilter::MapConfiguration& v6, const BPFFilter::MapConfiguration& qnames, BPFFilter::MapFormat format, bool external): d_mapFormat(format), d_external(external)
+BPFFilter::BPFFilter(std::unordered_map<std::string, MapConfiguration>& configs, BPFFilter::MapFormat format, bool external) :
+  d_mapFormat(format), d_external(external)
 {
   if (d_mapFormat != BPFFilter::MapFormat::Legacy && !d_external) {
     throw std::runtime_error("Unsupported eBPF map format, the current internal implemenation only supports the legacy format");
   }
 
+  struct rlimit old_limit;
+  if (getrlimit(RLIMIT_MEMLOCK, &old_limit) != 0) {
+    throw std::runtime_error("Unable to get memory lock limit: " + stringerror());
+  }
+
+  const rlim_t new_limit_size = 1024 * 1024;
+
+  /* Check if the current soft memlock limit is at least the limit */
+  if (old_limit.rlim_cur < new_limit_size) {
+    infolog("The current limit of locked memory (soft: %d, hard: %d) is too low for eBPF, trying to raise it to %d", old_limit.rlim_cur, old_limit.rlim_max, new_limit_size);
+
+    struct rlimit new_limit;
+    new_limit.rlim_cur = new_limit_size;
+    new_limit.rlim_max = new_limit_size;
+
+    if (setrlimit(RLIMIT_MEMLOCK, &new_limit) != 0) {
+      warnlog("Unable to raise the maximum amount of locked memory for eBPF from %d to %d, consider raising RLIMIT_MEMLOCK or setting LimitMEMLOCK in the systemd unit: %d", old_limit.rlim_cur, new_limit.rlim_cur, stringerror());
+    }
+  }
+
   auto maps = d_maps.lock();
 
-  maps->d_v4 = BPFFilter::Map(v4, d_mapFormat);
-  maps->d_v6 = BPFFilter::Map(v6, d_mapFormat);
-  maps->d_qnames = BPFFilter::Map(qnames, d_mapFormat);
+  maps->d_v4 = BPFFilter::Map(configs["ipv4"], d_mapFormat);
+  maps->d_v6 = BPFFilter::Map(configs["ipv6"], d_mapFormat);
+  maps->d_qnames = BPFFilter::Map(configs["qnames"], d_mapFormat);
+
+  if (d_mapFormat != BPFFilter::MapFormat::Legacy) {
+    maps->d_cidr4 = BPFFilter::Map(configs["cidr4"], d_mapFormat);
+    maps->d_cidr6 = BPFFilter::Map(configs["cidr6"], d_mapFormat);
+  }
+
   if (!external) {
     BPFFilter::MapConfiguration filters;
     filters.d_maxItems = 1;
@@ -365,8 +429,8 @@ BPFFilter::BPFFilter(const BPFFilter::MapConfiguration& v4, const BPFFilter::Map
 
 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());
@@ -375,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());
@@ -469,6 +533,109 @@ void BPFFilter::unblock(const ComboAddress& addr)
   }
 }
 
+void BPFFilter::addRangeRule(const Netmask& addr, bool force, BPFFilter::MatchAction action)
+{
+  CounterAndActionValue value;
+
+  int res = 0;
+  if (addr.isIPv4()) {
+    CIDR4 key(addr);
+    auto maps = d_maps.lock();
+    auto& map = maps->d_cidr4;
+    if (map.d_fd.getHandle() == -1) {
+      throw std::runtime_error("Trying to use an unsupported map type, likely adding a range to a legacy eBPF program");
+    }
+    if (map.d_count >= map.d_config.d_maxItems) {
+      throw std::runtime_error("Table full when trying to add this rule: " + addr.toString());
+    }
+
+    res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
+    if (((res != -1 && value.action == action) || (res == -1 && value.action == BPFFilter::MatchAction::Pass)) && !force) {
+      throw std::runtime_error("Trying to add a useless rule: " + addr.toString());
+    }
+
+    value.counter = 0;
+    value.action = action;
+
+    res = bpf_update_elem(map.d_fd.getHandle(), &key, &value, force ? BPF_ANY : BPF_NOEXIST);
+    if (res == 0) {
+      ++map.d_count;
+    }
+  }
+  else if (addr.isIPv6()) {
+    CIDR6 key(addr);
+
+    auto maps = d_maps.lock();
+    auto& map = maps->d_cidr6;
+    if (map.d_fd.getHandle() == -1) {
+      throw std::runtime_error("Trying to use an unsupported map type, likely adding a range to a legacy eBPF program");
+    }
+    if (map.d_count >= map.d_config.d_maxItems) {
+      throw std::runtime_error("Table full when trying to add this rule: " + addr.toString());
+    }
+
+    res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
+    if (((res != -1 && value.action == action) || (res == -1 && value.action == BPFFilter::MatchAction::Pass)) && !force) {
+      throw std::runtime_error("Trying to add a useless rule: " + addr.toString());
+    }
+
+    value.counter = 0;
+    value.action = action;
+
+    res = bpf_update_elem(map.d_fd.getHandle(), &key, &value, BPF_NOEXIST);
+    if (res == 0) {
+      map.d_count++;
+    }
+  }
+
+  if (res != 0) {
+    throw std::runtime_error("Error adding this rule: " + addr.toString() + ": " + stringerror());
+  }
+}
+
+void BPFFilter::rmRangeRule(const Netmask& addr)
+{
+  int res = 0;
+  CounterAndActionValue value;
+  value.counter = 0;
+  value.action = MatchAction::Pass;
+  if (addr.isIPv4()) {
+    CIDR4 key(addr);
+    auto maps = d_maps.lock();
+    auto& map = maps->d_cidr4;
+    if (map.d_fd.getHandle() == -1) {
+      throw std::runtime_error("Trying to use an unsupported map type, likely adding a range to a legacy eBPF program");
+    }
+    res = bpf_delete_elem(map.d_fd.getHandle(), &key);
+    if (res == 0) {
+      --map.d_count;
+    }
+    else {
+      throw std::runtime_error("Cannot remove '" + addr.toString() + "': No such rule");
+    }
+  }
+  else if (addr.isIPv6()) {
+    CIDR6 key(addr);
+
+    auto maps = d_maps.lock();
+    auto& map = maps->d_cidr6;
+    if (map.d_fd.getHandle() == -1) {
+      throw std::runtime_error("Trying to use an unsupported map type, likely adding a range to a legacy eBPF program");
+    }
+    res = bpf_delete_elem(map.d_fd.getHandle(), &key);
+    if (res == 0) {
+      --map.d_count;
+    }
+    else {
+      throw std::runtime_error("Cannot remove '" + addr.toString() + "': No such rule");
+    }
+  }
+
+  if (res != 0) {
+    throw std::runtime_error("Error removing this rule: " + addr.toString() + ": " + stringerror());
+  }
+}
+
 void BPFFilter::block(const DNSName& qname, BPFFilter::MatchAction action, uint16_t qtype)
 {
   CounterAndActionValue cadvalue;
@@ -503,12 +670,11 @@ void BPFFilter::block(const DNSName& qname, BPFFilter::MatchAction action, uint1
       throw std::runtime_error("Table full when trying to block " + qname.toLogString());
     }
 
-    int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
+    int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, value);
     if (res != -1) {
       throw std::runtime_error("Trying to block an already blocked qname: " + qname.toLogString());
     }
-
-    res = bpf_update_elem(map.d_fd.getHandle(), &key, &value, BPF_NOEXIST);
+    res = bpf_update_elem(map.d_fd.getHandle(), &key, value, BPF_NOEXIST);
     if (res == 0) {
       ++map.d_count;
     }
@@ -552,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();
@@ -588,22 +754,68 @@ 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());
     }
   }
 
   return result;
 }
 
+std::vector<std::pair<Netmask, CounterAndActionValue>> BPFFilter::getRangeRule()
+{
+  CIDR4 cidr4[2];
+  CIDR6 cidr6[2];
+  std::vector<std::pair<Netmask, CounterAndActionValue>> result;
+
+  sockaddr_in v4Addr;
+  sockaddr_in6 v6Addr;
+  CounterAndActionValue value;
+
+  memset(cidr4, 0, sizeof(cidr4));
+  memset(cidr6, 0, sizeof(cidr6));
+  memset(&v4Addr, 0, sizeof(v4Addr));
+  memset(&v6Addr, 0, sizeof(v6Addr));
+  v4Addr.sin_family = AF_INET;
+  v6Addr.sin6_family = AF_INET6;
+  auto maps = d_maps.lock();
+  result.reserve(maps->d_cidr4.d_count + maps->d_cidr6.d_count);
+  {
+    auto& map = maps->d_cidr4;
+    int res = bpf_get_next_key(map.d_fd.getHandle(), &cidr4[0], &cidr4[1]);
+    while (res == 0) {
+      if (bpf_lookup_elem(map.d_fd.getHandle(), &cidr4[1], &value) == 0) {
+        v4Addr.sin_addr.s_addr = cidr4[1].addr.s_addr;
+        result.emplace_back(Netmask(&v4Addr, cidr4[1].cidr), value);
+      }
+
+      res = bpf_get_next_key(map.d_fd.getHandle(), &cidr4[1], &cidr4[1]);
+    }
+  }
+
+  {
+    auto& map = maps->d_cidr6;
+    int res = bpf_get_next_key(map.d_fd.getHandle(), &cidr6[0], &cidr6[1]);
+    while (res == 0) {
+      if (bpf_lookup_elem(map.d_fd.getHandle(), &cidr6[1], &value) == 0) {
+        v6Addr.sin6_addr = cidr6[1].addr;
+        result.emplace_back(Netmask(&v6Addr, cidr6[1].cidr), value);
+      }
+
+      res = bpf_get_next_key(map.d_fd.getHandle(), &cidr6[1], &cidr6[1]);
+    }
+  }
+  return result;
+}
+
 std::vector<std::tuple<DNSName, uint16_t, uint64_t> > BPFFilter::getQNameStats()
 {
   std::vector<std::tuple<DNSName, uint16_t, uint64_t> > result;
@@ -621,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);
@@ -642,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);
@@ -686,7 +898,7 @@ uint64_t BPFFilter::getHits(const ComboAddress& requestor)
 
 #else
 
-BPFFilter::BPFFilter(const BPFFilter::MapConfiguration&, const BPFFilter::MapConfiguration&, const BPFFilter::MapConfiguration&, BPFFilter::MapFormat, bool)
+BPFFilter::BPFFilter(std::unordered_map<std::string, MapConfiguration>& configs, BPFFilter::MapFormat format, bool external)
 {
 }
 
@@ -720,6 +932,19 @@ void BPFFilter::unblock(const DNSName&, uint16_t)
   throw std::runtime_error("eBPF support not enabled");
 }
 
+void BPFFilter::addRangeRule(const Netmask&, bool, BPFFilter::MatchAction)
+{
+  throw std::runtime_error("eBPF support not enabled");
+}
+void BPFFilter::rmRangeRule(const Netmask&)
+{
+  throw std::runtime_error("eBPF support not enabled");
+}
+
+std::vector<std::pair<Netmask, CounterAndActionValue>> BPFFilter::getRangeRule(){
+  std::vector<std::pair<Netmask, CounterAndActionValue>> result;
+  return result;
+}
 std::vector<std::pair<ComboAddress, uint64_t> > BPFFilter::getAddrStats()
 {
   std::vector<std::pair<ComboAddress, uint64_t> > result;
index a8e2f5c8343092027b9f174e0d1be3865a5e1d29..2ab3aa561b7e4f2efbdbc86105ef99a589c8800f 100644 (file)
 #pragma once
 #include "config.h"
 
+#include <unordered_map>
+
 #include "iputils.hh"
 #include "lock.hh"
+#include <netinet/in.h>
+#include <stdexcept>
 
 class BPFFilter
 {
@@ -32,7 +36,9 @@ public:
     IPv4,
     IPv6,
     QNames,
-    Filters
+    Filters,
+    CIDR4,
+    CIDR6
   };
 
   enum class MapFormat : uint8_t {
@@ -45,6 +51,18 @@ public:
     Drop = 1,
     Truncate = 2
   };
+  static std::string toString(MatchAction s) noexcept
+  {
+    switch (s) {
+    case MatchAction::Pass:
+      return "Pass";
+    case MatchAction::Drop:
+      return "Drop";
+    case MatchAction::Truncate:
+      return "Truncate";
+    }
+    return "Unknown";
+  }
 
   struct MapConfiguration
   {
@@ -53,8 +71,14 @@ public:
     MapType d_type;
   };
 
+  struct CounterAndActionValue
+  {
+    uint64_t counter{0};
+    BPFFilter::MatchAction action{BPFFilter::MatchAction::Pass};
+  };
+  
 
-  BPFFilter(const BPFFilter::MapConfiguration& v4, const BPFFilter::MapConfiguration& v6, const BPFFilter::MapConfiguration& qnames, BPFFilter::MapFormat format, bool external);
+  BPFFilter(std::unordered_map<std::string, MapConfiguration>& configs, BPFFilter::MapFormat format, bool external);
   BPFFilter(const BPFFilter&) = delete;
   BPFFilter(BPFFilter&&) = delete;
   BPFFilter& operator=(const BPFFilter&) = delete;
@@ -63,11 +87,14 @@ public:
   void addSocket(int sock);
   void removeSocket(int sock);
   void block(const ComboAddress& addr, MatchAction action);
+  void addRangeRule(const Netmask& address, bool force, BPFFilter::MatchAction action);
   void block(const DNSName& qname, MatchAction action, uint16_t qtype=255);
   void unblock(const ComboAddress& addr);
+  void rmRangeRule(const Netmask& address);
   void unblock(const DNSName& qname, uint16_t qtype=255);
 
   std::vector<std::pair<ComboAddress, uint64_t> > getAddrStats();
+  std::vector<std::pair<Netmask, CounterAndActionValue>> getRangeRule();
   std::vector<std::tuple<DNSName, uint16_t, uint64_t> > getQNameStats();
 
   uint64_t getHits(const ComboAddress& requestor);
@@ -92,6 +119,8 @@ private:
   {
     Map d_v4;
     Map d_v6;
+    Map d_cidr4;
+    Map d_cidr6;
     Map d_qnames;
     /* The qname filter program held in d_qnamefilter is
        stored in an eBPF map, so we can call it from the
@@ -105,7 +134,34 @@ private:
   FDWrapper d_mainfilter;
   /* qname filtering program */
   FDWrapper d_qnamefilter;
-
+  struct CIDR4
+  {
+    uint32_t cidr;
+    struct in_addr addr;
+    explicit CIDR4(Netmask address)
+    {
+      if (!address.isIPv4()) {
+        throw std::runtime_error("ComboAddress is invalid");
+      }
+      addr = address.getNetwork().sin4.sin_addr;
+      cidr = address.getBits();
+    }
+    CIDR4() = default;
+  };
+  struct CIDR6
+  {
+    uint32_t cidr;
+    struct in6_addr addr;
+    CIDR6(Netmask address)
+    {
+      if (!address.isIPv6()) {
+        throw std::runtime_error("ComboAddress is invalid");
+      }
+      addr = address.getNetwork().sin6.sin6_addr;
+      cidr = address.getBits();
+    }
+    CIDR6() = default;
+  };
   /* whether the maps are in the 'old' format, which we need
      to keep to prevent going over the 4k instructions per eBPF
      program limit in kernels < 5.2, as well as the complexity limit:
@@ -122,3 +178,4 @@ private:
   bool d_external;
 #endif /* HAVE_EBPF */
 };
+using CounterAndActionValue = BPFFilter::CounterAndActionValue;
diff --git a/pdns/burtle.hh b/pdns/burtle.hh
new file mode 100644 (file)
index 0000000..53f8b8d
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * 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>
+
+inline void burtlemix(uint32_t& a, uint32_t& b, uint32_t& c)
+{
+  a -= b;
+  a -= c;
+  a ^= (c >> 13);
+  b -= c;
+  b -= a;
+  b ^= (a << 8);
+  c -= a;
+  c -= b;
+  c ^= (b >> 13);
+  a -= b;
+  a -= c;
+  a ^= (c >> 12);
+  b -= c;
+  b -= a;
+  b ^= (a << 16);
+  c -= a;
+  c -= b;
+  c ^= (b >> 5);
+  a -= b;
+  a -= c;
+  a ^= (c >> 3);
+  b -= c;
+  b -= a;
+  b ^= (a << 10);
+  c -= a;
+  c -= b;
+  c ^= (b >> 15);
+}
+
+inline uint32_t burtle(const unsigned char* k, uint32_t length, uint32_t initval)
+{
+  uint32_t a, b, c, len;
+
+  /* Set up the internal state */
+  len = length;
+  a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */
+  c = initval; /* the previous hash value */
+
+  /*---------------------------------------- handle most of the key */
+  while (len >= 12) {
+    a += (k[0] + ((uint32_t)k[1] << 8) + ((uint32_t)k[2] << 16) + ((uint32_t)k[3] << 24));
+    b += (k[4] + ((uint32_t)k[5] << 8) + ((uint32_t)k[6] << 16) + ((uint32_t)k[7] << 24));
+    c += (k[8] + ((uint32_t)k[9] << 8) + ((uint32_t)k[10] << 16) + ((uint32_t)k[11] << 24));
+    burtlemix(a, b, c);
+    k += 12;
+    len -= 12;
+  }
+
+  /*------------------------------------- handle the last 11 bytes */
+  c += length;
+  switch (len) { /* all the case statements fall through */
+  case 11:
+    c += ((uint32_t)k[10] << 24);
+    /* fall-through */
+  case 10:
+    c += ((uint32_t)k[9] << 16);
+    /* fall-through */
+  case 9:
+    c += ((uint32_t)k[8] << 8);
+    /* the first byte of c is reserved for the length */
+    /* fall-through */
+  case 8:
+    b += ((uint32_t)k[7] << 24);
+    /* fall-through */
+  case 7:
+    b += ((uint32_t)k[6] << 16);
+    /* fall-through */
+  case 6:
+    b += ((uint32_t)k[5] << 8);
+    /* fall-through */
+  case 5:
+    b += k[4];
+    /* fall-through */
+  case 4:
+    a += ((uint32_t)k[3] << 24);
+    /* fall-through */
+  case 3:
+    a += ((uint32_t)k[2] << 16);
+    /* fall-through */
+  case 2:
+    a += ((uint32_t)k[1] << 8);
+    /* fall-through */
+  case 1:
+    a += k[0];
+    /* case 0: nothing left to add */
+  }
+  burtlemix(a, b, c);
+  /*-------------------------------------------- report the result */
+  return c;
+}
+
+inline uint32_t burtleCI(const unsigned char* k, uint32_t length, uint32_t initval)
+{
+  uint32_t a, b, c, len;
+
+  /* Set up the internal state */
+  len = length;
+  a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */
+  c = initval; /* the previous hash value */
+
+  /*---------------------------------------- handle most of the key */
+  while (len >= 12) {
+    a += (dns_tolower(k[0]) + ((uint32_t)dns_tolower(k[1]) << 8) + ((uint32_t)dns_tolower(k[2]) << 16) + ((uint32_t)dns_tolower(k[3]) << 24));
+    b += (dns_tolower(k[4]) + ((uint32_t)dns_tolower(k[5]) << 8) + ((uint32_t)dns_tolower(k[6]) << 16) + ((uint32_t)dns_tolower(k[7]) << 24));
+    c += (dns_tolower(k[8]) + ((uint32_t)dns_tolower(k[9]) << 8) + ((uint32_t)dns_tolower(k[10]) << 16) + ((uint32_t)dns_tolower(k[11]) << 24));
+    burtlemix(a, b, c);
+    k += 12;
+    len -= 12;
+  }
+
+  /*------------------------------------- handle the last 11 bytes */
+  c += length;
+  switch (len) { /* all the case statements fall through */
+  case 11:
+    c += ((uint32_t)dns_tolower(k[10]) << 24);
+    /* fall-through */
+  case 10:
+    c += ((uint32_t)dns_tolower(k[9]) << 16);
+    /* fall-through */
+  case 9:
+    c += ((uint32_t)dns_tolower(k[8]) << 8);
+    /* the first byte of c is reserved for the length */
+    /* fall-through */
+  case 8:
+    b += ((uint32_t)dns_tolower(k[7]) << 24);
+    /* fall-through */
+  case 7:
+    b += ((uint32_t)dns_tolower(k[6]) << 16);
+    /* fall-through */
+  case 6:
+    b += ((uint32_t)dns_tolower(k[5]) << 8);
+    /* fall-through */
+  case 5:
+    b += dns_tolower(k[4]);
+    /* fall-through */
+  case 4:
+    a += ((uint32_t)dns_tolower(k[3]) << 24);
+    /* fall-through */
+  case 3:
+    a += ((uint32_t)dns_tolower(k[2]) << 16);
+    /* fall-through */
+  case 2:
+    a += ((uint32_t)dns_tolower(k[1]) << 8);
+    /* fall-through */
+  case 1:
+    a += dns_tolower(k[0]);
+    /* case 0: nothing left to add */
+  }
+  burtlemix(a, b, c);
+  /*-------------------------------------------- report the result */
+  return c;
+}
index f3af11af7765aa4a466f394cbf59fb37fe57da84..aaf798152aa335392d273265b0ce80f89663493f 100644 (file)
  */
 #pragma once
 
+#include <cmath>
 #include <boost/multi_index_container.hpp>
 
 #include "dnsname.hh"
 #include "lock.hh"
 
-// this function can clean any cache that has a getTTD() method on its entries, a preRemoval() method and a 'sequence' index as its second index
+// this function can clean any cache that has an isStale() method on its entries, a preRemoval() method and a 'sequence' index as its second index
 // the ritual is that the oldest entries are in *front* of the sequence collection, so on a hit, move an item to the end
-// on a miss, move it to the beginning
-template <typename S, typename C, typename T> void pruneCollection(C& container, T& collection, size_t maxCached, size_t scanFraction = 1000)
+// and optionally, on a miss, move it to the beginning
+template <typename S, typename T>
+void pruneCollection(T& collection, size_t maxCached, size_t scanFraction = 1000)
 {
-  const time_t now = time(0);
+  const time_t now = time(nullptr);
   size_t toTrim = 0;
   const size_t cacheSize = collection.size();
 
@@ -41,14 +43,15 @@ template <typename S, typename C, typename T> void pruneCollection(C& container,
 
   auto& sidx = collection.template get<S>();
 
-  // two modes - if toTrim is 0, just look through 1/scanFraction of all records 
+  // two modes - if toTrim is 0, just look through 1/scanFraction of all records
   // and nuke everything that is expired
   // otherwise, scan first 5*toTrim records, and stop once we've nuked enough
   const size_t lookAt = toTrim ? 5 * toTrim : cacheSize / scanFraction;
-  size_t tried = 0, erased = 0;
+  size_t tried = 0;
+  size_t erased = 0;
 
-  for (auto iter = sidx.begin(); iter != sidx.end() && tried < lookAt ; ++tried) {
-    if (iter->getTTD() < now) {
+  for (auto iter = sidx.begin(); iter != sidx.end() && tried < lookAt; ++tried) {
+    if (iter->isStale(now)) {
       iter = sidx.erase(iter);
       erased++;
     }
@@ -75,44 +78,49 @@ template <typename S, typename C, typename T> void pruneCollection(C& container,
 }
 
 // note: this expects iterator from first index
-template <typename S, typename T> void moveCacheItemToFrontOrBack(T& collection, typename T::iterator& iter, bool front)
+template <typename S, typename T>
+void moveCacheItemToFrontOrBack(T& collection, typename T::iterator& iter, bool front)
 {
   typedef typename T::template index<S>::type sequence_t;
-  sequence_t& sidx=collection.template get<S>();
-  typename sequence_t::iterator si=collection.template project<S>(iter);
-  if(front)
+  sequence_t& sidx = collection.template get<S>();
+  typename sequence_t::iterator si = collection.template project<S>(iter);
+  if (front)
     sidx.relocate(sidx.begin(), si); // at the beginning of the delete queue
   else
-    sidx.relocate(sidx.end(), si);  // back
+    sidx.relocate(sidx.end(), si); // back
 }
 
-template <typename S, typename T> void moveCacheItemToFront(T& collection, typename T::iterator& iter)
+template <typename S, typename T>
+void moveCacheItemToFront(T& collection, typename T::iterator& iter)
 {
   moveCacheItemToFrontOrBack<S>(collection, iter, true);
 }
 
-template <typename S, typename T> void moveCacheItemToBack(T& collection, typename T::iterator& iter)
+template <typename S, typename T>
+void moveCacheItemToBack(T& collection, typename T::iterator& iter)
 {
   moveCacheItemToFrontOrBack<S>(collection, iter, false);
 }
 
-template <typename S, typename T> uint64_t pruneLockedCollectionsVector(std::vector<T>& maps)
+template <typename S, typename T>
+uint64_t pruneLockedCollectionsVector(std::vector<T>& maps)
 {
   uint64_t totErased = 0;
   time_t now = time(nullptr);
 
-  for(auto& mc : maps) {
+  for (auto& mc : maps) {
     auto map = mc.d_map.write_lock();
 
     uint64_t lookAt = (map->size() + 9) / 10; // Look at 10% of this shard
     uint64_t erased = 0;
 
     auto& sidx = boost::multi_index::get<S>(*map);
-    for(auto i = sidx.begin(); i != sidx.end() && lookAt > 0; lookAt--) {
-      if(i->ttd < now) {
+    for (auto i = sidx.begin(); i != sidx.end() && lookAt > 0; lookAt--) {
+      if (i->ttd < now) {
         i = sidx.erase(i);
         erased++;
-      } else {
+      }
+      else {
         ++i;
       }
     }
@@ -122,51 +130,53 @@ template <typename S, typename T> uint64_t pruneLockedCollectionsVector(std::vec
   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)
 {
-  time_t now = time(nullptr);
   uint64_t totErased = 0;
   uint64_t toTrim = 0;
   uint64_t lookAt = 0;
 
   // two modes - if toTrim is 0, just look through 10%  of the cache and nuke everything that is expired
-  // otherwise, scan first 5*toTrim records, and stop once we've nuked enough
+  // otherwise, scan first max(5*toTrim, 10%) records, and stop once we've nuked enough
   if (cacheSize > maxCached) {
     toTrim = cacheSize - maxCached;
-    lookAt = 5 * toTrim;
-  } else {
+    lookAt = std::max(5 * toTrim, cacheSize / 10);
+  }
+  else {
     lookAt = cacheSize / 10;
   }
 
-  uint64_t maps_size = maps.size();
-  if (maps_size == 0) {
+  const uint64_t numberOfShards = maps.size();
+  if (numberOfShards == 0 || cacheSize == 0) {
     return 0;
   }
 
+  // first we scan a fraction of the shards for expired entries orderded by LRU
   for (auto& content : maps) {
-    auto mc = content.lock();
-    mc->invalidate();
-    auto& sidx = boost::multi_index::get<S>(mc->d_map);
-    uint64_t erased = 0, lookedAt = 0;
+    auto shard = content.lock();
+    const auto shardSize = shard->d_map.size();
+    const uint64_t toScanForThisShard = std::ceil(lookAt * ((1.0 * shardSize) / cacheSize));
+    shard->invalidate();
+    auto& sidx = boost::multi_index::get<S>(shard->d_map);
+    uint64_t erased = 0;
+    uint64_t lookedAt = 0;
     for (auto i = sidx.begin(); i != sidx.end(); lookedAt++) {
-      if (i->getTTD() < now) {
-        container.preRemoval(*mc, *i);
+      if (i->isStale(now)) {
+        shard->preRemoval(*i);
         i = sidx.erase(i);
         erased++;
-        --content.d_entriesCount;
-      } else {
+        content.decEntriesCount();
+      }
+      else {
         ++i;
       }
 
-      if (toTrim && erased >= toTrim / maps_size)
-        break;
-
-      if (lookedAt > lookAt / maps_size)
+      if (lookedAt >= toScanForThisShard) {
         break;
+      }
     }
     totErased += erased;
-    if (toTrim && totErased >= toTrim)
-      break;
   }
 
   if (totErased >= toTrim) { // done
@@ -175,34 +185,62 @@ template <typename S, typename C, typename T> uint64_t pruneMutexCollectionsVect
 
   toTrim -= totErased;
 
-  while (true) {
-    size_t pershard = toTrim / maps_size + 1;
-    for (auto& content : maps) {
-      auto mc = content.lock();
-      mc->invalidate();
-      auto& sidx = boost::multi_index::get<S>(mc->d_map);
-      size_t removed = 0;
-      for (auto i = sidx.begin(); i != sidx.end() && removed < pershard; removed++) {
-        container.preRemoval(*mc, *i);
-        i = sidx.erase(i);
-        --content.d_entriesCount;
-        totErased++;
-        toTrim--;
-        if (toTrim == 0) {
-          return totErased;
-        }
+  // It was not enough, so we need to remove entries that are not
+  // expired, still using the LRU index.
+
+  // From here on cacheSize is the total number of entries in the
+  // shards that still need to be cleaned. When a shard is processed,
+  // we subtract its original size from cacheSize as we use this value
+  // to compute the fraction of the next shards to clean. This way
+  // rounding issues do not cause over or undershoot of the target.
+  //
+  // Suppose we have 10 perfectly balanced shards, each filled with
+  // 100 entries. So cacheSize is 1000. When cleaning 10%, after shard
+  // 0 we still need to processs 900 entries, spread out of 9
+  // shards. So cacheSize becomes 900, and toTrim 90, since we cleaned
+  // 10 items from shard 0. Our fraction remains 10%. For the last
+  // shard, we would end up with cacheSize 100, and to clean 10.
+  //
+  // When the balance is not perfect, e.g. shard 0 has 54 entries, we
+  // would clean 5 entries due to rounding, and for the remaining
+  // shards we start with cacheSize 946 and toTrim 95: the fraction
+  // becomes slightly larger than 10%, since we "missed" one item in
+  // shard 0.
+
+  cacheSize -= totErased;
+
+  for (auto& content : maps) {
+    auto shard = content.lock();
+    const auto shardSize = shard->d_map.size();
+
+    const uint64_t toTrimForThisShard = std::round(static_cast<double>(toTrim) * shardSize / cacheSize);
+    // See explanation above
+    cacheSize -= shardSize;
+    if (toTrimForThisShard == 0) {
+      continue;
+    }
+    shard->invalidate();
+    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++) {
+      shard->preRemoval(*i);
+      i = sidx.erase(i);
+      content.decEntriesCount();
+      ++totErased;
+      if (--toTrim == 0) {
+        return totErased;
       }
     }
   }
-  // Not reached
   return totErased;
 }
 
-template <typename T> uint64_t purgeLockedCollectionsVector(std::vector<T>& maps)
+template <typename T>
+uint64_t purgeLockedCollectionsVector(std::vector<T>& maps)
 {
-  uint64_t delcount=0;
+  uint64_t delcount = 0;
 
-  for(auto& mc : maps) {
+  for (auto& mc : maps) {
     auto map = mc.d_map.write_lock();
     delcount += map->size();
     map->clear();
@@ -211,20 +249,21 @@ template <typename T> uint64_t purgeLockedCollectionsVector(std::vector<T>& maps
   return delcount;
 }
 
-template <typename N, typename T> uint64_t purgeLockedCollectionsVector(std::vector<T>& maps, const std::string& match)
+template <typename N, typename T>
+uint64_t purgeLockedCollectionsVector(std::vector<T>& maps, const std::string& match)
 {
-  uint64_t delcount=0;
+  uint64_t delcount = 0;
   std::string prefix(match);
-  prefix.resize(prefix.size()-1);
+  prefix.resize(prefix.size() - 1);
   DNSName dprefix(prefix);
-  for(auto& mc : maps) {
+  for (auto& mc : maps) {
     auto map = mc.d_map.write_lock();
     auto& idx = boost::multi_index::get<N>(*map);
     auto iter = idx.lower_bound(dprefix);
     auto start = iter;
 
-    for(; iter != idx.end(); ++iter) {
-      if(!iter->qname.isPartOf(dprefix)) {
+    for (; iter != idx.end(); ++iter) {
+      if (!iter->qname.isPartOf(dprefix)) {
         break;
       }
       delcount++;
@@ -235,13 +274,14 @@ template <typename N, typename T> uint64_t purgeLockedCollectionsVector(std::vec
   return delcount;
 }
 
-template <typename N, typename T> uint64_t purgeExactLockedCollection(T& mc, const DNSName& qname)
+template <typename N, typename T>
+uint64_t purgeExactLockedCollection(T& mc, const DNSName& qname)
 {
-  uint64_t delcount=0;
+  uint64_t delcount = 0;
   auto map = mc.d_map.write_lock();
   auto& idx = boost::multi_index::get<N>(*map);
   auto range = idx.equal_range(qname);
-  if(range.first != range.second) {
+  if (range.first != range.second) {
     delcount += distance(range.first, range.second);
     idx.erase(range.first, range.second);
   }
@@ -249,7 +289,7 @@ template <typename N, typename T> uint64_t purgeExactLockedCollection(T& mc, con
   return delcount;
 }
 
-template<typename S, typename Index>
+template <typename S, typename Index>
 bool lruReplacingInsert(Index& i, const typename Index::value_type& x)
 {
   auto inserted = i.insert(x);
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;
 }
index 534d66554229bc51f94764b8c81d070e319ce677..2747fc93f57e40bfbc31ddb63020923614705ba4 100644 (file)
 
 #ifdef HAVE_LIBCAP
 #include <sys/capability.h>
+#include <sys/prctl.h>
 #endif
 
 #include "capabilities.hh"
 #include "misc.hh"
 
-void dropCapabilities(std::set<std::string> capabilitiesToKeep)
+bool dropCapabilities([[maybe_unused]] std::set<std::string> capabilitiesToKeep)
 {
 #ifdef HAVE_LIBCAP
    cap_t caps = cap_get_proc();
@@ -66,10 +67,43 @@ void dropCapabilities(std::set<std::string> capabilitiesToKeep)
 
      if (cap_set_proc(caps) != 0) {
        cap_free(caps);
+       if (errno == EPERM) {
+         return false;
+       }
        throw std::runtime_error("Unable to drop capabilities: " + stringerror());
      }
 
      cap_free(caps);
+     return true;
    }
+#endif /* HAVE_LIBCAP */
+   return false;
+}
+
+bool dropCapabilitiesAfterSwitchingIDs()
+{
+#ifdef HAVE_LIBCAP
+#ifdef PR_SET_KEEPCAPS
+  if (prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0) == 0) {
+    return true;
+  }
+#endif /* PR_SET_KEEPCAPS */
+  return false;
+#else
+  return false;
+#endif /* HAVE_LIBCAP */
+}
+
+bool keepCapabilitiesAfterSwitchingIDs()
+{
+#ifdef HAVE_LIBCAP
+#ifdef PR_SET_KEEPCAPS
+  if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == 0) {
+    return true;
+  }
+#endif /* PR_SET_KEEPCAPS */
+  return false;
+#else
+  return false;
 #endif /* HAVE_LIBCAP */
 }
index 4d0b0ded3c2619afd338ed1d329c0e339ef44d9d..d12c6cb4d1d0d590ef3ef7773044e4c00df99e8f 100644 (file)
 
 #include <set>
 
-void dropCapabilities(std::set<std::string> capabilitiesToKeep = {});
+/* return true on success, false if support is not available or we don't
+   have enough capabilities to drop our capabilities (I know),
+   and throw on more unexpected errors.
+*/
+bool dropCapabilities(std::set<std::string> capabilitiesToKeep = {});
+/* drop capabilities on setuid()/setgid() */
+bool dropCapabilitiesAfterSwitchingIDs();
+/* retain capabilities on setuid()/setgid() */
+bool keepCapabilitiesAfterSwitchingIDs();
index 55ade934e5218db924f28ab5eb022d869b0e1ca0..ed8b8fda2e85c7821a7de6714faef93261fbb2e7 100644 (file)
@@ -145,9 +145,7 @@ vector<string> CDB::findall(string &key)
     throw std::runtime_error("Error looking up key '" + key + "' from CDB database: " + std::to_string(res));
   }
 
-  int x=0;
   while(cdb_findnext(&cdbf) > 0) {
-    x++;
     unsigned int vpos = cdb_datapos(&d_cdb);
     unsigned int vlen = cdb_datalen(&d_cdb);
     std::string val;
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
diff --git a/pdns/common_startup.cc b/pdns/common_startup.cc
deleted file mode 100644 (file)
index 34a4132..0000000
+++ /dev/null
@@ -1,818 +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 "common_startup.hh"
-#include "ws-auth.hh"
-#include "secpoll-auth.hh"
-#include <sys/time.h>
-#include <sys/resource.h>
-#include "dynhandler.hh"
-#include "dnsseckeeper.hh"
-#include "threadname.hh"
-#include "misc.hh"
-#include "query-local-address.hh"
-#include "trusted-notification-proxy.hh"
-#include "packethandler.hh"
-
-#include <thread>
-
-#ifdef HAVE_SYSTEMD
-#include <systemd/sd-daemon.h>
-#endif
-
-bool g_anyToTcp;
-bool g_8bitDNS;
-#ifdef HAVE_LUA_RECORDS
-bool g_doLuaRecord;
-int g_luaRecordExecLimit;
-time_t g_luaHealthChecksInterval{5};
-time_t g_luaHealthChecksExpireDelay{3600};
-#endif
-typedef Distributor<DNSPacket,DNSPacket,PacketHandler> DNSDistributor;
-
-ArgvMap theArg;
-StatBag S;  //!< Statistics are gathered across PDNS via the StatBag class S
-AuthPacketCache PC; //!< This is the main PacketCache, shared across all threads
-AuthQueryCache QC;
-AuthZoneCache g_zoneCache;
-std::unique_ptr<DNSProxy> DP{nullptr};
-std::unique_ptr<DynListener> dl{nullptr};
-CommunicatorClass Communicator;
-shared_ptr<UDPNameserver> N;
-double avg_latency{0.0}, receive_latency{0.0}, cache_latency{0.0}, backend_latency{0.0}, send_latency{0.0};
-unique_ptr<TCPNameserver> TN;
-static vector<DNSDistributor*> g_distributors;
-vector<std::shared_ptr<UDPNameserver> > g_udpReceivers;
-NetmaskGroup g_proxyProtocolACL;
-size_t g_proxyProtocolMaximumSize;
-
-ArgvMap &arg()
-{
-  return theArg;
-}
-
-void declareArguments()
-{
-  ::arg().set("config-dir","Location of configuration directory (pdns.conf)")=SYSCONFDIR;
-  ::arg().set("config-name","Name of this virtual configuration - will rename the binary image")="";
-  ::arg().set("socket-dir",string("Where the controlsocket will live, ")+LOCALSTATEDIR+"/pdns when unset and not chrooted"
-#ifdef HAVE_SYSTEMD
-      + ". Set to the RUNTIME_DIRECTORY environment variable when that variable has a value (e.g. under systemd).")="";
-   auto runtimeDir = getenv("RUNTIME_DIRECTORY");
-   if (runtimeDir != nullptr) {
-     ::arg().set("socket-dir") = runtimeDir;
-   }
-#else
-      )="";
-#endif
-  ::arg().set("module-dir","Default directory for modules")=PKGLIBDIR;
-  ::arg().set("chroot","If set, chroot to this directory for more security")="";
-  ::arg().set("logging-facility","Log under a specific facility")="";
-  ::arg().set("daemon","Operate as a daemon")="no";
-
-  ::arg().set("local-port","The port on which we listen")="53";
-  ::arg().setSwitch("dnsupdate","Enable/Disable DNS update (RFC2136) support. Default is no.")="no";
-  ::arg().setSwitch("write-pid","Write a PID file")="yes";
-  ::arg().set("allow-dnsupdate-from","A global setting to allow DNS updates from these IP ranges.")="127.0.0.0/8,::1";
-  ::arg().set("proxy-protocol-from", "A Proxy Protocol header is only allowed from these subnets, and is mandatory then too.")="";
-  ::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("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, ::";
-  ::arg().setSwitch("local-address-nonexist-fail","Fail to start if one or more of the local-address's do not exist on this server")="yes";
-  ::arg().setSwitch("non-local-bind", "Enable binding to non-local addresses by using FREEBIND / BINDANY socket options")="no";
-  ::arg().setSwitch("reuseport","Enable higher performance on compliant kernels by using SO_REUSEPORT allowing each receiver thread to open its own socket")="no";
-  ::arg().set("query-local-address","Source IP addresses for sending queries")="0.0.0.0 ::";
-  ::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().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";
-  ::arg().setSwitch("dname-processing", "If we should support DNAME records")="no";
-
-  ::arg().setCmd("help","Provide a helpful message");
-  ::arg().setCmd("version","Output version and compilation date");
-  ::arg().setCmd("config","Provide configuration file on standard output");
-  ::arg().setCmd("list-modules","Lists all modules available");
-  ::arg().setCmd("no-config","Don't parse configuration file");
-  
-  ::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().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().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";
-  ::arg().set("udp-truncation-threshold", "Maximum UDP response size before we truncate")="1232";
-  
-  ::arg().set("config-name","Name of this virtual configuration - will rename the binary image")="";
-
-  ::arg().set("load-modules","Load this module - supply absolute or relative path")="";
-  ::arg().set("launch","Which backends to launch and order to query them in")="";
-  ::arg().setSwitch("disable-axfr","Disable zonetransfers but do allow TCP queries")="no";
-  ::arg().set("allow-axfr-ips","Allow zonetransfers only to these subnets")="127.0.0.0/8,::1";
-  ::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")="60";
-  ::arg().set("xfr-cycle-interval","Schedule primary/secondary SOA freshness checks once every .. seconds")="60";
-
-  ::arg().set("tcp-control-address","If set, PowerDNS can be controlled over TCP on this address")="";
-  ::arg().set("tcp-control-port","If set, PowerDNS can be controlled over TCP on this address")="53000";
-  ::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("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("edns-cookie-secret", "When set, set a server cookie when responding to a query with a Client cookie (in hex)")="";
-
-  ::arg().setSwitch("webserver","Start a webserver for monitoring (api=yes also enables the HTTP listener)")="no";
-  ::arg().setSwitch("webserver-print-arguments","If the webserver should print arguments")="no";
-  ::arg().set("webserver-address","IP Address of webserver/API to listen on")="127.0.0.1";
-  ::arg().set("webserver-port","Port of webserver/API to listen on")="8081";
-  ::arg().set("webserver-password","Password required for accessing the webserver")="";
-  ::arg().set("webserver-allow-from","Webserver/API 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().set("webserver-max-bodysize","Webserver/API maximum request/response body size in megabytes")="2";
-  ::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().setSwitch("query-logging","Hint backends that queries should be logged")="no";
-
-  ::arg().set("carbon-namespace", "If set overwrites the first part of the carbon string")="pdns";
-  ::arg().set("carbon-ourname", "If set, overrides our reported hostname for carbon stats")="";
-  ::arg().set("carbon-instance", "If set overwrites the instance name default")="auth";
-  ::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("cache-ttl","Seconds to store packets in the PacketCache")="20";
-  ::arg().set("negquery-cache-ttl","Seconds to store negative query results in the QueryCache")="60";
-  ::arg().set("query-cache-ttl","Seconds to store query results in the QueryCache")="20";
-  ::arg().set("zone-cache-refresh-interval", "Seconds to cache list of known zones") = "300";
-  ::arg().set("server-id", "Returned when queried for 'id.server' TXT or NSID, defaults to hostname - disabled or custom")="";
-  ::arg().set("default-soa-content","Default SOA content")="a.misconfigured.dns.server.invalid hostmaster.@ 0 10800 3600 604800 3600";
-  ::arg().set("default-soa-edit","Default SOA-EDIT value")="";
-  ::arg().set("default-soa-edit-signed","Default SOA-EDIT value for signed zones")="";
-  ::arg().set("dnssec-key-cache-ttl","Seconds to cache DNSSEC keys from the database")="30";
-  ::arg().set("domain-metadata-cache-ttl", "Seconds to cache zone metadata from the database") = "";
-  ::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("default-ttl","Seconds a result is valid if not set otherwise")="3600";
-  ::arg().set("max-tcp-connections","Maximum number of TCP connections")="20";
-  ::arg().set("max-tcp-connections-per-client","Maximum number of simultaneous TCP connections per client")="0";
-  ::arg().set("max-tcp-transactions-per-conn","Maximum number of subsequent queries per TCP connection")="0";
-  ::arg().set("max-tcp-connection-duration","Maximum time in seconds that a TCP DNS connection is allowed to stay open.")="0";
-  ::arg().set("tcp-idle-timeout","Maximum time in seconds that a TCP DNS connection is allowed to stay open while being idle")="5";
-
-  ::arg().setSwitch("no-shuffle","Set this to prevent random shuffling of answers - for regression testing")="off";
-
-  ::arg().set("setuid","If set, change user id to this uid for more security")="";
-  ::arg().set("setgid","If set, change group id to this gid for more security")="";
-
-  ::arg().set("max-cache-entries", "Maximum number of entries in the query cache")="1000000";
-  ::arg().set("max-packet-cache-entries", "Maximum number of entries in the packet cache")="1000000";
-  ::arg().set("max-signature-cache-entries", "Maximum number of signatures cache entries")="";
-  ::arg().set("max-ent-entries", "Maximum number of empty non-terminals in a zone")="100000";
-  ::arg().set("entropy-source", "If set, read entropy from this file")="/dev/urandom";
-
-  ::arg().set("lua-prequery-script", "Lua script with prequery handler (DO NOT USE)")="";
-  ::arg().set("lua-dnsupdate-policy-script", "Lua script with DNS update policy handler")="";
-
-  ::arg().setSwitch("traceback-handler","Enable the traceback handler (Linux only)")="yes";
-  ::arg().setSwitch("direct-dnskey","Fetch DNSKEY, CDS and CDNSKEY RRs from backend during DNSKEY or CDS/CDNSKEY synthesis")="no";
-  ::arg().set("default-ksk-algorithm","Default KSK algorithm")="ecdsa256";
-  ::arg().set("default-ksk-size","Default KSK size (0 means default)")="0";
-  ::arg().set("default-zsk-algorithm","Default ZSK algorithm")="";
-  ::arg().set("default-zsk-size","Default ZSK size (0 means default)")="0";
-  ::arg().set("max-nsec3-iterations", "Limit the number of NSEC3 hash iterations") = "100";
-  ::arg().set("default-publish-cdnskey","Default value for PUBLISH-CDNSKEY")="";
-  ::arg().set("default-publish-cds","Default value for PUBLISH-CDS")="";
-
-  ::arg().set("include-dir","Include *.conf files from this directory");
-  ::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().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";
-#endif
-  ::arg().setSwitch("axfr-lower-serial", "Also AXFR a zone from a master 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";
-  ::arg().set("axfr-fetch-timeout", "Maximum time in seconds for inbound AXFR to start or be idle after starting")="10";
-
-  ::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("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file")="0";
-  ::arg().set("max-include-depth", "Maximum number of nested $INCLUDE directives while processing a zone file")="20";
-  ::arg().setSwitch("upgrade-unknown-types","Transparently upgrade known TYPExxx records. Recommended to keep off, except for PowerDNS upgrades until data sources are cleaned up")="no";
-  ::arg().setSwitch("svc-autohints", "Transparently fill ipv6hint=auto ipv4hint=auto SVC params with AAAA/A records for the target name of the record (if within the same zone)")="no";
-
-  ::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().setDefaults();
-}
-
-static time_t s_start=time(nullptr);
-static uint64_t uptimeOfProcess(const std::string& str)
-{
-  return time(nullptr) - s_start;
-}
-
-static uint64_t getSysUserTimeMsec(const std::string& str)
-{
-  struct rusage ru;
-  getrusage(RUSAGE_SELF, &ru);
-
-  if(str=="sys-msec") {
-    return (ru.ru_stime.tv_sec*1000ULL + ru.ru_stime.tv_usec/1000);
-  }
-  else
-    return (ru.ru_utime.tv_sec*1000ULL + ru.ru_utime.tv_usec/1000);
-
-}
-
-static uint64_t getTCPConnectionCount(const std::string& str)
-{
-  return TN->numTCPConnections();
-}
-
-static uint64_t getQCount(const std::string& str)
-try
-{
-  int totcount=0;
-  for(const auto& d : g_distributors) {
-    if(!d)
-      continue;
-    totcount += d->getQueueSize();  // this does locking and other things, so don't get smart
-  }
-  return totcount;
-}
-catch(std::exception& e)
-{
-  g_log<<Logger::Error<<"Had error retrieving queue sizes: "<<e.what()<<endl;
-  return 0;
-}
-catch(PDNSException& e)
-{
-  g_log<<Logger::Error<<"Had error retrieving queue sizes: "<<e.reason<<endl;
-  return 0;
-}
-
-static uint64_t getLatency(const std::string& str) 
-{
-  return round(avg_latency);
-}
-
-static uint64_t getReceiveLatency(const std::string& str)
-{
-  return round(receive_latency);
-}
-
-static uint64_t getCacheLatency(const std::string& str)
-{
-  return round(cache_latency);
-}
-
-static uint64_t getBackendLatency(const std::string& str)
-{
-  return round(backend_latency);
-}
-
-static uint64_t getSendLatency(const std::string& str)
-{
-  return round(send_latency);
-}
-
-void declareStats()
-{
-  S.declare("udp-queries","Number of UDP queries received");
-  S.declare("udp-do-queries","Number of UDP queries received with DO bit");
-  S.declare("udp-cookie-queries", "Number of UDP queries received with the COOKIE EDNS option");
-  S.declare("udp-answers","Number of answers sent out over UDP");
-  S.declare("udp-answers-bytes","Total size of answers sent out over UDP");
-  S.declare("udp4-answers-bytes","Total size of answers sent out over UDPv4");
-  S.declare("udp6-answers-bytes","Total size of answers sent out over UDPv6");
-
-  S.declare("udp4-answers","Number of IPv4 answers sent out over UDP");
-  S.declare("udp4-queries","Number of IPv4 UDP queries received");
-  S.declare("udp6-answers","Number of IPv6 answers sent out over UDP");
-  S.declare("udp6-queries","Number of IPv6 UDP queries received");
-  S.declare("overload-drops","Queries dropped because backends overloaded");
-
-  S.declare("rd-queries", "Number of recursion desired questions");
-  S.declare("recursion-unanswered", "Number of packets unanswered by configured recursor");
-  S.declare("recursing-answers","Number of recursive answers sent out");
-  S.declare("recursing-questions","Number of questions sent to recursor");
-  S.declare("corrupt-packets","Number of corrupt packets received");
-  S.declare("signatures", "Number of DNSSEC signatures made");
-  S.declare("tcp-queries","Number of TCP queries received");
-  S.declare("tcp-cookie-queries","Number of TCP queries received with the COOKIE option");
-  S.declare("tcp-answers","Number of answers sent out over TCP");
-  S.declare("tcp-answers-bytes","Total size of answers sent out over TCP");
-  S.declare("tcp4-answers-bytes","Total size of answers sent out over TCPv4");
-  S.declare("tcp6-answers-bytes","Total size of answers sent out over TCPv6");
-
-  S.declare("tcp4-queries","Number of IPv4 TCP queries received");
-  S.declare("tcp4-answers","Number of IPv4 answers sent out over TCP");
-  
-  S.declare("tcp6-queries","Number of IPv6 TCP queries received");
-  S.declare("tcp6-answers","Number of IPv6 answers sent out over TCP");
-
-  S.declare("open-tcp-connections","Number of currently open TCP connections", getTCPConnectionCount, StatType::gauge);
-
-  S.declare("qsize-q","Number of questions waiting for database attention", getQCount, StatType::gauge);
-
-  S.declare("dnsupdate-queries", "DNS update packets received.");
-  S.declare("dnsupdate-answers", "DNS update packets successfully answered.");
-  S.declare("dnsupdate-refused", "DNS update packets that are refused.");
-  S.declare("dnsupdate-changes", "DNS update changes to records in total.");
-
-  S.declare("incoming-notifications", "NOTIFY packets received.");
-
-  S.declare("uptime", "Uptime of process in seconds", uptimeOfProcess, StatType::counter);
-  S.declare("real-memory-usage", "Actual unique use of memory in bytes (approx)", getRealMemoryUsage, StatType::gauge);
-  S.declare("special-memory-usage", "Actual unique use of memory in bytes (approx)", getSpecialMemoryUsage, StatType::gauge);
-  S.declare("fd-usage", "Number of open filedescriptors", getOpenFileDescriptors, StatType::gauge);
-#ifdef __linux__
-  S.declare("udp-recvbuf-errors", "UDP 'recvbuf' errors", udpErrorStats, StatType::counter);
-  S.declare("udp-sndbuf-errors", "UDP 'sndbuf' errors", udpErrorStats, StatType::counter);
-  S.declare("udp-noport-errors", "UDP 'noport' errors", udpErrorStats, StatType::counter);
-  S.declare("udp-in-errors", "UDP 'in' errors", udpErrorStats, StatType::counter);
-  S.declare("udp-in-csum-errors", "UDP 'in checksum' errors", udpErrorStats, StatType::counter);
-  S.declare("udp6-in-errors", "UDP 'in' errors over IPv6", udp6ErrorStats, StatType::counter);
-  S.declare("udp6-recvbuf-errors", "UDP 'recvbuf' errors over IPv6", udp6ErrorStats, StatType::counter);
-  S.declare("udp6-sndbuf-errors", "UDP 'sndbuf' errors over IPv6", udp6ErrorStats, StatType::counter);
-  S.declare("udp6-noport-errors", "UDP 'noport' errors over IPv6", udp6ErrorStats, StatType::counter);
-  S.declare("udp6-in-csum-errors", "UDP 'in checksum' errors over IPv6", udp6ErrorStats, StatType::counter);
-#endif
-
-  S.declare("sys-msec", "Number of msec spent in system time", getSysUserTimeMsec, StatType::counter);
-  S.declare("user-msec", "Number of msec spent in user time", getSysUserTimeMsec, StatType::counter);
-
-#ifdef __linux__
-  S.declare("cpu-iowait", "Time spent waiting for I/O to complete by the whole system, in units of USER_HZ", getCPUIOWait, StatType::counter);
-  S.declare("cpu-steal", "Stolen time, which is the time spent by the whole system in other operating systems when running in a virtualized environment, in units of USER_HZ", getCPUSteal, StatType::counter);
-#endif
-
-  S.declare("meta-cache-size", "Number of entries in the metadata cache", DNSSECKeeper::dbdnssecCacheSizes, StatType::gauge);
-  S.declare("key-cache-size", "Number of entries in the key cache", DNSSECKeeper::dbdnssecCacheSizes, StatType::gauge);
-  S.declare("signature-cache-size", "Number of entries in the signature cache", signatureCacheSize, StatType::gauge);
-
-  S.declare("nxdomain-packets","Number of times an NXDOMAIN packet was sent out");
-  S.declare("noerror-packets","Number of times a NOERROR packet was sent out");
-  S.declare("servfail-packets","Number of times a server-failed packet was sent out");
-  S.declare("unauth-packets", "Number of times a zone we are not auth for was queried");
-  S.declare("latency","Average number of microseconds needed to answer a question", getLatency, StatType::gauge);
-  S.declare("receive-latency", "Average number of microseconds needed to receive a query", getReceiveLatency, StatType::gauge);
-  S.declare("cache-latency", "Average number of microseconds needed for a packet cache lookup", getCacheLatency, StatType::gauge);
-  S.declare("backend-latency", "Average number of microseconds needed for a backend lookup", getBackendLatency, StatType::gauge);
-  S.declare("send-latency", "Average number of microseconds needed to send the answer", getSendLatency, StatType::gauge);
-  S.declare("timedout-packets","Number of packets which weren't answered within timeout set");
-  S.declare("security-status", "Security status based on regular polling", StatType::gauge);
-  S.declare(
-    "xfr-queue", "Size of the queue of zones to be XFRd", [](const string&) { return Communicator.getSuckRequestsWaiting(); }, StatType::gauge);
-  S.declareDNSNameQTypeRing("queries","UDP Queries Received");
-  S.declareDNSNameQTypeRing("nxdomain-queries", "Queries for non-existent records within existent zones");
-  S.declareDNSNameQTypeRing("noerror-queries","Queries for existing records, but for type we don't have");
-  S.declareDNSNameQTypeRing("servfail-queries","Queries that could not be answered due to backend errors");
-  S.declareDNSNameQTypeRing("unauth-queries", "Queries for zones that we are not authoritative for");
-  S.declareRing("logmessages","Log Messages");
-  S.declareComboRing("remotes","Remote server IP addresses");
-  S.declareComboRing("remotes-unauth", "Remote hosts querying zones for which we are not auth");
-  S.declareComboRing("remotes-corrupt","Remote hosts sending corrupt packets");
-}
-
-int isGuarded(char **argv)
-{
-  char *p=strstr(argv[0],"-instance");
-
-  return !!p;
-}
-
-static void sendout(std::unique_ptr<DNSPacket>& a, int start)
-{
-  if(!a)
-    return;
-
-  try {
-    int diff = a->d_dt.udiffNoReset();
-    backend_latency = 0.999 * backend_latency + 0.001 * std::max(diff - start, 0);
-    start = diff;
-
-    N->send(*a);
-
-    diff = a->d_dt.udiff();
-    send_latency = 0.999 * send_latency + 0.001 * std::max(diff - start, 0);
-
-    avg_latency = 0.999 * avg_latency + 0.001 * std::max(diff, 0);
-  }
-  catch (const std::exception& e) {
-    g_log<<Logger::Error<<"Caught unhandled exception while sending a response: "<<e.what()<<endl;
-  }
-}
-
-//! The qthread receives questions over the internet via the Nameserver class, and hands them to the Distributor for further processing
-static void qthread(unsigned int num)
-try
-{
-  setThreadName("pdns/receiver");
-
-  g_distributors[num] = DNSDistributor::Create(::arg().asNum("distributor-threads", 1));
-  DNSDistributor* distributor = g_distributors[num]; // the big dispatcher!
-  DNSPacket question(true);
-  DNSPacket cached(false);
-
-  AtomicCounter &numreceived=*S.getPointer("udp-queries");
-  AtomicCounter &numreceiveddo=*S.getPointer("udp-do-queries");
-  AtomicCounter &numreceivedcookie=*S.getPointer("udp-cookie-queries");
-
-  AtomicCounter &numreceived4=*S.getPointer("udp4-queries");
-
-  AtomicCounter &numreceived6=*S.getPointer("udp6-queries");
-  AtomicCounter &overloadDrops=*S.getPointer("overload-drops");
-
-  int diff, start;
-  bool logDNSQueries = ::arg().mustDo("log-dns-queries");
-  shared_ptr<UDPNameserver> NS;
-  std::string buffer;
-  ComboAddress accountremote;
-
-  // If we have SO_REUSEPORT then create a new port for all receiver threads
-  // other than the first one.
-  if(N->canReusePort() ) {
-    NS = g_udpReceivers[num];
-    if (NS == nullptr) {
-      NS = N;
-    }
-  } else {
-    NS = N;
-  }
-
-  for(;;) {
-    try {
-      if (g_proxyProtocolACL.empty()) {
-        buffer.resize(DNSPacket::s_udpTruncationThreshold);
-      }
-      else {
-        buffer.resize(DNSPacket::s_udpTruncationThreshold + g_proxyProtocolMaximumSize);
-      }
-
-      if(!NS->receive(question, buffer)) { // receive a packet         inline
-        continue;                    // packet was broken, try again
-      }
-
-      diff = question.d_dt.udiffNoReset();
-      receive_latency = 0.999 * receive_latency + 0.001 * std::max(diff, 0);
-
-      numreceived++;
-
-      accountremote = question.d_remote;
-      if (question.d_inner_remote)
-        accountremote = *question.d_inner_remote;
-
-      if (accountremote.sin4.sin_family == AF_INET)
-        numreceived4++;
-      else
-        numreceived6++;
-
-      if(question.d_dnssecOk)
-        numreceiveddo++;
-
-      if(question.hasEDNSCookie())
-        numreceivedcookie++;
-
-      if(question.d.qr)
-        continue;
-
-      S.ringAccount("queries", question.qdomain, question.qtype);
-      S.ringAccount("remotes", question.d_remote);
-      if(logDNSQueries) {
-        g_log << Logger::Notice<<"Remote "<< question.getRemoteString() <<" wants '" << question.qdomain<<"|"<<question.qtype <<
-          "', do = " <<question.d_dnssecOk <<", bufsize = "<< question.getMaxReplyLen();
-        if(question.d_ednsRawPacketSizeLimit > 0 && question.getMaxReplyLen() != (unsigned int)question.d_ednsRawPacketSizeLimit)
-          g_log<<" ("<<question.d_ednsRawPacketSizeLimit<<")";
-      }
-
-      if(PC.enabled() && (question.d.opcode != Opcode::Notify && question.d.opcode != Opcode::Update) && question.couldBeCached()) {
-        start = diff;
-        bool haveSomething=PC.get(question, cached); // does the PacketCache recognize this question?
-        if (haveSomething) {
-          if(logDNSQueries)
-            g_log<<": packetcache HIT"<<endl;
-          cached.setRemote(&question.d_remote);  // inlined
-          cached.d_inner_remote = question.d_inner_remote;
-          cached.setSocket(question.getSocket());                               // inlined
-          cached.d_anyLocal = question.d_anyLocal;
-          cached.setMaxReplyLen(question.getMaxReplyLen());
-          cached.d.rd=question.d.rd; // copy in recursion desired bit
-          cached.d.id=question.d.id;
-          cached.commitD(); // commit d to the packet                        inlined
-
-          diff = question.d_dt.udiffNoReset();
-          cache_latency = 0.999 * cache_latency + 0.001 * std::max(diff - start, 0);
-          start = diff;
-
-          NS->send(cached); // answer it then                              inlined
-
-          diff=question.d_dt.udiff();
-          send_latency = 0.999 * send_latency + 0.001 * std::max(diff - start, 0);
-          avg_latency = 0.999 * avg_latency + 0.001 * std::max(diff, 0); // 'EWMA'
-          continue;
-        }
-        diff = question.d_dt.udiffNoReset();
-        cache_latency = 0.999 * cache_latency + 0.001 * std::max(diff - start, 0);
-      }
-
-      if(distributor->isOverloaded()) {
-        if(logDNSQueries)
-          g_log<<": Dropped query, backends are overloaded"<<endl;
-        overloadDrops++;
-        continue;
-      }
-
-      if (logDNSQueries) {
-        if (PC.enabled()) {
-          g_log<<": packetcache MISS"<<endl;
-        } else {
-          g_log<<endl;
-        }
-      }
-
-      try {
-        distributor->question(question, &sendout); // otherwise, give to the distributor
-      }
-      catch(DistributorFatal& df) { // when this happens, we have leaked loads of memory. Bailing out time.
-        _exit(1);
-      }
-    }
-    catch (const std::exception& e) {
-      g_log<<Logger::Error<<"Caught unhandled exception in question thread: "<<e.what()<<endl;
-    }
-  }
-}
-catch(PDNSException& pe)
-{
-  g_log<<Logger::Error<<"Fatal error in question thread: "<<pe.reason<<endl;
-  _exit(1);
-}
-
-static void dummyThread()
-{
-}
-
-static void triggerLoadOfLibraries()
-{
-  std::thread dummy(dummyThread);
-  dummy.join();
-}
-
-void mainthread()
-{
-   Utility::srandom();
-
-   gid_t newgid = 0;
-   if(!::arg()["setgid"].empty())
-     newgid = strToGID(::arg()["setgid"]);
-   uid_t newuid = 0;
-   if(!::arg()["setuid"].empty())
-     newuid = strToUID(::arg()["setuid"]);
-   
-   g_anyToTcp = ::arg().mustDo("any-to-tcp");
-   g_8bitDNS = ::arg().mustDo("8bit-dns");
-#ifdef HAVE_LUA_RECORDS
-   g_doLuaRecord = ::arg().mustDo("enable-lua-records");
-   g_LuaRecordSharedState = (::arg()["enable-lua-records"] == "shared");
-   g_luaRecordExecLimit = ::arg().asNum("lua-records-exec-limit");
-   g_luaHealthChecksInterval = ::arg().asNum("lua-health-checks-interval");
-   g_luaHealthChecksExpireDelay = ::arg().asNum("lua-health-checks-expire-delay");
-#endif
-
-   DNSPacket::s_udpTruncationThreshold = std::max(512, ::arg().asNum("udp-truncation-threshold"));
-   DNSPacket::s_doEDNSSubnetProcessing = ::arg().mustDo("edns-subnet-processing");
-   PacketHandler::s_SVCAutohints = ::arg().mustDo("svc-autohints");
-
-   g_proxyProtocolACL.toMasks(::arg()["proxy-protocol-from"]);
-   g_proxyProtocolMaximumSize = ::arg().asNum("proxy-protocol-maximum-size");
-
-   if (::arg()["edns-cookie-secret"].size() != 0) {
-     // User wants cookie processing
-#ifdef HAVE_CRYPTO_SHORTHASH // we can do siphash-based cookies
-     DNSPacket::s_doEDNSCookieProcessing = true;
-     try {
-       if (::arg()["edns-cookie-secret"].size() != EDNSCookiesOpt::EDNSCookieSecretSize) {
-         throw std::range_error("wrong size (" + std::to_string(::arg()["edns-cookie-secret"].size()) + "), must be " + std::to_string(EDNSCookiesOpt::EDNSCookieSecretSize));
-       }
-       DNSPacket::s_EDNSCookieKey = makeBytesFromHex(::arg()["edns-cookie-secret"]);
-     } catch(const std::range_error &e) {
-       g_log<<Logger::Error<<"edns-cookie-secret invalid: "<<e.what()<<endl;
-       exit(1);
-     }
-#else
-     g_log<<Logger::Error<<"Support for EDNS Cookies is not available because of missing cryptographic functions (libsodium support should be enabled, with the crypto_shorthash() function available)"<<endl;
-     exit(1);
-#endif
-   }
-
-   PC.setTTL(::arg().asNum("cache-ttl"));
-   PC.setMaxEntries(::arg().asNum("max-packet-cache-entries"));
-   QC.setMaxEntries(::arg().asNum("max-cache-entries"));
-   DNSSECKeeper::setMaxEntries(::arg().asNum("max-cache-entries"));
-
-   if (!PC.enabled() && ::arg().mustDo("log-dns-queries")) {
-     g_log<<Logger::Warning<<"Packet cache disabled, logging queries without HIT/MISS"<<endl;
-   }
-
-   stubParseResolveConf();
-
-   if(!::arg()["chroot"].empty()) {
-#ifdef HAVE_SYSTEMD
-     char *ns;
-     ns = getenv("NOTIFY_SOCKET");
-     if (ns != nullptr) {
-       g_log<<Logger::Error<<"Unable to chroot when running from systemd. Please disable chroot= or set the 'Type' for this service to 'simple'"<<endl;
-       exit(1);
-     }
-#endif
-     triggerLoadOfLibraries();
-     if(::arg().mustDo("primary") || ::arg().mustDo("secondary"))
-        gethostbyname("a.root-servers.net"); // this forces all lookup libraries to be loaded
-     Utility::dropGroupPrivs(newuid, newgid);
-     if(chroot(::arg()["chroot"].c_str())<0 || chdir("/")<0) {
-       g_log<<Logger::Error<<"Unable to chroot to '"+::arg()["chroot"]+"': "<<stringerror()<<", exiting"<<endl; 
-       exit(1);
-     }   
-     else
-       g_log<<Logger::Error<<"Chrooted to '"<<::arg()["chroot"]<<"'"<<endl;      
-   } else {
-     Utility::dropGroupPrivs(newuid, newgid);
-   }
-
-  AuthWebServer webserver;
-  Utility::dropUserPrivs(newuid);
-
-  if(::arg().mustDo("resolver")){
-    DP = std::make_unique<DNSProxy>(::arg()["resolver"]);
-    DP->go();
-  }
-
-  try {
-    doSecPoll(true);
-  }
-  catch(...) {}
-
-  {
-    // Some sanity checking on default key settings
-    bool hadKeyError = false;
-    int kskAlgo{0}, zskAlgo{0};
-    for (const string algotype : {"ksk", "zsk"}) {
-      int algo, size;
-      if (::arg()["default-"+algotype+"-algorithm"].empty())
-        continue;
-      algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-"+algotype+"-algorithm"]);
-      size = ::arg().asNum("default-"+algotype+"-size");
-      if (algo == -1) {
-        g_log<<Logger::Error<<"Error: default-"<<algotype<<"-algorithm set to unknown algorithm: "<<::arg()["default-"+algotype+"-algorithm"]<<endl;
-        hadKeyError = true;
-      }
-      else if (algo <= 10 && size == 0) {
-        g_log<<Logger::Error<<"Error: default-"<<algotype<<"-algorithm is set to an algorithm ("<<::arg()["default-"+algotype+"-algorithm"]<<") that requires a non-zero default-"<<algotype<<"-size!"<<endl;
-        hadKeyError = true;
-      }
-      if (algotype == "ksk") {
-        kskAlgo = algo;
-      } else {
-        zskAlgo = algo;
-      }
-    }
-    if (hadKeyError) {
-      exit(1);
-    }
-    if (kskAlgo == 0 && zskAlgo != 0) {
-      g_log<<Logger::Error<<"Error: default-zsk-algorithm is set, but default-ksk-algorithm is not set."<<endl;
-      exit(1);
-    }
-    if (zskAlgo != 0 && zskAlgo != kskAlgo) {
-      g_log<<Logger::Error<<"Error: default-zsk-algorithm ("<<::arg()["default-zsk-algorithm"]<<"), when set, can not be different from default-ksk-algorithm ("<<::arg()["default-ksk-algorithm"]<<")."<<endl;
-      exit(1);
-    }
-  }
-
-  pdns::parseQueryLocalAddress(::arg()["query-local-address"]);
-
-  pdns::parseTrustedNotificationProxy(::arg()["trusted-notification-proxy"]);
-
-  // NOW SAFE TO CREATE THREADS!
-  dl->go();
-
-  if(::arg().mustDo("webserver") || ::arg().mustDo("api"))
-    webserver.go();
-
-  if(::arg().mustDo("primary") || ::arg().mustDo("secondary")|| !::arg()["forward-notify"].empty())
-    Communicator.go(); 
-
-  TN->go(); // tcp nameserver launch
-
-  unsigned int max_rthreads= ::arg().asNum("receiver-threads", 1);
-  g_distributors.resize(max_rthreads);
-  for(unsigned int n=0; n < max_rthreads; ++n) {
-    std::thread t(qthread, n);
-    t.detach();
-  }
-
-  std::thread carbonThread(carbonDumpThread); // runs even w/o carbon, might change @ runtime    
-
-#ifdef HAVE_SYSTEMD
-  /* If we are here, notify systemd that we are ay-ok! This might have some
-   * timing issues with the backend-threads. e.g. if the initial MySQL connection
-   * is slow and times out (leading to process termination through the backend)
-   * We probably have told systemd already that we have started correctly.
-   */
-    sd_notify(0, "READY=1");
-#endif
-
-  const uint32_t secpollInterval = 1800;
-  uint32_t secpollSince = 0;
-  uint32_t zoneCacheUpdateSince = 0;
-  for(;;) {
-    const uint32_t sleeptime = g_zoneCache.getRefreshInterval() == 0 ? secpollInterval : std::min(secpollInterval, g_zoneCache.getRefreshInterval());
-    sleep(sleeptime);  // if any signals arrive, we might run more often than expected.
-
-    zoneCacheUpdateSince += sleeptime;
-    if (zoneCacheUpdateSince >= g_zoneCache.getRefreshInterval()) {
-      try {
-        UeberBackend B;
-        B.updateZoneCache();
-        zoneCacheUpdateSince = 0;
-      }
-      catch(PDNSException &e) {
-        g_log<<Logger::Error<<"PDNSException while updating zone cache: "<<e.reason<<endl;
-      }
-      catch(std::exception &e) {
-        g_log<<Logger::Error<<"STL Exception while updating zone cache: "<<e.what()<<endl;
-      }
-    }
-
-    secpollSince += sleeptime;
-    if (secpollSince >= secpollInterval) {
-      secpollSince = 0;
-      try {
-        doSecPoll(false);
-      }
-      catch(...){}
-    }
-  }
-  
-  g_log<<Logger::Error<<"Mainthread exiting - should never happen"<<endl;
-}
index fd44af19fe9238bdfd9e655d4c9d698212ea82ac..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,38 +122,38 @@ void CommunicatorClass::mainloop()
 {
   try {
     setThreadName("pdns/comm-main");
-    signal(SIGPIPE,SIG_IGN);
-    g_log<<Logger::Error<<"Primary/secondary communicator launching"<<endl;
+    signal(SIGPIPE, SIG_IGN);
+    g_log << Logger::Warning << "Primary/secondary communicator launching" << endl;
+
+    d_tickinterval = ::arg().asNum("xfr-cycle-interval");
+
+    int rc;
+    time_t next;
     PacketHandler P;
-    d_tickinterval=min(::arg().asNum("slave-cycle-interval"), ::arg().asNum("xfr-cycle-interval"));
+
     makeNotifySockets();
 
-    int rc;
-    time_t next, tick;
-
-    for(;;) {
-      slaveRefresh(&P);
-      masterUpdateCheck(&P);
-      tick=doNotifications(&P); // this processes any notification acknowledgements and actually send out our own notifications
-      
-      tick = min (tick, d_tickinterval); 
-      
-      next=time(nullptr)+tick;
-
-      while(time(nullptr) < next) {
-        rc=d_any_sem.tryWait();
-
-        if(rc) {
-          bool extraSlaveRefresh = false;
+    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();
+
+        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
@@ -156,24 +161,22 @@ void CommunicatorClass::mainloop()
           }
           break; // something happened
         }
-        // this gets executed at least once every second
+        // this gets executed about once per second
         doNotifications(&P);
       }
     }
   }
-  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 b799e0159e443e03eaf40a60999be0df09dccfbc..768b8dee716df650d726c493317b6849a45ed492 100644 (file)
@@ -44,52 +44,60 @@ 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);
   }
 
-  bool removeIf(const string &remote, uint16_t id, const DNSName &domain)
+  bool removeIf(const ComboAddress& remote, uint16_t id, const DNSName& domain)
   {
-    ServiceTuple stRemote, stQueued;
-    parseService(remote, stRemote);
-
-    for(d_nqueue_t::iterator i=d_nqueue.begin(); i!=d_nqueue.end(); ++i) {
-      parseService(i->ip, stQueued);
-      if(i->id==id && stQueued.host == stRemote.host && i->domain==domain) {
+    for (auto i = d_nqueue.begin(); i != d_nqueue.end(); ++i) {
+      ComboAddress stQueued{i->ip};
+      if (i->id == id && stQueued == remote && i->domain == domain) {
         d_nqueue.erase(i);
         return true;
       }
@@ -97,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;
@@ -119,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();
@@ -137,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;
@@ -150,43 +157,43 @@ struct ZoneStatus;
 class CommunicatorClass
 {
 public:
-  CommunicatorClass() 
+  CommunicatorClass()
   {
-    d_tickinterval=60;
-    d_masterschanged=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 ixfrSuck(const DNSName &domain, const TSIGTriplet& tt, const ComboAddress& laddr, const ComboAddress& remote, std::unique_ptr<AuthLua4>& pdl,
-                ZoneStatus& zs, vector<DNSRecord>* axfr);
+  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;
   Semaphore d_any_sem;
@@ -196,8 +203,9 @@ private:
   NotificationQueue d_nq;
 
   time_t d_tickinterval;
-  bool d_masterschanged, d_slaveschanged;
+  bool d_secondarieschanged;
   bool d_preventSelfNotification;
+  time_t d_delayNotifications{0};
 
   struct Data
   {
@@ -206,57 +214,59 @@ 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()
     {
       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.d_content->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;
   }
@@ -268,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 86%
rename from pdns/mtasker_context.cc
rename to pdns/coverage.hh
index 615af05dba72deae47bcb83a419b71d3b92d315d..960f28c8ddbe19427f0bb7b86a95fbe3c64430fa 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.
  */
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
+#pragma once
 
-#if defined(HAVE_BOOST_CONTEXT)
-#include "mtasker_fcontext.cc"
-#else
-#include "mtasker_ucontext.cc"
-#endif
+namespace pdns::coverage
+{
+void dumpCoverageData();
+}
index 3a7534f75705c657a5655cf1bd1bb416ef52bfb2..ec11732f246c04a5998da426becf9ab2b2e4ef3e 100644 (file)
@@ -28,7 +28,7 @@
 #include <sodium.h>
 #endif
 
-#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
 #include <openssl/evp.h>
 #include <openssl/kdf.h>
 #include <openssl/opensslv.h>
 #include <unistd.h>
 
 #include "base64.hh"
+#include "dns_random.hh"
 #include "credentials.hh"
 #include "misc.hh"
 
-#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
 static size_t const pwhash_max_size = 128U; /* maximum size of the output */
 static size_t const pwhash_output_size = 32U; /* size of the hashed output (before base64 encoding) */
 static unsigned int const pwhash_salt_size = 16U; /* size of the salt (before base64 encoding */
@@ -62,14 +63,16 @@ uint64_t const CredentialsHolder::s_defaultBlockSize{8U}; /* r */
 SensitiveData::SensitiveData(std::string&& data) :
   d_data(std::move(data))
 {
+  data.clear();
 #ifdef HAVE_LIBSODIUM
   sodium_mlock(d_data.data(), d_data.size());
 #endif
 }
 
-SensitiveData& SensitiveData::operator=(SensitiveData&& rhs)
+SensitiveData& SensitiveData::operator=(SensitiveData&& rhs) noexcept
 {
   d_data = std::move(rhs.d_data);
+  rhs.clear();
   return *this;
 }
 
@@ -94,9 +97,9 @@ 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)
 {
-#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+#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);
   if (!pctx) {
     throw std::runtime_error("Error getting a scrypt context to hash the supplied password");
@@ -148,7 +151,7 @@ static std::string hashPasswordInternal(const std::string& password, const std::
 
 static std::string generateRandomSalt()
 {
-#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
   /* generate a random salt */
   std::string salt;
   salt.resize(pwhash_salt_size);
@@ -163,9 +166,13 @@ 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)
 {
-#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
+  if (workFactor == 0) {
+    throw std::runtime_error("Invalid work factor of " + std::to_string(workFactor) + " passed to hashPassword()");
+  }
+
   std::string result;
   result.reserve(pwhash_max_size);
 
@@ -191,18 +198,18 @@ 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)
 {
-#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+#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);
 #else
   throw std::runtime_error("Hashing a password requires scrypt support in OpenSSL, and it is not available");
 #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)
 {
-#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
   auto expected = hashPasswordInternal(binaryPassword, salt, workFactor, parallelFactor, blockSize);
   return constantTimeStringEquals(expected, binaryHash);
 #else
@@ -211,9 +218,9 @@ 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)
 {
-#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
   auto parametersEnd = hash.find('$', pwhash_prefix.size());
   if (parametersEnd == std::string::npos || parametersEnd == hash.size()) {
     throw std::runtime_error("Invalid hashed password format, no parameters");
@@ -276,13 +283,13 @@ 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;
   }
 
-#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
   std::string salt;
   std::string hashedPassword;
   uint64_t workFactor = 0;
@@ -298,9 +305,9 @@ 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)
 {
-#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
   if (password.size() < pwhash_prefix_size || password.size() > pwhash_max_size) {
     return false;
   }
@@ -367,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);
   }
 }
@@ -395,14 +402,14 @@ bool CredentialsHolder::matches(const std::string& password) const
 
 bool CredentialsHolder::isHashingAvailable()
 {
-#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
   return true;
 #else
   return false;
 #endif
 }
 
-#include <signal.h>
+#include <csignal>
 #include <termios.h>
 
 SensitiveData CredentialsHolder::readFromTerminal()
@@ -438,7 +445,7 @@ SensitiveData CredentialsHolder::readFromTerminal()
   struct sigaction sa;
   sigemptyset(&sa.sa_mask);
   sa.sa_flags = 0;
-  sa.sa_handler = [](int s) {};
+  sa.sa_handler = [](int /* s */) {};
   sigaction(SIGALRM, &sa, &signals[SIGALRM]);
   sigaction(SIGHUP, &sa, &signals[SIGHUP]);
   sigaction(SIGINT, &sa, &signals[SIGINT]);
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 0321ecc012ac7145c466ab5ae286297b5f6e0909..717b00bda2f6fc5ce3b723ad141582a43f36ac30 100644 (file)
@@ -58,7 +58,7 @@ bool DNSSECKeeper::doesDNSSEC()
   return d_keymetadb->doesDNSSEC();
 }
 
-bool DNSSECKeeper::isSecuredZone(const DNSName& zone, bool useCache) 
+bool DNSSECKeeper::isSecuredZone(const DNSName& zone, bool useCache)
 {
   if(isPresigned(zone, useCache))
     return true;
@@ -103,16 +103,14 @@ bool DNSSECKeeper::addKey(const DNSName& name, bool setSEPBit, int algorithm, in
       }
     }
   }
-  DNSSECPrivateKey dspk;
   shared_ptr<DNSCryptoKeyEngine> dpk(DNSCryptoKeyEngine::make(algorithm));
   try{
     dpk->create(bits);
   } catch (const std::runtime_error& error){
     throw runtime_error("The algorithm does not support the given bit size.");
   }
-  dspk.setKey(dpk);
-  dspk.d_algorithm = algorithm;
-  dspk.d_flags = setSEPBit ? 257 : 256;
+  DNSSECPrivateKey dspk;
+  dspk.setKey(dpk, setSEPBit ? 257 : 256, algorithm);
   return addKey(name, dspk, id, active, published) && clearKeyCache(name);
 }
 
@@ -145,7 +143,7 @@ void DNSSECKeeper::clearCaches(const DNSName& name)
 bool DNSSECKeeper::addKey(const DNSName& name, const DNSSECPrivateKey& dpk, int64_t& id, bool active, bool published)
 {
   DNSBackend::KeyData kd;
-  kd.flags = dpk.d_flags; // the dpk doesn't get stored, only they key part
+  kd.flags = dpk.getFlags(); // the dpk doesn't get stored, only they key part
   kd.active = active;
   kd.published = published;
   kd.content = dpk.getKey()->convertToISC();
@@ -161,21 +159,19 @@ static bool keyCompareByKindAndID(const DNSSECKeeper::keyset_t::value_type& a, c
 }
 
 DNSSECPrivateKey DNSSECKeeper::getKeyById(const DNSName& zname, unsigned int id)
-{  
+{
   vector<DNSBackend::KeyData> keys;
   d_keymetadb->getDomainKeys(zname, keys);
   for(const DNSBackend::KeyData& kd :  keys) {
-    if(kd.id != id) 
+    if(kd.id != id)
       continue;
-    
-    DNSSECPrivateKey dpk;
+
     DNSKEYRecordContent dkrc;
     auto key = shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromISCString(dkrc, kd.content));
-    dpk.setKey(key);
-    dpk.d_flags = kd.flags;
-    dpk.d_algorithm = dkrc.d_algorithm;
-    
-    return dpk;    
+    DNSSECPrivateKey dpk;
+    dpk.setKey(key, kd.flags, dkrc.d_algorithm);
+
+    return dpk;
   }
   throw runtime_error("Can't find a key with id "+std::to_string(id)+" for zone '"+zname.toLogString()+"'");
 }
@@ -336,7 +332,7 @@ bool DNSSECKeeper::getNSEC3PARAM(const DNSName& zname, NSEC3PARAMRecordContent*
   }
 
   static int maxNSEC3Iterations=::arg().asNum("max-nsec3-iterations");
-  if(ns3p) {
+  if(ns3p != nullptr) {
     *ns3p = NSEC3PARAMRecordContent(value);
     if (ns3p->d_iterations > maxNSEC3Iterations && !isPresigned(zname, useCache)) {
       ns3p->d_iterations = maxNSEC3Iterations;
@@ -347,7 +343,7 @@ bool DNSSECKeeper::getNSEC3PARAM(const DNSName& zname, NSEC3PARAMRecordContent*
       ns3p->d_algorithm = 1;
     }
   }
-  if(narrow) {
+  if(narrow != nullptr) {
     if(useCache) {
       getFromMeta(zname, "NSEC3NARROW", value);
     }
@@ -400,10 +396,10 @@ bool DNSSECKeeper::setNSEC3PARAM(const DNSName& zname, const NSEC3PARAMRecordCon
   meta.push_back(descr);
   if (d_keymetadb->setDomainMetadata(zname, "NSEC3PARAM", meta)) {
     meta.clear();
-    
+
     if(narrow)
       meta.push_back("1");
-    
+
     return d_keymetadb->setDomainMetadata(zname, "NSEC3NARROW", meta) && clearMetaCache(zname);
   }
   return false;
@@ -537,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)) {
@@ -565,10 +562,10 @@ DNSSECKeeper::keyset_t DNSSECKeeper::getKeys(const DNSName& zone, bool useCache)
   set<uint8_t> algoSEP, algoNoSEP;
   vector<uint8_t> algoHasSeparateKSK;
   for(const DNSBackend::KeyData &keydata : dbkeyset) {
-    DNSSECPrivateKey dpk;
     DNSKEYRecordContent dkrc;
     auto key = shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromISCString(dkrc, keydata.content));
-    dpk.setKey(key);
+    DNSSECPrivateKey dpk;
+    dpk.setKey(key, dkrc.d_algorithm);
 
     if(keydata.active) {
       if(keydata.flags == 257)
@@ -582,13 +579,10 @@ DNSSECKeeper::keyset_t DNSSECKeeper::getKeys(const DNSName& zone, bool useCache)
 
   for(DNSBackend::KeyData& kd : dbkeyset)
   {
-    DNSSECPrivateKey dpk;
     DNSKEYRecordContent dkrc;
     auto key = shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromISCString(dkrc, kd.content));
-    dpk.setKey(key);
-
-    dpk.d_flags = kd.flags;
-    dpk.d_algorithm = dkrc.d_algorithm;
+    DNSSECPrivateKey dpk;
+    dpk.setKey(key, kd.flags, dkrc.d_algorithm);
 
     KeyMetaData kmd;
 
@@ -597,7 +591,7 @@ DNSSECKeeper::keyset_t DNSSECKeeper::getKeys(const DNSName& zone, bool useCache)
     kmd.hasSEPBit = (kd.flags == 257);
     kmd.id = kd.id;
 
-    if (find(algoHasSeparateKSK.begin(), algoHasSeparateKSK.end(), dpk.d_algorithm) == algoHasSeparateKSK.end())
+    if (find(algoHasSeparateKSK.begin(), algoHasSeparateKSK.end(), dpk.getAlgorithm()) == algoHasSeparateKSK.end())
       kmd.keyType = CSK;
     else if(kmd.hasSEPBit)
       kmd.keyType = KSK;
@@ -621,7 +615,7 @@ DNSSECKeeper::keyset_t DNSSECKeeper::getKeys(const DNSName& zone, bool useCache)
   return retkeyset;
 }
 
-bool DNSSECKeeper::checkKeys(const DNSName& zone, vector<string>* errorMessages)
+bool DNSSECKeeper::checkKeys(const DNSName& zone, std::optional<std::reference_wrapper<std::vector<std::string>>> errorMessages)
 {
   vector<DNSBackend::KeyData> dbkeyset;
   d_keymetadb->getDomainKeys(zone, dbkeyset);
@@ -645,12 +639,11 @@ void DNSSECKeeper::getPreRRSIGs(UeberBackend& db, vector<DNSZoneRecord>& rrs, ui
   const auto rr = *rrs.rbegin();
 
   DNSZoneRecord dzr;
-  std::shared_ptr<RRSIGRecordContent> rrsig;
 
   db.lookup(QType(QType::RRSIG), !rr.wildcardname.empty() ? rr.wildcardname : rr.dr.d_name, rr.domain_id);
   while(db.get(dzr)) {
-    rrsig = getRR<RRSIGRecordContent>(dzr.dr);
-    if(rrsig->d_type == rr.dr.d_type) {
+    auto rrsig = getRR<RRSIGRecordContent>(dzr.dr);
+    if (rrsig->d_type == rr.dr.d_type) {
       if(!rr.wildcardname.empty()) {
         dzr.dr.d_name = rr.dr.d_name;
       }
@@ -665,9 +658,9 @@ void DNSSECKeeper::getPreRRSIGs(UeberBackend& db, vector<DNSZoneRecord>& rrs, ui
 bool DNSSECKeeper::TSIGGrantsAccess(const DNSName& zone, const DNSName& keyname)
 {
   vector<string> allowed;
-  
+
   d_keymetadb->getDomainMetadata(zone, "TSIG-ALLOW-AXFR", allowed);
-  
+
   for(const string& dbkey :  allowed) {
     if(DNSName(dbkey)==keyname)
       return true;
@@ -675,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;
@@ -689,7 +682,7 @@ bool DNSSECKeeper::getTSIGForAccess(const DNSName& zone, const ComboAddress& mas
   return false;
 }
 
-bool DNSSECKeeper::unSecureZone(const DNSName& zone, string& error, string& info) {
+bool DNSSECKeeper::unSecureZone(const DNSName& zone, string& error) {
   // Not calling isSecuredZone(), as it will return false for zones with zero
   // active keys.
   DNSSECKeeper::keyset_t keyset=getKeys(zone);
@@ -968,10 +961,10 @@ void DNSSECKeeper::cleanup()
 
   if(now.tv_sec - s_last_prune > (time_t)(30)) {
     {
-      pruneCollection<SequencedTag>(*this, (*s_metacache.write_lock()), s_maxEntries);
+      pruneCollection<SequencedTag>((*s_metacache.write_lock()), s_maxEntries);
     }
     {
-      pruneCollection<SequencedTag>(*this, (*s_keycache.write_lock()), s_maxEntries);
+      pruneCollection<SequencedTag>((*s_keycache.write_lock()), s_maxEntries);
     }
     s_last_prune = time(nullptr);
   }
index 93585815a9d9793f24942a7e7a27c489c51741a3..6c8be67a4f4b73306e032f0be5b24fa04401f384 100644 (file)
@@ -1,9 +1,18 @@
+#include <openssl/err.h>
+#include <openssl/pem.h>
 #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"
 
@@ -12,18 +21,47 @@ using namespace decaf;
 class DecafED25519DNSCryptoKeyEngine : public DNSCryptoKeyEngine
 {
 public:
-  explicit DecafED25519DNSCryptoKeyEngine(unsigned int algo) : DNSCryptoKeyEngine(algo)
+  explicit DecafED25519DNSCryptoKeyEngine(unsigned int algo) :
+    DNSCryptoKeyEngine(algo)
   {
-
   }
   string getName() const override { return "Decaf ED25519"; }
   void create(unsigned int bits) override;
-  storvector_t convertToISCVector() const override;
-  std::string getPubKeyHash() const override;
-  std::string sign(const std::string& msg) const override;
-  bool verify(const std::string& msg, const std::string& signature) const override;
-  std::string getPublicKeyString() const override;
-  int getBits() const override;
+
+#if defined(HAVE_LIBCRYPTO_ED25519)
+  /**
+   * \brief Creates an ED25519 key engine from a PEM file.
+   *
+   * Receives an open file handle with PEM contents and creates an ED25519 key engine.
+   *
+   * \param[in] drc Key record contents to be populated.
+   *
+   * \param[in] inputFile An open file handle to a file containing ED25519 PEM contents.
+   *
+   * \param[in] filename Only used for providing filename information in error messages.
+   *
+   * \return An ED25519 key engine populated with the contents of the PEM file.
+   */
+  void createFromPEMFile(DNSKEYRecordContent& drc, std::FILE& inputFile, std::optional<std::reference_wrapper<const std::string>> filename = std::nullopt) override;
+
+  /**
+   * \brief Writes this key's contents to a file.
+   *
+   * Receives an open file handle and writes this key's contents to the
+   * file.
+   *
+   * \param[in] outputFile An open file handle for writing.
+   *
+   * \exception std::runtime_error In case of OpenSSL errors.
+   */
+  void convertToPEMFile(std::FILE& outputFile) const override;
+#endif
+
+  [[nodiscard]] storvector_t convertToISCVector() const override;
+  [[nodiscard]] std::string sign(const std::string& msg) const override;
+  [[nodiscard]] bool verify(const std::string& msg, const std::string& signature) const override;
+  [[nodiscard]] std::string getPublicKeyString() const override;
+  [[nodiscard]] int getBits() const override;
   void fromISCMap(DNSKEYRecordContent& drc, std::map<std::string, std::string>& stormap) override;
   void fromPublicKeyString(const std::string& content) override;
 
@@ -39,8 +77,8 @@ private:
 
 void DecafED25519DNSCryptoKeyEngine::create(unsigned int bits)
 {
-  if(bits != (unsigned int)getBits()) {
-    throw runtime_error("Unsupported key length of "+std::to_string(bits)+" bits requested, DecafED25519 class");
+  if (bits != (unsigned int)getBits()) {
+    throw runtime_error("Unsupported key length of " + std::to_string(bits) + " bits requested, DecafED25519 class");
   }
 
   SpongeRng rng("/dev/urandom");
@@ -52,6 +90,54 @@ void DecafED25519DNSCryptoKeyEngine::create(unsigned int bits)
   pub.serialize_into(d_pubkey);
 }
 
+#if defined(HAVE_LIBCRYPTO_ED25519)
+void DecafED25519DNSCryptoKeyEngine::createFromPEMFile(DNSKEYRecordContent& drc, std::FILE& inputFile, std::optional<std::reference_wrapper<const std::string>> filename)
+{
+  drc.d_algorithm = d_algorithm;
+  auto key = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>(PEM_read_PrivateKey(&inputFile, nullptr, nullptr, nullptr), &EVP_PKEY_free);
+  if (key == nullptr) {
+    if (filename.has_value()) {
+      throw runtime_error(getName() + ": Failed to read private key from PEM file `" + filename->get() + "`");
+    }
+
+    throw runtime_error(getName() + ": Failed to read private key from PEM contents");
+  }
+
+  std::size_t keylen = DECAF_EDDSA_25519_PRIVATE_BYTES;
+  int ret = EVP_PKEY_get_raw_private_key(key.get(), d_seckey, &keylen);
+  if (ret == 0) {
+    if (filename.has_value()) {
+      throw runtime_error(getName() + ": Failed to get private key from PEM file contents `" + filename->get() + "`");
+    }
+
+    throw runtime_error(getName() + ": Failed to get private key from PEM contents");
+  }
+
+  keylen = DECAF_EDDSA_25519_PUBLIC_BYTES;
+  ret = EVP_PKEY_get_raw_public_key(key.get(), d_pubkey, &keylen);
+  if (ret == 0) {
+    if (filename.has_value()) {
+      throw runtime_error(getName() + ": Failed to get public key from PEM file contents `" + filename->get() + "`");
+    }
+
+    throw runtime_error(getName() + ": Failed to get public key from PEM contents");
+  }
+}
+
+void DecafED25519DNSCryptoKeyEngine::convertToPEMFile(std::FILE& outputFile) const
+{
+  auto key = std::unique_ptr<EVP_PKEY, void (*)(EVP_PKEY*)>(EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, nullptr, d_seckey, DECAF_EDDSA_25519_PRIVATE_BYTES), EVP_PKEY_free);
+  if (key == nullptr) {
+    throw runtime_error(getName() + ": Could not create private key from buffer");
+  }
+
+  auto ret = PEM_write_PrivateKey(&outputFile, key.get(), nullptr, nullptr, 0, nullptr, nullptr);
+  if (ret == 0) {
+    throw runtime_error(getName() + ": Could not convert private key to PEM");
+  }
+}
+#endif
+
 int DecafED25519DNSCryptoKeyEngine::getBits() const
 {
   return DECAF_EDDSA_25519_PRIVATE_BYTES << 3;
@@ -73,7 +159,7 @@ DNSCryptoKeyEngine::storvector_t DecafED25519DNSCryptoKeyEngine::convertToISCVec
   return storvector;
 }
 
-void DecafED25519DNSCryptoKeyEngine::fromISCMap(DNSKEYRecordContent& drc, std::map<std::string, std::string>& stormap )
+void DecafED25519DNSCryptoKeyEngine::fromISCMap(DNSKEYRecordContent& drc, std::map<std::string, std::string>& stormap)
 {
   /*
     Private-key-format: v1.2
@@ -94,11 +180,6 @@ void DecafED25519DNSCryptoKeyEngine::fromISCMap(DNSKEYRecordContent& drc, std::m
   pub.serialize_into(d_pubkey);
 }
 
-std::string DecafED25519DNSCryptoKeyEngine::getPubKeyHash() const
-{
-  return this->getPublicKeyString();
-}
-
 std::string DecafED25519DNSCryptoKeyEngine::getPublicKeyString() const
 {
   return string((char*)d_pubkey, DECAF_EDDSA_25519_PUBLIC_BYTES);
@@ -135,33 +216,60 @@ bool DecafED25519DNSCryptoKeyEngine::verify(const std::string& msg, const std::s
 
   try {
     pub.verify(sig, message);
-  } catch(const CryptoException& e) {
+  }
+  catch (const CryptoException& e) {
     return false;
   }
 
   return true;
 }
 
-
 class DecafED448DNSCryptoKeyEngine : public DNSCryptoKeyEngine
 {
 public:
-  explicit DecafED448DNSCryptoKeyEngine(unsigned int algo) : DNSCryptoKeyEngine(algo)
+  explicit DecafED448DNSCryptoKeyEngine(unsigned int algo) :
+    DNSCryptoKeyEngine(algo)
   {
-
   }
   string getName() const override { return "Decaf ED448"; }
   void create(unsigned int bits) override;
+
+#if defined(HAVE_LIBCRYPTO_ED448)
+  /**
+   * \brief Creates an ED448 key engine from a PEM file.
+   *
+   * Receives an open file handle with PEM contents and creates an ED448 key engine.
+   *
+   * \param[in] drc Key record contents to be populated.
+   *
+   * \param[in] inputFile An open file handle to a file containing ED448 PEM contents.
+   *
+   * \param[in] filename Only used for providing filename information in error messages.
+   *
+   * \return An ED448 key engine populated with the contents of the PEM file.
+   */
+  void createFromPEMFile(DNSKEYRecordContent& drc, std::FILE& inputFile, std::optional<std::reference_wrapper<const std::string>> filename = std::nullopt) override;
+
+  /**
+   * \brief Writes this key's contents to a file.
+   *
+   * Receives an open file handle and writes this key's contents to the
+   * file.
+   *
+   * \param[in] outputFile An open file handle for writing.
+   *
+   * \exception std::runtime_error In case of OpenSSL errors.
+   */
+  void convertToPEMFile(std::FILE& outputFile) const override;
+#endif
+
   storvector_t convertToISCVector() const override;
-  std::string getPubKeyHash() const override;
   std::string sign(const std::string& msg) const override;
   bool verify(const std::string& msg, const std::string& signature) const override;
   std::string getPublicKeyString() const override;
   int getBits() const override;
   void fromISCMap(DNSKEYRecordContent& drc, std::map<std::string, std::string>& stormap) override;
   void fromPublicKeyString(const std::string& content) override;
-  void fromPEMString(DNSKEYRecordContent& drc, const std::string& raw) override
-  {}
 
   static std::unique_ptr<DNSCryptoKeyEngine> maker(unsigned int algorithm)
   {
@@ -175,8 +283,8 @@ private:
 
 void DecafED448DNSCryptoKeyEngine::create(unsigned int bits)
 {
-  if(bits != (unsigned int)getBits()) {
-    throw runtime_error("Unsupported key length of "+std::to_string(bits)+" bits requested, DecafED448 class");
+  if (bits != (unsigned int)getBits()) {
+    throw runtime_error("Unsupported key length of " + std::to_string(bits) + " bits requested, DecafED448 class");
   }
 
   SpongeRng rng("/dev/urandom");
@@ -188,6 +296,54 @@ void DecafED448DNSCryptoKeyEngine::create(unsigned int bits)
   pub.serialize_into(d_pubkey);
 }
 
+#if defined(HAVE_LIBCRYPTO_ED448)
+void DecafED448DNSCryptoKeyEngine::createFromPEMFile(DNSKEYRecordContent& drc, std::FILE& inputFile, std::optional<std::reference_wrapper<const std::string>> filename)
+{
+  drc.d_algorithm = d_algorithm;
+  auto key = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>(PEM_read_PrivateKey(&inputFile, nullptr, nullptr, nullptr), &EVP_PKEY_free);
+  if (key == nullptr) {
+    if (filename.has_value()) {
+      throw runtime_error(getName() + ": Failed to read private key from PEM file `" + filename->get() + "`");
+    }
+
+    throw runtime_error(getName() + ": Failed to read private key from PEM contents");
+  }
+
+  std::size_t keylen = DECAF_EDDSA_448_PRIVATE_BYTES;
+  int ret = EVP_PKEY_get_raw_private_key(key.get(), d_seckey, &keylen);
+  if (ret == 0) {
+    if (filename.has_value()) {
+      throw runtime_error(getName() + ": Failed to get private key from PEM file contents `" + filename->get() + "`");
+    }
+
+    throw runtime_error(getName() + ": Failed to get private key from PEM contents");
+  }
+
+  keylen = DECAF_EDDSA_448_PUBLIC_BYTES;
+  ret = EVP_PKEY_get_raw_public_key(key.get(), d_pubkey, &keylen);
+  if (ret == 0) {
+    if (filename.has_value()) {
+      throw runtime_error(getName() + ": Failed to get public key from PEM file contents `" + filename->get() + "`");
+    }
+
+    throw runtime_error(getName() + ": Failed to get public key from PEM contents");
+  }
+}
+
+void DecafED448DNSCryptoKeyEngine::convertToPEMFile(std::FILE& outputFile) const
+{
+  auto key = std::unique_ptr<EVP_PKEY, void (*)(EVP_PKEY*)>(EVP_PKEY_new_raw_private_key(EVP_PKEY_ED448, nullptr, d_seckey, DECAF_EDDSA_448_PRIVATE_BYTES), EVP_PKEY_free);
+  if (key == nullptr) {
+    throw runtime_error(getName() + ": Could not create private key from buffer");
+  }
+
+  auto ret = PEM_write_PrivateKey(&outputFile, key.get(), nullptr, nullptr, 0, nullptr, nullptr);
+  if (ret == 0) {
+    throw runtime_error(getName() + ": Could not convert private key to PEM");
+  }
+}
+#endif
+
 int DecafED448DNSCryptoKeyEngine::getBits() const
 {
   return DECAF_EDDSA_448_PRIVATE_BYTES << 3;
@@ -209,7 +365,7 @@ DNSCryptoKeyEngine::storvector_t DecafED448DNSCryptoKeyEngine::convertToISCVecto
   return storvector;
 }
 
-void DecafED448DNSCryptoKeyEngine::fromISCMap(DNSKEYRecordContent& drc, std::map<std::string, std::string>& stormap )
+void DecafED448DNSCryptoKeyEngine::fromISCMap(DNSKEYRecordContent& drc, std::map<std::string, std::string>& stormap)
 {
   /*
     Private-key-format: v1.2
@@ -230,11 +386,6 @@ void DecafED448DNSCryptoKeyEngine::fromISCMap(DNSKEYRecordContent& drc, std::map
   pub.serialize_into(d_pubkey);
 }
 
-std::string DecafED448DNSCryptoKeyEngine::getPubKeyHash() const
-{
-  return this->getPublicKeyString();
-}
-
 std::string DecafED448DNSCryptoKeyEngine::getPublicKeyString() const
 {
   return string((char*)d_pubkey, DECAF_EDDSA_448_PUBLIC_BYTES);
@@ -271,21 +422,22 @@ bool DecafED448DNSCryptoKeyEngine::verify(const std::string& msg, const std::str
 
   try {
     pub.verify(sig, message);
-  } catch(const CryptoException& e) {
+  }
+  catch (const CryptoException& e) {
     return false;
   }
 
   return true;
 }
 
-
-namespace {
-struct LoaderDecafStruct
+namespace
+{
+const struct LoaderDecafStruct
 {
   LoaderDecafStruct()
   {
-    DNSCryptoKeyEngine::report(15, &DecafED25519DNSCryptoKeyEngine::maker, true);
-    DNSCryptoKeyEngine::report(16, &DecafED448DNSCryptoKeyEngine::maker);
+    DNSCryptoKeyEngine::report(DNSSECKeeper::ED25519, &DecafED25519DNSCryptoKeyEngine::maker, true);
+    DNSCryptoKeyEngine::report(DNSSECKeeper::ED448, &DecafED448DNSCryptoKeyEngine::maker);
   }
 } loaderdecaf;
 }
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 37a0d487fff60c37816822c9113c9bc1d4cb52ed..071ee9e94597a130789639b4ed1b0ddc8d43a32f 100644 (file)
@@ -62,7 +62,7 @@ private:
   int d_devpollfd;
 };
 
-static FDMultiplexer* makeDevPoll()
+static FDMultiplexer* makeDevPoll(unsigned int)
 {
   return new DevPollFDMultiplexer();
 }
index 0f30ba8306ac1376cd3becb14c524f2a1e820159..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_md5sum(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_sha1sum(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 c3fce94cd5e489c760096928ca175d5dd158c2e9..cdee87b67dc7b33a36cc3dd1a9c940b1e78535d8 100644 (file)
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #pragma once
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
 #include <string>
 #include <deque>
 #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"
@@ -35,6 +39,7 @@
 #include "arguments.hh"
 #include <atomic>
 #include "statbag.hh"
+#include "gss_context.hh"
 
 extern StatBag S;
 
@@ -113,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 )
@@ -150,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 :-)
   }
@@ -187,78 +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);
-      QD.reset();
+      questionData->callback(a, questionData->start);
+#ifdef ENABLE_GSS_TSIG
+      if (g_doGssTSIG && a != nullptr) {
+        questionData->Q.cleanupGSS(a->d.rcode);
+      }
+#endif
+      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);
   }
@@ -294,18 +299,23 @@ 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;
     }
   }
   callback(a, start);
+#ifdef ENABLE_GSS_TSIG
+  if (g_doGssTSIG && a != nullptr) {
+    q.cleanupGSS(a->d.rcode);
+  }
+#endif
   return 0;
 }
 
@@ -314,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 12689d9831420e7124b18b445bba45cfb1e184c0..99bbd72aa65e8749e66ee8237914427ab80ba681 100644 (file)
 #include <boost/assign/list_of.hpp>
 #include "dnsparser.hh"
 
-std::vector<std::string> RCode::rcodes_s = boost::assign::list_of 
-  ("No Error")
-  ("Form Error")
-  ("Server Failure")
-  ("Non-Existent domain")
-  ("Not Implemented")
-  ("Query Refused")
-  ("Name Exists when it should not")
-  ("RR Set Exists when it should not")
-  ("RR Set that should exist does not")
-  ("Server Not Authoritative for zone / Not Authorized")
-  ("Name not contained in zone")
-  ("Err#11")
-  ("Err#12")
-  ("Err#13")
-  ("Err#14")
-  ("Err#15")  // Last non-extended RCode
-  ("Bad OPT Version / TSIG Signature Failure")
-  ("Key not recognized")
-  ("Signature out of time window")
-  ("Bad TKEY Mode")
-  ("Duplicate key name")
-  ("Algorithm not supported")
-  ("Bad Truncation")
-  ("Bad/missing Server Cookie")
-;
+const std::array<std::string, 24> RCode::rcodes_s = {
+  "No Error",
+  "Form Error",
+  "Server Failure",
+  "Non-Existent domain",
+  "Not Implemented",
+  "Query Refused",
+  "Name Exists when it should not",
+  "RR Set Exists when it should not",
+  "RR Set that should exist does not",
+  "Server Not Authoritative for zone / Not Authorized",
+  "Name not contained in zone",
+  "Err#11",
+  "Err#12",
+  "Err#13",
+  "Err#14",
+  "Err#15",  // Last non-extended RCode
+  "Bad OPT Version / TSIG Signature Failure",
+  "Key not recognized",
+  "Signature out of time window",
+  "Bad TKEY Mode",
+  "Duplicate key name",
+  "Algorithm not supported",
+  "Bad Truncation",
+  "Bad/missing Server Cookie"
+};
+
+static const std::array<std::string, 10> rcodes_short_s =  {
+  "noerror",
+  "formerr",
+  "servfail",
+  "nxdomain",
+  "notimp",
+  "refused",
+  "yxdomain",
+  "yxrrset",
+  "nxrrset",
+  "notauth",
+};
 
 std::string RCode::to_s(uint8_t rcode) {
-  if (rcode > 0xF)
+  if (rcode > 0xF) {
     return std::string("ErrOutOfRange");
+  }
   return ERCode::to_s(rcode);
 }
 
-std::string ERCode::to_s(uint8_t rcode) {
-  if (rcode > RCode::rcodes_s.size()-1)
+std::string RCode::to_short_s(uint8_t rcode) {
+  if (rcode >= rcodes_short_s.size()) {
+    return "rcode" + std::to_string(rcode);
+  }
+  return rcodes_short_s.at(rcode);
+}
+
+std::string ERCode::to_s(uint16_t rcode) {
+  if (rcode >= RCode::rcodes_s.size()) {
     return std::string("Err#")+std::to_string(rcode);
-  return RCode::rcodes_s[rcode];
+  }
+  return RCode::rcodes_s.at(rcode);
 }
 
 std::string Opcode::to_s(uint8_t opcode) {
-  static const std::vector<std::string> s_opcodes = { "Query", "IQuery", "Status", "3", "Notify", "Update" };
+  static const std::array<std::string, 6> s_opcodes = { "Query", "IQuery", "Status", "3", "Notify", "Update" };
 
   if (opcode >= s_opcodes.size()) {
     return std::to_string(opcode);
index f0b204ee23d57d39cb380cacfb09f36553b920eb..c95a62f1c52fdd49e9fc1c7b8b79e7f1bb7870f3 100644 (file)
 #pragma once
 #include "qtype.hh"
 #include "dnsname.hh"
-#include <time.h>
+#include <ctime>
 #include <sys/types.h>
 
 #undef BADSIG  // signal.h SIG_ERR
 
-class DNSBackend;
 struct DNSRecord;
 
-struct SOAData
-{
-  SOAData() : ttl(0), serial(0), refresh(0), retry(0), expire(0), minimum(0), db(0), domain_id(-1) {};
-
-  DNSName qname;
-  DNSName nameserver;
-  DNSName hostmaster;
-  uint32_t ttl;
-  uint32_t serial;
-  uint32_t refresh;
-  uint32_t retry;
-  uint32_t expire;
-  uint32_t minimum;
-  DNSBackend *db;
-  int domain_id;
-
-  uint32_t getNegativeTTL() const { return min(ttl, minimum); }
-};
-
 class RCode
 {
 public:
-  enum rcodes_ { NoError=0, FormErr=1, ServFail=2, NXDomain=3, NotImp=4, Refused=5, YXDomain=6, YXRRSet=7, NXRRSet=8, NotAuth=9, NotZone=10};
+  enum rcodes_ : uint8_t { NoError=0, FormErr=1, ServFail=2, NXDomain=3, NotImp=4, Refused=5, YXDomain=6, YXRRSet=7, NXRRSet=8, NotAuth=9, NotZone=10};
   static std::string to_s(uint8_t rcode);
-  static std::vector<std::string> rcodes_s;
+  static std::string to_short_s(uint8_t rcode);
+  const static std::array<std::string, 24> rcodes_s;
 };
 
 class ERCode
 {
 public:
-  enum rcodes_ { BADVERS=16, BADSIG=16, BADKEY=17, BADTIME=18, BADMODE=19, BADNAME=20, BADALG=21, BADTRUNC=22, BADCOOKIE=23 };
-  static std::string to_s(uint8_t rcode);
+  enum rcodes_ : uint16_t { BADVERS=16, BADSIG=16, BADKEY=17, BADTIME=18, BADMODE=19, BADNAME=20, BADALG=21, BADTRUNC=22, BADCOOKIE=23 };
+  static std::string to_s(uint16_t rcode);
 };
 
 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);
 };
 
@@ -75,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
@@ -91,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;
   }
 };
@@ -139,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) */
@@ -170,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 */
@@ -199,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");
@@ -210,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));
@@ -221,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)
 {
-  return (uint16_t*) (((char *) dh) + sizeof(uint16_t));
+  // 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)
+{
+  // 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)
@@ -253,8 +263,6 @@ inline uint16_t * getFlagsFromDNSHeader(struct dnsheader * dh)
 #define FLAGS_CD_OFFSET (12)
 #endif
 
-extern time_t s_starttime;
-
 uint32_t hashQuestion(const uint8_t* packet, uint16_t len, uint32_t init, bool& ok);
 
 struct TSIGTriplet
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 f0ae76a896c0c950a126289a327c0f83fb207ff0..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;
@@ -304,7 +306,7 @@ bool DNSBackend::getBeforeAndAfterNames(uint32_t id, const DNSName& zonename, co
   return ret;
 }
 
-void DNSBackend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool include_disabled)
+void DNSBackend::getAllDomains(vector<DomainInfo>* /* domains */, bool /* getSerial */, bool /* include_disabled */)
 {
   if (g_zoneCache.isEnabled()) {
     g_log << Logger::Error << "One of the backends does not support zone caching. Put zone-cache-refresh-interval=0 in the config file to disable this cache." << endl;
@@ -312,48 +314,50 @@ void DNSBackend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool
   }
 }
 
-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 84689c8087894132db7af4030c00dcb66bde79a4..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>
@@ -36,84 +39,105 @@ class DNSPacket;
 #include "misc.hh"
 #include "qtype.hh"
 #include "dns.hh"
-#include <vector>
 #include "namespaces.hh"
 #include "comment.hh"
 #include "dnsname.hh"
 #include "dnsrecords.hh"
 #include "iputils.hh"
+#include "sha.hh"
+#include "auth-catalogzone.hh"
+
+class DNSBackend;
+struct SOAData;
 
-class DNSBackend;  
 struct DomainInfo
 {
-  DomainInfo() : last_check(0), backend(nullptr), id(0), notified_serial(0), receivedNotify(false), serial(0), kind(DomainInfo::Native) {}
+  DomainInfo() = default;
 
   DNSName zone;
-  time_t last_check;
+  DNSName catalog;
+  time_t last_check{};
+  string options;
   string account;
-  vector<ComboAddress> masters; 
-  DNSBackend *backend;
+  vector<ComboAddress> primaries;
+  DNSBackend* backend{};
+
+  uint32_t id{};
+  uint32_t notified_serial{};
 
-  uint32_t id;
-  uint32_t notified_serial;
+  bool receivedNotify{};
 
-  bool receivedNotify;
+  uint32_t serial{};
 
-  uint32_t serial;
-  enum DomainKind : uint8_t { Master, Slave, Native } kind;
-  
   bool operator<(const DomainInfo& rhs) const
   {
     return zone < rhs.zone;
   }
 
-  const char *getKindString() const
+  // Do not reorder (lmdbbackend)!!! One exception 'All' is always last.
+  enum DomainKind : uint8_t
+  {
+    Primary,
+    Secondary,
+    Native,
+    Producer,
+    Consumer,
+    All
+  } kind{DomainInfo::Native};
+
+  [[nodiscard]] const char* getKindString() const
   {
     return DomainInfo::getKindString(kind);
   }
 
-  static const char *getKindString(enum DomainKind kind)
+  static const chargetKindString(enum DomainKind kind)
   {
-    const char *kinds[]={"Master", "Slave", "Native"};
-    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;
-    else if (pdns_iequals(kind, "PRIMARY") || pdns_iequals(kind, "MASTER"))
-      return DomainInfo::Master;
-    else
-      return DomainInfo::Native;
+    if (pdns_iequals(kind, "SECONDARY") || pdns_iequals(kind, "SLAVE")) {
+      return DomainInfo::Secondary;
+    }
+    if (pdns_iequals(kind, "PRIMARY") || pdns_iequals(kind, "MASTER")) {
+      return DomainInfo::Primary;
+    }
+    if (pdns_iequals(kind, "PRODUCER")) {
+      return DomainInfo::Producer;
+    }
+    if (pdns_iequals(kind, "CONSUMER")) {
+      return DomainInfo::Consumer;
+    }
+    // No "ALL" here please. Yes, I really mean it...
+    return DomainInfo::Native;
   }
 
-  bool isMaster(const ComboAddress& ip) const
+  [[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 isPrimary(const ComboAddress& ipAddress) const
   {
-    for( const auto& master: masters) {
-      if(ComboAddress::addressOnlyEqual()(ip, master))
-        return true;
-    }
-    return false;
+    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) {
-      this->ip = new_ip;
-      this->nameserver = new_nameserver;
-      this->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;
@@ -133,43 +157,44 @@ 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)
+  virtual bool replaceRRSet(uint32_t /* domain_id */, const DNSName& /* qname */, const QType& /* qt */, const vector<DNSResourceRecord>& /* rrset */)
   {
     return false;
   }
 
-  virtual bool listSubZone(const DNSName &zone, int domain_id)
+  virtual bool listSubZone(const DNSName& /* zone */, int /* domain_id */)
   {
     return false;
   }
 
   // the DNSSEC related (getDomainMetadata has broader uses too)
-  bool isDnssecDomainMetadata (const string& name) {
+  static bool isDnssecDomainMetadata(const string& name)
+  {
     return (name == "PRESIGNED" || name == "NSEC3PARAM" || name == "NSEC3NARROW");
   }
-  virtual bool getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string> >& meta) { return false; };
-  virtual bool getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta) { return false; }
+  virtual bool getAllDomainMetadata(const DNSName& /* name */, std::map<std::string, std::vector<std::string>>& /* meta */) { return false; };
+  virtual bool getDomainMetadata(const DNSName& /* name */, const std::string& /* kind */, std::vector<std::string>& /* meta */) { return false; }
   virtual bool getDomainMetadataOne(const DNSName& name, const std::string& kind, std::string& value)
   {
     std::vector<std::string> meta;
     if (getDomainMetadata(name, kind, meta)) {
-      if(!meta.empty()) {
+      if (!meta.empty()) {
         value = *meta.begin();
         return true;
       }
@@ -177,7 +202,7 @@ public:
     return false;
   }
 
-  virtual bool setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta) {return false;}
+  virtual bool setDomainMetadata(const DNSName& /* name */, const std::string& /* kind */, const std::vector<std::string>& /* meta */) { return false; }
   virtual bool setDomainMetadataOne(const DNSName& name, const std::string& kind, const std::string& value)
   {
     const std::vector<std::string> meta(1, value);
@@ -187,44 +212,45 @@ public:
   virtual void getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool include_disabled);
 
   /** Determines if we are authoritative for a zone, and at what level */
-  virtual bool getAuth(const DNSName &target, SOAData *sd);
+  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;}
-  virtual bool removeDomainKey(const DNSName& name, unsigned int id) { return false; }
-  virtual bool addDomainKey(const DNSName& name, const KeyData& key, int64_t& id){ return false; }
-  virtual bool activateDomainKey(const DNSName& name, unsigned int id) { return false; }
-  virtual bool deactivateDomainKey(const DNSName& name, unsigned int id) { return false; }
-  virtual bool publishDomainKey(const DNSName& name, unsigned int id) { return false; }
-  virtual bool unpublishDomainKey(const DNSName& name, unsigned int id) { return false; }
+  virtual bool getDomainKeys(const DNSName& /* name */, std::vector<KeyData>& /* keys */) { return false; }
+  virtual bool removeDomainKey(const DNSName& /* name */, unsigned int /* id */) { return false; }
+  virtual bool addDomainKey(const DNSName& /* name */, const KeyData& /* key */, int64_t& /* id */) { return false; }
+  virtual bool activateDomainKey(const DNSName& /* name */, unsigned int /* id */) { return false; }
+  virtual bool deactivateDomainKey(const DNSName& /* name */, unsigned int /* id */) { return false; }
+  virtual bool publishDomainKey(const DNSName& /* name */, unsigned int /* id */) { return false; }
+  virtual bool unpublishDomainKey(const DNSName& /* name */, unsigned int /* id */) { return false; }
 
-  virtual bool getTSIGKey(const DNSName& name, DNSName* algorithm, string* content) { return false; }
-  virtual bool setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content) { return false; }
-  virtual bool deleteTSIGKey(const DNSName& name) { return false; }
-  virtual bool getTSIGKeys(std::vector< struct TSIGKey > &keys) { return false; }
+  virtual bool setTSIGKey(const DNSName& /* name */, const DNSName& /* algorithm */, const string& /* content */) { return false; }
+  virtual bool getTSIGKey(const DNSName& /* name */, DNSName& /* algorithm */, string& /* content */) { return false; }
+  virtual bool getTSIGKeys(std::vector<struct TSIGKey>& /* keys */) { return false; }
+  virtual bool deleteTSIGKey(const DNSName& /* name */) { return false; }
 
-  virtual bool getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
+  virtual bool getBeforeAndAfterNamesAbsolute(uint32_t /* id */, const DNSName& /* qname */, DNSName& /* unhashed */, DNSName& /* before */, DNSName& /* after */)
   {
-    std::cerr<<"Default beforeAndAfterAbsolute called!"<<std::endl;
+    std::cerr << "Default beforeAndAfterAbsolute called!" << std::endl;
     abort();
     return false;
   }
 
-  virtual bool getBeforeAndAfterNames(uint32_t id, const DNSName& zonename, const DNSName& qname, DNSName& before, DNSName& after);
+  virtual bool getBeforeAndAfterNames(uint32_t /* id */, const DNSName& zonename, const DNSName& qname, DNSName& before, DNSName& after);
 
-  virtual bool updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName& qname, const DNSName& ordername, bool auth, const uint16_t qtype=QType::ANY)
+  virtual bool updateDNSSECOrderNameAndAuth(uint32_t /* domain_id */, const DNSName& /* qname */, const DNSName& /* ordername */, bool /* auth */, const uint16_t /* qtype */ = QType::ANY)
   {
     return false;
   }
 
-  virtual bool updateEmptyNonTerminals(uint32_t domain_id, set<DNSName>& insert, set<DNSName>& erase, bool remove)
+  virtual bool updateEmptyNonTerminals(uint32_t /* domain_id */, set<DNSName>& /* insert */, set<DNSName>& /* erase */, bool /* remove */)
   {
     return false;
   }
@@ -237,28 +263,29 @@ public:
   // end DNSSEC
 
   // comments support
-  virtual bool listComments(uint32_t domain_id)
+  virtual bool listComments(uint32_t /* domain_id */)
   {
     return false; // unsupported by this backend
   }
 
-  virtual bool getComment(Comment& comment)
+  virtual bool getComment(Comment& /* comment */)
   {
     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)
+  virtual bool replaceComments(const uint32_t /* domain_id */, const DNSName& /* qname */, const QType& /* qt */, const vector<Comment>& /* comments */)
   {
     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)
+  virtual bool startTransaction(const DNSName& /* qname */, int /* id */ = -1)
   {
     return false;
   }
@@ -284,144 +311,166 @@ public:
   {
   }
 
-  virtual void rediscover(string* status=0)
+  virtual void rediscover(string* /* status */ = nullptr)
   {
   }
 
   //! feeds a record to a zone, needs a call to startTransaction first
-  virtual bool feedRecord(const DNSResourceRecord &rr, const DNSName &ordername, bool ordernameIsNSEC3=false)
+  virtual bool feedRecord(const DNSResourceRecord& /* rr */, const DNSName& /* ordername */, bool /* ordernameIsNSEC3 */ = false)
   {
     return false; // no problem!
   }
-  virtual bool feedEnts(int domain_id, map<DNSName,bool> &nonterm)
+  virtual bool feedEnts(int /* domain_id */, map<DNSName, bool>& /* nonterm */)
   {
     return false;
   }
-  virtual bool feedEnts3(int domain_id, const DNSName &domain, map<DNSName,bool> &nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow)
+  virtual bool feedEnts3(int /* domain_id */, const DNSName& /* domain */, map<DNSName, bool>& /* nonterm */, const NSEC3PARAMRecordContent& /* ns3prc */, bool /* narrow */)
   {
     return false;
   }
 
   //! if this returns true, DomainInfo di contains information about the domain
-  virtual bool getDomainInfo(const DNSName &domain, DomainInfo &di, bool getSerial=true)
+  virtual bool getDomainInfo(const DNSName& /* domain */, DomainInfo& /* di */, bool /* getSerial */ = true)
   {
     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 */)
   {
   }
 
   //! get a list of IP addresses that should also be notified for a domain
-  virtual void alsoNotifies(const DNSName &domain, set<string> *ips)
+  virtual void alsoNotifies(const DNSName& domain, set<string>* ips)
   {
+    std::vector<std::string> meta;
+    getDomainMetadata(domain, "ALSO-NOTIFY", meta);
+    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)
+  //! 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 */)
   {
   }
 
+  //! get list of all members in a catalog
+  virtual bool getCatalogMembers(const DNSName& /* catalog */, vector<CatalogInfo>& /* members */, CatalogInfo::CatalogType /* type */)
+  {
+    return false;
+  }
+
   //! Called by PowerDNS to inform a backend that a domain need to be checked for freshness
-  virtual void setStale(uint32_t domain_id)
+  virtual void setStale(uint32_t /* domain_id */)
   {
   }
 
   //! Called by PowerDNS to inform a backend that a domain has been checked for freshness
-  virtual void setFresh(uint32_t domain_id)
+  virtual void setFresh(uint32_t /* domain_id */)
   {
   }
 
-  //! Called by PowerDNS to inform a backend that the changes in the domain have been reported to slaves
-  virtual void setNotified(uint32_t id, uint32_t serial)
+  //! 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)
-  virtual bool setKind(const DNSName &domain, const DomainInfo::DomainKind kind)
+  //! 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;
+  }
+
+  //! Called when the options of a domain should be changed
+  virtual bool setOptions(const DNSName& /* domain */, const string& /* options */)
+  {
+    return false;
+  }
+
+  //! Called when the catalog of a domain should be changed
+  virtual bool setCatalog(const DNSName& /* domain */, const DNSName& /* catalog */)
   {
     return false;
   }
 
   //! Called when the Account of a domain should be changed
-  virtual bool setAccount(const DNSName &domain, const string &account)
+  virtual bool setAccount(const DNSName& /* domain */, const string& /* account */)
   {
     return false;
   }
 
   //! 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; 
+    return false;
   }
 
-  //! Remove an entry for a super master
-  virtual bool autoPrimaryRemove(const struct AutoPrimary& primary)
+  //! 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.
-  virtual bool autoPrimariesList(std::vector<AutoPrimary>& primaries)
+  //! 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;
   }
 
   //! called to delete a domain, incl. all metadata, zone contents, etc.
-  virtual bool deleteDomain(const DNSName &domain)
+  virtual bool deleteDomain(const DNSName& /* domain */)
   {
     return false;
   }
 
-  virtual string directBackendCmd(const string &query)
+  virtual string directBackendCmd(const string& /* query */)
   {
     return "directBackendCmd not supported for this backend\n";
   }
 
   //! 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;
@@ -430,53 +479,75 @@ 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);
   }
-  virtual void declareArguments(const string &suffix=""){}
-  const string &getName() const;
-  
+  virtual void declareArguments(const string& /* suffix */ = "") {}
+  [[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);
-  size_t numLauncheable() const;
+  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();
-  typedef map<string,BackendFactory *>d_repository_t;
+  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){};
+
+  DNSName qname;
+  DNSName nameserver;
+  DNSName rname;
+  uint32_t ttl{};
+  uint32_t serial{};
+  uint32_t refresh{};
+  uint32_t retry{};
+  uint32_t expire{};
+  uint32_t minimum{};
+  DNSBackend* db{};
+  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 5441c182bd3efb70e41abff38aa8eeaa29dadf24..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,8 +62,8 @@ bool g_envoutput=false;
 struct DNSResult
 {
   vector<ComboAddress> ips;
-  int rcode;
-  bool seenauthsoa;
+  int rcode{0};
+  bool seenauthsoa{false};
 };
 
 struct TypedQuery
@@ -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.d_content->getZoneRepresentation()));
-        if(i->first.d_place == 2 && i->first.d_type == QType::SOA) {
-          dr.seenauthsoa = 1;
+      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.d_content->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<<" msec)\n";
-    if(!g_quiet) {
-      cout<<domain.name<<"|"<<DNSRecordContent::NumberToType(domain.type)<<": ("<<usec/1000.0<<"msec) 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) << " msec"<<", median: "<<median(*sr.d_acc)<< " msec\n";
-  
-  boost::format statfmt("Time < %6.03f msec %|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]);
-    }
+  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");
 
-  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 3a627c890250717ed3b5114a2740dc6afcf4c60e..9be46d722fab336ac2a6297cb613ee34fc7a98c8 100644 (file)
@@ -215,7 +215,9 @@ void DNSCryptContext::generateCertificate(uint32_t serial, time_t begin, time_t
   memcpy(cert.signedData.resolverPK, pubK, sizeof(cert.signedData.resolverPK));
   memcpy(cert.signedData.clientMagic, pubK, sizeof(cert.signedData.clientMagic));
   cert.signedData.serial = htonl(serial);
+  // coverity[store_truncates_time_t]
   cert.signedData.tsStart = htonl((uint32_t) begin);
+  // coverity[store_truncates_time_t]
   cert.signedData.tsEnd = htonl((uint32_t) end);
 
   unsigned long long signatureSize = 0;
@@ -287,7 +289,7 @@ void DNSCryptContext::addNewCertificate(std::shared_ptr<DNSCryptCertificatePair>
 {
   auto certs = d_certs.write_lock();
 
-  for (auto pair : *certs) {
+  for (const auto& pair : *certs) {
     if (pair->cert.getSerial() == newCert->cert.getSerial()) {
       if (reload) {
         /* on reload we just assume that this is the same certificate */
@@ -354,7 +356,7 @@ std::vector<std::shared_ptr<DNSCryptCertificatePair>> DNSCryptContext::getCertif
 
 void DNSCryptContext::markActive(uint32_t serial)
 {
-  for (auto pair : *d_certs.write_lock()) {
+  for (const auto& pair : *d_certs.write_lock()) {
     if (pair->active == false && pair->cert.getSerial() == serial) {
       pair->active = true;
       return;
@@ -365,7 +367,7 @@ void DNSCryptContext::markActive(uint32_t serial)
 
 void DNSCryptContext::markInactive(uint32_t serial)
 {
-  for (auto pair : *d_certs.write_lock()) {
+  for (const auto& pair : *d_certs.write_lock()) {
     if (pair->active == true && pair->cert.getSerial() == serial) {
       pair->active = false;
       return;
@@ -397,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;
@@ -416,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 ff4d94c4663965861c140ac4218e7253dbd191b3..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>
@@ -119,6 +119,7 @@ public:
   }
   bool isValid(time_t now) const
   {
+    // coverity[store_truncates_time_t]
     return ntohl(getTSStart()) <= static_cast<uint32_t>(now) && static_cast<uint32_t>(now) <= ntohl(getTSEnd());
   }
   unsigned char magic[DNSCRYPT_CERT_MAGIC_SIZE];
index cdf02ad7626900824d37fc151261118a1863c8df..c3b0e75ef68dfb75bdda970de0c4a892232cc807 100644 (file)
 
 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
@@ -101,7 +105,7 @@ void DNSDistPacketCache::insertLocked(CacheShard& shard, std::unordered_map<uint
   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++;
+    ++d_insertCollisions;
     return;
   }
 
@@ -115,7 +119,11 @@ void DNSDistPacketCache::insertLocked(CacheShard& shard, std::unordered_map<uint
 
 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)) {
+  if (response.size() < sizeof(dnsheader) || response.size() > getMaximumEntrySize()) {
+    return;
+  }
+
+  if (qtype == QType::AXFR || qtype == QType::IXFR) {
     return;
   }
 
@@ -144,7 +152,7 @@ void DNSDistPacketCache::insert(uint32_t key, const boost::optional<Netmask>& su
     }
 
     if (minTTL < d_minTTL) {
-      d_ttlTooShorts++;
+      ++d_ttlTooShorts;
       return;
     }
   }
@@ -176,7 +184,7 @@ void DNSDistPacketCache::insert(uint32_t key, const boost::optional<Netmask>& su
     auto w = shard.d_map.try_write_lock();
 
     if (!w.owns_lock()) {
-      d_deferredInserts++;
+      ++d_deferredInserts;
       return;
     }
     insertLocked(shard, *w, key, newValue);
@@ -188,17 +196,22 @@ void DNSDistPacketCache::insert(uint32_t key, const boost::optional<Netmask>& su
   }
 }
 
-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 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)
 {
-  const auto& dnsQName = dq.qname->getStorage();
-  uint32_t key = getKey(dnsQName, dq.qname->wirelength(), dq.getData(), receivedOverUDP);
+  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.qname->wirelength(), subnet);
+    getClientSubnet(dq.getData(), dq.ids.qname.wirelength(), subnet);
   }
 
   uint32_t shardIndex = getShardIndex(key);
@@ -210,20 +223,24 @@ bool DNSDistPacketCache::get(DNSQuestion& dq, uint16_t queryId, uint32_t* keyOut
   {
     auto map = shard.d_map.try_read_lock();
     if (!map.owns_lock()) {
-      d_deferredLookups++;
+      ++d_deferredLookups;
       return false;
     }
 
     std::unordered_map<uint32_t,CacheValue>::const_iterator it = map->find(key);
     if (it == map->end()) {
-      d_misses++;
+      if (recordMiss) {
+        ++d_misses;
+      }
       return false;
     }
 
     const CacheValue& value = it->second;
     if (value.validity <= now) {
       if ((now - value.validity) >= static_cast<time_t>(allowExpired)) {
-        d_misses++;
+        if (recordMiss) {
+          ++d_misses;
+        }
         return false;
       }
       else {
@@ -236,18 +253,26 @@ bool DNSDistPacketCache::get(DNSQuestion& dq, uint16_t queryId, uint32_t* keyOut
     }
 
     /* check for collision */
-    if (!cachedValueMatches(value, *(getFlagsFromDNSHeader(dq.getHeader())), *dq.qname, dq.qtype, dq.qclass, receivedOverUDP, dnssecOK, subnet)) {
-      d_lookupCollisions++;
+    if (!cachedValueMatches(value, *(getFlagsFromDNSHeader(dq.getHeader().get())), 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++;
+      ++d_hits;
       return true;
     }
 
@@ -271,15 +296,17 @@ bool DNSDistPacketCache::get(DNSQuestion& dq, uint16_t queryId, uint32_t* keyOut
 
   if (!d_dontAge && !skipAging) {
     if (!stale) {
-      ageDNSPacket(reinterpret_cast<char *>(&response[0]), response.size(), age);
+      // 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; });
+      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++;
+  ++d_hits;
   return true;
 }
 
@@ -294,6 +321,7 @@ size_t DNSDistPacketCache::purgeExpired(size_t upTo, const time_t now)
 
   size_t removed = 0;
 
+  ++d_cleanupCount;
   for (auto& shard : d_shards) {
     auto map = shard.d_map.write_lock();
     if (map->size() <= maxPerShard) {
@@ -418,7 +446,7 @@ uint32_t DNSDistPacketCache::getKey(const DNSName::string_t& qname, size_t qname
   if (packet.size() > ((sizeof(dnsheader) + qnameWireLength))) {
     if (!d_optionsToSkip.empty()) {
       /* skip EDNS options if any */
-      result = PacketCache::hashAfterQname(pdns_string_view(reinterpret_cast<const char*>(packet.data()), packet.size()), result, sizeof(dnsheader) + qnameWireLength, d_optionsToSkip);
+      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);
@@ -484,3 +512,117 @@ void DNSDistPacketCache::setSkippedOptions(const std::unordered_set<uint16_t>& o
 {
   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;
+}
+
+void DNSDistPacketCache::setMaximumEntrySize(size_t maxSize)
+{
+  d_maximumEntrySize = maxSize;
+}
index 3309459ecb9b16b3dcea5817b5ab0bf53ef916bd..95667bd4cd56c766c503e8543d8cc816feb996eb 100644 (file)
@@ -38,23 +38,30 @@ 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 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 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; }
+  uint64_t getTTLTooShorts() const { return d_ttlTooShorts.load(); }
+  uint64_t getCleanupCount() const { return d_cleanupCount.load(); }
   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; }
@@ -68,12 +75,14 @@ public:
     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);
@@ -103,7 +112,7 @@ private:
     CacheShard()
     {
     }
-    CacheShard(const CacheShard& old)
+    CacheShard(const CacheShard& /* old */)
     {
     }
 
@@ -130,16 +139,18 @@ private:
   pdns::stat_t d_insertCollisions{0};
   pdns::stat_t d_lookupCollisions{0};
   pdns::stat_t d_ttlTooShorts{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;
+  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};
 };
index cdad9dd69a74b69abbb624b1d3d722fa4c040ad4..1b1c0fe5525ba375eca8805a5bae4780b87c942d 100644 (file)
 
 #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"
 
-GlobalStateHolder<vector<CarbonConfig> > g_carbon;
+namespace dnsdist
+{
+
+LockGuarded<Carbon::Config> Carbon::s_config;
 
-void carbonDumpThread()
+static bool doOneCarbonExport(const Carbon::Endpoint& endpoint)
 {
-  try
-  {
-    setThreadName("dnsdist/carbon");
-    auto localCarbon = g_carbon.getLocal();
-    for(int numloops=0;;++numloops) {
-      if(localCarbon->empty()) {
-        sleep(1);
-        continue;
-      }
-      /* this is wrong, we use the interval of the first server
-         for every single one of them */
-      if(numloops) {
-        const unsigned int interval = localCarbon->at(0).interval;
-        sleep(interval);
-      }
+  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& conf : *localCarbon) {
-        const auto& server = conf.server;
-        const std::string& namespace_name = conf.namespace_name;
-        std::string hostname = conf.ourname;
-        if (hostname.empty()) {
-          try {
-            hostname = getCarbonHostName();
-          }
-          catch(const std::exception& e) {
-            throw std::runtime_error(std::string("The 'ourname' setting in 'carbonServer()' has not been set and we are unable to determine the system's hostname: ") + e.what());
-          }
+    {
+      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();
         }
-        const std::string& instance_name = conf.instance_name;
+        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";
+      }
+    }
 
-        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;
-          time_t now=time(0);
-          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& dval = boost::get<double*>(&e.second))
-              str<<**dval;
-            else
-              str<<(*boost::get<DNSDistStats::statfunction_t>(&e.second))(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<<"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";
-          }
+    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";
+      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;
+    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);
-            }
+      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";
-            }
-          }
+      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 << "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";
 
-          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";
-            }
-          }
+      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_";
+      }
+      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, "]", "_");
+    {
+      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 = name + "_" + std::to_string(dupPair.first->second);
-                ++(dupPair.first->second);
-              }
+        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}
-              };
+        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";
-              }
-            }
-          }
+        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();
-          }
+    {
+      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;
+}
 
-          const string msg = str.str();
+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);
 
-          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"));
-            continue;
-          }
-          s.setBlocking();
-          writen2(s.getHandle(), msg.c_str(), msg.size());
+  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);
         }
-        catch(const std::exception& e) {
-          warnlog("Problem sending carbon data: %s", e.what());
+        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);
+        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());
     }
   }
-  catch(const std::exception& e)
-  {
-    errlog("Carbon thread died: %s", 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();
   }
-  catch(const PDNSException& e)
-  {
-    errlog("Carbon thread died, PDNSException: %s", e.reason);
+  else {
+    config->d_endpoints.push_back(std::move(endpoint));
   }
-  catch(...)
-  {
-    errlog("Carbon thread died");
+  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 */
 
index 5d66c21bcfa1290e0ebb9e846fb5b2295708f8ca..f40abb3446dc43f83a529db2bfe120251474afb8 100644 (file)
@@ -43,7 +43,7 @@
 #include "dolog.hh"
 #include "dnsdist.hh"
 #include "dnsdist-console.hh"
-#include "sodcrypto.hh"
+#include "dnsdist-crypto.hh"
 #include "threadname.hh"
 
 GlobalStateHolder<NetmaskGroup> g_consoleACL;
@@ -58,42 +58,40 @@ static ConcurrentConnectionManager s_connManager(100);
 class ConsoleConnection
 {
 public:
-  ConsoleConnection(const ComboAddress& client, int fd): d_client(client), d_fd(fd)
+  ConsoleConnection(const ComboAddress& client, FDWrapper&& fileDesc): d_client(client), d_fileDesc(std::move(fileDesc))
   {
     if (!s_connManager.registerConnection()) {
-      close(fd);
       throw std::runtime_error("Too many concurrent console connections");
     }
   }
-  ConsoleConnection(ConsoleConnection&& rhs): d_client(rhs.d_client), d_fd(rhs.d_fd)
+  ConsoleConnection(ConsoleConnection&& rhs) noexcept: d_client(rhs.d_client), d_fileDesc(std::move(rhs.d_fileDesc))
   {
-    rhs.d_fd = -1;
   }
 
   ConsoleConnection(const ConsoleConnection&) = delete;
   ConsoleConnection& operator=(const ConsoleConnection&) = delete;
+  ConsoleConnection& operator=(ConsoleConnection&&) = delete;
 
   ~ConsoleConnection()
   {
-    if (d_fd != -1) {
-      close(d_fd);
+    if (d_fileDesc.getHandle() != -1) {
       s_connManager.releaseConnection();
     }
   }
 
-  int getFD() const
+  [[nodiscard]] int getFD() const
   {
-    return d_fd;
+    return d_fileDesc.getHandle();
   }
 
-  const ComboAddress& getClient() const
+  [[nodiscard]] const ComboAddress& getClient() const
   {
     return d_client;
   }
 
 private:
   ComboAddress d_client;
-  int d_fd{-1};
+  FDWrapper d_fileDesc;
 };
 
 void setConsoleMaximumConcurrentConnections(size_t max)
@@ -104,10 +102,11 @@ void setConsoleMaximumConcurrentConnections(size_t max)
 // MUST BE CALLED UNDER A LOCK - right now the LuaLock
 static void feedConfigDelta(const std::string& line)
 {
-  if(line.empty())
+  if (line.empty()) {
     return;
-  struct timeval now;
-  gettimeofday(&now, 0);
+  }
+  timeval now{};
+  gettimeofday(&now, nullptr);
   g_confDelta.emplace_back(now, line);
 }
 
@@ -116,52 +115,61 @@ 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);
+  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)
+  if (result != nullptr) {
     ret = string(pwd.pw_dir);
-  if (homedir && !ignoreHOME) // $HOME overrides what the OS tells us
+  }
+  if (homedir != nullptr && !ignoreHOME) { // $HOME overrides what the OS tells us
     ret = string(homedir);
-  if (ret.empty())
+  }
+  if (ret.empty()) {
     ret = "."; // CWD if nothing works..
+  }
   ret.append("/.dnsdist_history");
   return ret;
 }
 #endif /* HAVE_LIBEDIT */
 
-static bool getMsgLen32(int fd, uint32_t* len)
+enum class ConsoleCommandResult : uint8_t {
+  Valid = 0,
+  ConnectionClosed,
+  TooLarge
+};
+
+static ConsoleCommandResult getMsgLen32(int fileDesc, uint32_t* len)
 {
-  try
-  {
-    uint32_t raw;
-    size_t ret = readn2(fd, &raw, sizeof raw);
+  try {
+    uint32_t raw{0};
+    size_t ret = readn2(fileDesc, &raw, sizeof(raw));
 
     if (ret != sizeof raw) {
-      return false;
+      return ConsoleCommandResult::ConnectionClosed;
     }
 
     *len = ntohl(raw);
     if (*len > g_consoleOutputMsgMaxSize) {
-      return false;
+      return ConsoleCommandResult::TooLarge;
     }
 
-    return true;
+    return ConsoleCommandResult::Valid;
   }
-  catch(...) {
-    return false;
+  catch (...) {
+    return ConsoleCommandResult::ConnectionClosed;
   }
 }
 
-static bool putMsgLen32(int fd, uint32_t len)
+static bool putMsgLen32(int fileDesc, uint32_t len)
 {
   try
   {
     uint32_t raw = htonl(len);
-    size_t ret = writen2(fd, &raw, sizeof raw);
+    size_t ret = writen2(fileDesc, &raw, sizeof raw);
     return ret == sizeof raw;
   }
   catch(...) {
@@ -169,25 +177,30 @@ static bool putMsgLen32(int fd, uint32_t len)
   }
 }
 
-static bool sendMessageToServer(int fd, const std::string& line, SodiumNonce& readingNonce, SodiumNonce& writingNonce, const bool outputEmptyLine)
+static ConsoleCommandResult sendMessageToServer(int fileDesc, const std::string& line, dnsdist::crypto::authenticated::Nonce& readingNonce, dnsdist::crypto::authenticated::Nonce& writingNonce, const bool outputEmptyLine)
 {
-  string msg = sodEncryptSym(line, g_consoleKey, writingNonce);
+  string msg = dnsdist::crypto::authenticated::encryptSym(line, g_consoleKey, writingNonce);
   const auto msgLen = msg.length();
   if (msgLen > std::numeric_limits<uint32_t>::max()) {
-    cout << "Encrypted message is too long to be sent to the server, "<< std::to_string(msgLen) << " > " << std::numeric_limits<uint32_t>::max() << endl;
-    return true;
+    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));
+  putMsgLen32(fileDesc, static_cast<uint32_t>(msgLen));
 
   if (!msg.empty()) {
-    writen2(fd, msg);
+    writen2(fileDesc, msg);
   }
 
-  uint32_t len;
-  if(!getMsgLen32(fd, &len)) {
+  uint32_t len{0};
+  auto commandResult = getMsgLen32(fileDesc, &len);
+  if (commandResult == ConsoleCommandResult::ConnectionClosed) {
     cout << "Connection closed by the server." << endl;
-    return false;
+    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) {
@@ -195,22 +208,22 @@ static bool sendMessageToServer(int fd, const std::string& line, SodiumNonce& re
       cout << endl;
     }
 
-    return true;
+    return ConsoleCommandResult::Valid;
   }
 
   msg.clear();
   msg.resize(len);
-  readn2(fd, msg.data(), len);
-  msg = sodDecryptSym(msg, g_consoleKey, readingNonce);
+  readn2(fileDesc, msg.data(), len);
+  msg = dnsdist::crypto::authenticated::decryptSym(msg, g_consoleKey, readingNonce);
   cout << msg;
   cout.flush();
 
-  return true;
+  return ConsoleCommandResult::Valid;
 }
 
 void doClient(ComboAddress server, const std::string& command)
 {
-  if (!sodIsValidKey(g_consoleKey)) {
+  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;
   }
@@ -219,35 +232,39 @@ void doClient(ComboAddress server, const std::string& command)
     cout<<"Connecting to "<<server.toStringWithPort()<<endl;
   }
 
-  int fd=socket(server.sin4.sin_family, SOCK_STREAM, 0);
-  if (fd < 0) {
+  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(fd, server);
-  setTCPNoDelay(fd);
-  SodiumNonce theirs, ours, readingNonce, writingNonce;
+  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(fd, (const char*)ours.value, sizeof(ours.value));
-  readn2(fd, (char*)theirs.value, sizeof(theirs.value));
+  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. */
-  if (!sendMessageToServer(fd, "", readingNonce, writingNonce, false)) {
+  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;
-    close(fd);
+    return;
+  }
+  if (commandResult == ConsoleCommandResult::TooLarge) {
     return;
   }
 
   if (!command.empty()) {
-    sendMessageToServer(fd, command, readingNonce, writingNonce, false);
-
-    close(fd);
-    return; 
+    sendMessageToServer(fileDesc.getHandle(), command, readingNonce, writingNonce, false);
+    return;
   }
 
 #ifdef HAVE_LIBEDIT
@@ -261,38 +278,43 @@ void doClient(ComboAddress server, const std::string& command)
   }
   ofstream history(histfile, std::ios_base::app);
   string lastline;
-  for(;;) {
+  for (;;) {
     char* sline = readline("> ");
     rl_bind_key('\t',rl_complete);
-    if(!sline)
+    if (sline == nullptr) {
       break;
+    }
 
     string line(sline);
-    if(!line.empty() && line != lastline) {
+    if (!line.empty() && line != lastline) {
       add_history(sline);
       history << sline <<endl;
       history.flush();
     }
-    lastline=line;
+    lastline = line;
+    // NOLINTNEXTLINE(cppcoreguidelines-no-malloc,cppcoreguidelines-owning-memory): readline
     free(sline);
-    
-    if(line=="quit")
+
+    if (line == "quit") {
       break;
-    if(line=="help" || line=="?")
-      line="help()";
+    }
+    if (line == "help" || line == "?") {
+      line = "help()";
+    }
 
     /* no need to send an empty line to the server */
-    if(line.empty())
+    if (line.empty()) {
       continue;
+    }
 
-    if (!sendMessageToServer(fd, line, readingNonce, writingNonce, true)) {
+    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 */
-  close(fd);
 }
 
 #ifdef HAVE_LIBEDIT
@@ -300,7 +322,7 @@ static std::optional<std::string> getNextConsoleLine(ofstream& history, std::str
 {
   char* sline = readline("> ");
   rl_bind_key('\t', rl_complete);
-  if (!sline) {
+  if (sline == nullptr) {
     return std::nullopt;
   }
 
@@ -312,6 +334,7 @@ static std::optional<std::string> getNextConsoleLine(ofstream& history, std::str
   }
 
   lastline = line;
+  // NOLINTNEXTLINE(cppcoreguidelines-no-malloc,cppcoreguidelines-owning-memory): readline
   free(sline);
 
   return line;
@@ -370,7 +393,7 @@ void doConsole()
         auto ret = lua->executeCode<
           boost::optional<
             boost::variant<
-              string, 
+              string,
               shared_ptr<DownstreamState>,
               ClientState*,
               std::unordered_map<string, double>
@@ -378,25 +401,26 @@ void doConsole()
             >
           >(withReturn ? ("return "+*line) : *line);
         if (ret) {
-          if (const auto dsValue = boost::get<shared_ptr<DownstreamState>>(&*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) {
+          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)) {
+          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)) {
+          else if (const auto* mapValue = 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;
+            Json::object obj;
+            for (const auto& value : *mapValue) {
+              obj[value.first] = value.second;
+            }
+            Json out = obj;
             cout<<out.dump()<<endl;
           }
         }
@@ -410,7 +434,8 @@ void doConsole()
       }
       catch (const LuaContext::SyntaxErrorException&) {
         if (withReturn) {
-          withReturn=false;
+          withReturn = false;
+          // NOLINTNEXTLINE(cppcoreguidelines-avoid-goto)
           goto retry;
         }
         throw;
@@ -421,7 +446,7 @@ void doConsole()
       // tried to return something we don't understand
     }
     catch (const LuaContext::ExecutionErrorException& e) {
-      if (!strcmp(e.what(), "invalid key to 'next'")) {
+      if (strcmp(e.what(), "invalid key to 'next'") == 0) {
         std::cerr<<"Error parsing parameters, did you forget parameter name?";
       }
       else {
@@ -442,7 +467,7 @@ void doConsole()
       }
     }
     catch (const std::exception& e) {
-      std::cerr << e.what() << std::endl;      
+      std::cerr << e.what() << std::endl;
     }
   }
 }
@@ -452,18 +477,23 @@ void doConsole()
 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" },
+  { "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, "\"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" },
+  { "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, "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" },
-  { "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" },
+  { "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" },
@@ -477,6 +507,8 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "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" },
@@ -499,34 +531,52 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "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" },
+  { "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" },
+  { "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" },
-  { "grepq", true, "Netmask|DNS Name|100ms|{\"::1\", \"powerdns.com\", \"100ms\"} [, n]", "shows the last n queries and responses matching the specified client address or range (Netmask), or the specified DNS Name, or slower than 100ms" },
+  { "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'"},
@@ -534,6 +584,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "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" },
@@ -543,7 +594,12 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "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" },
@@ -559,10 +615,12 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
 #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]]]]]", "matches traffic exceeding the qps limit per subnet" },
+  { "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" },
@@ -570,7 +628,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "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, "maxV4, maxV6, maxQNames", "Return a new eBPF socket filter with a maximum of maxV4 IPv4, maxV6 IPv6 and maxQNames qname entries in the block table" },
+  { "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" },
@@ -587,8 +645,8 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "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" },
+  { "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" },
@@ -597,10 +655,10 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "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" },
+  { "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, "\"/path/to/providerPublic.key\"", "display the fingerprint of the provided resolver public key" },
+  { "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" },
@@ -609,7 +667,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "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" },
+  { "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" },
@@ -624,6 +682,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "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" },
@@ -652,7 +711,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "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" },
+  { "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" },
@@ -661,6 +720,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "setMaxTCPQueriesPerConnection", true, "n", "set the maximum number of queries in an incoming TCP connection. 0 means unlimited" },
   { "setMaxTCPQueuedConnections", true, "n", "set the maximum number of TCP connections queued (waiting to be picked up by a client thread)" },
   { "setMaxUDPOutstanding", true, "n", "set the maximum number of outstanding UDP queries to a given backend server. This can only be set at configuration time and defaults to 65535" },
+  { "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'" },
@@ -671,7 +731,9 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "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()`)" },
@@ -683,8 +745,10 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "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" },
@@ -692,7 +756,9 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "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" },
@@ -703,7 +769,9 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "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" },
@@ -729,6 +797,8 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "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'" },
@@ -748,11 +818,13 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "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]", "send copy of query to remote, optionally adding ECS info" },
+  { "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"}, 
+  { "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" },
@@ -773,16 +845,17 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
 extern "C" {
 static char* my_generator(const char* text, int state)
 {
-  string t(text);
+  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)
-    s_counter=0;
+  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, t) && counter++ == s_counter)  {
+  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) {
@@ -794,14 +867,16 @@ static char* my_generator(const char* text, int state)
       return strdup(value.c_str());
     }
   }
-  return 0;
+  return nullptr;
 }
 
 char** my_completion( const char * text , int start,  int end)
 {
-  char **matches=0;
-  if (start == 0)
-    matches = rl_completion_matches ((char*)text, &my_generator);
+  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;
@@ -814,22 +889,24 @@ char** my_completion( const char * text , int start,  int end)
 
 static void controlClientThread(ConsoleConnection&& conn)
 {
-  try
-  {
+  try {
     setThreadName("dnsdist/conscli");
 
     setTCPNoDelay(conn.getFD());
 
-    SodiumNonce theirs, ours, readingNonce, writingNonce;
+    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(), (char*)theirs.value, sizeof(theirs.value));
-    writen2(conn.getFD(), (char*)ours.value, sizeof(ours.value));
+    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;
-      if (!getMsgLen32(conn.getFD(), &len)) {
+    for (;;) {
+      uint32_t len{0};
+      if (getMsgLen32(conn.getFD(), &len) != ConsoleCommandResult::Valid) {
         break;
       }
 
@@ -844,21 +921,21 @@ static void controlClientThread(ConsoleConnection&& conn)
       line.resize(len);
       readn2(conn.getFD(), line.data(), len);
 
-      line = sodDecryptSym(line, g_consoleKey, readingNonce);
+      line = dnsdist::crypto::authenticated::decryptSym(line, g_consoleKey, readingNonce);
 
       string response;
       try {
-        bool withReturn=true;
+        bool withReturn = true;
       retry:;
         try {
           auto lua = g_lua.lock();
-        
+
           g_outputBuffer.clear();
           resetLuaSideEffect();
           auto ret = lua->executeCode<
             boost::optional<
               boost::variant<
-                string, 
+                string,
                 shared_ptr<DownstreamState>,
                 ClientState*,
                 std::unordered_map<string, double>
@@ -866,41 +943,45 @@ static void controlClientThread(ConsoleConnection&& conn)
               >
             >(withReturn ? ("return "+line) : line);
 
-          if(ret) {
-            if (const auto dsValue = boost::get<shared_ptr<DownstreamState>>(&*ret)) {
+          if (ret) {
+            if (const auto* dsValue = boost::get<shared_ptr<DownstreamState>>(&*ret)) {
               if (*dsValue) {
-                response=(*dsValue)->getName()+"\n";
+                response = (*dsValue)->getName()+"\n";
               } else {
-                response="";
+                response = "";
               }
             }
-            else if (const auto csValue = boost::get<ClientState*>(&*ret)) {
-              if (*csValue) {
-                response=(*csValue)->local.toStringWithPort()+"\n";
+            else if (const auto* csValue = boost::get<ClientState*>(&*ret)) {
+              if (*csValue != nullptr) {
+                response = (*csValue)->local.toStringWithPort()+"\n";
               } else {
-                response="";
+                response = "";
               }
             }
-            else if (const auto strValue = boost::get<string>(&*ret)) {
-              response=*strValue+"\n";
+            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)) {
+            else if (const auto* mapValue = 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";
+              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())
+          else {
+            response = g_outputBuffer;
+          }
+          if (!getLuaNoSideEffect()) {
             feedConfigDelta(line);
+          }
         }
-        catch(const LuaContext::SyntaxErrorException&) {
-          if(withReturn) {
-            withReturn=false;
+        catch (const LuaContext::SyntaxErrorException&) {
+          if (withReturn) {
+            withReturn = false;
+            // NOLINTNEXTLINE(cppcoreguidelines-avoid-goto)
             goto retry;
           }
           throw;
@@ -910,26 +991,29 @@ static void controlClientThread(ConsoleConnection&& conn)
         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'"))
+      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
+        }
+        else {
           response = "Error: " + string(e.what());
+        }
+
         try {
           std::rethrow_if_nested(e);
-        } catch(const std::exception& ne) {
+        } catch (const std::exception& ne) {
           // ne is the exception that was thrown from inside the lambda
           response+= ": " + string(ne.what());
         }
-        catch(const PDNSException& ne) {
+        catch (const PDNSException& ne) {
           // ne is the exception that was thrown from inside the lambda
           response += ": " + string(ne.reason);
         }
       }
-      catch(const LuaContext::SyntaxErrorException& e) {
+      catch (const LuaContext::SyntaxErrorException& e) {
         response = "Error: " + string(e.what()) + ": ";
       }
-      response = sodEncryptSym(response, g_consoleKey, writingNonce);
+      response = dnsdist::crypto::authenticated::encryptSym(response, g_consoleKey, writingNonce);
       putMsgLen32(conn.getFD(), response.length());
       writen2(conn.getFD(), response.c_str(), response.length());
     }
@@ -937,53 +1021,50 @@ static void controlClientThread(ConsoleConnection&& conn)
       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());
+  catch (const std::exception& e) {
+    infolog("Got an exception in client connection from %s: %s", conn.getClient().toStringWithPort(), e.what());
   }
 }
 
-void controlThread(int fd, ComboAddress local)
+// NOLINTNEXTLINE(performance-unnecessary-value-param): this is thread
+void controlThread(std::shared_ptr<Socket> acceptFD, ComboAddress local)
 {
   try
   {
     setThreadName("dnsdist/control");
     ComboAddress client;
-    int sock;
+    int sock{-1};
     auto localACL = g_consoleACL.getLocal();
     infolog("Accepting control connections on %s", local.toStringWithPort());
 
-    while ((sock = SAccept(fd, client)) >= 0) {
+    while ((sock = SAccept(acceptFD->getHandle(), client)) >= 0) {
 
-      if (!sodIsValidKey(g_consoleKey)) {
+      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());
-        close(sock);
         continue;
       }
 
       if (!localACL->match(client)) {
         vinfolog("Control connection from %s dropped because of ACL", client.toStringWithPort());
-        close(sock);
         continue;
       }
 
       try {
-        ConsoleConnection conn(client, sock);
+        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();
+        std::thread clientThread(controlClientThread, std::move(conn));
+        clientThread.detach();
       }
       catch (const std::exception& e) {
-        errlog("Control connection died: %s", e.what());
+        infolog("Control connection died: %s", e.what());
       }
     }
   }
-  catch (const std::exception& e)
-  {
-    close(fd);
+  catch (const std::exception& e) {
     errlog("Control thread died: %s", e.what());
   }
 }
index 1ef046c395f5569a19243f6ad95069c4fb616fab..6e227d47165db16e6af7f77c66f6893c26d5f192 100644 (file)
@@ -22,6 +22,7 @@
 #pragma once
 
 #include "config.h"
+#include "sstuff.hh"
 
 #ifndef DISABLE_COMPLETION
 struct ConsoleKeyword {
@@ -55,7 +56,7 @@ extern uint32_t g_consoleOutputMsgMaxSize;
 
 void doClient(ComboAddress server, const std::string& command);
 void doConsole();
-void controlThread(int fd, ComboAddress local);
+void controlThread(std::shared_ptr<Socket> acceptFD, ComboAddress local);
 void clearConsoleHistory();
 
 void setConsoleMaximumConcurrentConnections(size_t max);
index 99301445c3f7aeec73ede31710433d1af15a0d7e..8f02910aaa8a8ff5f15626ff2a65f939803bf577 100644 (file)
@@ -21,6 +21,7 @@
  */
 #include "dolog.hh"
 #include "dnsdist.hh"
+#include "dnsdist-metrics.hh"
 #include "dnscrypt.hh"
 
 #ifdef HAVE_DNSCRYPT
@@ -40,7 +41,7 @@ int handleDNSCryptQuery(PacketBuffer& packet, DNSCryptQuery& query, bool tcp, ti
   }
 
   if (packet.size() < static_cast<uint16_t>(sizeof(struct dnsheader))) {
-    ++g_stats.nonCompliantQueries;
+    ++dnsdist::metrics::g_stats.nonCompliantQueries;
     return false;
   }
 
diff --git a/pdns/dnsdist-doh-common.hh b/pdns/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 7bd9d97e1d4a03e7fbac13af200931712cdd5484..1a8b3a68e81fc51ed97df06367b92ba750d93c75 100644 (file)
  */
 #pragma once
 
+#ifndef DISABLE_DYNBLOCKS
 #include <unordered_set>
 
 #include "dolog.hh"
 #include "dnsdist-rings.hh"
 #include "statnode.hh"
 
-#include "dnsdist-lua-inspection-ffi.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};
-    }
+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;
+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_, std::optional<std::string>& reason_): node(node_), self(self_), children(children_), reason(reason_)
+  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;
-  std::optional<std::string>& reason;
+  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;
@@ -66,83 +80,27 @@ private:
     uint64_t queries{0};
     uint64_t responses{0};
     uint64_t respBytes{0};
+    uint64_t cacheMisses{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
+    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)
     {
-      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 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
-    {
-      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 toString() const;
 
     std::string d_blockReason;
     struct timespec d_cutOff;
@@ -155,119 +113,97 @@ private:
     bool d_enabled{false};
   };
 
-  struct DynBlockRatioRule: DynBlockRule
+  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)
+    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
-    {
-      if (!d_enabled) {
-        return false;
-      }
-
-      if (total < d_minimumNumberOfResponses) {
-        return false;
-      }
+    bool ratioExceeded(unsigned int total, unsigned int count) const;
+    bool warningRatioExceeded(unsigned int total, unsigned int count) const;
+    std::string toString() const;
 
-      double allowed = d_ratio * static_cast<double>(total);
-      return (count > allowed);
-    }
+    size_t d_minimumNumberOfResponses{0};
+    double d_ratio{0.0};
+    double d_warningRatio{0.0};
+  };
 
-    bool warningRatioExceeded(unsigned int total, unsigned int count) const
+  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)
     {
-      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();
-    }
+    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;
 
-    size_t d_minimumNumberOfResponses{0};
-    double d_ratio{0.0};
-    double d_warningRatio{0.0};
+    double d_minimumGlobalCacheHitRatio{0.0};
   };
 
-  typedef std::unordered_map<AddressAndPortRange, Counts, AddressAndPortRange::hash> counts_t;
+  using counts_t = std::unordered_map<AddressAndPortRange, Counts, AddressAndPortRange::hash>;
 
 public:
   DynBlockRulesGroup()
   {
   }
 
-  void setQueryRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
+  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, std::string reason, unsigned int blockDuration, DNSAction::Action action)
+  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, std::string reason, unsigned int blockDuration, DNSAction::Action 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, std::string reason, unsigned int blockDuration, DNSAction::Action action, size_t minimumNumberOfResponses)
+  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, std::string reason, unsigned int blockDuration, DNSAction::Action action)
+  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 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, std::string reason, unsigned int blockDuration, DNSAction::Action action, smtVisitor_t visitor)
+  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;
+    d_smtVisitor = std::move(visitor);
   }
 
-  void setSuffixMatchRuleFFI(unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action, dnsdist_ffi_stat_node_visitor_t 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;
+    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)
@@ -307,6 +243,16 @@ public:
     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);
@@ -319,6 +265,7 @@ public:
     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;
@@ -342,18 +289,18 @@ public:
   }
 
 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 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)
+  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)
+  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);
   }
@@ -365,7 +312,7 @@ private:
 
   bool hasResponseRules() const
   {
-    return d_respRateRule.isEnabled() || !d_rcodeRules.empty() || !d_rcodeRatioRules.empty();
+    return d_respRateRule.isEnabled() || !d_rcodeRules.empty() || !d_rcodeRatioRules.empty() || d_respCacheMissRatioRule.isEnabled();
   }
 
   bool hasSuffixMatchRules() const
@@ -387,10 +334,12 @@ private:
   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};
@@ -435,3 +384,10 @@ private:
   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 */
index c19844de73de005e1c9a87b1a9b3a58aa020ab85..9ede03fabf864f5b3165c75b75c099c5595e833e 100644 (file)
@@ -78,7 +78,7 @@ std::vector<std::tuple<ComboAddress, uint64_t, struct timespec> > DynBPFFilter::
   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));
+      result.emplace_back(stat.first, stat.second, it->d_until);
     }
   }
   return result;
index e4e3bd6e8bd5b0ba4ba6819b26ed030e84ee55d8..2cad1945bca8de18beb3eb745743f62dad02b21d 100644 (file)
@@ -21,6 +21,7 @@
  */
 #include "dolog.hh"
 #include "dnsdist.hh"
+#include "dnsdist-dnsparser.hh"
 #include "dnsdist-ecs.hh"
 #include "dnsparser.hh"
 #include "dnswriter.hh"
@@ -44,15 +45,17 @@ 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());
+  const dnsheader_aligned dh(initialPacket.data());
 
-  if (ntohs(dh->arcount) == 0)
+  if (ntohs(dh->arcount) == 0) {
     return ENOENT;
+  }
 
-  if (ntohs(dh->qdcount) == 0)
+  if (ntohs(dh->qdcount) == 0) {
     return ENOENT;
+  }
 
-  PacketReader pr(pdns_string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()));
+  PacketReader pr(std::string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()));
 
   size_t idx = 0;
   DNSName rrname;
@@ -152,7 +155,7 @@ static bool addOrReplaceEDNSOption(std::vector<std::pair<uint16_t, std::string>>
 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());
+  const dnsheader_aligned dh(initialPacket.data());
 
   if (ntohs(dh->qdcount) == 0) {
     return false;
@@ -165,7 +168,7 @@ bool slowRewriteEDNSOptionInQueryWithRecords(const PacketBuffer& initialPacket,
   optionAdded = false;
   ednsAdded = true;
 
-  PacketReader pr(pdns_string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()));
+  PacketReader pr(std::string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()));
 
   size_t idx = 0;
   DNSName rrname;
@@ -240,9 +243,11 @@ bool slowRewriteEDNSOptionInQueryWithRecords(const PacketBuffer& initialPacket,
       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(ah.d_ttl), "sizeof(EDNS0Record) must match sizeof(uint32_t) AKA RR TTL size");
-      memcpy(&edns0, &ah.d_ttl, sizeof(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;
@@ -261,13 +266,13 @@ bool slowRewriteEDNSOptionInQueryWithRecords(const PacketBuffer& initialPacket,
   return true;
 }
 
-static bool slowParseEDNSOptions(const PacketBuffer& packet, std::shared_ptr<std::map<uint16_t, EDNSOptionView> >& options)
+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());
+  const dnsheader_aligned dh(packet.data());
 
   if (ntohs(dh->qdcount) == 0) {
     return false;
@@ -302,7 +307,7 @@ static bool slowParseEDNSOptions(const PacketBuffer& packet, std::shared_ptr<std
         }
         /* 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;
+        return getEDNSOptions(reinterpret_cast<const char*>(&packet.at(offset)), packet.size() - offset, options) == 0;
       }
       else {
         dpm.skipRData();
@@ -322,12 +327,13 @@ int locateEDNSOptRR(const PacketBuffer& packet, uint16_t * optStart, size_t * op
   assert(optStart != NULL);
   assert(optLen != NULL);
   assert(last != NULL);
-  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet.data());
+  const dnsheader_aligned dh(packet.data());
 
-  if (ntohs(dh->arcount) == 0)
+  if (ntohs(dh->arcount) == 0) {
     return ENOENT;
+  }
 
-  PacketReader pr(pdns_string_view(reinterpret_cast<const char*>(packet.data()), packet.size()));
+  PacketReader pr(std::string_view(reinterpret_cast<const char*>(packet.data()), packet.size()));
 
   size_t idx = 0;
   DNSName rrname;
@@ -388,14 +394,15 @@ int getEDNSOptionsStart(const PacketBuffer& packet, const size_t offset, uint16_
 {
   assert(optRDPosition != nullptr);
   assert(remaining != nullptr);
-  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet.data());
+  const dnsheader_aligned dh(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)
+  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;
@@ -513,7 +520,8 @@ bool parseEDNSOptions(const DNSQuestion& dq)
     return true;
   }
 
-  dq.ednsOptions = std::make_shared<std::map<uint16_t, EDNSOptionView> >();
+  // dq.ednsOptions is mutable
+  dq.ednsOptions = std::make_unique<EDNSOptionViewMap>();
 
   if (ntohs(dh->arcount) == 0) {
     /* nothing in additional so no EDNS */
@@ -521,12 +529,12 @@ bool parseEDNSOptions(const DNSQuestion& dq)
   }
 
   if (ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || ntohs(dh->arcount) > 1) {
-    return slowParseEDNSOptions(dq.getData(), dq.ednsOptions);
+    return slowParseEDNSOptions(dq.getData(), *dq.ednsOptions);
   }
 
   size_t remaining = 0;
   uint16_t optRDPosition;
-  int res = getEDNSOptionsStart(dq.getData(), dq.qname->wirelength(), &optRDPosition, &remaining);
+  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);
@@ -568,10 +576,12 @@ static bool addEDNSWithECS(PacketBuffer& packet, size_t maximumSize, const strin
     return false;
   }
 
-  struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(packet.data());
-  uint16_t arcount = ntohs(dh->arcount);
-  arcount++;
-  dh->arcount = htons(arcount);
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [](dnsheader& header) {
+    uint16_t arcount = ntohs(header.arcount);
+    arcount++;
+    header.arcount = htons(arcount);
+    return true;
+  });
   ednsAdded = true;
   ecsAdded = true;
 
@@ -582,7 +592,7 @@ bool handleEDNSClientSubnet(PacketBuffer& packet, const size_t maximumSize, cons
 {
   assert(qnameWireLength <= packet.size());
 
-  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet.data());
+  const dnsheader_aligned dh(packet.data());
 
   if (ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || (ntohs(dh->arcount) != 0 && ntohs(dh->arcount) != 1)) {
     PacketBuffer newContent;
@@ -647,11 +657,10 @@ bool handleEDNSClientSubnet(PacketBuffer& packet, const size_t maximumSize, cons
 
 bool handleEDNSClientSubnet(DNSQuestion& dq, bool& ednsAdded, bool& ecsAdded)
 {
-  assert(dq.remote != nullptr);
   string newECSOption;
-  generateECSOption(dq.ecsSet ? dq.ecs.getNetwork() : *dq.remote, newECSOption, dq.ecsSet ? dq.ecs.getBits() : dq.ecsPrefixLength);
+  generateECSOption(dq.ecs ? dq.ecs->getNetwork() : dq.ids.origRemote, newECSOption, dq.ecs ? dq.ecs->getBits() : dq.ecsPrefixLength);
 
-  return handleEDNSClientSubnet(dq.getMutableData(), dq.getMaximumSize(), dq.qname->wirelength(), ednsAdded, ecsAdded, dq.ecsOverride, newECSOption);
+  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)
@@ -750,7 +759,7 @@ bool isEDNSOptionInOpt(const PacketBuffer& packet, const size_t optStart, const
 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());
+  const dnsheader_aligned dh(initialPacket.data());
 
   if (ntohs(dh->arcount) == 0)
     return ENOENT;
@@ -758,7 +767,7 @@ int rewriteResponseWithoutEDNSOption(const PacketBuffer& initialPacket, const ui
   if (ntohs(dh->qdcount) == 0)
     return ENOENT;
 
-  PacketReader pr(pdns_string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()));
+  PacketReader pr(std::string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()));
 
   size_t idx = 0;
   DNSName rrname;
@@ -850,17 +859,19 @@ bool addEDNS(PacketBuffer& packet, size_t maximumSize, bool dnssecOK, uint16_t p
     return false;
   }
 
-  auto dh = reinterpret_cast<dnsheader*>(packet.data());
-  dh->arcount = htons(ntohs(dh->arcount) + 1);
+  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.
+  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 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();
@@ -868,7 +879,7 @@ bool setNegativeAndAdditionalSOA(DNSQuestion& dq, bool nxd, const DNSName& zone,
     return false;
   }
 
-  size_t queryPartSize = sizeof(dnsheader) + dq.qname->wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE;
+  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;
@@ -892,17 +903,19 @@ bool setNegativeAndAdditionalSOA(DNSQuestion& dq, bool nxd, const DNSName& zone,
 
   /* 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;
+  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);
@@ -932,8 +945,18 @@ bool setNegativeAndAdditionalSOA(DNSQuestion& dq, bool nxd, const DNSName& zone,
   }
 
   packet.insert(packet.end(), soa.begin(), soa.end());
-  dh = dq.getHeader();
-  dh->arcount = htons(1);
+
+  /* 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 */
@@ -950,7 +973,7 @@ bool addEDNSToQueryTurnedResponse(DNSQuestion& dq)
   size_t remaining = 0;
 
   auto& packet = dq.getMutableData();
-  int res = getEDNSOptionsStart(packet, dq.qname->wirelength(), &optRDPosition, &remaining);
+  int res = getEDNSOptionsStart(packet, dq.ids.qname.wirelength(), &optRDPosition, &remaining);
 
   if (res != 0) {
     /* if the initial query did not have EDNS0, we are done */
@@ -972,7 +995,10 @@ bool addEDNSToQueryTurnedResponse(DNSQuestion& dq)
 
   /* 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;
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [](dnsheader& header) {
+    header.arcount = 0;
+    return true;
+  });
 
   if (g_addEDNSToSelfGeneratedResponses) {
     /* now we need to add a new OPT record */
@@ -997,7 +1023,7 @@ int getEDNSZ(const DNSQuestion& dq)
       return 0;
     }
 
-    size_t pos = sizeof(dnsheader) + dq.qname->wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE;
+    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;
@@ -1034,7 +1060,7 @@ bool queryHasEDNS(const DNSQuestion& dq)
   uint16_t optRDPosition;
   size_t ecsRemaining = 0;
 
-  int res = getEDNSOptionsStart(dq.getData(), dq.qname->wirelength(), &optRDPosition, &ecsRemaining);
+  int res = getEDNSOptionsStart(dq.getData(), dq.ids.qname.wirelength(), &optRDPosition, &ecsRemaining);
   if (res == 0) {
     return true;
   }
@@ -1042,12 +1068,11 @@ bool queryHasEDNS(const DNSQuestion& dq)
   return false;
 }
 
-bool getEDNS0Record(const DNSQuestion& dq, EDNS0Record& edns0)
+bool getEDNS0Record(const PacketBuffer& packet, EDNS0Record& edns0)
 {
   uint16_t optStart;
   size_t optLen = 0;
   bool last = false;
-  const auto& packet = dq.getData();
   int res = locateEDNSOptRR(packet, &optStart, &optLen, &last);
   if (res != 0) {
     // no EDNS OPT RR
@@ -1068,3 +1093,86 @@ bool getEDNS0Record(const DNSQuestion& dq, EDNS0Record& edns0)
   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)) {
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.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
+    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);
+  }
+
+  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 dq(state, buffer);
+      if (!addEDNS(buffer, dq.getMaximumSize(), edns0.extFlags & htons(EDNS_HEADER_FLAG_DO), g_PayloadSizeSelfGenAnswers, 0)) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+}
index 89e6237963f81091bd4f74f19e5f440f337a93c8..f5d215f1a06587953774b3af7e96c31e06830827 100644 (file)
  */
 #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;
 
@@ -38,7 +45,7 @@ int getEDNSOptionsStart(const PacketBuffer& packet, const size_t offset, uint16_
 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 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);
@@ -47,4 +54,11 @@ bool parseEDNSOptions(const DNSQuestion& dq);
 
 int getEDNSZ(const DNSQuestion& dq);
 bool queryHasEDNS(const DNSQuestion& dq);
-bool getEDNS0Record(const DNSQuestion& dq, EDNS0Record& edns0);
+bool getEDNS0Record(const PacketBuffer& packet, EDNS0Record& edns0);
+
+bool setEDNSOption(DNSQuestion& dq, uint16_t ednsCode, const std::string& data);
+
+struct InternalQueryState;
+namespace dnsdist {
+bool setInternalQueryRCode(InternalQueryState& state, PacketBuffer& buffer,  uint8_t rcode, bool clearAnswers);
+}
index 0bbf87560da525ae623855af577961748cf8bff3..f83f9b71f5200e16a7c785c0c1c4b84c2d5a7d0f 100644 (file)
  */
 #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 DOHUnit;
+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
 {
@@ -66,13 +74,9 @@ struct StopWatch
     return ret;
   }
 
-  struct timespec getCurrentTime() const
+  struct timespec getStartTime() const
   {
-    struct timespec now;
-    if (gettime(&now, d_needRealTime) < 0) {
-      unixDie("Getting timestamp");
-    }
-    return now;
+    return d_start;
   }
 
   struct timespec d_start
@@ -81,124 +85,126 @@ struct StopWatch
   };
 
 private:
+  struct timespec getCurrentTime() const
+  {
+    struct timespec now;
+    if (gettime(&now, d_needRealTime) < 0) {
+      unixDie("Getting timestamp");
+    }
+    return now;
+  }
+
   bool d_needRealTime;
 };
 
-/* 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
+class CrossProtocolContext;
 
-struct IDState
+struct InternalQueryState
 {
-  IDState() :
-    sentTime(true), tempFailureTTL(boost::none) { origDest.sin4.sin_family = 0; }
-  IDState(const IDState& orig) = delete;
-  IDState(IDState&& rhs) :
-    subnet(rhs.subnet), origRemote(rhs.origRemote), origDest(rhs.origDest), hopRemote(rhs.hopRemote), hopLocal(rhs.hopLocal), qname(std::move(rhs.qname)), sentTime(rhs.sentTime), packetCache(std::move(rhs.packetCache)), dnsCryptQuery(std::move(rhs.dnsCryptQuery)), qTag(std::move(rhs.qTag)), tempFailureTTL(rhs.tempFailureTTL), cs(rhs.cs), du(std::move(rhs.du)), cacheKey(rhs.cacheKey), cacheKeyNoECS(rhs.cacheKeyNoECS), cacheKeyUDP(rhs.cacheKeyUDP), origFD(rhs.origFD), backendFD(rhs.backendFD), delayMsec(rhs.delayMsec), qtype(rhs.qtype), qclass(rhs.qclass), origID(rhs.origID), origFlags(rhs.origFlags), cacheFlags(rhs.cacheFlags), protocol(rhs.protocol), ednsAdded(rhs.ednsAdded), ecsAdded(rhs.ecsAdded), skipCache(rhs.skipCache), destHarvested(rhs.destHarvested), dnssecOK(rhs.dnssecOK), useZeroScope(rhs.useZeroScope)
+  struct ProtoBufData
   {
-    if (rhs.isInUse()) {
-      throw std::runtime_error("Trying to move an in-use IDState");
-    }
+    std::optional<boost::uuids::uuid> uniqueId{std::nullopt}; // 17
+    std::string d_deviceName;
+    std::string d_deviceID;
+    std::string d_requestorID;
+  };
 
-    uniqueId = std::move(rhs.uniqueId);
-#ifdef __SANITIZE_THREAD__
-    age.store(rhs.age.load());
-#else
-    age = rhs.age;
-#endif
+  InternalQueryState()
+  {
+    origDest.sin4.sin_family = 0;
   }
 
-  IDState& operator=(IDState&& rhs)
-  {
-    if (isInUse()) {
-      throw std::runtime_error("Trying to overwrite an in-use IDState");
-    }
+  InternalQueryState(InternalQueryState&& rhs) = default;
+  InternalQueryState& operator=(InternalQueryState&& rhs) = default;
 
-    if (rhs.isInUse()) {
-      throw std::runtime_error("Trying to move an in-use IDState");
-    }
+  InternalQueryState(const InternalQueryState& orig) = delete;
+  InternalQueryState& operator=(const InternalQueryState& orig) = delete;
 
-    subnet = std::move(rhs.subnet);
-    origRemote = rhs.origRemote;
-    origDest = rhs.origDest;
-    hopRemote = rhs.hopRemote;
-    hopLocal = rhs.hopLocal;
-    qname = std::move(rhs.qname);
-    sentTime = rhs.sentTime;
-    dnsCryptQuery = std::move(rhs.dnsCryptQuery);
-    packetCache = std::move(rhs.packetCache);
-    qTag = std::move(rhs.qTag);
-    tempFailureTTL = std::move(rhs.tempFailureTTL);
-    cs = rhs.cs;
-    du = std::move(rhs.du);
-    cacheKey = rhs.cacheKey;
-    cacheKeyNoECS = rhs.cacheKeyNoECS;
-    cacheKeyUDP = rhs.cacheKeyUDP;
-    origFD = rhs.origFD;
-    backendFD = rhs.backendFD;
-    delayMsec = rhs.delayMsec;
-#ifdef __SANITIZE_THREAD__
-    age.store(rhs.age.load());
+  bool isXSK() const noexcept
+  {
+#ifdef HAVE_XSK
+    return !xskPacketHeader.empty();
 #else
-    age = rhs.age;
-#endif
-    qtype = rhs.qtype;
-    qclass = rhs.qclass;
-    origID = rhs.origID;
-    origFlags = rhs.origFlags;
-    cacheFlags = rhs.cacheFlags;
-    protocol = rhs.protocol;
-    uniqueId = std::move(rhs.uniqueId);
-    ednsAdded = rhs.ednsAdded;
-    ecsAdded = rhs.ecsAdded;
-    skipCache = rhs.skipCache;
-    destHarvested = rhs.destHarvested;
-    dnssecOK = rhs.dnssecOK;
-    useZeroScope = rhs.useZeroScope;
-
-    return *this;
+    return false;
+#endif /* HAVE_XSK */
   }
 
-  static const int64_t unusedIndicator = -1;
-
-  static bool isInUse(int64_t usageIndicator)
-  {
-    return usageIndicator != unusedIndicator;
-  }
+  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
+#ifdef HAVE_XSK
+  PacketBuffer xskPacketHeader; // 8
+#endif /* HAVE_XSK */
+  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
+  int32_t d_streamID{-1}; // 4
+  std::unique_ptr<DOQUnit> doqu{nullptr}; // 8
+  std::unique_ptr<DOH3Unit> doh3u{nullptr}; // 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};
+};
 
-  bool isInUse() const
+struct IDState
+{
+  IDState()
   {
-    return usageIndicator != unusedIndicator;
   }
 
-  /* return true if the value has been successfully replaced meaning that
-     no-one updated the usage indicator in the meantime */
-  bool tryMarkUnused(int64_t expectedUsageIndicator)
+  IDState(const IDState& orig) = delete;
+  IDState(IDState&& rhs) noexcept :
+    internal(std::move(rhs.internal))
   {
-    return usageIndicator.compare_exchange_strong(expectedUsageIndicator, unusedIndicator);
+    inUse.store(rhs.inUse.load());
+    age.store(rhs.age.load());
   }
 
-  /* mark as used no matter what, return true if the state was in use before */
-  bool markAsUsed()
+  IDState& operator=(IDState&& rhs) noexcept
   {
-    auto currentGeneration = generation++;
-    return markAsUsed(currentGeneration);
+    inUse.store(rhs.inUse.load());
+    age.store(rhs.age.load());
+    internal = std::move(rhs.internal);
+    return *this;
   }
 
-  /* mark as used no matter what, return true if the state was in use before */
-  bool markAsUsed(int64_t currentGeneration)
+  bool isInUse() const
   {
-    int64_t oldUsage = usageIndicator.exchange(currentGeneration);
-    return oldUsage != unusedIndicator;
+    return inUse;
   }
 
-  /* We use this value to detect whether this state is in use.
-     For performance reasons we don't want to use a lock here, but that means
+  /* 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
@@ -216,59 +222,52 @@ struct IDState
        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 previously based that logic on the origFD (FD on which the query was received,
-     and therefore from where the response should be sent) but this suffered from an
-     ABA problem since it was quite likely that a UDP 'client thread' would reset it to the
-     same value since we only have so much incoming sockets:
-     - 1/ 'client' thread gets a query and set origFD to its FD, say 5 ;
-     - 2/ 'receiver' thread gets a response, read the value of origFD to 5, check that the qname,
-       qtype and qclass match
-     - 3/ during that time the 'client' thread reuses the state, setting again origFD to 5 ;
-     - 4/ the 'receiver' thread uses compare_exchange_strong() to only replace the value if it's still
-       5, except it's not the same 5 anymore and it overrides a fresh state.
-     We now use a 32-bit unsigned counter instead, which is incremented every time the state is set,
-     wrapping around if necessary, and we set an atomic signed 64-bit value, so that we still have -1
-     when the state is unused and the value of our counter otherwise.
+
+     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
   */
-  boost::optional<Netmask> subnet{boost::none}; // 40
-  ComboAddress origRemote; // 28
-  ComboAddress origDest; // 28
-  ComboAddress hopRemote;
-  ComboAddress hopLocal;
-  DNSName qname; // 24
-  StopWatch sentTime; // 16
-  std::shared_ptr<DNSDistPacketCache> packetCache{nullptr}; // 16
-  std::unique_ptr<DNSCryptQuery> dnsCryptQuery{nullptr}; // 8
-  std::unique_ptr<QTag> qTag{nullptr}; // 8
-  boost::optional<uint32_t> tempFailureTTL; // 8
-  const ClientState* cs{nullptr}; // 8
-  DOHUnit* du{nullptr}; // 8 (not a unique_ptr because we currently need to be able to peek at it without taking ownership until later)
-  std::atomic<int64_t> usageIndicator{unusedIndicator}; // set to unusedIndicator to indicate this state is empty   // 8
-  std::atomic<uint32_t> generation{0}; // increased every time a state is used, to be able to detect an ABA issue    // 4
-  uint32_t cacheKey{0}; // 4
-  uint32_t cacheKeyNoECS{0}; // 4
-  // DoH-only */
-  uint32_t cacheKeyUDP{0}; // 4
-  int origFD{-1}; // 4
-  int backendFD{-1}; // 4
-  int delayMsec{0};
-#ifdef __SANITIZE_THREAD__
+  InternalQueryState internal;
   std::atomic<uint16_t> age{0};
-#else
-  uint16_t age{0}; // 2
-#endif
-  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
-  dnsdist::Protocol protocol; // 1
-  boost::optional<boost::uuids::uuid> uniqueId{boost::none}; // 17 (placed here to reduce the space lost to padding)
-  bool ednsAdded{false};
-  bool ecsAdded{false};
-  bool skipCache{false};
-  bool destHarvested{false}; // if true, origDest holds the original dest addr, otherwise the listening addr
-  bool dnssecOK{false};
-  bool useZeroScope{false};
+
+  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 395deec2b5e9e00534c9c0445559566790818dea..72443402d10f9708bafbd53c5e739d1d9b579dfc 100644 (file)
@@ -37,11 +37,11 @@ public:
   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_, 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(policy_), d_isLua(true), d_isFFI(true)
+  ServerPolicy(const std::string& name_, ffipolicyfunc_t policy_): d_name(name_), d_ffipolicy(std::move(policy_)), d_isLua(true), d_isFFI(true)
   {
   }
 
@@ -96,7 +96,7 @@ void setPoolPolicy(pools_t& pools, const string& poolName, std::shared_ptr<Serve
 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<ServerPolicy::NumberedServerVector> getDownstreamCandidates(const map<std::string,std::shared_ptr<ServerPool>>& pools, const std::string& poolName);
+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);
 
index 167baf36e3dc774f68d601eb04aca7eb72aaeaea..e6430077c90aaa7179e394989248992ab59ed346 100644 (file)
 #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-svc.hh"
 
-#include "dolog.hh"
 #include "dnstap.hh"
 #include "dnswriter.hh"
 #include "ednsoptions.hh"
 class DropAction : public DNSAction
 {
 public:
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
     return Action::Drop;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "drop";
   }
@@ -57,11 +61,11 @@ public:
 class AllowAction : public DNSAction
 {
 public:
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
     return Action::Allow;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "allow";
   }
@@ -71,11 +75,11 @@ class NoneAction : public DNSAction
 {
 public:
   // this action does not stop the processing
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
     return Action::None;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "no op";
   }
@@ -84,22 +88,22 @@ public:
 class QPSAction : public DNSAction
 {
 public:
-  QPSAction(int limit) : d_qps(QPSLimiter(limit, limit))
+  QPSAction(int limit) :
+    d_qps(QPSLimiter(limit, limit))
   {
   }
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
     if (d_qps.lock()->check()) {
       return Action::None;
     }
-    else {
-      return Action::Drop;
-    }
+    return Action::Drop;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
-    return "qps limit to "+std::to_string(d_qps.lock()->getRate());
+    return "qps limit to " + std::to_string(d_qps.lock()->getRate());
   }
+
 private:
   mutable LockGuarded<QPSLimiter> d_qps;
 };
@@ -107,18 +111,20 @@ private:
 class DelayAction : public DNSAction
 {
 public:
-  DelayAction(int msec) : d_msec(msec)
+  DelayAction(int msec) :
+    d_msec(msec)
   {
   }
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
     *ruleresult = std::to_string(d_msec);
     return Action::Delay;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
-    return "delay by "+std::to_string(d_msec)+ " msec";
+    return "delay by " + std::to_string(d_msec) + " ms";
   }
+
 private:
   int d_msec;
 };
@@ -127,18 +133,22 @@ class TeeAction : public DNSAction
 {
 public:
   // this action does not stop the processing
-  TeeAction(const ComboAddress& ca, bool addECS=false);
+  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* dq, std::string* ruleresult) const override;
-  std::string toString() const 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:
-  ComboAddress d_remote;
-  std::thread d_worker;
   void worker();
 
-  int d_fd{-1};
+  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};
@@ -153,57 +163,65 @@ private:
   stat_t d_otherrcode{0};
   std::atomic<bool> d_pleaseQuit{false};
   bool d_addECS{false};
+  bool d_addProxyProtocol{false};
 };
 
-TeeAction::TeeAction(const ComboAddress& ca, bool addECS) : d_remote(ca), d_addECS(addECS)
+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)
 {
-  d_fd=SSocket(d_remote.sin4.sin_family, SOCK_DGRAM, 0);
-  try {
-    SConnect(d_fd, d_remote);
-    setNonBlocking(d_fd);
-    d_worker=std::thread([this](){worker();});
-  }
-  catch (...) {
-    if (d_fd != -1) {
-      close(d_fd);
-    }
-    throw;
+  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_fd);
+  d_pleaseQuit = true;
+  close(d_socket.releaseHandle());
   d_worker.join();
 }
 
-DNSAction::Action TeeAction::operator()(DNSQuestion* dq, std::string* ruleresult) const
+DNSAction::Action TeeAction::operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const
 {
-  if (dq->overTCP()) {
+  if (dnsquestion->overTCP()) {
     d_tcpdrops++;
+    return DNSAction::Action::None;
   }
-  else {
-    ssize_t res;
-    d_queries++;
 
-    if(d_addECS) {
-      PacketBuffer query(dq->getData());
-      bool ednsAdded = false;
-      bool ecsAdded = false;
+  d_queries++;
 
-      std::string newECSOption;
-      generateECSOption(dq->ecsSet ? dq->ecs.getNetwork() : *dq->remote, newECSOption, dq->ecsSet ? dq->ecs.getBits() :  dq->ecsPrefixLength);
+  PacketBuffer query;
+  if (d_addECS) {
+    query = dnsquestion->getData();
+    bool ednsAdded = false;
+    bool ecsAdded = false;
 
-      if (!handleEDNSClientSubnet(query, dq->getMaximumSize(), dq->qname->wirelength(), ednsAdded, ecsAdded, dq->ecsOverride, newECSOption)) {
-        return DNSAction::Action::None;
-      }
+    std::string newECSOption;
+    generateECSOption(dnsquestion->ecs ? dnsquestion->ecs->getNetwork() : dnsquestion->ids.origRemote, newECSOption, dnsquestion->ecs ? dnsquestion->ecs->getBits() : dnsquestion->ecsPrefixLength);
 
-      res = send(d_fd, query.data(), query.size(), 0);
+    if (!handleEDNSClientSubnet(query, dnsquestion->getMaximumSize(), dnsquestion->ids.qname.wirelength(), ednsAdded, ecsAdded, dnsquestion->ecsOverride, newECSOption)) {
+      return DNSAction::Action::None;
     }
-    else {
-      res = send(d_fd, dq->getData().data(), dq->getData().size(), 0);
+  }
+
+  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++;
@@ -215,10 +233,10 @@ DNSAction::Action TeeAction::operator()(DNSQuestion* dq, std::string* ruleresult
 
 std::string TeeAction::toString() const
 {
-  return "tee to "+d_remote.toStringWithPort();
+  return "tee to " + d_remote.toStringWithPort();
 }
 
-std::map<std::string,double> TeeAction::getStats() const
+std::map<std::string, double> TeeAction::getStats() const
 {
   return {{"queries", d_queries},
           {"responses", d_responses},
@@ -229,81 +247,98 @@ std::map<std::string,double> TeeAction::getStats() const
           {"refuseds", d_refuseds},
           {"servfails", d_servfails},
           {"other-rcode", d_otherrcode},
-          {"tcp-drops", d_tcpdrops}
-  };
+          {"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)
+  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) {
+    }
+
+    if (res < 0) {
       usleep(250000);
       continue;
     }
-    if(res==0)
+    if (res == 0) {
       continue;
-    res=recv(d_fd, packet, sizeof(packet), 0);
-    if(res <= (int)sizeof(struct dnsheader))
+    }
+    res = recv(d_socket.getHandle(), packet.data(), packet.size(), 0);
+    if (static_cast<size_t>(res) <= sizeof(struct dnsheader)) {
       d_recverrors++;
-    else
+    }
+    else {
       d_responses++;
+    }
 
-    if(dh->rcode == RCode::NoError)
+    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well
+    if (dnsheader->rcode == RCode::NoError) {
       d_noerrors++;
-    else if(dh->rcode == RCode::ServFail)
+    }
+    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well
+    else if (dnsheader->rcode == RCode::ServFail) {
       d_servfails++;
-    else if(dh->rcode == RCode::NXDomain)
+    }
+    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well
+    else if (dnsheader->rcode == RCode::NXDomain) {
       d_nxdomains++;
-    else if(dh->rcode == RCode::Refused)
+    }
+    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well
+    else if (dnsheader->rcode == RCode::Refused) {
       d_refuseds++;
-    else if(dh->rcode == RCode::FormErr)
+    }
+    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well
+    else if (dnsheader->rcode == RCode::FormErr) {
       d_formerrs++;
-    else if(dh->rcode == RCode::NotImp)
+    }
+    // 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(const std::string& pool, bool stopProcessing) : d_pool(pool), d_stopProcessing(stopProcessing) {}
+  PoolAction(std::string pool, bool stopProcessing) :
+    d_pool(std::move(pool)), d_stopProcessing(stopProcessing) {}
 
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  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;
     }
-    else {
-      dq->poolname = d_pool;
-      return Action::None;
-    }
+    dnsquestion->ids.poolName = d_pool;
+    return Action::None;
   }
 
-  std::string toString() const override
+  [[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, 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
+  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) {
@@ -311,66 +346,79 @@ public:
         *ruleresult = d_pool;
         return Action::Pool;
       }
-      else {
-        dq->poolname = d_pool;
-        return Action::None;
-      }
-    }
-    else {
-      return Action::None;
+      dnsquestion->ids.poolName = d_pool;
     }
+    return Action::None;
   }
-  std::string toString() const override
+  [[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* dq, std::string* ruleresult) const override
+  RCodeAction(uint8_t rcode) :
+    d_rcode(rcode) {}
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    dq->getHeader()->rcode = d_rcode;
-    dq->getHeader()->qr = true; // for good measure
-    setResponseHeadersFromConfig(*dq->getHeader(), d_responseConfig);
+    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;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
+  {
+    return "set rcode " + std::to_string(d_rcode);
+  }
+  [[nodiscard]] ResponseConfig& getResponseConfig()
   {
-    return "set rcode "+std::to_string(d_rcode);
+    return d_responseConfig;
   }
 
-  ResponseConfig 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* dq, std::string* ruleresult) const override
+  ERCodeAction(uint8_t rcode) :
+    d_rcode(rcode) {}
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, 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);
+    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;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
+  {
+    return "set ercode " + ERCode::to_s(d_rcode);
+  }
+  [[nodiscard]] ResponseConfig& getResponseConfig()
   {
-    return "set ercode "+ERCode::to_s(d_rcode);
+    return d_responseConfig;
   }
 
-  ResponseConfig d_responseConfig;
 private:
+  ResponseConfig d_responseConfig;
   uint8_t d_rcode;
 };
 
@@ -391,88 +439,106 @@ public:
       d_payloads.push_back(std::move(payload));
 
       for (const auto& hint : param.second.ipv4hints) {
-        d_additionals4.insert({ param.second.target, ComboAddress(hint) });
+        d_additionals4.insert({param.second.target, ComboAddress(hint)});
       }
 
       for (const auto& hint : param.second.ipv6hints) {
-        d_additionals6.insert({ param.second.target, ComboAddress(hint) });
+        d_additionals6.insert({param.second.target, ComboAddress(hint)});
       }
     }
   }
 
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, 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->qname->wirelength();
-    if (dq->getMaximumSize() < (sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords*12 /* recordstart */ + d_totalPayloadsSize)) {
+    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> pw(newPacket, *dq->qname, dq->qtype);
+    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) {
-      pw.startRecord(*dq->qname, dq->qtype, d_responseConfig.ttl);
-      pw.xfrBlob(payload);
-      pw.commit();
+      packetWriter.startRecord(dnsquestion->ids.qname, dnsquestion->ids.qtype, d_responseConfig.ttl);
+      packetWriter.xfrBlob(payload);
+      packetWriter.commit();
     }
 
-    if (newPacket.size() < dq->getMaximumSize()) {
+    if (newPacket.size() < dnsquestion->getMaximumSize()) {
       for (const auto& additional : d_additionals4) {
-        pw.startRecord(additional.first.isRoot() ? *dq->qname : additional.first, QType::A, d_responseConfig.ttl, QClass::IN, DNSResourceRecord::ADDITIONAL);
-        pw.xfrCAWithoutPort(4, additional.second);
-        pw.commit();
+        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() < dq->getMaximumSize()) {
+    if (newPacket.size() < dnsquestion->getMaximumSize()) {
       for (const auto& additional : d_additionals6) {
-        pw.startRecord(additional.first.isRoot() ? *dq->qname : additional.first, QType::AAAA, d_responseConfig.ttl, QClass::IN, DNSResourceRecord::ADDITIONAL);
-        pw.xfrCAWithoutPort(6, additional.second);
-        pw.commit();
+        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(*dq)) {
-      bool dnssecOK = getEDNSZ(*dq) & EDNS_HEADER_FLAG_DO;
-      pw.addOpt(g_PayloadSizeSelfGenAnswers, 0, dnssecOK ? EDNS_HEADER_FLAG_DO : 0);
-      pw.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() >= dq->getMaximumSize()) {
+    if (newPacket.size() >= dnsquestion->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);
+    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;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "spoof SVC record ";
   }
 
-  ResponseConfig d_responseConfig;
+  [[nodiscard]] ResponseConfig& getResponseConfig()
+  {
+    return 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;
+  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* dq, std::string* ruleresult) const override
+  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;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "tc=1 answer";
   }
@@ -481,37 +547,46 @@ public:
 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)
+  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* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    auto lock = g_lua.lock();
     try {
-      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();
+      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));
       }
-      return static_cast<Action>(std::get<0>(ret));
-    } catch (const std::exception &e) {
+      dnsdist::handleQueuedAsynchronousEvents();
+      return result;
+    }
+    catch (const std::exception& e) {
       warnlog("LuaAction failed inside Lua, returning ServFail: %s", e.what());
-    } catch (...) {
+    }
+    catch (...) {
       warnlog("LuaAction failed inside Lua, returning ServFail: [unknown exception]");
     }
     return DNSAction::Action::ServFail;
   }
 
-  string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "Lua script";
   }
+
 private:
   func_t d_func;
 };
@@ -519,91 +594,110 @@ private:
 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)
+  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* dr, std::string* ruleresult) const override
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
   {
-    auto lock = g_lua.lock();
     try {
-      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();
+      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));
       }
-      return static_cast<Action>(std::get<0>(ret));
-    } catch (const std::exception &e) {
+      dnsdist::handleQueuedAsynchronousEvents();
+      return result;
+    }
+    catch (const std::exception& e) {
       warnlog("LuaResponseAction failed inside Lua, returning ServFail: %s", e.what());
-    } catch (...) {
+    }
+    catch (...) {
       warnlog("LuaResponseAction failed inside Lua, returning ServFail: [unknown exception]");
     }
     return DNSResponseAction::Action::ServFail;
   }
 
-  string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "Lua response script";
   }
+
 private:
   func_t d_func;
 };
 
-class LuaFFIAction: public DNSAction
+class LuaFFIAction : public DNSAction
 {
 public:
-  typedef std::function<int(dnsdist_ffi_dnsquestion_t* dq)> func_t;
+  using func_t = std::function<int(dnsdist_ffi_dnsquestion_t* dnsquestion)>;
 
-  LuaFFIAction(const LuaFFIAction::func_t& func): d_func(func)
+  LuaFFIAction(LuaFFIAction::func_t func) :
+    d_func(std::move(func))
   {
   }
 
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    dnsdist_ffi_dnsquestion_t dqffi(dq);
+    dnsdist_ffi_dnsquestion_t dqffi(dnsquestion);
     try {
-      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();
+      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);
       }
-      return static_cast<DNSAction::Action>(ret);
-    } catch (const std::exception &e) {
+      dnsdist::handleQueuedAsynchronousEvents();
+      return result;
+    }
+    catch (const std::exception& e) {
       warnlog("LuaFFIAction failed inside Lua, returning ServFail: %s", e.what());
-    } catch (...) {
+    }
+    catch (...) {
       warnlog("LuaFFIAction failed inside Lua, returning ServFail: [unknown exception]");
     }
     return DNSAction::Action::ServFail;
   }
 
-  string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "Lua FFI script";
   }
+
 private:
   func_t d_func;
 };
 
-class LuaFFIPerThreadAction: public DNSAction
+class LuaFFIPerThreadAction : public DNSAction
 {
 public:
-  typedef std::function<int(dnsdist_ffi_dnsquestion_t* dq)> func_t;
+  using func_t = std::function<int(dnsdist_ffi_dnsquestion_t* dnsquestion)>;
 
-  LuaFFIPerThreadAction(const std::string& code): d_functionCode(code), d_functionID(s_functionsCounter++)
+  LuaFFIPerThreadAction(std::string code) :
+    d_functionCode(std::move(code)), d_functionID(s_functionsCounter++)
   {
   }
 
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
     try {
       auto& state = t_perThreadStates[d_functionID];
@@ -620,9 +714,9 @@ public:
         return DNSAction::Action::None;
       }
 
-      dnsdist_ffi_dnsquestion_t dqffi(dq);
+      dnsdist_ffi_dnsquestion_t dqffi(dnsquestion);
       auto ret = state.d_func(&dqffi);
-      if (ruleresult) {
+      if (ruleresult != nullptr) {
         if (dqffi.result) {
           *ruleresult = *dqffi.result;
         }
@@ -631,9 +725,10 @@ public:
           ruleresult->clear();
         }
       }
+      dnsdist::handleQueuedAsynchronousEvents();
       return static_cast<DNSAction::Action>(ret);
     }
-    catch (const std::exception &e) {
+    catch (const std::exceptione) {
       warnlog("LuaFFIPerThreadAction failed inside Lua, returning ServFail: %s", e.what());
     }
     catch (...) {
@@ -642,7 +737,7 @@ public:
     return DNSAction::Action::ServFail;
   }
 
-  string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "Lua FFI per-thread script";
   }
@@ -656,64 +751,76 @@ private:
   };
   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
+class LuaFFIResponseAction : public DNSResponseAction
 {
 public:
-  typedef std::function<int(dnsdist_ffi_dnsresponse_t* dq)> func_t;
+  using func_t = std::function<int(dnsdist_ffi_dnsresponse_t* dnsquestion)>;
 
-  LuaFFIResponseAction(const LuaFFIResponseAction::func_t& func): d_func(func)
+  LuaFFIResponseAction(LuaFFIResponseAction::func_t func) :
+    d_func(std::move(func))
   {
   }
 
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
   {
-    dnsdist_ffi_dnsresponse_t drffi(dr);
+    dnsdist_ffi_dnsresponse_t ffiResponse(response);
     try {
-      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();
+      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);
       }
-      return static_cast<DNSResponseAction::Action>(ret);
-    } catch (const std::exception &e) {
+      dnsdist::handleQueuedAsynchronousEvents();
+      return result;
+    }
+    catch (const std::exception& e) {
       warnlog("LuaFFIResponseAction failed inside Lua, returning ServFail: %s", e.what());
-    } catch (...) {
+    }
+    catch (...) {
       warnlog("LuaFFIResponseAction failed inside Lua, returning ServFail: [unknown exception]");
     }
     return DNSResponseAction::Action::ServFail;
   }
 
-  string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "Lua FFI script";
   }
+
 private:
   func_t d_func;
 };
 
-class LuaFFIPerThreadResponseAction: public DNSResponseAction
+class LuaFFIPerThreadResponseAction : public DNSResponseAction
 {
 public:
-  typedef std::function<int(dnsdist_ffi_dnsresponse_t* dr)> func_t;
+  using func_t = std::function<int(dnsdist_ffi_dnsresponse_t* response)>;
 
-  LuaFFIPerThreadResponseAction(const std::string& code): d_functionCode(code), d_functionID(s_functionsCounter++)
+  LuaFFIPerThreadResponseAction(std::string code) :
+    d_functionCode(std::move(code)), d_functionID(s_functionsCounter++)
   {
   }
 
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
   {
     try {
       auto& state = t_perThreadStates[d_functionID];
@@ -730,20 +837,21 @@ public:
         return DNSResponseAction::Action::None;
       }
 
-      dnsdist_ffi_dnsresponse_t drffi(dr);
-      auto ret = state.d_func(&drffi);
-      if (ruleresult) {
-        if (drffi.result) {
-          *ruleresult = *drffi.result;
+      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) {
+    catch (const std::exceptione) {
       warnlog("LuaFFIPerThreadResponseAction failed inside Lua, returning ServFail: %s", e.what());
     }
     catch (...) {
@@ -752,7 +860,7 @@ public:
     return DNSResponseAction::Action::ServFail;
   }
 
-  string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "Lua FFI per-thread script";
   }
@@ -767,7 +875,9 @@ private:
 
   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;
 };
 
@@ -776,35 +886,37 @@ thread_local std::map<uint64_t, LuaFFIPerThreadResponseAction::PerThreadState> L
 
 thread_local std::default_random_engine SpoofAction::t_randomEngine;
 
-DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresult) const
+DNSAction::Action SpoofAction::operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const
 {
-  uint16_t qtype = dq->qtype;
+  uint16_t qtype = dnsquestion->ids.qtype;
   // do we even have a response?
-  if (d_cname.empty() &&
-      d_rawResponses.empty() &&
+  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) {
+      (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;
+    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;
   }
-  vector<ComboAddress> addrs;
-  vector<std::string> rawResponses;
+  std::vector<ComboAddress> addrs = {};
+  std::vector<std::string> rawResponses = {};
   unsigned int totrdatalen = 0;
-  uint16_t numberOfRecords = 0;
+  size_t numberOfRecords = 0;
   if (!d_cname.empty()) {
     qtype = QType::CNAME;
     totrdatalen += d_cname.getStorage().size();
     numberOfRecords = 1;
-  } else if (!d_rawResponses.empty()) {
+  }
+  else if (!d_rawResponses.empty()) {
     rawResponses.reserve(d_rawResponses.size());
-    for(const auto& rawResponse : d_rawResponses){
+    for (const auto& rawResponse : d_rawResponses) {
       totrdatalen += rawResponse.size();
       rawResponses.push_back(rawResponse);
       ++numberOfRecords;
@@ -814,9 +926,8 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu
     }
   }
   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))) {
+    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);
@@ -829,36 +940,44 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu
     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);
+  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 (dq->getMaximumSize() < (sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords*12 /* recordstart */ + totrdatalen)) {
+  if (dnsquestion->getMaximumSize() < (sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords * 12 /* recordstart */ + totrdatalen)) {
     return Action::None;
   }
 
   bool dnssecOK = false;
   bool hadEDNS = false;
-  if (g_addEDNSToSelfGeneratedResponses && queryHasEDNS(*dq)) {
+  if (g_addEDNSToSelfGeneratedResponses && queryHasEDNS(*dnsquestion)) {
     hadEDNS = true;
-    dnssecOK = getEDNSZ(*dq) & EDNS_HEADER_FLAG_DO;
+    dnssecOK = ((getEDNSZ(*dnsquestion) & EDNS_HEADER_FLAG_DO) != 0);
   }
 
-  auto& data = dq->getMutableData();
-  data.resize(sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords*12 /* recordstart */ + totrdatalen); // there goes your EDNS
+  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));
 
-  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
+  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);
-  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");
+  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;
 
@@ -869,50 +988,72 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu
     memcpy(&recordstart[2], &qtype, sizeof(qtype));
     memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen));
 
-    memcpy(dest, recordstart, sizeof(recordstart));
-    dest += sizeof(recordstart);
+    memcpy(dest, recordstart.data(), recordstart.size());
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+    dest += recordstart.size();
     memcpy(dest, wireData.c_str(), wireData.length());
-    dq->getHeader()->ancount++;
+    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){
+    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, 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();
 
-      dq->getHeader()->ancount++;
+      dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) {
+        header.ancount++;
+        return true;
+      });
     }
     raw = true;
   }
   else {
-    for(const auto& addr : addrs) {
+    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));
+      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));
-      dq->getHeader()->ancount++;
+      dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) {
+        header.ancount++;
+        return true;
+      });
     }
   }
 
-  dq->getHeader()->ancount = htons(dq->getHeader()->ancount);
+  auto finalANCount = dnsquestion->getHeader()->ancount;
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [finalANCount](dnsheader& header) {
+    header.ancount = htons(finalANCount);
+    return true;
+  });
 
-  if (hadEDNS && raw == false) {
-    addEDNS(dq->getMutableData(), dq->getMaximumSize(), dnssecOK, g_PayloadSizeSelfGenAnswers, 0);
+  if (hadEDNS && !raw) {
+    addEDNS(dnsquestion->getMutableData(), dnsquestion->getMaximumSize(), dnssecOK, g_PayloadSizeSelfGenAnswers, 0);
   }
 
   return Action::HeaderModify;
@@ -922,106 +1063,82 @@ class SetMacAddrAction : public DNSAction
 {
 public:
   // this action does not stop the processing
-  SetMacAddrAction(uint16_t code) : d_code(code)
+  SetMacAddrAction(uint16_t code) :
+    d_code(code)
   {
   }
 
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    std::string mac = getMACAddress(*dq->remote);
-    if (mac.empty()) {
+    dnsdist::MacAddress mac{};
+    int res = dnsdist::MacAddressesCache::get(dnsquestion->ids.origRemote, mac.data(), mac.size());
+    if (res != 0) {
       return Action::None;
     }
 
     std::string optRData;
-    generateEDNSOption(d_code, mac, optRData);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    generateEDNSOption(d_code, reinterpret_cast<const char*>(mac.data()), optRData);
 
-    if (dq->getHeader()->arcount) {
+    if (dnsquestion->getHeader()->arcount > 0) {
       bool ednsAdded = false;
       bool optionAdded = false;
       PacketBuffer newContent;
-      newContent.reserve(dq->getData().size());
+      newContent.reserve(dnsquestion->getData().size());
 
-      if (!slowRewriteEDNSOptionInQueryWithRecords(dq->getData(), newContent, ednsAdded, d_code, optionAdded, true, optRData)) {
+      if (!slowRewriteEDNSOptionInQueryWithRecords(dnsquestion->getData(), newContent, ednsAdded, d_code, optionAdded, true, optRData)) {
         return Action::None;
       }
 
-      if (newContent.size() > dq->getMaximumSize()) {
+      if (newContent.size() > dnsquestion->getMaximumSize()) {
         return Action::None;
       }
 
-      dq->getMutableData() = std::move(newContent);
-      if (!dq->ednsAdded && ednsAdded) {
-        dq->ednsAdded = true;
+      dnsquestion->getMutableData() = std::move(newContent);
+      if (!dnsquestion->ids.ednsAdded && ednsAdded) {
+        dnsquestion->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);
+    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
-      dq->ednsAdded = true;
+      dnsquestion->ids.ednsAdded = true;
     }
 
     return Action::None;
   }
-  std::string toString() const override
+  [[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, const std::string& data) : d_code(code), d_data(data)
+  SetEDNSOptionAction(uint16_t code, std::string data) :
+    d_code(code), d_data(std::move(data))
   {
   }
 
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    std::string optRData;
-    generateEDNSOption(d_code, d_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->ednsAdded && ednsAdded) {
-        dq->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->ednsAdded = true;
-    }
-
+    setEDNSOption(*dnsquestion, d_code, d_data);
     return Action::None;
   }
 
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "add EDNS Option (code=" + std::to_string(d_code) + ")";
   }
@@ -1035,12 +1152,15 @@ class SetNoRecurseAction : public DNSAction
 {
 public:
   // this action does not stop the processing
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    dq->getHeader()->rd = false;
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) {
+      header.rd = false;
+      return true;
+    });
     return Action::None;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "set rd=0";
   }
@@ -1050,69 +1170,68 @@ class LogAction : public DNSAction, public boost::noncopyable
 {
 public:
   // this action does not stop the processing
-  LogAction()
-  {
-  }
+  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)
+  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())  {
+    if (!reopenLogFile()) {
       throw std::runtime_error("Unable to open file '" + str + "' for logging: " + stringerror());
     }
   }
 
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    auto fp = std::atomic_load_explicit(&d_fp, std::memory_order_acquire);
-    if (!fp) {
+    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>(dq->queryTime->tv_sec), static_cast<unsigned long>(dq->queryTime->tv_nsec), dq->remote->toStringWithPort(), dq->qname->toString(), QType(dq->qtype).toString(), dq->getHeader()->id);
+          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", dq->remote->toStringWithPort(), dq->qname->toString(), QType(dq->qtype).toString(), dq->getHeader()->id);
+          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 = dq->qname->getStorage();
+        const auto& out = dnsquestion->ids.qname.getStorage();
         if (d_includeTimestamp) {
-          uint64_t tv_sec = static_cast<uint64_t>(dq->queryTime->tv_sec);
-          uint32_t tv_nsec = static_cast<uint32_t>(dq->queryTime->tv_nsec);
-          fwrite(&tv_sec, sizeof(tv_sec), 1, fp.get());
-          fwrite(&tv_nsec, sizeof(tv_nsec), 1, fp.get());
+          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 id = dq->getHeader()->id;
-        fwrite(&id, sizeof(id), 1, fp.get());
-        fwrite(out.c_str(), 1, out.size(), fp.get());
-        fwrite(&dq->qtype, sizeof(dq->qtype), 1, fp.get());
-        fwrite(&dq->remote->sin4.sin_family, sizeof(dq->remote->sin4.sin_family), 1, fp.get());
-        if (dq->remote->sin4.sin_family == AF_INET) {
-          fwrite(&dq->remote->sin4.sin_addr.s_addr, sizeof(dq->remote->sin4.sin_addr.s_addr), 1, fp.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 (dq->remote->sin4.sin_family == AF_INET6) {
-          fwrite(&dq->remote->sin6.sin6_addr.s6_addr, sizeof(dq->remote->sin6.sin6_addr.s6_addr), 1, fp.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(&dq->remote->sin4.sin_port, sizeof(dq->remote->sin4.sin_port), 1, fp.get());
+        fwrite(&dnsquestion->ids.origRemote.sin4.sin_port, sizeof(dnsquestion->ids.origRemote.sin4.sin_port), 1, filepointer.get());
       }
       else {
         if (d_includeTimestamp) {
-          fprintf(fp.get(), "[%llu.%lu] Packet from %s for %s %s with id %d\n", static_cast<unsigned long long>(dq->queryTime->tv_sec), static_cast<unsigned long>(dq->queryTime->tv_nsec), dq->remote->toStringWithPort().c_str(), dq->qname->toString().c_str(), QType(dq->qtype).toString().c_str(), dq->getHeader()->id);
+          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(fp.get(), "Packet from %s for %s %s with id %d\n", dq->remote->toStringWithPort().c_str(), dq->qname->toString().c_str(), QType(dq->qtype).toString().c_str(), dq->getHeader()->id);
+          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;
   }
 
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     if (!d_fname.empty()) {
       return "log to " + d_fname;
@@ -1133,20 +1252,21 @@ private:
     // 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) {
+    // 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 fp = std::shared_ptr<FILE>(nfp, fclose);
+    auto filepointer = std::shared_ptr<FILE>(nfp, fclose);
     nfp = nullptr;
 
     if (!d_buffered) {
-      setbuf(fp.get(), 0);
+      setbuf(filepointer.get(), nullptr);
     }
 
-    std::atomic_store_explicit(&d_fp, fp, std::memory_order_release);
+    std::atomic_store_explicit(&d_fp, std::move(filepointer), std::memory_order_release);
     return true;
   }
 
@@ -1162,11 +1282,10 @@ private:
 class LogResponseAction : public DNSResponseAction, public boost::noncopyable
 {
 public:
-  LogResponseAction()
-  {
-  }
+  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)
+  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;
@@ -1177,31 +1296,31 @@ public:
     }
   }
 
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
   {
-    auto fp = std::atomic_load_explicit(&d_fp, std::memory_order_acquire);
-    if (!fp) {
+    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 %d", static_cast<unsigned long long>(dr->queryTime->tv_sec), static_cast<unsigned long>(dr->queryTime->tv_nsec), dr->remote->toStringWithPort(), dr->qname->toString(), QType(dr->qtype).toString(), RCode::to_s(dr->getHeader()->rcode), dr->getHeader()->id);
+          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 %d", dr->remote->toStringWithPort(), dr->qname->toString(), QType(dr->qtype).toString(), RCode::to_s(dr->getHeader()->rcode), dr->getHeader()->id);
+          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(fp.get(), "[%llu.%lu] Answer to %s for %s %s (%s) with id %d\n", static_cast<unsigned long long>(dr->queryTime->tv_sec), static_cast<unsigned long>(dr->queryTime->tv_nsec), dr->remote->toStringWithPort().c_str(), dr->qname->toString().c_str(), QType(dr->qtype).toString().c_str(), RCode::to_s(dr->getHeader()->rcode).c_str(), dr->getHeader()->id);
+        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(fp.get(), "Answer to %s for %s %s (%s) with id %d\n", dr->remote->toStringWithPort().c_str(), dr->qname->toString().c_str(), QType(dr->qtype).toString().c_str(), RCode::to_s(dr->getHeader()->rcode).c_str(), dr->getHeader()->id);
+        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;
   }
 
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     if (!d_fname.empty()) {
       return "log to " + d_fname;
@@ -1222,20 +1341,21 @@ private:
     // 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) {
+    // 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 fp = std::shared_ptr<FILE>(nfp, fclose);
+    auto filepointer = std::shared_ptr<FILE>(nfp, fclose);
     nfp = nullptr;
 
     if (!d_buffered) {
-      setbuf(fp.get(), 0);
+      setbuf(filepointer.get(), nullptr);
     }
 
-    std::atomic_store_explicit(&d_fp, fp, std::memory_order_release);
+    std::atomic_store_explicit(&d_fp, std::move(filepointer), std::memory_order_release);
     return true;
   }
 
@@ -1251,12 +1371,15 @@ class SetDisableValidationAction : public DNSAction
 {
 public:
   // this action does not stop the processing
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    dq->getHeader()->cd = true;
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) {
+      header.cd = true;
+      return true;
+    });
     return Action::None;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "set cd=1";
   }
@@ -1266,12 +1389,12 @@ class SetSkipCacheAction : public DNSAction
 {
 public:
   // this action does not stop the processing
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    dq->skipCache = true;
+    dnsquestion->ids.skipCache = true;
     return Action::None;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "skip cache";
   }
@@ -1280,12 +1403,12 @@ public:
 class SetSkipCacheResponseAction : public DNSResponseAction
 {
 public:
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
   {
-    dr->skipCache = true;
+    response->ids.skipCache = true;
     return Action::None;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "skip cache";
   }
@@ -1295,18 +1418,20 @@ class SetTempFailureCacheTTLAction : public DNSAction
 {
 public:
   // this action does not stop the processing
-  SetTempFailureCacheTTLAction(uint32_t ttl) : d_ttl(ttl)
+  SetTempFailureCacheTTLAction(uint32_t ttl) :
+    d_ttl(ttl)
   {
   }
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    dq->tempFailureTTL = d_ttl;
+    dnsquestion->ids.tempFailureTTL = d_ttl;
     return Action::None;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
-    return "set tempfailure cache ttl to "+std::to_string(d_ttl);
+    return "set tempfailure cache ttl to " + std::to_string(d_ttl);
   }
+
 private:
   uint32_t d_ttl;
 };
@@ -1315,18 +1440,20 @@ 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)
+  SetECSPrefixLengthAction(uint16_t v4Length, uint16_t v6Length) :
+    d_v4PrefixLength(v4Length), d_v6PrefixLength(v6Length)
   {
   }
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    dq->ecsPrefixLength = dq->remote->sin4.sin_family == AF_INET ? d_v4PrefixLength : d_v6PrefixLength;
+    dnsquestion->ecsPrefixLength = dnsquestion->ids.origRemote.sin4.sin_family == AF_INET ? d_v4PrefixLength : d_v6PrefixLength;
     return Action::None;
   }
-  std::string toString() const override
+  [[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;
@@ -1336,33 +1463,34 @@ class SetECSOverrideAction : public DNSAction
 {
 public:
   // this action does not stop the processing
-  SetECSOverrideAction(bool ecsOverride) : d_ecsOverride(ecsOverride)
+  SetECSOverrideAction(bool ecsOverride) :
+    d_ecsOverride(ecsOverride)
   {
   }
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    dq->ecsOverride = d_ecsOverride;
+    dnsquestion->ecsOverride = d_ecsOverride;
     return Action::None;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
-    return "set ECS override to " + std::to_string(d_ecsOverride);
+    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* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    dq->useECS = false;
+    dnsquestion->useECS = false;
     return Action::None;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "disable ECS";
   }
@@ -1372,29 +1500,29 @@ 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& v4Netmask) :
+    d_v4(v4Netmask), d_hasV6(false)
   {
   }
 
-  SetECSAction(const Netmask& v4, const Netmask& v6): d_v4(v4), d_v6(v6), d_hasV6(true)
+  SetECSAction(const Netmask& v4Netmask, const Netmask& v6Netmask) :
+    d_v4(v4Netmask), d_v6(v6Netmask), d_hasV6(true)
   {
   }
 
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    dq->ecsSet = true;
-
     if (d_hasV6) {
-      dq->ecs = dq->remote->isIPv4() ? d_v4 : d_v6;
+      dnsquestion->ecs = std::make_unique<Netmask>(dnsquestion->ids.origRemote.isIPv4() ? d_v4 : d_v6);
     }
     else {
-      dq->ecs = d_v4;
+      dnsquestion->ecs = std::make_unique<Netmask>(d_v4);
     }
 
     return Action::None;
   }
 
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     std::string result = "set ECS to " + d_v4.toString();
     if (d_hasV6) {
@@ -1415,103 +1543,204 @@ static DnstapMessage::ProtocolType ProtocolToDNSTap(dnsdist::Protocol protocol)
   if (protocol == dnsdist::Protocol::DoUDP) {
     return DnstapMessage::ProtocolType::DoUDP;
   }
-  else if (protocol == dnsdist::Protocol::DoTCP) {
+  if (protocol == dnsdist::Protocol::DoTCP) {
     return DnstapMessage::ProtocolType::DoTCP;
   }
-  else if (protocol == dnsdist::Protocol::DoT) {
+  if (protocol == dnsdist::Protocol::DoT) {
     return DnstapMessage::ProtocolType::DoT;
   }
-  else if (protocol == dnsdist::Protocol::DoH) {
+  if (protocol == dnsdist::Protocol::DoH || protocol == dnsdist::Protocol::DoH3) {
     return DnstapMessage::ProtocolType::DoH;
   }
-  else if (protocol == dnsdist::Protocol::DNSCryptUDP) {
+  if (protocol == dnsdist::Protocol::DNSCryptUDP) {
     return DnstapMessage::ProtocolType::DNSCryptUDP;
   }
-  else if (protocol == dnsdist::Protocol::DNSCryptTCP) {
+  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(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)
+  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* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, 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->remote, dq->local, protocol, reinterpret_cast<const char*>(dq->getData().data()), dq->getData().size(), dq->queryTime, nullptr);
+    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)(dq, &message);
+        (*d_alterFunc)(dnsquestion, &message);
       }
     }
 
-    d_logger->queueData(data);
+    data = message.getBuffer();
+    remoteLoggerQueueData(*d_logger, data);
 
     return Action::None;
   }
-  std::string toString() const override
+  [[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;
+  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(std::shared_ptr<RemoteLoggerInterface>& logger, boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)> > alterFunc, const std::string& serverID, const std::string& ipEncryptKey): d_logger(logger), d_alterFunc(alterFunc), d_serverID(serverID), d_ipEncryptKey(ipEncryptKey)
+  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* dq, std::string* ruleresult) const override
+
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    if (!dq->uniqueId) {
-      dq->uniqueId = getUniqueID();
+    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(*dq);
+    DNSDistProtoBufMessage message(*dnsquestion);
     if (!d_serverID.empty()) {
       message.setServerIdentity(d_serverID);
     }
 
 #if HAVE_IPCIPHER
-    if (!d_ipEncryptKey.empty())
-    {
-      message.setRequestor(encryptCA(*dq->remote, d_ipEncryptKey));
+    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)(dq, &message);
+      (*d_alterFunc)(dnsquestion, &message);
     }
 
     static thread_local std::string data;
     data.clear();
     message.serialize(data);
-    d_logger->queueData(data);
+    remoteLoggerQueueData(*d_logger, data);
 
     return Action::None;
   }
-  std::string toString() const override
+  [[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;
+  boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)>> d_alterFunc;
   std::string d_serverID;
   std::string d_ipEncryptKey;
 };
@@ -1522,21 +1751,23 @@ class SNMPTrapAction : public DNSAction
 {
 public:
   // this action does not stop the processing
-  SNMPTrapAction(const std::string& reason): d_reason(reason)
+  SNMPTrapAction(std::string reason) :
+    d_reason(std::move(reason))
   {
   }
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    if (g_snmpAgent && g_snmpTrapsEnabled) {
-      g_snmpAgent->sendDNSTrap(*dq, d_reason);
+    if (g_snmpAgent != nullptr && g_snmpTrapsEnabled) {
+      g_snmpAgent->sendDNSTrap(*dnsquestion, d_reason);
     }
 
     return Action::None;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "send SNMP trap";
   }
+
 private:
   std::string d_reason;
 };
@@ -1545,19 +1776,21 @@ 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)
+  SetTagAction(std::string tag, std::string value) :
+    d_tag(std::move(tag)), d_value(std::move(value))
   {
   }
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    dq->setTag(d_tag, d_value);
+    dnsquestion->setTag(d_tag, d_value);
 
     return Action::None;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "set tag '" + d_tag + "' to value '" + d_value + "'";
   }
+
 private:
   std::string d_tag;
   std::string d_value;
@@ -1568,67 +1801,84 @@ class DnstapLogResponseAction : public DNSResponseAction, public boost::noncopya
 {
 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)
+  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* dr, std::string* ruleresult) const override
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
   {
     static thread_local std::string data;
-    struct timespec now;
+    struct timespec now = {};
     gettime(&now, true);
     data.clear();
 
-    DnstapMessage::ProtocolType protocol = ProtocolToDNSTap(dr->getProtocol());
-    DnstapMessage message(data, DnstapMessage::MessageType::client_response, d_identity, dr->remote, dr->local, protocol, reinterpret_cast<const char*>(dr->getData().data()), dr->getData().size(), dr->queryTime, &now);
+    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)(dr, &message);
+        (*d_alterFunc)(response, &message);
       }
     }
 
-    d_logger->queueData(data);
+    data = message.getBuffer();
+    remoteLoggerQueueData(*d_logger, data);
 
     return Action::None;
   }
-  std::string toString() const override
+  [[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;
+  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): d_logger(logger), d_alterFunc(alterFunc), d_serverID(serverID), d_ipEncryptKey(ipEncryptKey), d_includeCNAME(includeCNAME)
+  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* dr, std::string* ruleresult) const override
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
   {
-    if (!dr->uniqueId) {
-      dr->uniqueId = getUniqueID();
+    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(*dr, d_includeCNAME);
+    DNSDistProtoBufMessage message(*response, d_includeCNAME);
     if (!d_serverID.empty()) {
       message.setServerIdentity(d_serverID);
     }
 
 #if HAVE_IPCIPHER
-    if (!d_ipEncryptKey.empty())
-    {
-      message.setRequestor(encryptCA(*dr->remote, d_ipEncryptKey));
+    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)(dr, &message);
+      (*d_alterFunc)(response, &message);
     }
 
     static thread_local std::string data;
@@ -1638,15 +1888,19 @@ public:
 
     return Action::None;
   }
-  std::string toString() const override
+  [[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;
+  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;
 };
 
@@ -1655,11 +1909,11 @@ private:
 class DropResponseAction : public DNSResponseAction
 {
 public:
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
   {
     return Action::Drop;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "drop";
   }
@@ -1668,11 +1922,11 @@ public:
 class AllowResponseAction : public DNSResponseAction
 {
 public:
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
   {
     return Action::Allow;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "allow";
   }
@@ -1681,18 +1935,20 @@ public:
 class DelayResponseAction : public DNSResponseAction
 {
 public:
-  DelayResponseAction(int msec) : d_msec(msec)
+  DelayResponseAction(int msec) :
+    d_msec(msec)
   {
   }
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
   {
     *ruleresult = std::to_string(d_msec);
     return Action::Delay;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
-    return "delay by "+std::to_string(d_msec)+ " msec";
+    return "delay by " + std::to_string(d_msec) + " ms";
   }
+
 private:
   int d_msec;
 };
@@ -1702,21 +1958,23 @@ class SNMPTrapResponseAction : public DNSResponseAction
 {
 public:
   // this action does not stop the processing
-  SNMPTrapResponseAction(const std::string& reason): d_reason(reason)
+  SNMPTrapResponseAction(std::string reason) :
+    d_reason(std::move(reason))
   {
   }
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
   {
     if (g_snmpAgent && g_snmpTrapsEnabled) {
-      g_snmpAgent->sendDNSTrap(*dr, d_reason);
+      g_snmpAgent->sendDNSTrap(*response, d_reason);
     }
 
     return Action::None;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "send SNMP trap";
   }
+
 private:
   std::string d_reason;
 };
@@ -1726,19 +1984,21 @@ 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)
+  SetTagResponseAction(std::string tag, std::string value) :
+    d_tag(std::move(tag)), d_value(std::move(value))
   {
   }
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
   {
-    dr->setTag(d_tag, d_value);
+    response->setTag(d_tag, d_value);
 
     return Action::None;
   }
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "set tag '" + d_tag + "' to value '" + d_value + "'";
   }
+
 private:
   std::string d_tag;
   std::string d_value;
@@ -1747,59 +2007,57 @@ private:
 class ClearRecordTypesResponseAction : public DNSResponseAction, public boost::noncopyable
 {
 public:
-  ClearRecordTypesResponseAction() {}
-
-  ClearRecordTypesResponseAction(const std::set<QType>& qtypes) : d_qtypes(qtypes)
+  ClearRecordTypesResponseAction(std::unordered_set<QType> qtypes) :
+    d_qtypes(std::move(qtypes))
   {
   }
 
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
   {
-    if (d_qtypes.size() > 0) {
-      clearDNSPacketRecordTypes(dr->getMutableData(), d_qtypes);
+    if (!d_qtypes.empty()) {
+      clearDNSPacketRecordTypes(response->getMutableData(), d_qtypes);
     }
     return DNSResponseAction::Action::None;
   }
 
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "clear record types";
   }
 
 private:
-  std::set<QType> d_qtypes{};
+  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)
+  ContinueAction(std::shared_ptr<DNSAction>& action) :
+    d_action(action)
   {
   }
 
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
     if (d_action) {
       /* call the action */
-      auto action = (*d_action)(dq, ruleresult);
+      auto action = (*d_action)(dnsquestion, ruleresult);
       bool drop = false;
       /* apply the changes if needed (pool selection, flags, etc */
-      processRulesResult(action, *dq, *ruleresult, drop);
+      processRulesResult(action, *dnsquestion, *ruleresult, drop);
     }
 
     /* but ignore the resulting action no matter what */
     return Action::None;
   }
 
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     if (d_action) {
       return "continue after: " + (d_action ? d_action->toString() : "");
     }
-    else {
-      return "no op";
-    }
+    return "no op";
   }
 
 private:
@@ -1807,32 +2065,41 @@ private:
 };
 
 #ifdef HAVE_DNS_OVER_HTTPS
-class HTTPStatusAction: public DNSAction
+class HTTPStatusAction : public DNSAction
 {
 public:
-  HTTPStatusAction(int code, const PacketBuffer& body, const std::string& contentType): d_body(body), d_contentType(contentType), d_code(code)
+  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* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    if (!dq->du) {
+    if (!dnsquestion->ids.du) {
       return Action::None;
     }
 
-    dq->du->setHTTPResponse(d_code, PacketBuffer(d_body), d_contentType);
-    dq->getHeader()->qr = true; // for good measure
-    setResponseHeadersFromConfig(*dq->getHeader(), d_responseConfig);
+    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;
   }
 
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "return an HTTP status of " + std::to_string(d_code);
   }
 
-  ResponseConfig d_responseConfig;
+  [[nodiscard]] ResponseConfig& getResponseConfig()
+  {
+    return d_responseConfig;
+  }
+
 private:
+  ResponseConfig d_responseConfig;
   PacketBuffer d_body;
   std::string d_contentType;
   int d_code;
@@ -1844,26 +2111,27 @@ 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)
+  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* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    std::vector<std::string> keys = d_key->getKeys(*dq);
+    std::vector<std::string> keys = d_key->getKeys(*dnsquestion);
     std::string result;
     for (const auto& key : keys) {
-      if (d_kvs->getValue(key, result) == true) {
+      if (d_kvs->getValue(key, result)) {
         break;
       }
     }
 
-    dq->setTag(d_tag, std::move(result));
+    dnsquestion->setTag(d_tag, std::move(result));
 
     return Action::None;
   }
 
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "lookup key-value store based on '" + d_key->toString() + "' and set the result in tag '" + d_tag + "'";
   }
@@ -1878,26 +2146,27 @@ 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)
+  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* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    std::vector<std::string> keys = d_key->getKeys(*dq);
+    std::vector<std::string> keys = d_key->getKeys(*dnsquestion);
     std::string result;
     for (const auto& key : keys) {
-      if (d_kvs->getRangeValue(key, result) == true) {
+      if (d_kvs->getRangeValue(key, result)) {
         break;
       }
     }
 
-    dq->setTag(d_tag, std::move(result));
+    dnsquestion->setTag(d_tag, std::move(result));
 
     return Action::None;
   }
 
-  std::string toString() const override
+  [[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 + "'";
   }
@@ -1909,68 +2178,128 @@ private:
 };
 #endif /* defined(HAVE_LMDB) || defined(HAVE_CDB) */
 
-class NegativeAndSOAAction: public DNSAction
+class MaxReturnedTTLAction : 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): 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)
+  MaxReturnedTTLAction(uint32_t cap) :
+    d_cap(cap)
   {
   }
 
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, 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)) {
-      return Action::None;
-    }
-
-    setResponseHeadersFromConfig(*dq->getHeader(), d_responseConfig);
-
-    return Action::Allow;
+    dnsquestion->ids.ttlCap = d_cap;
+    return DNSAction::Action::None;
   }
 
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
-    return std::string(d_nxd ? "NXD " : "NODATA") + " with SOA";
+    return "cap the TTL of the returned response to " + std::to_string(d_cap);
   }
 
-  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;
+  uint32_t d_cap;
 };
 
-class SetProxyProtocolValuesAction : public DNSAction
+class MaxReturnedTTLResponseAction : public DNSResponseAction
 {
 public:
-  // this action does not stop the processing
-  SetProxyProtocolValuesAction(const std::vector<std::pair<uint8_t, std::string>>& values)
+  MaxReturnedTTLResponseAction(uint32_t cap) :
+    d_cap(cap)
   {
-    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
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
   {
-    if (!dq->proxyProtocolValues) {
-      dq->proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
-    }
-
-    *(dq->proxyProtocolValues) = d_values;
+    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;
   }
 
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "set Proxy-Protocol values";
   }
@@ -1983,22 +2312,23 @@ 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)
+  SetAdditionalProxyProtocolValueAction(uint8_t type, std::string value) :
+    d_value(std::move(value)), d_type(type)
   {
   }
 
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
   {
-    if (!dq->proxyProtocolValues) {
-      dq->proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
+    if (!dnsquestion->proxyProtocolValues) {
+      dnsquestion->proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
     }
 
-    dq->proxyProtocolValues->push_back({ d_value, d_type });
+    dnsquestion->proxyProtocolValues->push_back({d_value, d_type});
 
     return Action::None;
   }
 
-  std::string toString() const override
+  [[nodiscard]] std::string toString() const override
   {
     return "add a Proxy-Protocol value of type " + std::to_string(d_type);
   }
@@ -2008,216 +2338,316 @@ private:
   uint8_t d_type;
 };
 
-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) {
+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;
+  boost::uuids::uuid uuid{};
+  uint64_t creationOrder = 0;
   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});
-    });
+  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});
+  });
 }
 
-typedef std::unordered_map<std::string, boost::variant<bool, uint32_t> > responseParams_t;
+using responseParams_t = std::unordered_map<std::string, boost::variant<bool, uint32_t>>;
 
-static void parseResponseConfig(boost::optional<responseParams_t> vars, ResponseConfig& config)
+static void parseResponseConfig(boost::optional<responseParams_t>& vars, ResponseConfig& config)
 {
-  if (vars) {
-    if (vars->count("ttl")) {
-      config.ttl = boost::get<uint32_t>((*vars)["ttl"]);
-    }
-    if (vars->count("aa")) {
-      config.setAA = boost::get<bool>((*vars)["aa"]);
-    }
-    if (vars->count("ad")) {
-      config.setAD = boost::get<bool>((*vars)["ad"]);
-    }
-    if (vars->count("ra")) {
-      config.setRA = boost::get<bool>((*vars)["ra"]);
-    }
-  }
+  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)
+void setResponseHeadersFromConfig(dnsheader& dnsheader, const ResponseConfig& config)
 {
   if (config.setAA) {
-    dh.aa = *config.setAA;
+    dnsheader.aa = *config.setAA;
   }
   if (config.setAD) {
-    dh.ad = *config.setAD;
+    dnsheader.ad = *config.setAD;
   }
   else {
-    dh.ad = false;
+    dnsheader.ad = false;
   }
   if (config.setRA) {
-    dh.ra = *config.setRA;
+    dnsheader.ra = *config.setRA;
   }
   else {
-    dh.ra = dh.rd; // for good measure
+    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", [](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);
+  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");
+    DNSDistRuleAction ruleaction({std::move(rule), std::move(action), std::move(name), uuid, creationOrder});
+    return std::make_shared<DNSDistRuleAction>(ruleaction);
+  });
 
-      auto rule = makeRule(dnsrule);
-      DNSDistRuleAction ra({std::move(rule), action, std::move(name), uuid, creationOrder});
-      return std::make_shared<DNSDistRuleAction>(ra);
-    });
+  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()?");
+    }
 
-  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);
+  });
 
-      addAction(&g_ruleactions, var, boost::get<std::shared_ptr<DNSAction> >(era), params);
-    });
+  luaCtx.writeFunction("addResponseAction", [](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("addResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?");
+    }
 
-  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);
+  });
 
-      addAction(&g_respruleactions, var, boost::get<std::shared_ptr<DNSResponseAction> >(era), params);
-    });
+  luaCtx.writeFunction("addCacheHitResponseAction", [](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("addCacheHitResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?");
+    }
 
-  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);
+  });
 
-      addAction(&g_cachehitrespruleactions, var, boost::get<std::shared_ptr<DNSResponseAction> >(era), params);
-    });
+  luaCtx.writeFunction("addCacheInsertedResponseAction", [](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("addCacheInsertedResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?");
+    }
 
-  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_cacheInsertedRespRuleActions, var, boost::get<std::shared_ptr<DNSResponseAction>>(era), params);
+  });
 
-      addAction(&g_selfansweredrespruleactions, var, boost::get<std::shared_ptr<DNSResponseAction> >(era), params);
-    });
+  luaCtx.writeFunction("addSelfAnsweredResponseAction", [](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("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.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 = g_ruleactions.getCopy();
-      if(num < ruleactions.size())
-        ret=ruleactions[num].d_action;
-      return ret;
-    });
+    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));
-    });
+    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(func));
-    });
+    setLuaSideEffect();
+    return std::shared_ptr<DNSAction>(new LuaFFIAction(std::move(func)));
+  });
 
-  luaCtx.writeFunction("LuaFFIPerThreadAction", [](std::string code) {
-      setLuaSideEffect();
-      return std::shared_ptr<DNSAction>(new LuaFFIPerThreadAction(code));
-    });
+  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);
-    });
+    return std::shared_ptr<DNSAction>(new SetNoRecurseAction);
+  });
 
   luaCtx.writeFunction("SetMacAddrAction", [](int code) {
-      return std::shared_ptr<DNSAction>(new SetMacAddrAction(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));
-    });
+    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.get_value_or(true)));
-    });
+  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));
-    });
+    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.get_value_or(true)));
-    });
+  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 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));
-        }
+    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 sa = std::dynamic_pointer_cast<SpoofAction>(ret);
-      parseResponseConfig(vars, sa->d_responseConfig);
-      return ret;
-    });
+    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 sa = std::dynamic_pointer_cast<SpoofSVCAction>(ret);
-      parseResponseConfig(vars, sa->d_responseConfig);
-      return ret;
-    });
+    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& a, boost::optional<responseParams_t> vars) {
-      auto ret = std::shared_ptr<DNSAction>(new SpoofAction(DNSName(a)));
-      auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
-      parseResponseConfig(vars, sa->d_responseConfig);
-      return ret;
-    });
+  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(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);
-        }
+    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);
       }
-
-      auto ret = std::shared_ptr<DNSAction>(new SpoofAction(raws));
-      auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
-      parseResponseConfig(vars, sa->d_responseConfig);
-      return ret;
-    });
+    }
+    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)) {
@@ -2225,252 +2655,338 @@ void setupLuaActions(LuaContext& luaCtx)
     }
     auto ret = std::shared_ptr<DNSAction>(new SpoofAction(response.c_str(), len));
     return ret;
-    });
+  });
 
   luaCtx.writeFunction("DropAction", []() {
-      return std::shared_ptr<DNSAction>(new DropAction);
-    });
+    return std::shared_ptr<DNSAction>(new DropAction);
+  });
 
   luaCtx.writeFunction("AllowAction", []() {
-      return std::shared_ptr<DNSAction>(new AllowAction);
-    });
+    return std::shared_ptr<DNSAction>(new AllowAction);
+  });
 
   luaCtx.writeFunction("NoneAction", []() {
-      return std::shared_ptr<DNSAction>(new NoneAction);
-    });
+    return std::shared_ptr<DNSAction>(new NoneAction);
+  });
 
   luaCtx.writeFunction("DelayAction", [](int msec) {
-      return std::shared_ptr<DNSAction>(new DelayAction(msec));
-    });
+    return std::shared_ptr<DNSAction>(new DelayAction(msec));
+  });
 
   luaCtx.writeFunction("TCAction", []() {
-      return std::shared_ptr<DNSAction>(new 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);
-    });
+    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));
-    });
+    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));
-    });
+    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) {
-      return std::shared_ptr<DNSResponseAction>(new LimitTTLResponseAction(min, max));
-    });
+  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));
-    });
+    return std::shared_ptr<DNSResponseAction>(new LimitTTLResponseAction(min));
+  });
 
   luaCtx.writeFunction("SetMaxTTLResponseAction", [](uint32_t max) {
-      return std::shared_ptr<DNSResponseAction>(new LimitTTLResponseAction(0, 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::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);
-        }
+    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(qtypes));
-    });
+    }
+    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->d_responseConfig);
-      return ret;
-    });
+    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->d_responseConfig);
-      return ret;
-    });
+    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);
-    });
+    return std::shared_ptr<DNSAction>(new SetSkipCacheAction);
+  });
 
   luaCtx.writeFunction("SetSkipCacheResponseAction", []() {
-      return std::shared_ptr<DNSResponseAction>(new SetSkipCacheResponseAction);
-    });
+    return std::shared_ptr<DNSResponseAction>(new SetSkipCacheResponseAction);
+  });
 
   luaCtx.writeFunction("SetTempFailureCacheTTLAction", [](int maxTTL) {
-      return std::shared_ptr<DNSAction>(new SetTempFailureCacheTTLAction(maxTTL));
-    });
+    return std::shared_ptr<DNSAction>(new SetTempFailureCacheTTLAction(maxTTL));
+  });
 
   luaCtx.writeFunction("DropResponseAction", []() {
-      return std::shared_ptr<DNSResponseAction>(new DropResponseAction);
-    });
+    return std::shared_ptr<DNSResponseAction>(new DropResponseAction);
+  });
 
   luaCtx.writeFunction("AllowResponseAction", []() {
-      return std::shared_ptr<DNSResponseAction>(new AllowResponseAction);
-    });
+    return std::shared_ptr<DNSResponseAction>(new AllowResponseAction);
+  });
 
   luaCtx.writeFunction("DelayResponseAction", [](int msec) {
-      return std::shared_ptr<DNSResponseAction>(new DelayResponseAction(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));
-    });
+    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(func));
-    });
+    setLuaSideEffect();
+    return std::shared_ptr<DNSResponseAction>(new LuaFFIResponseAction(std::move(func)));
+  });
 
-  luaCtx.writeFunction("LuaFFIPerThreadResponseAction", [](std::string code) {
-      setLuaSideEffect();
-      return std::shared_ptr<DNSResponseAction>(new LuaFFIPerThreadResponseAction(code));
-    });
+  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) {
-      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."));
-        }
+  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 serverID;
-      std::string ipEncryptKey;
-      if (vars) {
-        if (vars->count("serverID")) {
-          serverID = boost::get<std::string>((*vars)["serverID"]);
-        }
-        if (vars->count("ipEncryptKey")) {
-          ipEncryptKey = boost::get<std::string>((*vars)["ipEncryptKey"]);
+    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));
         }
       }
+    }
 
-      return std::shared_ptr<DNSAction>(new RemoteLogAction(logger, alterFunc, serverID, ipEncryptKey));
-    });
+    checkAllParametersConsumed("RemoteLogAction", vars);
 
-  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) {
-      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.");
-        }
+    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));
       }
+    }
 
-      std::string serverID;
-      std::string ipEncryptKey;
-      if (vars) {
-        if (vars->count("serverID")) {
-          serverID = boost::get<std::string>((*vars)["serverID"]);
-        }
-        if (vars->count("ipEncryptKey")) {
-          ipEncryptKey = boost::get<std::string>((*vars)["ipEncryptKey"]);
+    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));
         }
       }
+    }
 
-      return std::shared_ptr<DNSResponseAction>(new RemoteLogResponseAction(logger, alterFunc, serverID, ipEncryptKey, includeCNAME ? *includeCNAME : false));
-    });
+    checkAllParametersConsumed("RemoteLogResponseAction", vars);
 
-  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));
-    });
+    return std::shared_ptr<DNSResponseAction>(new RemoteLogResponseAction(config));
+  });
 
-  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));
-    });
+  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) {
-      return std::shared_ptr<DNSAction>(new TeeAction(ComboAddress(remote, 53), addECS ? *addECS : false));
-    });
+  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));
-    });
+    return std::shared_ptr<DNSAction>(new SetECSPrefixLengthAction(v4PrefixLength, v6PrefixLength));
+  });
 
   luaCtx.writeFunction("SetECSOverrideAction", [](bool ecsOverride) {
-      return std::shared_ptr<DNSAction>(new SetECSOverrideAction(ecsOverride));
-    });
+    return std::shared_ptr<DNSAction>(new SetECSOverrideAction(ecsOverride));
+  });
 
   luaCtx.writeFunction("SetDisableECSAction", []() {
-      return std::shared_ptr<DNSAction>(new 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)));
-    });
+  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 : ""));
-    });
+    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 : ""));
-    });
+    return std::shared_ptr<DNSResponseAction>(new SNMPTrapResponseAction(reason ? *reason : ""));
+  });
 #endif /* HAVE_NET_SNMP */
 
-  luaCtx.writeFunction("SetTagAction", [](std::string tag, std::string value) {
-      return std::shared_ptr<DNSAction>(new SetTagAction(tag, value));
-    });
+  luaCtx.writeFunction("SetTagAction", [](const std::string& tag, const std::string& value) {
+    return std::shared_ptr<DNSAction>(new SetTagAction(tag, value));
+  });
 
-  luaCtx.writeFunction("SetTagResponseAction", [](std::string tag, std::string value) {
-      return std::shared_ptr<DNSResponseAction>(new SetTagResponseAction(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));
-    });
+    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);
-      return ret;
-    });
+    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));
-    });
+    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));
-    });
+    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) {
-      auto ret = std::shared_ptr<DNSAction>(new NegativeAndSOAAction(nxd, DNSName(zone), ttl, DNSName(mname), DNSName(rname), serial, refresh, retry, expire, minimum));
-      auto action = std::dynamic_pointer_cast<NegativeAndSOAAction>(ret);
-      parseResponseConfig(vars, action->d_responseConfig);
-      return ret;
-    });
+    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));
-    });
+    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 04e37de43a711e9eb5ad56220df3c021b3a49c00..4512fc5ef4f81b4a6d8e06da64005a931f22b5c0 100644 (file)
  * 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& dq) -> const ComboAddress { return *dq.local; }, [](DNSQuestion& dq, const ComboAddress newLocal) { (void) newLocal; });
-  luaCtx.registerMember<const DNSName (DNSQuestion::*)>("qname", [](const DNSQuestion& dq) -> const DNSName { return *dq.qname; }, [](DNSQuestion& dq, const DNSName newName) { (void) newName; });
-  luaCtx.registerMember<uint16_t (DNSQuestion::*)>("qtype", [](const DNSQuestion& dq) -> uint16_t { return dq.qtype; }, [](DNSQuestion& dq, uint16_t newType) { (void) newType; });
-  luaCtx.registerMember<uint16_t (DNSQuestion::*)>("qclass", [](const DNSQuestion& dq) -> uint16_t { return dq.qclass; }, [](DNSQuestion& dq, uint16_t newClass) { (void) newClass; });
-  luaCtx.registerMember<int (DNSQuestion::*)>("rcode", [](const DNSQuestion& dq) -> int { return dq.getHeader()->rcode; }, [](DNSQuestion& dq, int newRCode) { dq.getHeader()->rcode = newRCode; });
-  luaCtx.registerMember<const ComboAddress (DNSQuestion::*)>("remoteaddr", [](const DNSQuestion& dq) -> const ComboAddress { return *dq.remote; }, [](DNSQuestion& dq, const ComboAddress newRemote) { (void) newRemote; });
+  luaCtx.registerMember<const ComboAddress (DNSQuestion::*)>("localaddr", [](const DNSQuestion& dq) -> const ComboAddress { return dq.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 static_cast<int>(dq.getHeader()->rcode); }, [](DNSQuestion& dq, int newRCode) {
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.getMutableData(), [newRCode](dnsheader& header) {
+      header.rcode = static_cast<decltype(header.rcode)>(newRCode);
+      return true;
+    });
+  });
+  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<dnsheader* (DNSQuestion::*)>("dh", [](const DNSQuestion& dq) -> dnsheader* { return dq.getMutableHeader(); }, [](DNSQuestion& dq, const dnsheader* dh) {
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.getMutableData(), [&dh](dnsheader& header) {
+      header = *dh;
+      return true;
+    });
+  });
   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.skipCache; }, [](DNSQuestion& dq, bool newSkipCache) { dq.skipCache = newSkipCache; });
+  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.tempFailureTTL;
+        return dq.ids.tempFailureTTL;
       },
       [](DNSQuestion& dq, boost::optional<uint32_t> newValue) {
-        dq.tempFailureTTL = 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;
+    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());
+
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(buffer, [oldID](dnsheader& header) {
+      header.id = oldID;
+      return true;
+    });
+  });
   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;
@@ -78,6 +142,10 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx)
     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) {
@@ -95,24 +163,24 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx)
       }
     });
   luaCtx.registerFunction<string(DNSQuestion::*)(std::string)const>("getTag", [](const DNSQuestion& dq, const std::string& strLabel) {
-      if (!dq.qTag) {
+      if (!dq.ids.qTag) {
         return string();
       }
 
       std::string strValue;
-      const auto it = dq.qTag->find(strLabel);
-      if (it == dq.qTag->cend()) {
+      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.qTag) {
+      if (!dq.ids.qTag) {
         QTag empty;
         return empty;
       }
 
-      return *dq.qTag;
+      return *dq.ids.qTag;
     });
 
   luaCtx.registerFunction<void(DNSQuestion::*)(LuaArray<std::string>)>("setProxyProtocolValues", [](DNSQuestion& dq, const LuaArray<std::string>& values) {
@@ -134,7 +202,7 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx)
       dq.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
     }
 
-    dq.proxyProtocolValues->push_back({value, static_cast<uint8_t>(type)});
+    dq.proxyProtocolValues->push_back({std::move(value), static_cast<uint8_t>(type)});
   });
 
   luaCtx.registerFunction<LuaArray<std::string>(DNSQuestion::*)()>("getProxyProtocolValues", [](const DNSQuestion& dq) {
@@ -151,7 +219,15 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx)
     return result;
   });
 
-  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) {
+  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>>&, 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);
@@ -160,8 +236,8 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx)
             data.push_back(resp.second);
           }
           std::string result;
-          SpoofAction sa(data);
-          sa(&dq, &result);
+          SpoofAction tempSpoofAction(data);
+          tempSpoofAction(&dnsQuestion, &result);
          return;
       }
       if (response.type() == typeid(LuaArray<std::string>)) {
@@ -172,33 +248,159 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx)
             data.push_back(resp.second);
           }
           std::string result;
-          SpoofAction sa(data);
-          sa(&dq, &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& dq, uint16_t code, const std::string& data) {
+    setEDNSOption(dq, 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& 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(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 AsynchronousObject(std::move(query));
+  });
+
   /* LuaWrapper doesn't support inheritance */
-  luaCtx.registerMember<const ComboAddress (DNSResponse::*)>("localaddr", [](const DNSResponse& dq) -> const ComboAddress { return *dq.local; }, [](DNSResponse& dq, const ComboAddress newLocal) { (void) newLocal; });
-  luaCtx.registerMember<const DNSName (DNSResponse::*)>("qname", [](const DNSResponse& dq) -> const DNSName { return *dq.qname; }, [](DNSResponse& dq, const DNSName newName) { (void) newName; });
-  luaCtx.registerMember<uint16_t (DNSResponse::*)>("qtype", [](const DNSResponse& dq) -> uint16_t { return dq.qtype; }, [](DNSResponse& dq, uint16_t newType) { (void) newType; });
-  luaCtx.registerMember<uint16_t (DNSResponse::*)>("qclass", [](const DNSResponse& dq) -> uint16_t { return dq.qclass; }, [](DNSResponse& dq, uint16_t newClass) { (void) newClass; });
-  luaCtx.registerMember<int (DNSResponse::*)>("rcode", [](const DNSResponse& dq) -> int { return dq.getHeader()->rcode; }, [](DNSResponse& dq, int newRCode) { dq.getHeader()->rcode = newRCode; });
-  luaCtx.registerMember<const ComboAddress (DNSResponse::*)>("remoteaddr", [](const DNSResponse& dq) -> const ComboAddress { return *dq.remote; }, [](DNSResponse& dq, const ComboAddress newRemote) { (void) newRemote; });
-  luaCtx.registerMember<dnsheader* (DNSResponse::*)>("dh", [](const DNSResponse& dr) -> dnsheader* { return const_cast<DNSResponse&>(dr).getHeader(); }, [](DNSResponse& dr, const dnsheader* dh) { *(dr.getHeader()) = *dh; });
+  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 static_cast<int>(dq.getHeader()->rcode); }, [](DNSResponse& dq, int newRCode) {
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.getMutableData(), [newRCode](dnsheader& header) {
+      header.rcode = static_cast<decltype(header.rcode)>(newRCode);
+      return true;
+    });
+  });
+  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 dr.getMutableHeader(); }, [](DNSResponse& dr, const dnsheader* dh) {
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dr.getMutableData(), [&dh](dnsheader& header) {
+      header = *dh;
+      return true;
+    });
+  });
   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.skipCache; }, [](DNSResponse& dq, bool newSkipCache) { dq.skipCache = newSkipCache; });
+  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());
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(buffer, [oldID](dnsheader& header) {
+      header.id = oldID;
+      return true;
+    });
+  });
+
   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;
@@ -220,30 +422,34 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx)
       }
     });
   luaCtx.registerFunction<string(DNSResponse::*)(std::string)const>("getTag", [](const DNSResponse& dr, const std::string& strLabel) {
-      if (!dr.qTag) {
+      if (!dr.ids.qTag) {
         return string();
       }
 
       std::string strValue;
-      const auto it = dr.qTag->find(strLabel);
-      if (it == dr.qTag->cend()) {
+      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.qTag) {
+      if (!dr.ids.qTag) {
         QTag empty;
         return empty;
       }
 
-      return *dr.qTag;
+      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) {
@@ -254,47 +460,47 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx)
 
 #ifdef HAVE_DNS_OVER_HTTPS
     luaCtx.registerFunction<std::string(DNSQuestion::*)(void)const>("getHTTPPath", [](const DNSQuestion& dq) {
-      if (dq.du == nullptr) {
+      if (dq.ids.du == nullptr) {
         return std::string();
       }
-      return dq.du->getHTTPPath();
+      return dq.ids.du->getHTTPPath();
     });
 
     luaCtx.registerFunction<std::string(DNSQuestion::*)(void)const>("getHTTPQueryString", [](const DNSQuestion& dq) {
-      if (dq.du == nullptr) {
+      if (dq.ids.du == nullptr) {
         return std::string();
       }
-      return dq.du->getHTTPQueryString();
+      return dq.ids.du->getHTTPQueryString();
     });
 
     luaCtx.registerFunction<std::string(DNSQuestion::*)(void)const>("getHTTPHost", [](const DNSQuestion& dq) {
-      if (dq.du == nullptr) {
+      if (dq.ids.du == nullptr) {
         return std::string();
       }
-      return dq.du->getHTTPHost();
+      return dq.ids.du->getHTTPHost();
     });
 
     luaCtx.registerFunction<std::string(DNSQuestion::*)(void)const>("getHTTPScheme", [](const DNSQuestion& dq) {
-      if (dq.du == nullptr) {
+      if (dq.ids.du == nullptr) {
         return std::string();
       }
-      return dq.du->getHTTPScheme();
+      return dq.ids.du->getHTTPScheme();
     });
 
     luaCtx.registerFunction<LuaAssociativeTable<std::string>(DNSQuestion::*)(void)const>("getHTTPHeaders", [](const DNSQuestion& dq) {
-      if (dq.du == nullptr) {
+      if (dq.ids.du == nullptr) {
         return LuaAssociativeTable<std::string>();
       }
-      return dq.du->getHTTPHeaders();
+      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.du == nullptr) {
+      if (dq.ids.du == nullptr) {
         return;
       }
       checkParameterBound("DNSQuestion::setHTTPResponse", statusCode, std::numeric_limits<uint16_t>::max());
       PacketBuffer vect(body.begin(), body.end());
-      dq.du->setHTTPResponse(statusCode, std::move(vect), contentType ? *contentType : "");
+      dq.ids.du->setHTTPResponse(statusCode, std::move(vect), contentType ? *contentType : "");
     });
 #endif /* HAVE_DNS_OVER_HTTPS */
 
@@ -305,7 +511,44 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx)
       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);
+
+      return setNegativeAndAdditionalSOA(dq, 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& 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));
+  });
+
+  luaCtx.registerFunction<std::shared_ptr<DownstreamState>(DNSResponse::*)(void)const>("getSelectedBackend", [](const DNSResponse& dr) {
+    return dr.d_downstream;
+  });
 #endif /* DISABLE_NON_FFI_DQ_BINDINGS */
 }
index 30eff5d39d607aad388faa22e95a54da218f6b31..3f5d6e21159af449016a59064b752804e2aae183 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 "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"
 
-void setupLuaBindings(LuaContext& luaCtx, bool client)
+// 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);
     });
@@ -43,7 +52,7 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
     });
 
   /* Exceptions */
-  luaCtx.registerFunction<string(std::exception_ptr::*)()const>("__tostring", [](const std::exception_ptr& eptr) {
+  luaCtx.registerFunction<string(std::exception_ptr::*)()const>("__tostring", [](const std::exception_ptr& eptr) -> std::string {
       try {
         if (eptr) {
           std::rethrow_exception(eptr);
@@ -69,16 +78,16 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
   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}
+  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 (auto& policy : policies) {
-    luaCtx.writeVariable(policy.d_name, policy);
+  for (const auto& policy : policies) {
+    luaCtx.writeVariable(policy->d_name, policy);
   }
 
 #endif /* DISABLE_POLICIES_BINDINGS */
@@ -86,7 +95,7 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
   /* 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;
+        pool->packetCache = std::move(cache);
       }
     });
   luaCtx.registerFunction("getCache", &ServerPool::getCache);
@@ -100,44 +109,50 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
 
 #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) {
+  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, s);
+      addServerToPool(localPools, pool, state);
       g_pools.setState(localPools);
-      s->d_config.pools.insert(pool);
+      state->d_config.pools.insert(pool);
     });
-  luaCtx.registerFunction<void(std::shared_ptr<DownstreamState>::*)(string)>("rmPool", [](std::shared_ptr<DownstreamState> s, string 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, s);
+      removeServerFromPool(localPools, pool, state);
       g_pools.setState(localPools);
-      s->d_config.pools.erase(pool);
+      state->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.latencyUsec; });
+  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& s, boost::optional<bool> newStatus) {
+  luaCtx.registerFunction<void(DownstreamState::*)(boost::optional<bool> newStatus)>("setAuto", [](DownstreamState& state, boost::optional<bool> newStatus) {
       if (newStatus) {
-        s.setUpStatus(*newStatus);
+        state.setUpStatus(*newStatus);
       }
-      s.setAuto();
+      state.setAuto();
     });
-  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.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& s) -> int {return s.d_config.d_weight;},
-    [](DownstreamState& s, int newWeight) { s.setWeight(newWeight); }
+    [](const DownstreamState& state) -> int {return state.d_config.d_weight;},
+    [](DownstreamState& state, int newWeight) { state.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; }
+    [](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) -> 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); });
+  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
@@ -146,7 +161,7 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
       dh.rd=v;
     });
 
-  luaCtx.registerFunction<bool(dnsheader::*)()>("getRD", [](dnsheader& dh) {
+  luaCtx.registerFunction<bool(dnsheader::*)()const>("getRD", [](const dnsheader& dh) {
       return (bool)dh.rd;
     });
 
@@ -154,7 +169,7 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
       dh.ra=v;
     });
 
-  luaCtx.registerFunction<bool(dnsheader::*)()>("getRA", [](dnsheader& dh) {
+  luaCtx.registerFunction<bool(dnsheader::*)()const>("getRA", [](const dnsheader& dh) {
       return (bool)dh.ra;
     });
 
@@ -162,7 +177,7 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
       dh.ad=v;
     });
 
-  luaCtx.registerFunction<bool(dnsheader::*)()>("getAD", [](dnsheader& dh) {
+  luaCtx.registerFunction<bool(dnsheader::*)()const>("getAD", [](const dnsheader& dh) {
       return (bool)dh.ad;
     });
 
@@ -170,7 +185,7 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
       dh.aa=v;
     });
 
-  luaCtx.registerFunction<bool(dnsheader::*)()>("getAA", [](dnsheader& dh) {
+  luaCtx.registerFunction<bool(dnsheader::*)()const>("getAA", [](const dnsheader& dh) {
       return (bool)dh.aa;
     });
 
@@ -178,10 +193,18 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
       dh.cd=v;
     });
 
-  luaCtx.registerFunction<bool(dnsheader::*)()>("getCD", [](dnsheader& dh) {
+  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<bool(dnsheader::*)()const>("getTC", [](const dnsheader& dh) {
+      return (bool)dh.tc;
+    });
+
   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
@@ -241,8 +264,10 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
   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(); });
@@ -263,74 +288,82 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
   /* 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);
+          const auto& actualName = boost::get<DNSName>(name);
+          smn.add(actualName);
           return;
       }
       if (name.type() == typeid(std::string)) {
-          auto n = boost::get<std::string>(name);
-          smn.add(n);
+          const auto& actualName = boost::get<std::string>(name);
+          smn.add(actualName);
           return;
       }
       if (name.type() == typeid(LuaArray<DNSName>)) {
-          auto names = boost::get<LuaArray<DNSName>>(name);
-          for (const auto& n : names) {
-            smn.add(n.second);
+          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>)) {
-          auto names = boost::get<LuaArray<string>>(name);
-          for (const auto& n : names) {
-            smn.add(n.second);
+          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)) {
-          auto n = boost::get<DNSName>(name);
-          smn.remove(n);
+          const auto& actualName = boost::get<DNSName>(name);
+          smn.remove(actualName);
           return;
       }
       if (name.type() == typeid(string)) {
-          auto n = boost::get<string>(name);
-          DNSName d(n);
-          smn.remove(d);
+          const auto& actualName = boost::get<string>(name);
+          DNSName dnsName(actualName);
+          smn.remove(dnsName);
           return;
       }
       if (name.type() == typeid(LuaArray<DNSName>)) {
-          auto names = boost::get<LuaArray<DNSName>>(name);
-          for (const auto& n : names) {
-            smn.remove(n.second);
+          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>)) {
-          auto names = boost::get<LuaArray<std::string>>(name);
-          for (const auto& n : names) {
-            DNSName d(n.second);
-            smn.remove(d);
+          const auto& names = boost::get<LuaArray<std::string>>(name);
+          for (const auto& actualName : names) {
+            DNSName dnsName(actualName.second);
+            smn.remove(dnsName);
           }
           return;
       }
   });
 
-  luaCtx.registerFunction("check",(bool (SuffixMatchNode::*)(const DNSName&) const) &SuffixMatchNode::check);
+  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);
+  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(ca, *bits);
+        return Netmask(comboAddr, *bits);
       }
-      return Netmask(ca);
+      return Netmask(comboAddr);
     }
-    else if (s.type() == typeid(std::string)) {
-      auto str = boost::get<std::string>(s);
+    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()'");
@@ -351,10 +384,17 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
 
   /* NetmaskGroup */
   luaCtx.writeFunction("newNMG", []() { return NetmaskGroup(); });
-  luaCtx.registerFunction<void(NetmaskGroup::*)(const std::string&mask)>("addMask", [](NetmaskGroup&nmg, const std::string& mask)
+  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) {
@@ -413,52 +453,67 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
 #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.attachFilter(bpf, frontend.getSocket());
       }
     });
   luaCtx.registerFunction<void(ClientState::*)()>("detachFilter", [](ClientState& frontend) {
-      frontend.detachFilter();
+      frontend.detachFilter(frontend.getSocket());
     });
 #endif /* HAVE_EBPF */
 #endif /* DISABLE_CLIENT_STATE_BINDINGS */
 
   /* BPF Filter */
 #ifdef HAVE_EBPF
-  using bpfFilterMapParams = boost::variant<uint32_t, LuaAssociativeTable<boost::variant<uint32_t, std::string>>>;
-  luaCtx.writeFunction("newBPFFilter", [client](bpfFilterMapParams v4Params, bpfFilterMapParams v6Params, bpfFilterMapParams qnameParams, boost::optional<bool> external) {
+  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;
 
-      BPFFilter::MapConfiguration v4Config, v6Config, qnameConfig;
-
-      auto convertParamsToConfig = [](bpfFilterMapParams& params, BPFFilter::MapType type, BPFFilter::MapConfiguration& config) {
+      const auto convertParamsToConfig = [&](const std::string& name, BPFFilter::MapType type) {
+        BPFFilter::MapConfiguration config;
         config.d_type = type;
-        if (params.type() == typeid(uint32_t)) {
-          config.d_maxItems = boost::get<uint32_t>(params);
-        }
-        else if (params.type() == typeid(LuaAssociativeTable<boost::variant<uint32_t, std::string>>)) {
-          auto map = boost::get<LuaAssociativeTable<boost::variant<uint32_t, std::string>>>(params);
-          if (map.count("maxItems")) {
-            config.d_maxItems = boost::get<uint32_t>(map.at("maxItems"));
+        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");
           }
-          if (map.count("pinnedPath")) {
-            config.d_pinnedPath = boost::get<std::string>(map.at("pinnedPath"));
+          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(v4Params, BPFFilter::MapType::IPv4, v4Config);
-      convertParamsToConfig(v6Params, BPFFilter::MapType::IPv6, v6Config);
-      convertParamsToConfig(qnameParams, BPFFilter::MapType::QNames, qnameConfig);
+      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;
-      if (external && *external) {
-        format = BPFFilter::MapFormat::WithActions;
+      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>(v4Config, v6Config, qnameConfig, format, external.value_or(false));
-    });
+      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) {
@@ -485,11 +540,30 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
         }
       }
     });
-
+  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.value_or(255));
+          return bpf->block(qname, BPFFilter::MatchAction::Drop, qtype ? *qtype : 255);
         }
         else {
           BPFFilter::MatchAction match;
@@ -507,7 +581,7 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
           default:
             throw std::runtime_error("Unsupported action for BPFFilter::blockQName");
           }
-          return bpf->block(qname, match, qtype.value_or(255));
+          return bpf->block(qname, match, qtype ? *qtype : 255);
         }
       }
     });
@@ -517,7 +591,29 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
         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);
@@ -537,6 +633,15 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
             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";
@@ -547,9 +652,13 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
 
   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->attachFilter(bpf, frontend->getSocket());
         }
       }
     });
@@ -608,7 +717,48 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
       }
     });
 #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();
@@ -665,4 +815,45 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
     }
     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;
+  });
+
+  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 112172ce9b4e36676653f1857d1e81139e3121f3..35b5c8b9b3445c435c5e778ea70a70227ce7932f 100644 (file)
@@ -19,6 +19,8 @@
  * 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"
@@ -72,10 +74,10 @@ static LuaArray<std::vector<boost::variant<string,double>>> getGenResponses(uint
 
   LuaArray<vector<boost::variant<string,double>>> ret;
   ret.reserve(std::min(rcounts.size(), static_cast<size_t>(top + 1U)));
-  uint64_t count = 1;
+  int count = 1;
   unsigned int rest = 0;
   for (const auto& rc : rcounts) {
-    if (count == top+1) {
+    if (count == static_cast<int>(top + 1)) {
       rest+=rc.first;
     }
     else {
@@ -94,6 +96,7 @@ static LuaArray<std::vector<boost::variant<string,double>>> getGenResponses(uint
 }
 #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;
@@ -113,7 +116,7 @@ static counts_t filterScore(const counts_t& counts,
   return ret;
 }
 
-typedef std::function<void(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)> statvisitor_t;
+using statvisitor_t = std::function<void(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)>;
 
 static void statNodeRespRing(statvisitor_t visitor, uint64_t seconds)
 {
@@ -127,18 +130,21 @@ static void statNodeRespRing(statvisitor_t visitor, uint64_t seconds)
     auto rl = shard->respRing.lock();
 
     for(const auto& c : *rl) {
-      if (now < c.when)
+      if (now < c.when){
         continue;
+      }
 
-      if (seconds && c.when < cutoff)
+      if (seconds && c.when < cutoff) {
         continue;
+      }
 
-      root.submit(c.name, ((c.dh.rcode == 0 && c.usec == std::numeric_limits<unsigned int>::max()) ? -1 : c.dh.rcode), c.size, boost::none);
+      const bool hit = c.isACacheHit();
+      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) {
+  root.visit([visitor = std::move(visitor)](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) {
       visitor(*node_, self, children);},  node);
 }
 
@@ -241,13 +247,14 @@ static counts_t exceedRespByterate(unsigned int rate, int seconds)
 }
 
 #endif /* DISABLE_DEPRECATED_DYNBLOCK */
-
+#endif /* DISABLE_DYNBLOCKS */
+// 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();
-      auto top = top_.get_value_or(10);
+      uint64_t top = top_ ? *top_ : 10U;
       map<ComboAddress, unsigned int,ComboAddress::addressOnlyLessThan > counts;
       unsigned int total=0;
       {
@@ -296,7 +303,8 @@ void setupLuaInspection(LuaContext& luaCtx)
        unsigned int lab = *labels;
         for (const auto& shard : g_rings.d_shards) {
           auto rl = shard->queryRing.lock();
-          for(auto a : *rl) {
+          // coverity[auto_causes_copy]
+          for (auto a : *rl) {
             a.name.trimToLabels(lab);
             counts[a.name]++;
             total++;
@@ -398,38 +406,59 @@ void setupLuaInspection(LuaContext& luaCtx)
       }
     });
 
-  luaCtx.writeFunction("grepq", [](LuaTypeOrArrayOf<std::string> inp, boost::optional<unsigned int> limit) {
+  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;
+      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)
+      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)
+        for (const auto& a: v) {
           vec.push_back(a.second);
+        }
       }
 
-      for(const auto& s : vec) {
-        try
-          {
+      for (const auto& s : vec) {
+        try {
             nm = Netmask(s);
-          }
-        catch(...) {
-          if(boost::ends_with(s,"ms") && sscanf(s.c_str(), "%ums", &msec)) {
+        }
+        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;
-              }
+            try {
+              dn = DNSName(s);
+            }
+            catch (...) {
+              g_outputBuffer = "Could not parse '"+s+"' as domain name or netmask";
+              return;
+            }
           }
         }
       }
@@ -467,12 +496,19 @@ void setupLuaInspection(LuaContext& luaCtx)
 
       std::multimap<struct timespec, string> out;
 
-      boost::format      fmt("%-7.1f %-47s %-12s %-12s %-5d %-25s %-5s %-6.1f %-2s %-2s %-2s %-s\n");
-      g_outputBuffer+= (fmt % "Time" % "Client" % "Protocol" % "Server" % "ID" % "Name" % "Type" % "Lat." % "TC" % "RD" % "AA" % "Rcode").str();
+      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, dnmatch=true;
+      if (msec == -1) {
+        for (const auto& c : qr) {
+          bool nmmatch = true;
+          bool dnmatch = true;
           if (nm) {
             nmmatch = nm->match(c.requestor);
           }
@@ -492,17 +528,19 @@ void setupLuaInspection(LuaContext& luaCtx)
             }
             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)
+            if (limit && *limit == ++num) {
               break;
+            }
           }
         }
       }
-      num=0;
-
+      num = 0;
 
       string extra;
-      for(const auto& c : rr) {
-        bool nmmatch=true, dnmatch=true, msecmatch=true;
+      for (const auto& c : rr) {
+        bool nmmatch = true;
+        bool dnmatch = true;
+        bool msecmatch = true;
         if (nm) {
           nmmatch = nm->match(c.requestor);
         }
@@ -515,23 +553,29 @@ void setupLuaInspection(LuaContext& luaCtx)
           }
         }
         if (msec != -1) {
-          msecmatch=(c.usec/1000 > (unsigned int)msec);
+          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";
+           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() % dnsdist::Protocol(c.protocol).toString() % c.ds.toStringWithPort() % 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());
+            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() % dnsdist::Protocol(c.protocol).toString() % c.ds.toStringWithPort() % 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());
+            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) {
@@ -540,8 +584,13 @@ void setupLuaInspection(LuaContext& luaCtx)
         }
       }
 
-      for(const auto& p : out) {
-        g_outputBuffer+=p.second;
+      for (const auto& p : out) {
+        if (!outputFile) {
+          g_outputBuffer += p.second;
+        }
+        else {
+          fprintf(outputFile.get(), "%s", p.second.c_str());
+        }
       }
     });
 
@@ -580,14 +629,14 @@ void setupLuaInspection(LuaContext& luaCtx)
         return;
       }
 
-      g_outputBuffer = (boost::format("Average response latency: %.02f msec\n") % (0.001*totlat/size)).str();
+      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 % "msec" % "").str();
+      g_outputBuffer += (fmt % "ms" % "").str();
 
       for(auto iter = histo.cbegin(); iter != histo.cend(); ++iter) {
        int stars = (70.0 * iter->second/highest);
@@ -623,13 +672,13 @@ void setupLuaInspection(LuaContext& luaCtx)
       ret << endl;
 
       ret << "Backends:" << endl;
-      fmt = boost::format("%-3d %-20.20s %-20.20s %-20d %-20d %-25d %-25d %-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" % "Total connections" % "Reused connections" % "TLS resumptions" % "Avg queries/conn" % "Avg duration") << 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->tcpNewConnections % s->tcpReusedConnections % s->tlsResumptions % s->tcpAvgQueriesPerConnection % s->tcpAvgConnectionDuration) << endl;
+        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;
       }
 
@@ -653,7 +702,7 @@ void setupLuaInspection(LuaContext& luaCtx)
           errorCounters = &f->tlsFrontend->d_tlsCounters;
         }
         else if (f->dohFrontend != nullptr) {
-          errorCounters = &f->dohFrontend->d_tlsCounters;
+          errorCounters = &f->dohFrontend->d_tlsContext.d_tlsCounters;
         }
         if (errorCounters == nullptr) {
           continue;
@@ -675,7 +724,9 @@ void setupLuaInspection(LuaContext& luaCtx)
 
   luaCtx.writeFunction("requestDoHStatesDump", [] {
     setLuaNoSideEffect();
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
     g_dohStatesDumpRequested += g_dohClientThreads->getThreadsCount();
+#endif
   });
 
   luaCtx.writeFunction("dumpStats", [] {
@@ -684,25 +735,33 @@ void setupLuaInspection(LuaContext& luaCtx)
 
       boost::format fmt("%-35s\t%+11s");
       g_outputBuffer.clear();
-      auto entries = g_stats.entries;
+      auto entries = *dnsdist::metrics::g_stats.entries.read_lock();
       sort(entries.begin(), entries.end(),
-          [](const decltype(entries)::value_type& a, const decltype(entries)::value_type& b) {
-            return a.first < b.first;
-          });
+           [](const decltype(entries)::value_type& a, const decltype(entries)::value_type& b) {
+             return a.d_name < b.d_name;
+           });
       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& dval = boost::get<double*>(&e.second))
-         second=(flt % (**dval)).str();
-       else
-         second=std::to_string((*boost::get<DNSDistStats::statfunction_t>(&e.second))(e.first));
+      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() < g_stats.entries.size()/2)
-         leftcolumn.push_back((fmt % e.first % second).str());
-       else
-         rightcolumn.push_back((fmt % e.first % second).str());
+        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(), rightiter=rightcolumn.begin();
@@ -722,6 +781,7 @@ void setupLuaInspection(LuaContext& luaCtx)
       }
     });
 
+#ifndef DISABLE_DYNBLOCKS
 #ifndef DISABLE_DEPRECATED_DYNBLOCK
   luaCtx.writeFunction("exceedServFails", [](unsigned int rate, int seconds) {
       setLuaNoSideEffect();
@@ -755,10 +815,10 @@ void setupLuaInspection(LuaContext& luaCtx)
   luaCtx.writeFunction("getRespRing", getRespRing);
 
   /* StatNode */
-  luaCtx.registerFunction<StatNode, unsigned int()>("numChildren",
-                                                   [](StatNode& sn) -> unsigned int {
-                                                     return sn.children.size();
-                                                   } );
+  luaCtx.registerFunction<unsigned int(StatNode::*)()const>("numChildren",
+                                                            [](const StatNode& sn) -> unsigned int {
+                                                              return sn.children.size();
+                                                            } );
   luaCtx.registerMember("fullname", &StatNode::fullname);
   luaCtx.registerMember("labelsCount", &StatNode::labelsCount);
   luaCtx.registerMember("servfails", &StatNode::Stat::servfails);
@@ -767,9 +827,10 @@ void setupLuaInspection(LuaContext& luaCtx)
   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);
+      statNodeRespRing(std::move(visitor), seconds ? *seconds : 0U);
     });
 #endif /* DISABLE_DEPRECATED_DYNBLOCK */
 
@@ -787,12 +848,17 @@ void setupLuaInspection(LuaContext& luaCtx)
     });
   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);
+        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, visitor);
+        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) {
@@ -810,6 +876,11 @@ void setupLuaInspection(LuaContext& luaCtx)
         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 v4, uint8_t v6, uint8_t port) {
       if (group) {
         if (v4 > 32) {
@@ -853,6 +924,19 @@ void setupLuaInspection(LuaContext& luaCtx)
         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)) {
@@ -868,4 +952,14 @@ void setupLuaInspection(LuaContext& luaCtx)
   });
   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 5fd9c9cd74ed9819b97a389028e53579153ed37a..f53b98be3932ee6f25c19426b497781bb5339062 100644 (file)
 #include "dnsdist.hh"
 #include "dnsdist-lua.hh"
 #include "dnsdist-rules.hh"
+#include "dns_random.hh"
 
-std::shared_ptr<DNSRule> makeRule(const luadnsrule_t& var)
+std::shared_ptr<DNSRule> makeRule(const luadnsrule_t& var, const std::string& calledFrom)
 {
-  if (var.type() == typeid(std::shared_ptr<DNSRule>))
+  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=[&](string src) {
+  auto add = [&nmg, &smn, &suffixSeen](const string& src) {
     try {
       nmg.addMask(src); // need to try mask first, all masks are domain names!
-    } catch(...) {
+    } catch (...) {
+      suffixSeen = true;
       smn.add(DNSName(src));
     }
   };
 
-  if (var.type() == typeid(string))
+  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))
+  }
+  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);
+    }
+  }
 
-  else if (var.type() == typeid(LuaArray<DNSName>))
-    for(const auto& a : *boost::get<LuaArray<DNSName>>(&var))
-      smn.add(a.second);
-
-  if(nmg.empty())
+  if (nmg.empty()) {
     return std::make_shared<SuffixMatchNodeRule>(smn);
-  else
-    return std::make_shared<NetmaskGroupRule>(nmg, true);
+  }
+  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& id)
@@ -67,20 +78,14 @@ static boost::uuids::uuid makeRuleID(std::string& id)
   return getUniqueID(id);
 }
 
-void parseRuleParams(boost::optional<luaruleparams_t> params, boost::uuids::uuid& uuid, std::string& name, uint64_t& creationOrder)
+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;
 
-  if (params) {
-    if (params->count("uuid")) {
-      uuidStr = params->at("uuid");
-    }
-    if (params->count("name")) {
-      name = params->at("name");
-    }
-  }
+  getOptionalValue<std::string>(params, "uuid", uuidStr);
+  getOptionalValue<std::string>(params, "name", name);
 
   uuid = makeRuleID(uuidStr);
   creationOrder = s_creationOrder++;
@@ -89,21 +94,16 @@ void parseRuleParams(boost::optional<luaruleparams_t> params, boost::uuids::uuid
 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)
+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;
 
-  if (vars) {
-    if (vars->count("showUUIDs")) {
-      showUUIDs = boost::get<bool>((*vars)["showUUIDs"]);
-    }
-    if (vars->count("truncateRuleWidth")) {
-      truncateRuleWidth = boost::get<int>((*vars)["truncateRuleWidth"]);
-    }
-  }
+  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");
@@ -127,7 +127,7 @@ static std::string rulesToString(const std::vector<T>& rules, boost::optional<ru
 }
 
 template<typename T>
-static void showRules(GlobalStateHolder<vector<T> > *someRuleActions, boost::optional<ruleparams_t> vars) {
+static void showRules(GlobalStateHolder<vector<T> > *someRuleActions, boost::optional<ruleparams_t>& vars) {
   setLuaNoSideEffect();
 
   auto rules = someRuleActions->getLocal();
@@ -135,29 +135,33 @@ static void showRules(GlobalStateHolder<vector<T> > *someRuleActions, boost::opt
 }
 
 template<typename T>
-static void rmRule(GlobalStateHolder<vector<T> > *someRuleActions, boost::variant<unsigned int, std::string> id) {
+static void rmRule(GlobalStateHolder<vector<T> > *someRuleActions, const 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(),
+      auto removeIt = std::remove_if(rules.begin(),
                                      rules.end(),
-                                     [uuid](const T& a) { return a.d_id == uuid; }),
-                      rules.end()) == 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 */
-      if (rules.erase(std::remove_if(rules.begin(),
+      auto removeIt = std::remove_if(rules.begin(),
                                      rules.end(),
-                                     [&str](const T& a) { return a.d_name == *str; }),
-                      rules.end()) == 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 (auto pos = boost::get<unsigned int>(&id)) {
@@ -234,12 +238,93 @@ static std::vector<T> getTopRules(const std::vector<T>& rules, unsigned int top)
   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 (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 (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", makeRule);
+  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>(DNSDistRuleAction::*)()const>("getSelector", [](const DNSDistRuleAction& rule) { return rule.d_rule; });
+
+  luaCtx.registerFunction<std::shared_ptr<DNSAction>(DNSDistRuleAction::*)()const>("getAction", [](const DNSDistRuleAction& rule) { return rule.d_action; });
+
+  luaCtx.registerFunction<std::shared_ptr<DNSRule>(DNSDistResponseRuleAction::*)()const>("getSelector", [](const DNSDistResponseRuleAction& rule) { return rule.d_rule; });
+
+  luaCtx.registerFunction<std::shared_ptr<DNSResponseAction>(DNSDistResponseRuleAction::*)()const>("getAction", [](const DNSDistResponseRuleAction& rule) { return rule.d_action; });
+
   luaCtx.writeFunction("showResponseRules", [](boost::optional<ruleparams_t> vars) {
       showRules(&g_respruleactions, vars);
     });
@@ -272,6 +357,22 @@ void setupLuaRules(LuaContext& luaCtx)
       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);
     });
@@ -314,63 +415,100 @@ void setupLuaRules(LuaContext& luaCtx)
           for (const auto& pair : newruleactions) {
             const auto& newruleaction = pair.second;
             if (newruleaction->d_action) {
-              auto rule = makeRule(newruleaction->d_rule);
+              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", [](boost::variant<int, std::string> selector) -> boost::optional<DNSDistRuleAction> {
+    auto rules = g_ruleactions.getLocal();
+    return getRuleFromSelector(*rules, selector);
+  });
+
   luaCtx.writeFunction("getTopRules", [](boost::optional<unsigned int> top) {
     setLuaNoSideEffect();
     auto rules = g_ruleactions.getLocal();
-    return getTopRules(*rules, top.get_value_or(10));
+    return toLuaArray(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.get_value_or(10)), vars);
+    return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
+  });
+
+  luaCtx.writeFunction("getCacheHitResponseRule", [](boost::variant<int, std::string> selector) -> boost::optional<DNSDistResponseRuleAction> {
+    auto rules = g_cachehitrespruleactions.getLocal();
+    return getRuleFromSelector(*rules, selector);
   });
 
-  luaCtx.writeFunction("getCacheHitResponseRules", [](boost::optional<unsigned int> top) {
+  luaCtx.writeFunction("getTopCacheHitResponseRules", [](boost::optional<unsigned int> top) {
     setLuaNoSideEffect();
     auto rules = g_cachehitrespruleactions.getLocal();
-    return getTopRules(*rules, top.get_value_or(10));
+    return toLuaArray(getTopRules(*rules, (top ? *top : 10)));
   });
 
-  luaCtx.writeFunction("topCacheHitRules", [](boost::optional<unsigned int> top, boost::optional<ruleparams_t> vars) {
+  luaCtx.writeFunction("topCacheHitResponseRules", [](boost::optional<unsigned int> top, boost::optional<ruleparams_t> vars) {
     setLuaNoSideEffect();
     auto rules = g_cachehitrespruleactions.getLocal();
-    return rulesToString(getTopRules(*rules, top.get_value_or(10)), vars);
+    return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
+  });
+
+  luaCtx.writeFunction("getCacheInsertedResponseRule", [](boost::variant<int, std::string> selector) -> boost::optional<DNSDistResponseRuleAction> {
+    auto rules = g_cacheInsertedRespRuleActions.getLocal();
+    return getRuleFromSelector(*rules, selector);
+  });
+
+  luaCtx.writeFunction("getTopCacheInsertedResponseRules", [](boost::optional<unsigned int> top) {
+    setLuaNoSideEffect();
+    auto rules = g_cacheInsertedRespRuleActions.getLocal();
+    return toLuaArray(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("getResponseRule", [](boost::variant<int, std::string> selector) -> boost::optional<DNSDistResponseRuleAction> {
+    auto rules = g_respruleactions.getLocal();
+    return getRuleFromSelector(*rules, selector);
   });
 
   luaCtx.writeFunction("getTopResponseRules", [](boost::optional<unsigned int> top) {
     setLuaNoSideEffect();
     auto rules = g_respruleactions.getLocal();
-    return getTopRules(*rules, top.get_value_or(10));
+    return toLuaArray(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.get_value_or(10)), vars);
+    return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
+  });
+
+  luaCtx.writeFunction("getSelfAnsweredResponseRule", [](boost::variant<int, std::string> selector) -> boost::optional<DNSDistResponseRuleAction> {
+    auto rules = g_selfansweredrespruleactions.getLocal();
+    return getRuleFromSelector(*rules, selector);
   });
 
   luaCtx.writeFunction("getTopSelfAnsweredResponseRules", [](boost::optional<unsigned int> top) {
     setLuaNoSideEffect();
     auto rules = g_selfansweredrespruleactions.getLocal();
-    return getTopRules(*rules, top.get_value_or(10));
+    return toLuaArray(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.get_value_or(10)), vars);
+    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) {
-      return std::shared_ptr<DNSRule>(new MaxQPSIPRule(qps, burst.get_value_or(qps), ipv4trunc.get_value_or(32), ipv6trunc.get_value_or(64), expiration.get_value_or(300), cleanupDelay.get_value_or(60), scanFraction.get_value_or(10)));
+  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) {
@@ -406,51 +544,65 @@ void setupLuaRules(LuaContext& luaCtx)
       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("SuffixMatchNodeRule", qnameSuffixRule);
 
-  luaCtx.writeFunction("NetmaskGroupRule", [](const NetmaskGroup& nmg, boost::optional<bool> src, boost::optional<bool> quiet) {
+  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", [](std::shared_ptr<DNSRule> rule, boost::optional<unsigned int> times_, boost::optional<string> suffix_)  {
       setLuaNoSideEffect();
-      unsigned int times = times_.get_value_or(100000);
-      DNSName suffix(suffix_.get_value_or("powerdns.com"));
+      unsigned int times = times_ ? *times_ : 100000;
+      DNSName suffix(suffix_ ? *suffix_ : "powerdns.com");
       struct item {
         PacketBuffer packet;
-        ComboAddress rem;
-        DNSName qname;
-        uint16_t qtype, qclass;
+        InternalQueryState ids;
       };
       vector<item> items;
       items.reserve(1000);
-      for(int n=0; n < 1000; ++n) {
+      for (int n = 0; n < 1000; ++n) {
         struct item i;
-        i.qname=DNSName(std::to_string(random()));
-        i.qname += suffix;
-        i.qtype = random() % 0xff;
-        i.qclass = 1;
-        i.rem=ComboAddress("127.0.0.1");
-        i.rem.sin4.sin_addr.s_addr = random();
-        GenericDNSPacketWriter<PacketBuffer> pw(i.packet, i.qname, i.qtype);
-        items.push_back(i);
+        i.ids.qname = DNSName(std::to_string(dns_random_uint32()));
+        i.ids.qname += suffix;
+        i.ids.qtype = dns_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;
+      int matches = 0;
       ComboAddress dummy("127.0.0.1");
       StopWatch sw;
       sw.start();
-      for(unsigned int n=0; n < times; ++n) {
+      for (unsigned int n = 0; n < times; ++n) {
         item& i = items[n % items.size()];
-        DNSQuestion dq(&i.qname, i.qtype, i.qclass, &i.rem, &i.rem, i.packet, dnsdist::Protocol::DoUDP, &sw.d_start);
+        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 usec\n") % matches % times % (1000000*(1.0*times/udiff)) % udiff).str();
+      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();
 
     });
 
@@ -466,6 +618,8 @@ void setupLuaRules(LuaContext& luaCtx)
       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 (auto dir = boost::get<unsigned int>(&str)) {
@@ -491,11 +645,11 @@ void setupLuaRules(LuaContext& luaCtx)
       return std::shared_ptr<DNSRule>(new OpcodeRule(code));
     });
 
-  luaCtx.writeFunction("AndRule", [](LuaArray<std::shared_ptr<DNSRule>> a) {
+  luaCtx.writeFunction("AndRule", [](const LuaArray<std::shared_ptr<DNSRule>>& a) {
       return std::shared_ptr<DNSRule>(new AndRule(a));
     });
 
-  luaCtx.writeFunction("OrRule", [](LuaArray<std::shared_ptr<DNSRule>>a) {
+  luaCtx.writeFunction("OrRule", [](const LuaArray<std::shared_ptr<DNSRule>>& a) {
       return std::shared_ptr<DNSRule>(new OrRule(a));
     });
 
@@ -512,7 +666,7 @@ void setupLuaRules(LuaContext& luaCtx)
       return std::shared_ptr<DNSRule>(new DNSSECRule());
     });
 
-  luaCtx.writeFunction("NotRule", [](std::shared_ptr<DNSRule>rule) {
+  luaCtx.writeFunction("NotRule", [](const std::shared_ptr<DNSRule>& rule) {
       return std::shared_ptr<DNSRule>(new NotRule(rule));
     });
 
@@ -573,19 +727,19 @@ void setupLuaRules(LuaContext& luaCtx)
       return std::shared_ptr<DNSRule>(new RDRule());
     });
 
-  luaCtx.writeFunction("TagRule", [](std::string tag, boost::optional<std::string> value) {
-      return std::shared_ptr<DNSRule>(new TagRule(tag, value));
+  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::shared_ptr<TimedIPSetRule>(new TimedIPSetRule());
     });
 
-  luaCtx.writeFunction("PoolAvailableRule", [](std::string poolname) {
+  luaCtx.writeFunction("PoolAvailableRule", [](const std::string& poolname) {
     return std::shared_ptr<DNSRule>(new PoolAvailableRule(poolname));
   });
 
-  luaCtx.writeFunction("PoolOutstandingRule", [](std::string poolname, uint64_t limit) {
+  luaCtx.writeFunction("PoolOutstandingRule", [](const std::string& poolname, uint64_t limit) {
     return std::shared_ptr<DNSRule>(new PoolOutstandingRule(poolname, limit));
   });
 
@@ -630,11 +784,15 @@ void setupLuaRules(LuaContext& luaCtx)
       return std::shared_ptr<DNSRule>(new LuaFFIRule(func));
     });
 
-  luaCtx.writeFunction("LuaFFIPerThreadRule", [](std::string code) {
-    return std::shared_ptr<DNSRule>(new LuaFFIPerThreadRule(code));
-  });
+  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));
+      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));
     });
 }
index 265568f9ffda2c4e2c86a77e56731ac5c1e74c1e..89927b72456a5a7bb1c5fe499c89695375ff7d69 100644 (file)
@@ -51,6 +51,7 @@ void setupLuaVars(LuaContext& luaCtx)
       {"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         }
     });
 
index d7458a2c5b659c6561345f052242215b3ec2dc03..73a8567b241aa09ab1d6b2f886751e1e0441590c 100644 (file)
  * 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-web.hh"
 
 #include "base64.hh"
+#include "coverage.hh"
+#include "doh.hh"
+#include "doq-common.hh"
 #include "dolog.hh"
-#include "sodcrypto.hh"
+#include "threadname.hh"
 
 #ifdef HAVE_LIBSSL
 #include "libssl.hh"
@@ -100,39 +113,40 @@ 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>>>;
+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)
+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) {
-    if (vars->count("reusePort")) {
-      reusePort = boost::get<bool>((*vars)["reusePort"]);
-    }
-    if (vars->count("tcpFastOpenQueueSize")) {
-      tcpFastOpenQueueSize = boost::get<int>((*vars)["tcpFastOpenQueueSize"]);
-    }
-    if (vars->count("tcpListenQueueSize")) {
-      tcpListenQueueSize = boost::get<int>((*vars)["tcpListenQueueSize"]);
-    }
-    if (vars->count("maxConcurrentTCPConnections")) {
-      tcpMaxConcurrentConnections = boost::get<int>((*vars)["maxConcurrentTCPConnections"]);
-    }
-    if (vars->count("maxInFlight")) {
-      maxInFlightQueriesPerConnection = boost::get<int>((*vars)["maxInFlight"]);
-    }
-    if (vars->count("interface")) {
-      interface = boost::get<std::string>((*vars)["interface"]);
-    }
-    if (vars->count("cpus")) {
-      for (const auto& cpu : boost::get<LuaArray<int>>((*vars)["cpus"])) {
+    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)
-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 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);
@@ -176,82 +190,59 @@ static bool loadTLSCertificateAndKeys(const std::string& context, std::vector<TL
   return true;
 }
 
-static void parseTLSConfig(TLSConfig& config, const std::string& context, boost::optional<localbind_t> vars)
+static void parseTLSConfig(TLSConfig& config, const std::string& context, boost::optional<localbind_t>& vars)
 {
-  if (vars->count("ciphers")) {
-    config.d_ciphers = boost::get<const string>((*vars)["ciphers"]);
-  }
-
-  if (vars->count("ciphersTLS13")) {
-    config.d_ciphers13 = boost::get<const string>((*vars)["ciphersTLS13"]);
-  }
+  getOptionalValue<std::string>(vars, "ciphers", config.d_ciphers);
+  getOptionalValue<std::string>(vars, "ciphersTLS13", config.d_ciphers13);
 
 #ifdef HAVE_LIBSSL
-  if (vars->count("minTLSVersion")) {
-    config.d_minTLSVersion = libssl_tls_version_from_string(boost::get<const string>((*vars)["minTLSVersion"]));
+  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 */
 
-  if (vars->count("ticketKeyFile")) {
-    config.d_ticketKeyFile = boost::get<const string>((*vars)["ticketKeyFile"]);
-  }
-
-  if (vars->count("ticketsKeysRotationDelay")) {
-    config.d_ticketsKeyRotationDelay = boost::get<int>((*vars)["ticketsKeysRotationDelay"]);
-  }
-
-  if (vars->count("numberOfTicketsKeys")) {
-    config.d_numberOfTicketsKeys = boost::get<int>((*vars)["numberOfTicketsKeys"]);
-  }
-
-  if (vars->count("preferServerCiphers")) {
-    config.d_preferServerCiphers = boost::get<bool>((*vars)["preferServerCiphers"]);
-  }
-
-  if (vars->count("sessionTimeout")) {
-    config.d_sessionTimeout = boost::get<int>((*vars)["sessionTimeout"]);
-  }
-
-  if (vars->count("sessionTickets")) {
-    config.d_enableTickets = boost::get<bool>((*vars)["sessionTickets"]);
-  }
-
-  if (vars->count("numberOfStoredSessions")) {
-    auto value = boost::get<int>((*vars)["numberOfStoredSessions"]);
-    if (value < 0) {
-      errlog("Invalid value '%d' for %s() parameter 'numberOfStoredSessions', should be >= 0, dismissing", value, context);
-      g_outputBuffer = "Invalid value '" + std::to_string(value) + "' for " + context + "() parameter 'numberOfStoredSessions', should be >= 0, dismissing";
+  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;
     }
-    config.d_maxStoredSessions = value;
   }
 
-  if (vars->count("ocspResponses")) {
-    auto files = boost::get<LuaArray<std::string>>((*vars)["ocspResponses"]);
+  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")) {
+  if (vars->count("keyLogFile") > 0) {
 #ifdef HAVE_SSL_CTX_SET_KEYLOG_CALLBACK
-    config.d_keyLogFile = boost::get<const string>((*vars)["keyLogFile"]);
+    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
   }
 
-  if (vars->count("releaseBuffers")) {
-    config.d_releaseBuffers = boost::get<bool>((*vars)["releaseBuffers"]);
-  }
-
-  if (vars->count("enableRenegotiation")) {
-    config.d_enableRenegotiation = boost::get<bool>((*vars)["enableRenegotiation"]);
-  }
-
-  if (vars->count("tlsAsyncMode")) {
-    config.d_asyncMode = boost::get<bool>((*vars).at("tlsAsyncMode"));
-  }
+  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)
@@ -263,9 +254,19 @@ void checkParameterBound(const std::string& parameter, uint64_t value, size_t ma
   }
 }
 
-static void LuaThread(const std::string code)
+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) {
@@ -273,7 +274,7 @@ static void LuaThread(const std::string code)
     // 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);
+      func.get()(std::move(cmd), std::move(data));
     }
     else {
       errlog("Lua thread called submitToMainThread but no threadmessage receiver is defined");
@@ -297,9 +298,111 @@ static void LuaThread(const std::string code)
   }
 }
 
+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);
+}
+
+// NOLINTNEXTLINE(readability-function-cognitive-complexity): 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)
 {
-  typedef LuaAssociativeTable<boost::variant<bool, std::string, LuaArray<std::string>, DownstreamState::checkfunc_t>> newserver_t;
   luaCtx.writeFunction("inClientStartup", [client]() {
     return client && !g_configurationDone;
   });
@@ -312,22 +415,23 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
                        [client, configCheck](boost::variant<string, newserver_t> pvars, boost::optional<int> qps) {
                          setLuaSideEffect();
 
-                         newserver_t vars;
+                         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);
+                             (*vars)["qps"] = std::to_string(*qps);
                            }
                          }
                          else {
                            vars = boost::get<newserver_t>(pvars);
-                           serverAddressStr = boost::get<string>(vars["address"]);
+                           getOptionalValue<std::string>(vars, "address", serverAddressStr);
                          }
 
-                         if (vars.count("source")) {
+                         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")
@@ -335,7 +439,6 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
                               - v4 address and interface name ("192.0.2.1@eth0")
                               - v6 address and interface name ("2001:DB8::1@eth0")
                            */
-                           const string source = boost::get<string>(vars["source"]);
                            bool parsed = false;
                            std::string::size_type pos = source.find("@");
                            if (pos == std::string::npos) {
@@ -352,7 +455,6 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
                              /* 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" */
@@ -374,180 +476,82 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
                            }
                          }
 
-                         if (vars.count("sockets")) {
-                           config.d_numberOfSockets = std::stoul(boost::get<string>(vars["sockets"]));
+                         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", boost::get<string>(vars["sockets"]));
+                             warnlog("Dismissing invalid number of sockets '%s', using 1 instead", valueStr);
                              config.d_numberOfSockets = 1;
                            }
                          }
 
-                         if (vars.count("qps")) {
-                           config.d_qpsLimit = std::stoi(boost::get<string>(vars["qps"]));
-                         }
-
-                         if (vars.count("order")) {
-                           config.order = std::stoi(boost::get<string>(vars["order"]));
-                         }
-
-                         if (vars.count("weight")) {
-                           try {
-                             config.d_weight = std::stoi(boost::get<string>(vars["weight"]));
-
-                             if (config.d_weight < 1) {
-                               errlog("Error creating new server: downstream weight value must be greater than 0.");
-                               return std::shared_ptr<DownstreamState>();
-                             }
-                           }
-                           catch (const std::exception& e) {
-                             // std::stoi will throw an exception if the string isn't in a value int range
-                             errlog("Error creating new server: downstream weight value must be between %s and %s", 1, std::numeric_limits<int>::max());
-                             return std::shared_ptr<DownstreamState>();
-                           }
-                         }
-
-                         if (vars.count("retries")) {
-                           config.d_retries = std::stoi(boost::get<string>(vars["retries"]));
-                         }
-
-                         if (vars.count("checkInterval")) {
-                           config.checkInterval = static_cast<unsigned int>(std::stoul(boost::get<string>(vars["checkInterval"])));
-                         }
-
-                         if (vars.count("tcpConnectTimeout")) {
-                           config.tcpConnectTimeout = std::stoi(boost::get<string>(vars["tcpConnectTimeout"]));
+                         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>();
                          }
 
-                         if (vars.count("tcpSendTimeout")) {
-                           config.tcpSendTimeout = std::stoi(boost::get<string>(vars["tcpSendTimeout"]));
-                         }
+                         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 (vars.count("tcpRecvTimeout")) {
-                           config.tcpRecvTimeout = std::stoi(boost::get<string>(vars["tcpRecvTimeout"]));
-                         }
+                         handleNewServerHealthCheckParameters(vars, config);
 
-                         if (vars.count("tcpFastOpen")) {
-                           bool fastOpen = boost::get<bool>(vars["tcpFastOpen"]);
+                         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", boost::get<string>(vars["address"]));
+          warnlog("TCP Fast Open has been configured on downstream server %s but is not supported", serverAddressStr);
 #endif
                            }
                          }
 
-                         if (vars.count("maxInFlight")) {
-                           config.d_maxInFlightQueriesPerConn = std::stoi(boost::get<string>(vars["maxInFlight"]));
-                         }
-
-                         if (vars.count("name")) {
-                           config.name = boost::get<string>(vars["name"]);
-                         }
-
-                         if (vars.count("id")) {
-                           config.id = boost::uuids::string_generator()(boost::get<string>(vars["id"]));
-                         }
-
-                         if (vars.count("checkName")) {
-                           config.checkName = DNSName(boost::get<string>(vars["checkName"]));
-                         }
-
-                         if (vars.count("checkType")) {
-                           config.checkType = boost::get<string>(vars["checkType"]);
-                         }
-
-                         if (vars.count("checkClass")) {
-                           config.checkClass = std::stoi(boost::get<string>(vars["checkClass"]));
-                         }
-
-                         if (vars.count("checkFunction")) {
-                           config.checkFunction = boost::get<DownstreamState::checkfunc_t>(vars["checkFunction"]);
-                         }
-
-                         if (vars.count("checkTimeout")) {
-                           config.checkTimeout = std::stoi(boost::get<string>(vars["checkTimeout"]));
-                         }
-
-                         if (vars.count("checkTCP")) {
-                           config.d_tcpCheck = boost::get<bool>(vars.at("checkTCP"));
-                         }
-
-                         if (vars.count("setCD")) {
-                           config.setCD = boost::get<bool>(vars["setCD"]);
-                         }
-
-                         if (vars.count("mustResolve")) {
-                           config.mustResolve = boost::get<bool>(vars["mustResolve"]);
-                         }
-
-                         if (vars.count("useClientSubnet")) {
-                           config.useECS = boost::get<bool>(vars["useClientSubnet"]);
-                         }
-
-                         if (vars.count("useProxyProtocol")) {
-                           config.useProxyProtocol = boost::get<bool>(vars["useProxyProtocol"]);
-                         }
-
-                         if (vars.count("disableZeroScope")) {
-                           config.disableZeroScope = boost::get<bool>(vars["disableZeroScope"]);
-                         }
+                         getOptionalIntegerValue("newServer", vars, "maxInFlight", config.d_maxInFlightQueriesPerConn);
+                         getOptionalIntegerValue("newServer", vars, "maxConcurrentTCPConnections", config.d_tcpConcurrentConnectionsLimit);
 
-                         if (vars.count("ipBindAddrNoPort")) {
-                           config.ipBindAddrNoPort = boost::get<bool>(vars["ipBindAddrNoPort"]);
-                         }
+                         getOptionalValue<std::string>(vars, "name", config.name);
 
-                         if (vars.count("addXPF")) {
-                           config.xpfRRCode = std::stoi(boost::get<string>(vars["addXPF"]));
+                         if (getOptionalValue<std::string>(vars, "id", valueStr) > 0) {
+                           config.id = boost::uuids::string_generator()(valueStr);
                          }
 
-                         if (vars.count("maxCheckFailures")) {
-                           config.maxCheckFailures = std::stoi(boost::get<string>(vars["maxCheckFailures"]));
-                         }
+                         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);
 
-                         if (vars.count("rise")) {
-                           config.minRiseSuccesses = std::stoi(boost::get<string>(vars["rise"]));
-                         }
+                         getOptionalIntegerValue("newServer", vars, "addXPF", config.xpfRRCode);
 
-                         if (vars.count("reconnectOnUp")) {
-                           config.reconnectOnUp = boost::get<bool>(vars["reconnectOnUp"]);
-                         }
+                         getOptionalValue<bool>(vars, "reconnectOnUp", config.reconnectOnUp);
 
-                         if (vars.count("cpus")) {
-                           for (const auto& cpu : boost::get<LuaArray<std::string>>(vars["cpus"])) {
+                         LuaArray<string> cpuMap;
+                         if (getOptionalValue<decltype(cpuMap)>(vars, "cpus", cpuMap) > 0) {
+                           for (const auto& cpu : cpuMap) {
                              config.d_cpus.insert(std::stoi(cpu.second));
                            }
                          }
 
-                         if (vars.count("tcpOnly")) {
-                           config.d_tcpOnly = boost::get<bool>(vars.at("tcpOnly"));
-                         }
+                         getOptionalValue<bool>(vars, "tcpOnly", config.d_tcpOnly);
 
                          std::shared_ptr<TLSCtx> tlsCtx;
-                         if (vars.count("ciphers")) {
-                           config.d_tlsParams.d_ciphers = boost::get<string>(vars.at("ciphers"));
-                         }
-                         if (vars.count("ciphers13")) {
-                           config.d_tlsParams.d_ciphers13 = boost::get<string>(vars.at("ciphers13"));
-                         }
-                         if (vars.count("caStore")) {
-                           config.d_tlsParams.d_caStore = boost::get<string>(vars.at("caStore"));
-                         }
-                         if (vars.count("validateCertificates")) {
-                           config.d_tlsParams.d_validateCertificates = boost::get<bool>(vars.at("validateCertificates"));
-                         }
-                         if (vars.count("releaseBuffers")) {
-                           config.d_tlsParams.d_releaseBuffers = boost::get<bool>(vars.at("releaseBuffers"));
-                         }
-                         if (vars.count("enableRenegotiation")) {
-                           config.d_tlsParams.d_enableRenegotiation = boost::get<bool>(vars.at("enableRenegotiation"));
-                         }
-                         if (vars.count("subjectName")) {
-                           config.d_tlsSubjectName = boost::get<string>(vars.at("subjectName"));
-                         }
-                         else if (vars.count("subjectAddr")) {
+                         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(boost::get<string>(vars.at("subjectAddr")));
+                             ComboAddress ca(valueStr);
                              config.d_tlsSubjectName = ca.toString();
                              config.d_tlsSubjectIsAddr = true;
                            }
@@ -559,22 +563,20 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 
                          uint16_t serverPort = 53;
 
-                         if (vars.count("tls")) {
+                         if (getOptionalValue<std::string>(vars, "tls", valueStr) > 0) {
                            serverPort = 853;
-                           config.d_tlsParams.d_provider = boost::get<string>(vars.at("tls"));
+                           config.d_tlsParams.d_provider = valueStr;
                            tlsCtx = getTLSContext(config.d_tlsParams);
 
-                           if (vars.count("dohPath")) {
-#ifndef HAVE_NGHTTP2
-                             throw std::runtime_error("Outgoing DNS over HTTPS support requested (via 'dohPath' on newServer()) but nghttp2 support is not available");
+                           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 = boost::get<string>(vars.at("dohPath"));
+                             config.d_dohPath = valueStr;
 
-                             if (vars.count("addXForwardedHeaders")) {
-                               config.d_addXForwardedHeaders = boost::get<bool>(vars.at("addXForwardedHeaders"));
-                             }
+                             getOptionalValue<bool>(vars, "addXForwardedHeaders", config.d_addXForwardedHeaders);
                            }
                          }
 
@@ -598,15 +600,13 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
                            return std::shared_ptr<DownstreamState>();
                          }
 
-                         if (vars.count("pool")) {
-                           if (auto* pool = boost::get<string>(&vars["pool"])) {
-                             config.pools.insert(*pool);
-                           }
-                           else {
-                             auto pools = boost::get<LuaArray<std::string>>(vars["pool"]);
-                             for (auto& p : pools) {
-                               config.pools.insert(p.second);
-                             }
+                         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);
                            }
                          }
 
@@ -616,26 +616,21 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
                          uint16_t upgradeDoHKey = dnsdist::ServiceDiscovery::s_defaultDoHSVCKey;
                          std::string upgradePool;
 
-                         if (vars.count("autoUpgrade") && boost::get<bool>(vars.at("autoUpgrade"))) {
-                           autoUpgrade = true;
-
-                           if (vars.count("autoUpgradeInterval")) {
+                         getOptionalValue<bool>(vars, "autoUpgrade", autoUpgrade);
+                         if (autoUpgrade) {
+                           if (getOptionalValue<std::string>(vars, "autoUpgradeInterval", valueStr) > 0) {
                              try {
-                               upgradeInterval = static_cast<uint32_t>(std::stoul(boost::get<string>(vars.at("autoUpgradeInterval"))));
+                               upgradeInterval = static_cast<uint32_t>(std::stoul(valueStr));
                              }
                              catch (const std::exception& e) {
                                warnlog("Error parsing 'autoUpgradeInterval' value: %s", e.what());
                              }
                            }
-                           if (vars.count("autoUpgradeKeep")) {
-                             keepAfterUpgrade = boost::get<bool>(vars.at("autoUpgradeKeep"));
-                           }
-                           if (vars.count("autoUpgradePool")) {
-                             upgradePool = boost::get<string>(vars.at("autoUpgradePool"));
-                           }
-                           if (vars.count("autoUpgradeDoHKey")) {
+                           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(boost::get<string>(vars.at("autoUpgradeDoHKey"))));
+                               upgradeDoHKey = static_cast<uint16_t>(std::stoul(valueStr));
                              }
                              catch (const std::exception& e) {
                                warnlog("Error parsing 'autoUpgradeDoHKey' value: %s", e.what());
@@ -645,11 +640,40 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 
                          // 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)) {
+#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[0];
+                             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());
                          }
-
-                         if (autoUpgrade) {
+#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);
                          }
 
@@ -683,6 +707,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
                            return a->d_config.order < b->d_config.order;
                          });
                          g_dstates.setState(states);
+                         checkAllParametersConsumed("newServer", vars);
                          return ret;
                        });
 
@@ -736,12 +761,14 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 
   luaCtx.writeFunction("setLocal", [client](const std::string& addr, boost::optional<localbind_t> vars) {
     setLuaSideEffect();
-    if (client)
+    if (client) {
       return;
-    if (g_configurationDone) {
-      g_outputBuffer = "setLocal cannot be used at runtime!\n";
+    }
+
+    if (!checkConfigurationTime("setLocal")) {
       return;
     }
+
     bool reusePort = false;
     int tcpFastOpenQueueSize = 0;
     int tcpListenQueueSize = 0;
@@ -749,8 +776,9 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     uint64_t tcpMaxConcurrentConnections = 0;
     std::string interface;
     std::set<int> cpus;
+    bool enableProxyProtocol = true;
 
-    parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections);
+    parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol);
 
     try {
       ComboAddress loc(addr, 53);
@@ -765,8 +793,8 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       }
 
       // 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);
+      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;
       }
@@ -777,7 +805,21 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
         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";
@@ -788,8 +830,8 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     setLuaSideEffect();
     if (client)
       return;
-    if (g_configurationDone) {
-      g_outputBuffer = "addLocal cannot be used at runtime!\n";
+
+    if (!checkConfigurationTime("addLocal")) {
       return;
     }
     bool reusePort = false;
@@ -799,14 +841,15 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     uint64_t tcpMaxConcurrentConnections = 0;
     std::string interface;
     std::set<int> cpus;
+    bool enableProxyProtocol = true;
 
-    parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections);
+    parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol);
 
     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);
+      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;
       }
@@ -816,7 +859,21 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       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";
@@ -864,12 +921,11 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 
   luaCtx.writeFunction("showACL", []() {
     setLuaNoSideEffect();
-    vector<string> vec;
+    auto aclEntries = g_ACL.getLocal()->toStringVector();
 
-    g_ACL.getLocal()->toStringVector(&vec);
-
-    for (const auto& s : vec)
-      g_outputBuffer += s + "\n";
+    for (const auto& entry : aclEntries) {
+      g_outputBuffer += entry + "\n";
+    }
   });
 
   luaCtx.writeFunction("shutdown", []() {
@@ -877,14 +933,15 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     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();
+    // 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);
   });
 
@@ -893,22 +950,22 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
   luaCtx.writeFunction("showServers", [](boost::optional<showserversopts_t> vars) {
     setLuaNoSideEffect();
     bool showUUIDs = false;
-    if (vars) {
-      if (vars->count("showUUIDs")) {
-        showUUIDs = boost::get<bool>((*vars)["showUUIDs"]);
-      }
-    }
+    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% %|92t|%4$5s %|88t|%5$7.1f %|103t|%6$7d %|106t|%7$3d %|115t|%8$2d %|117t|%9$10d %|123t|%10$7d %|128t|%11$5.1f %|146t|%12$5.1f %|152t|%13$11d %14%");
-        //             1        2          3       4        5       6       7       8           9        10        11       12     13              14        15
-        ret << (fmt % "#" % "Name" % "Address" % "State" % "Qps" % "Qlim" % "Ord" % "Wt" % "Queries" % "Drops" % "Drate" % "Lat" % "Outstanding" % "Pools" % "UUID") << endl;
+        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% %|55t|%4$5s %|51t|%5$7.1f %|66t|%6$7d %|69t|%7$3d %|78t|%8$2d %|80t|%9$10d %|86t|%10$7d %|91t|%11$5.1f %|109t|%12$5.1f %|115t|%13$11d %14%");
-        ret << (fmt % "#" % "Name" % "Address" % "State" % "Qps" % "Qlim" % "Ord" % "Wt" % "Queries" % "Drops" % "Drate" % "Lat" % "Outstanding" % "Pools") << endl;
+        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};
@@ -923,11 +980,13 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
           }
           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) % (s->latencyUsec / 1000.0) % s->outstanding.load() % pools % *s->d_config.id) << endl;
+          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) % (s->latencyUsec / 1000.0) % s->outstanding.load() % pools) << endl;
+          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();
@@ -936,12 +995,12 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       }
       if (showUUIDs) {
         ret << (fmt % "All" % "" % "" % ""
-                % (double)totQPS % "" % "" % "" % totQueries % totDrops % "" % "" % "" % "" % "")
+                % (double)totQPS % "" % "" % "" % totQueries % totDrops % "" % "" % "" % "" % "" % "")
             << endl;
       }
       else {
         ret << (fmt % "All" % "" % "" % ""
-                % (double)totQPS % "" % "" % "" % totQueries % totDrops % "" % "" % "" % "")
+                % (double)totQPS % "" % "" % "" % totQueries % totDrops % "" % "" % "" % "" % "")
             << endl;
       }
 
@@ -958,12 +1017,12 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     LuaArray<std::shared_ptr<DownstreamState>> ret;
     int count = 1;
     for (const auto& s : g_dstates.getCopy()) {
-      ret.push_back(make_pair(count++, s));
+      ret.emplace_back(count++, s);
     }
     return ret;
   });
 
-  luaCtx.writeFunction("getPoolServers", [](string pool) {
+  luaCtx.writeFunction("getPoolServers", [](const string& pool) {
     const auto poolServers = getDownstreamCandidates(g_pools.getCopy(), pool);
     return *poolServers;
   });
@@ -992,13 +1051,12 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 #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();
-    auto ours = g_carbon.getCopy();
-    ours.push_back({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});
-    g_carbon.setState(ours);
+    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 */
 
@@ -1048,12 +1106,18 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     }
 
     bool hashPlaintextCredentials = false;
-    if (vars->count("hashPlaintextCredentials")) {
-      hashPlaintextCredentials = boost::get<bool>(vars->at("hashPlaintextCredentials"));
-    }
-
-    if (vars->count("password")) {
-      std::string password = boost::get<std::string>(vars->at("password"));
+    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.");
@@ -1062,8 +1126,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       setWebserverPassword(std::move(holder));
     }
 
-    if (vars->count("apiKey")) {
-      std::string apiKey = boost::get<std::string>(vars->at("apiKey"));
+    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.");
@@ -1072,24 +1135,28 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       setWebserverAPIKey(std::move(holder));
     }
 
-    if (vars->count("acl")) {
-      const std::string acl = boost::get<std::string>(vars->at("acl"));
-
+    if (getOptionalValue<std::string>(vars, "acl", acl) > 0) {
       setWebserverACL(acl);
     }
 
-    if (vars->count("customHeaders")) {
-      const auto headers = boost::get<std::unordered_map<std::string, std::string>>(vars->at("customHeaders"));
-
+    if (getOptionalValue<decltype(headers)>(vars, "customHeaders", headers) > 0) {
       setWebserverCustomHeaders(headers);
     }
 
-    if (vars->count("statsRequireAuthentication")) {
-      setWebserverStatsRequireAuthentication(boost::get<bool>(vars->at("statsRequireAuthentication")));
+    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 (vars->count("maxConcurrentConnections")) {
-      setWebserverMaxConcurrentConnections(std::stoi(boost::get<std::string>(vars->at("maxConcurrentConnections"))));
+    if (getOptionalIntegerValue("setWebserverConfig", vars, "maxConcurrentConnections", maxConcurrentConnections) > 0) {
+      setWebserverMaxConcurrentConnections(maxConcurrentConnections);
     }
   });
 
@@ -1115,23 +1182,22 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     }
 
     g_consoleEnabled = true;
-#ifdef HAVE_LIBSODIUM
+#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 {
-      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();
+      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->push_back(launch);
+        g_launchWork->emplace_back(std::move(launch));
       }
       else {
         launch();
@@ -1145,8 +1211,8 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 
   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");
+#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); });
@@ -1155,8 +1221,8 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
   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");
+#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;
@@ -1173,15 +1239,14 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
   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");
+#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
 
-    vector<string> vec;
-    g_consoleACL.getLocal()->toStringVector(&vec);
+    auto aclEntries = g_consoleACL.getLocal()->toStringVector();
 
-    for (const auto& s : vec) {
-      g_outputBuffer += s + "\n";
+    for (const auto& entry : aclEntries) {
+      g_outputBuffer += entry + "\n";
     }
   });
 
@@ -1220,20 +1285,20 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
   luaCtx.writeFunction("setQueryCount", [](bool enabled) { g_qcount.enabled = enabled; });
 
   luaCtx.writeFunction("setQueryCountFilter", [](QueryCountFilter func) {
-    g_qcount.filter = func;
+    g_qcount.filter = std::move(func);
   });
 
   luaCtx.writeFunction("makeKey", []() {
     setLuaNoSideEffect();
-    g_outputBuffer = "setKey(" + newKey() + ")\n";
+    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
     }
-#ifndef HAVE_LIBSODIUM
-    warnlog("Calling setKey() while libsodium support has not been enabled is not secure, and will result in cleartext communications");
+#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();
@@ -1243,7 +1308,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       errlog("%s", g_outputBuffer);
     }
     else
-      g_consoleKey = newkey;
+      g_consoleKey = std::move(newkey);
   });
 
   luaCtx.writeFunction("clearConsoleHistory", []() {
@@ -1252,7 +1317,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 
   luaCtx.writeFunction("testCrypto", [](boost::optional<string> optTestMsg) {
     setLuaNoSideEffect();
-#ifdef HAVE_LIBSODIUM
+#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
     try {
       string testmsg;
 
@@ -1263,22 +1328,25 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
         testmsg = "testStringForCryptoTests";
       }
 
-      SodiumNonce sn, sn2;
-      sn.init();
-      sn2 = sn;
-      string encrypted = sodEncryptSym(testmsg, g_consoleKey, sn);
-      string decrypted = sodDecryptSym(encrypted, g_consoleKey, sn2);
+      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);
 
-      sn.increment();
-      sn2.increment();
+      nonce1.increment();
+      nonce2.increment();
 
-      encrypted = sodEncryptSym(testmsg, g_consoleKey, sn);
-      decrypted = sodDecryptSym(encrypted, g_consoleKey, sn2);
+      encrypted = dnsdist::crypto::authenticated::encryptSym(testmsg, g_consoleKey, nonce1);
+      decrypted = dnsdist::crypto::authenticated::decryptSym(encrypted, g_consoleKey, nonce2);
 
-      if (testmsg == decrypted)
+      if (testmsg == decrypted) {
         g_outputBuffer = "Everything is ok!\n";
-      else
+      }
+      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";
@@ -1298,96 +1366,82 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
   luaCtx.writeFunction("setUDPTimeout", [](int timeout) { DownstreamState::s_udpTimeout = timeout; });
 
   luaCtx.writeFunction("setMaxUDPOutstanding", [](uint64_t max) {
-    if (!g_configurationDone) {
-      checkParameterBound("setMaxUDPOutstanding", max);
-      g_maxOutstanding = max;
-    }
-    else {
-      g_outputBuffer = "Max UDP outstanding cannot be altered at runtime!\n";
+    if (!checkConfigurationTime("setMaxUDPOutstanding")) {
+      return;
     }
+
+    checkParameterBound("setMaxUDPOutstanding", max);
+    g_maxOutstanding = max;
   });
 
   luaCtx.writeFunction("setMaxTCPClientThreads", [](uint64_t max) {
-    if (!g_configurationDone) {
-      g_maxTCPClientThreads = max;
-    }
-    else {
-      g_outputBuffer = "Maximum TCP client threads count cannot be altered at runtime!\n";
+    if (!checkConfigurationTime("setMaxTCPClientThreads")) {
+      return;
     }
+    g_maxTCPClientThreads = max;
   });
 
   luaCtx.writeFunction("setMaxTCPQueuedConnections", [](uint64_t max) {
-    if (!g_configurationDone) {
-      g_maxTCPQueuedConnections = max;
-    }
-    else {
-      g_outputBuffer = "The maximum number of queued TCP connections cannot be altered at runtime!\n";
+    if (!checkConfigurationTime("setMaxTCPQueuedConnections")) {
+      return;
     }
+    g_maxTCPQueuedConnections = max;
   });
 
   luaCtx.writeFunction("setMaxTCPQueriesPerConnection", [](uint64_t max) {
-    if (!g_configurationDone) {
-      g_maxTCPQueriesPerConn = max;
-    }
-    else {
-      g_outputBuffer = "The maximum number of queries per TCP connection cannot be altered at runtime!\n";
+    if (!checkConfigurationTime("setMaxTCPQueriesPerConnection")) {
+      return;
     }
+    g_maxTCPQueriesPerConn = max;
   });
 
   luaCtx.writeFunction("setMaxTCPConnectionsPerClient", [](uint64_t max) {
-    if (!g_configurationDone) {
-      g_maxTCPConnectionsPerClient = max;
-    }
-    else {
-      g_outputBuffer = "The maximum number of TCP connection per client cannot be altered at runtime!\n";
+    if (!checkConfigurationTime("setMaxTCPConnectionsPerClient")) {
+      return;
     }
+    dnsdist::IncomingConcurrentTCPConnectionsManager::setMaxTCPConnectionsPerClient(max);
   });
 
   luaCtx.writeFunction("setMaxTCPConnectionDuration", [](uint64_t max) {
-    if (!g_configurationDone) {
-      g_maxTCPConnectionDuration = max;
-    }
-    else {
-      g_outputBuffer = "The maximum duration of a TCP connection cannot be altered at runtime!\n";
+    if (!checkConfigurationTime("setMaxTCPConnectionDuration")) {
+      return;
     }
+    g_maxTCPConnectionDuration = max;
   });
 
   luaCtx.writeFunction("setMaxCachedTCPConnectionsPerDownstream", [](uint64_t max) {
-    DownstreamTCPConnectionsManager::setMaxIdleConnectionsPerDownstream(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 (!g_configurationDone) {
-      g_outgoingDoHWorkerThreads = workers;
-    }
-    else {
-      g_outputBuffer = "The amount of outgoing DoH worker threads cannot be altered at runtime!\n";
+    if (!checkConfigurationTime("setOutgoingDoHWorkerThreads")) {
+      return;
     }
+    g_outgoingDoHWorkerThreads = workers;
   });
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
 
   luaCtx.writeFunction("setOutgoingTLSSessionsCacheMaxTicketsPerBackend", [](uint64_t max) {
-    if (g_configurationDone) {
-      g_outputBuffer = "setOutgoingTLSSessionsCacheMaxTicketsPerBackend() cannot be called at runtime!\n";
+    if (!checkConfigurationTime("setOutgoingTLSSessionsCacheMaxTicketsPerBackend")) {
       return;
     }
     TLSSessionCache::setMaxTicketsPerBackend(max);
   });
 
   luaCtx.writeFunction("setOutgoingTLSSessionsCacheCleanupDelay", [](time_t delay) {
-    if (g_configurationDone) {
-      g_outputBuffer = "setOutgoingTLSSessionsCacheCleanupDelay() cannot be called at runtime!\n";
+    if (!checkConfigurationTime("setOutgoingTLSSessionsCacheCleanupDelay")) {
       return;
     }
     TLSSessionCache::setCleanupDelay(delay);
   });
 
   luaCtx.writeFunction("setOutgoingTLSSessionsCacheMaxTicketValidity", [](time_t validity) {
-    if (g_configurationDone) {
-      g_outputBuffer = "setOutgoingTLSSessionsCacheMaxTicketValidity() cannot be called at runtime!\n";
+    if (!checkConfigurationTime("setOutgoingTLSSessionsCacheMaxTicketValidity")) {
       return;
     }
     TLSSessionCache::setSessionValidity(validity);
@@ -1417,20 +1471,21 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 
   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();
+    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& 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();
+        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) % (g_defaultBPFFilter && e.second.bpf ? "*" : "") % e.second.reason).str();
       }
     }
     auto slow2 = g_dynblockSMT.getCopy();
@@ -1439,9 +1494,61 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
         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();
+        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();
+    struct 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();
+    struct 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", []() {
@@ -1489,72 +1596,96 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
                            if (!got || expired) {
                              warnlog("Inserting dynamic block for %s for %d seconds: %s", capair.first.toString(), actualSeconds, msg);
                            }
-                           slow.insert(requestor).second = db;
+                           slow.insert(requestor).second = std::move(db);
                          }
                          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();
-                         auto slow = g_dynblockSMT.getCopy();
-                         struct timespec until, now;
+                         struct timespec now
+                         {
+                         };
                          gettime(&now);
-                         until = now;
-                         int actualSeconds = seconds ? *seconds : 10;
-                         until.tv_sec += actualSeconds;
+                         unsigned int actualSeconds = seconds ? *seconds : 10;
 
+                         bool needUpdate = false;
+                         auto slow = g_dynblockSMT.getCopy();
                          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;
+
+                           if (dnsdist::DynamicBlocks::addOrRefreshBlockSMT(slow, now, domain, msg, actualSeconds, action ? *action : DNSAction::Action::None, false)) {
+                             needUpdate = 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));
+                         if (needUpdate) {
+                           g_dynblockSMT.setState(slow);
                          }
-                         g_dynblockSMT.setState(slow);
                        });
 
-  luaCtx.writeFunction("setDynBlocksAction", [](DNSAction::Action action) {
-    if (!g_configurationDone) {
-      if (action == DNSAction::Action::Drop || action == DNSAction::Action::NoOp || action == DNSAction::Action::Nxdomain || action == DNSAction::Action::Refused || action == DNSAction::Action::Truncate || action == DNSAction::Action::NoRecurse) {
-        g_dynBlockAction = action;
-      }
-      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";
-      }
-    }
-    else {
-      g_outputBuffer = "Dynamic blocks action cannot be altered at runtime!\n";
-    }
-  });
-#endif /* DISABLE_DEPRECATED_DYNBLOCK */
+  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;
+
+                         struct 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 = 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 (g_configurationDone) {
-      g_outputBuffer = "addDNSCryptBind cannot be used at runtime!\n";
+    if (!checkConfigurationTime("addDNSCryptBind")) {
       return;
     }
     bool reusePort = false;
@@ -1565,13 +1696,15 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     std::string interface;
     std::set<int> cpus;
     std::vector<DNSCryptContext::CertKeyPaths> certKeys;
+    bool enableProxyProtocol = true;
 
-    parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections);
+    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({certFile, keyFile});
+      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);
@@ -1597,29 +1730,29 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       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;
+      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(cs));
+      g_frontends.push_back(std::move(clientState));
 
       /* TCP */
-      cs = std::make_unique<ClientState>(ComboAddress(addr, 443), true, reusePort, tcpFastOpenQueueSize, interface, cpus);
-      cs->dnscryptCtx = ctx;
+      clientState = std::make_unique<ClientState>(ComboAddress(addr, 443), true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+      clientState->dnscryptCtx = std::move(ctx);
       if (tcpListenQueueSize > 0) {
-        cs->tcpListenQueueSize = tcpListenQueueSize;
+        clientState->tcpListenQueueSize = tcpListenQueueSize;
       }
       if (maxInFlightQueriesPerConn > 0) {
-        cs->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
+        clientState->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
       }
       if (tcpMaxConcurrentConnections > 0) {
-        cs->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections;
+        clientState->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections;
       }
 
-      g_frontends.push_back(std::move(cs));
+      g_frontends.push_back(std::move(clientState));
     }
-    catch (std::exception& e) {
-      errlog(e.what());
-      g_outputBuffer = "Error: " + string(e.what()) + "\n";
+    catch (const std::exception& e) {
+      errlog("Error during addDNSCryptBind() processing: %s", e.what());
+      g_outputBuffer = "Error during addDNSCryptBind() processing: " + string(e.what()) + "\n";
     }
   });
 
@@ -1700,6 +1833,18 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     }
   });
 
+  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>();
@@ -1710,7 +1855,43 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     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;
@@ -1777,16 +1958,15 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 
 #ifdef HAVE_EBPF
   luaCtx.writeFunction("setDefaultBPFFilter", [](std::shared_ptr<BPFFilter> bpf) {
-    if (g_configurationDone) {
-      g_outputBuffer = "setDefaultBPFFilter() cannot be used at runtime!\n";
+    if (!checkConfigurationTime("setDefaultBPFFilter")) {
       return;
     }
-    g_defaultBPFFilter = bpf;
+    g_defaultBPFFilter = std::move(bpf);
   });
 
   luaCtx.writeFunction("registerDynBPFFilter", [](std::shared_ptr<DynBPFFilter> dbpf) {
     if (dbpf) {
-      g_dynBPFFilters.push_back(dbpf);
+      g_dynBPFFilters.push_back(std::move(dbpf));
     }
   });
 
@@ -1801,6 +1981,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     }
   });
 
+#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) {
@@ -1819,26 +2000,29 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     }
   });
 #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();
+    {
+      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 (g_configurationDone) {
-      errlog("includeDirectory() cannot be used at runtime!");
-      g_outputBuffer = "includeDirectory() cannot be used at runtime!\n";
+    if (!checkConfigurationTime("includeDirectory")) {
       return;
     }
-
     if (g_included) {
       errlog("includeDirectory() cannot be used recursively!");
       g_outputBuffer = "includeDirectory() cannot be used recursively!\n";
@@ -1858,33 +2042,30 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       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;
+    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(ent->d_name, ".conf")) {
+      if (boost::ends_with(name, ".conf")) {
         std::ostringstream namebuf;
-        namebuf << dirname.c_str() << "/" << ent->d_name;
-
-        if (stat(namebuf.str().c_str(), &st) || !S_ISREG(st.st_mode)) {
-          continue;
+        namebuf << dirname << "/" << name;
+        struct stat fileStat
+        {
+        };
+        if (stat(namebuf.str().c_str(), &fileStat) == 0 && S_ISREG(fileStat.st_mode)) {
+          files.push_back(namebuf.str());
         }
-
-        files.push_back(namebuf.str());
       }
+      return true;
+    });
+
+    if (directoryError) {
+      errlog("Error opening included directory: %s!", *directoryError);
+      g_outputBuffer = "Error opening included directory: " + *directoryError + "!";
+      return;
     }
 
-    closedir(dirp);
     std::sort(files.begin(), files.end());
 
     g_included = true;
@@ -1962,9 +2143,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 
   luaCtx.writeFunction("setRingBuffersSize", [client](uint64_t capacity, boost::optional<uint64_t> numberOfShards) {
     setLuaSideEffect();
-    if (g_configurationDone) {
-      errlog("setRingBuffersSize() cannot be used at runtime!");
-      g_outputBuffer = "setRingBuffersSize() cannot be used at runtime!\n";
+    if (!checkConfigurationTime("setRingBuffersSize")) {
       return;
     }
     if (!client) {
@@ -1980,6 +2159,25 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     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());
@@ -1987,17 +2185,28 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
   });
 
   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)
+    if (client || configCheck) {
       return;
-    if (g_configurationDone) {
-      errlog("snmpAgent() cannot be used at runtime!");
-      g_outputBuffer = "snmpAgent() cannot be used at runtime!\n";
+    }
+    if (!checkConfigurationTime("snmpAgent")) {
       return;
     }
-
     if (g_snmpEnabled) {
       errlog("snmpAgent() cannot be used twice!");
       g_outputBuffer = "snmpAgent() cannot be used twice!\n";
@@ -2017,23 +2226,23 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 #endif /* HAVE_NET_SNMP */
 
 #ifndef DISABLE_POLICIES_BINDINGS
-  luaCtx.writeFunction("setServerPolicy", [](const ServerPolicy& policy) {
+  luaCtx.writeFunction("setServerPolicy", [](const std::shared_ptr<ServerPolicy>& policy) {
     setLuaSideEffect();
-    g_policy.setState(policy);
+    g_policy.setState(*policy);
   });
 
-  luaCtx.writeFunction("setServerPolicyLua", [](string name, ServerPolicy::policyfunc_t policy) {
+  luaCtx.writeFunction("setServerPolicyLua", [](const string& name, ServerPolicy::policyfunc_t policy) {
     setLuaSideEffect();
     g_policy.setState(ServerPolicy{name, policy, true});
   });
 
-  luaCtx.writeFunction("setServerPolicyLuaFFI", [](string name, ServerPolicy::ffipolicyfunc_t policy) {
+  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", [](string name, const std::string& policyCode) {
+  luaCtx.writeFunction("setServerPolicyLuaFFIPerThread", [](const string& name, const std::string& policyCode) {
     setLuaSideEffect();
     auto pol = ServerPolicy(name, policyCode);
     g_policy.setState(std::move(pol));
@@ -2044,35 +2253,35 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     g_outputBuffer = g_policy.getLocal()->getName() + "\n";
   });
 
-  luaCtx.writeFunction("setPoolServerPolicy", [](ServerPolicy policy, string pool) {
+  luaCtx.writeFunction("setPoolServerPolicy", [](const std::shared_ptr<ServerPolicy>& policy, const string& pool) {
     setLuaSideEffect();
     auto localPools = g_pools.getCopy();
-    setPoolPolicy(localPools, pool, std::make_shared<ServerPolicy>(policy));
+    setPoolPolicy(localPools, pool, policy);
     g_pools.setState(localPools);
   });
 
-  luaCtx.writeFunction("setPoolServerPolicyLua", [](string name, ServerPolicy::policyfunc_t policy, string pool) {
+  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}));
+    setPoolPolicy(localPools, pool, std::make_shared<ServerPolicy>(ServerPolicy{name, std::move(policy), true}));
     g_pools.setState(localPools);
   });
 
-  luaCtx.writeFunction("setPoolServerPolicyLuaFFI", [](string name, ServerPolicy::ffipolicyfunc_t policy, string pool) {
+  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}));
+    setPoolPolicy(localPools, pool, std::make_shared<ServerPolicy>(ServerPolicy{name, std::move(policy)}));
     g_pools.setState(localPools);
   });
 
-  luaCtx.writeFunction("setPoolServerPolicyLuaFFIPerThread", [](string name, const std::string& policyCode, string pool) {
+  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", [](string pool) {
+  luaCtx.writeFunction("showPoolServerPolicy", [](const std::string& pool) {
     setLuaSideEffect();
     auto localPools = g_pools.getCopy();
     auto poolObj = getPool(localPools, pool);
@@ -2088,26 +2297,30 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
   luaCtx.writeFunction("setTCPDownstreamCleanupInterval", [](uint64_t interval) {
     setLuaSideEffect();
     checkParameterBound("setTCPDownstreamCleanupInterval", interval);
-    DownstreamTCPConnectionsManager::setCleanupInterval(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);
-    DownstreamTCPConnectionsManager::setMaxIdleTime(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;
@@ -2119,9 +2332,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
   });
 
   luaCtx.writeFunction("setProxyProtocolACL", [](LuaTypeOrArrayOf<std::string> inp) {
-    if (g_configurationDone) {
-      errlog("setProxyProtocolACL() cannot be used at runtime!");
-      g_outputBuffer = "setProxyProtocolACL() cannot be used at runtime!\n";
+    if (!checkConfigurationTime("setProxyProtocolACL")) {
       return;
     }
     setLuaSideEffect();
@@ -2138,9 +2349,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
   });
 
   luaCtx.writeFunction("setProxyProtocolApplyACLToProxiedClients", [](bool apply) {
-    if (g_configurationDone) {
-      errlog("setProxyProtocolApplyACLToProxiedClients() cannot be used at runtime!");
-      g_outputBuffer = "setProxyProtocolApplyACLToProxiedClients() cannot be used at runtime!\n";
+    if (!checkConfigurationTime("setProxyProtocolApplyACLToProxiedClients")) {
       return;
     }
     setLuaSideEffect();
@@ -2148,9 +2357,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
   });
 
   luaCtx.writeFunction("setProxyProtocolMaximumPayloadSize", [](uint64_t size) {
-    if (g_configurationDone) {
-      errlog("setProxyProtocolMaximumPayloadSize() cannot be used at runtime!");
-      g_outputBuffer = "setProxyProtocolMaximumPayloadSize() cannot be used at runtime!\n";
+    if (!checkConfigurationTime("setProxyProtocolMaximumPayloadSize")) {
       return;
     }
     setLuaSideEffect();
@@ -2159,9 +2366,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 
 #ifndef DISABLE_RECVMMSG
   luaCtx.writeFunction("setUDPMultipleMessagesVectorSize", [](uint64_t vSize) {
-    if (g_configurationDone) {
-      errlog("setUDPMultipleMessagesVectorSize() cannot be used at runtime!");
-      g_outputBuffer = "setUDPMultipleMessagesVectorSize() cannot be used at runtime!\n";
+    if (!checkConfigurationTime("setUDPMultipleMessagesVectorSize")) {
       return;
     }
 #if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
@@ -2195,15 +2400,13 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 #ifndef DISABLE_SECPOLL
   luaCtx.writeFunction("showSecurityStatus", []() {
     setLuaNoSideEffect();
-    g_outputBuffer = std::to_string(g_stats.securityStatus) + "\n";
+    g_outputBuffer = std::to_string(dnsdist::metrics::g_stats.securityStatus) + "\n";
   });
 
   luaCtx.writeFunction("setSecurityPollSuffix", [](const std::string& suffix) {
-    if (g_configurationDone) {
-      g_outputBuffer = "setSecurityPollSuffix() cannot be used at runtime!\n";
+    if (!checkConfigurationTime("setSecurityPollSuffix")) {
       return;
     }
-
     g_secPollSuffix = suffix;
   });
 
@@ -2218,11 +2421,10 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 #endif /* DISABLE_SECPOLL */
 
   luaCtx.writeFunction("setSyslogFacility", [](boost::variant<int, std::string> facility) {
-    setLuaSideEffect();
-    if (g_configurationDone) {
-      g_outputBuffer = "setSyslogFacility cannot be used at runtime!\n";
+    if (!checkConfigurationTime("setSyslogFacility")) {
       return;
     }
+    setLuaSideEffect();
     if (facility.type() == typeid(std::string)) {
       static std::map<std::string, int> const facilities = {
         {"local0", LOG_LOCAL0},
@@ -2297,7 +2499,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
         password = boost::get<const string>((*opts)["password"]);
       }
     }
-    result = std::make_shared<TLSCertKeyPair>(TLSCertKeyPair{cert, key, password});
+    result = std::make_shared<TLSCertKeyPair>(cert, std::move(key), std::move(password));
 #endif
     return result;
   });
@@ -2307,38 +2509,67 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       return;
     }
 #ifdef HAVE_DNS_OVER_HTTPS
-    setLuaSideEffect();
-    if (g_configurationDone) {
-      g_outputBuffer = "addDOHLocal cannot be used at runtime!\n";
+    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_tlsConfig.d_certKeyPairs, *certFiles, *keyFiles)) {
+      if (!loadTLSCertificateAndKeys("addDOHLocal", frontend->d_tlsContext.d_tlsConfig.d_certKeyPairs, *certFiles, *keyFiles)) {
         return;
       }
 
-      frontend->d_local = ComboAddress(addr, 443);
+      frontend->d_tlsContext.d_addr = 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());
+      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.push_back(boost::get<std::string>(*urls));
+        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& p : urlsVect) {
-          frontend->d_urls.push_back(p.second);
+          frontend->d_urls.insert(p.second);
         }
       }
     }
     else {
-      frontend->d_urls = {"/dns-query"};
+      frontend->d_urls.insert("/dns-query");
     }
 
     bool reusePort = false;
@@ -2348,57 +2579,304 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     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);
-
-      if (vars->count("idleTimeout")) {
-        frontend->d_idleTimeout = boost::get<int>((*vars)["idleTimeout"]);
+      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);
+        }
       }
 
-      if (vars->count("serverTokens")) {
-        frontend->d_serverTokens = boost::get<const string>((*vars)["serverTokens"]);
+      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;
+          }
+        }
       }
 
-      if (vars->count("customResponseHeaders")) {
-        for (auto const& headerMap : boost::get<LuaAssociativeTable<std::string>>((*vars).at("customResponseHeaders"))) {
-          frontend->d_customResponseHeaders[boost::to_lower_copy(headerMap.first)] = headerMap.second;
+      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;
         }
       }
 
-      if (vars->count("sendCacheControlHeaders")) {
-        frontend->d_sendCacheControlHeaders = boost::get<bool>((*vars)["sendCacheControlHeaders"]);
+      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 (vars->count("trustForwardedForHeader")) {
-        frontend->d_trustForwardedForHeader = boost::get<bool>((*vars)["trustForwardedForHeader"]);
+    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
+  });
 
-      if (vars->count("internalPipeBufferSize")) {
-        frontend->d_internalPipeBufferSize = boost::get<int>((*vars)["internalPipeBufferSize"]);
+  // 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);
 
-      if (vars->count("exactPathMatching")) {
-        frontend->d_exactPathMatching = boost::get<bool>((*vars)["exactPathMatching"]);
+      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;
+        }
       }
 
-      parseTLSConfig(frontend->d_tlsConfig, "addDOHLocal", vars);
+      checkAllParametersConsumed("addDOQLocal", vars);
     }
-    g_dohlocals.push_back(frontend);
-    auto cs = std::make_unique<ClientState>(frontend->d_local, true, reusePort, tcpFastOpenQueueSize, interface, cpus);
-    cs->dohFrontend = frontend;
-    if (tcpListenQueueSize > 0) {
-      cs->tcpListenQueueSize = tcpListenQueueSize;
+    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();
     }
-    if (tcpMaxConcurrentConnections > 0) {
-      cs->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections;
+    catch (const std::exception& e) {
+      g_outputBuffer = e.what();
+      throw;
     }
-    g_frontends.push_back(std::move(cs));
 #else
-      throw std::runtime_error("addDOHLocal() called but DNS over HTTPS support is not present!");
+      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();
@@ -2408,7 +2886,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       ret << (fmt % "#" % "Address" % "HTTP" % "HTTP/1" % "HTTP/2" % "GET" % "POST" % "Bad" % "Errors" % "Redirects" % "Valid" % "# ticket keys" % "Rotation delay" % "Next rotation") << endl;
       size_t counter = 0;
       for (const auto& ctx : g_dohlocals) {
-        ret << (fmt % counter % ctx->d_local.toStringWithPort() % ctx->d_httpconnects % ctx->d_http1Stats.d_nbQueries % ctx->d_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;
+        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();
@@ -2422,6 +2900,64 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 #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();
@@ -2432,7 +2968,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       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;
+        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();
@@ -2442,7 +2978,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       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;
+        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();
@@ -2468,13 +3004,13 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
         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());
+        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 %zu: %s\n", index, string(e.what()));
+      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";
@@ -2487,7 +3023,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     return g_dohlocals.size();
   });
 
-  luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)()>("reloadCertificates", [](std::shared_ptr<DOHFrontend> frontend) {
+  luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)()>("reloadCertificates", [](const std::shared_ptr<DOHFrontend>& frontend) {
     if (frontend != nullptr) {
       frontend->reloadCertificates();
     }
@@ -2496,7 +3032,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
   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)) {
+      if (loadTLSCertificateAndKeys("DOHFrontend::loadNewCertificatesAndKeys", frontend->d_tlsContext.d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
         frontend->reloadCertificates();
       }
     }
@@ -2533,13 +3069,12 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       return;
     }
 #ifdef HAVE_DNS_OVER_TLS
-    setLuaSideEffect();
-    if (g_configurationDone) {
-      g_outputBuffer = "addTLSLocal cannot be used at runtime!\n";
+    if (!checkConfigurationTime("addTLSLocal")) {
       return;
     }
-    shared_ptr<TLSFrontend> frontend = std::make_shared<TLSFrontend>();
+    setLuaSideEffect();
 
+    auto frontend = std::make_shared<TLSFrontend>(TLSFrontend::ALPN::DoT);
     if (!loadTLSCertificateAndKeys("addTLSLocal", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
       return;
     }
@@ -2551,16 +3086,47 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     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);
-
-      if (vars->count("provider")) {
-        frontend->d_provider = boost::get<const string>((*vars)["provider"]);
-        boost::algorithm::to_lower(frontend->d_provider);
+      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 {
@@ -2570,26 +3136,28 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       }
       else {
 #ifdef HAVE_LIBSSL
-        vinfolog("Loading default TLS provider 'openssl'");
+        const std::string provider("openssl");
 #else
-          vinfolog("Loading default TLS provider 'gnutls'");
+          const std::string provider("gnutls");
 #endif
+        vinfolog("Loading default TLS provider '%s'", provider);
       }
       // 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;
+      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) {
-        cs->tcpListenQueueSize = tcpListenQueueSize;
+        clientState->tcpListenQueueSize = tcpListenQueueSize;
       }
       if (maxInFlightQueriesPerConn > 0) {
-        cs->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
+        clientState->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
       }
       if (tcpMaxConcurrentConns > 0) {
-        cs->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConns;
+        clientState->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConns;
       }
 
-      g_tlslocals.push_back(cs->tlsFrontend);
-      g_frontends.push_back(std::move(cs));
+      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";
@@ -2632,13 +3200,13 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
         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());
+        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 %zu: %s\n", index, string(e.what()));
+      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";
@@ -2655,13 +3223,13 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
         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());
+        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 %zu: %s\n", index, string(e.what()));
+      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";
@@ -2686,6 +3254,13 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     }
   });
 
+  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;
@@ -2706,7 +3281,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     }
   });
 
-  luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)()>("reloadCertificates", [](std::shared_ptr<TLSFrontend>& frontend) {
+  luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)()>("reloadCertificates", [](const std::shared_ptr<TLSFrontend>& frontend) {
     if (frontend == nullptr) {
       return;
     }
@@ -2742,6 +3317,16 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
           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());
@@ -2752,7 +3337,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
   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)
+#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;
@@ -2760,14 +3345,13 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 
     libssl_generate_ocsp_response(certFile, caCert, caKey, outFile, ndays, nmin);
   });
-#endif /* HAVE_LIBSSL && HAVE_OCSP_BASIC_SIGN*/
+#endif /* HAVE_LIBSSL && HAVE_OCSP_BASIC_SIGN && !DISABLE_OCSP_STAPLING */
 
   luaCtx.writeFunction("addCapabilitiesToRetain", [](LuaTypeOrArrayOf<std::string> caps) {
-    setLuaSideEffect();
-    if (g_configurationDone) {
-      g_outputBuffer = "addCapabilitiesToRetain cannot be used at runtime!\n";
+    if (!checkConfigurationTime("addCapabilitiesToRetain")) {
       return;
     }
+    setLuaSideEffect();
     if (caps.type() == typeid(std::string)) {
       g_capabilitiesToRetain.insert(boost::get<std::string>(caps));
     }
@@ -2782,15 +3366,13 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     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();
 
-    if (g_configurationDone) {
-      g_outputBuffer = "setUDPSocketBufferSizes cannot be used at runtime!\n";
-      return;
-    }
-
     g_socketUDPSendBuffer = snd;
     g_socketUDPRecvBuffer = recv;
   });
@@ -2803,7 +3385,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     DownstreamState::s_randomizeIDs = randomized;
   });
 
-#if defined(HAVE_LIBSSL)
+#if defined(HAVE_LIBSSL) && !defined(HAVE_TLS_PROVIDERS)
   luaCtx.writeFunction("loadTLSEngine", [client](const std::string& engineName, boost::optional<std::string> defaultString) {
     if (client) {
       return;
@@ -2815,15 +3397,76 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       errlog("Error while trying to load TLS engine '%s': %s", engineName, error);
     }
   });
-#endif /* HAVE_LIBSSL */
+#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)
@@ -2834,12 +3477,16 @@ vector<std::function<void(void)>> setupLua(LuaContext& luaCtx, bool client, bool
 
   setupLuaActions(luaCtx);
   setupLuaConfig(luaCtx, client, configCheck);
-  setupLuaBindings(luaCtx, client);
+  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);
@@ -2850,10 +3497,17 @@ vector<std::function<void(void)>> setupLua(LuaContext& luaCtx, bool client, bool
 #endif
 
   std::ifstream ifs(config);
-  if (!ifs)
-    warnlog("Unable to read configuration from '%s'", config);
-  else
+  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);
 
index cf22e9163aabb458193deee7d58972d3267c7dd4..5c35c3fb9d0245fee20977e1f6e34527d7bf1cbe 100644 (file)
@@ -21,6 +21,8 @@
  */
 #pragma once
 
+#include "dolog.hh"
+#include "dnsdist.hh"
 #include "dnsparser.hh"
 #include <random>
 
@@ -31,7 +33,7 @@ struct ResponseConfig
   boost::optional<bool> setRA{boost::none};
   uint32_t ttl{60};
 };
-void setResponseHeadersFromConfig(dnsheader& dh, const ResponseConfig& config);
+void setResponseHeadersFromConfig(dnsheader& dnsheader, const ResponseConfig& config);
 
 class SpoofAction : public DNSAction
 {
@@ -60,11 +62,11 @@ public:
   {
   }
 
-  SpoofAction(const vector<std::string>& raws): d_rawResponses(raws)
+  SpoofAction(const vector<std::string>& raws, std::optional<uint16_t> typeForAny): d_rawResponses(raws), d_rawTypeForAny(typeForAny)
   {
   }
 
-  DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override;
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, string* ruleresult) const override;
 
   string toString() const override
   {
@@ -82,15 +84,20 @@ public:
     return ret;
   }
 
+  [[nodiscard]] ResponseConfig& getResponseConfig()
+  {
+    return d_responseConfig;
+  }
 
-  ResponseConfig d_responseConfig;
 private:
+  ResponseConfig d_responseConfig;
   static thread_local std::default_random_engine t_randomEngine;
   std::vector<ComboAddress> d_addrs;
-  std::set<uint16_t> d_types;
+  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
@@ -98,13 +105,17 @@ class LimitTTLResponseAction : public DNSResponseAction, public boost::noncopyab
 public:
   LimitTTLResponseAction() {}
 
-  LimitTTLResponseAction(uint32_t min, uint32_t max = std::numeric_limits<uint32_t>::max()) : d_min(min), d_max(max)
+  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;
@@ -121,10 +132,27 @@ public:
 
   std::string toString() const override
   {
-    return "limit ttl (" + std::to_string(d_min) + " <= ttl <= " + std::to_string(d_max) + ")";
+    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()};
 };
@@ -133,24 +161,85 @@ 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);
+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);
+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);
+  }
+}
index 571d8776f23f357066c9a6d82d6dc42b7f5dbc33..30445ed24f187721ccb511346ee6bd7028dc2c86 100644 (file)
 #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 DNSQuestion& dnsquestion) :
+  d_dq(dnsquestion)
 {
 }
 
-DNSDistProtoBufMessage::DNSDistProtoBufMessage(const DNSResponse& dr, bool includeCNAME): d_dq(dr), d_dr(&dr), d_type(pdns::ProtoZero::Message::MessageType::DNSResponseType), d_includeCNAME(includeCNAME)
+DNSDistProtoBufMessage::DNSDistProtoBufMessage(const DNSResponse& dnsresponse, bool includeCNAME) :
+  d_dq(dnsresponse), d_dr(&dnsresponse), d_type(pdns::ProtoZero::Message::MessageType::DNSResponseType), d_includeCNAME(includeCNAME)
 {
 }
 
@@ -103,6 +106,17 @@ 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});
@@ -137,6 +151,11 @@ void DNSDistProtoBufMessage::serialize(std::string& data) const
   }
   else if (distProto == dnsdist::Protocol::DoH) {
     protocol = pdns::ProtoZero::Message::TransportProtocol::DoH;
+    m.setHTTPVersion(pdns::ProtoZero::Message::HTTPVersion::HTTP2);
+  }
+  else if (distProto == dnsdist::Protocol::DoH3) {
+    protocol = pdns::ProtoZero::Message::TransportProtocol::DoH;
+    m.setHTTPVersion(pdns::ProtoZero::Message::HTTPVersion::HTTP3);
   }
   else if (distProto == dnsdist::Protocol::DNSCryptUDP) {
     protocol = pdns::ProtoZero::Message::TransportProtocol::DNSCryptUDP;
@@ -144,8 +163,11 @@ void DNSDistProtoBufMessage::serialize(std::string& data) const
   else if (distProto == dnsdist::Protocol::DNSCryptTCP) {
     protocol = pdns::ProtoZero::Message::TransportProtocol::DNSCryptTCP;
   }
+  else if (distProto == dnsdist::Protocol::DoQ) {
+    protocol = pdns::ProtoZero::Message::TransportProtocol::DoQ;
+  }
 
-  m.setRequest(d_dq.uniqueId ? *d_dq.uniqueId : getUniqueID(), d_requestor ? *d_requestor : *d_dq.remote, d_responder ? *d_responder : *d_dq.local, d_question ? d_question->d_name : *d_dq.qname, d_question ? d_question->d_type : d_dq.qtype, d_question ? d_question->d_class : d_dq.qclass, d_dq.getHeader()->id, protocol, d_bytes ? *d_bytes : d_dq.getData().size());
+  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);
@@ -160,10 +182,11 @@ void DNSDistProtoBufMessage::serialize(std::string& data) const
 
   m.startResponse();
   if (d_queryTime) {
+    // coverity[store_truncates_time_t]
     m.setQueryTime(d_queryTime->first, d_queryTime->second);
   }
   else {
-    m.setQueryTime(d_dq.queryTime->tv_sec, d_dq.queryTime->tv_nsec / 1000);
+    m.setQueryTime(d_dq.getQueryRealTime().tv_sec, d_dq.getQueryRealTime().tv_nsec / 1000);
   }
 
   if (d_dr != nullptr) {
@@ -185,6 +208,185 @@ void DNSDistProtoBufMessage::serialize(std::string& data) const
   }
 
   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.d_strings.empty() || !values.d_integers.empty()) {
+      m.setMeta(key, values.d_strings, values.d_integers);
+    }
+    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& dnsquestion) 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)(dnsquestion, 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& 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 */
index 4a5cbd9914054ad33a4c2ad6eb3cba78464bb7f7..c2dee817daf37526ffc3358790adb204c49f4c7f 100644 (file)
 class DNSDistProtoBufMessage
 {
 public:
-  DNSDistProtoBufMessage(const DNSQuestion& dq);
-  DNSDistProtoBufMessage(const DNSResponse& dr, bool includeCNAME);
+  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);
@@ -40,18 +42,20 @@ public:
   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& nm);
+  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;
 
-  std::string toDebugString() const;
+  [[nodiscard]] std::string toDebugString() const;
 
 private:
   struct PBRecord
@@ -64,7 +68,8 @@ private:
   };
   struct PBQuestion
   {
-    PBQuestion(const DNSName& name, uint16_t type, uint16_t class_): d_name(name), d_type(type), d_class(class_)
+    PBQuestion(DNSName name, uint16_t type, uint16_t class_) :
+      d_name(std::move(name)), d_type(type), d_class(class_)
     {
     }
 
@@ -75,7 +80,14 @@ private:
 
   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};
@@ -94,4 +106,62 @@ private:
   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 */
index 8af8fe836891a5a58af65e61cca04f744a1f0a5f..886e7ee42064654bdd5d73545f131d47d4e6420e 100644 (file)
 
 namespace dnsdist
 {
-static const std::vector<std::string> names = {
+const std::array<std::string, Protocol::s_numberOfProtocols> Protocol::s_names = {
   "DoUDP",
   "DoTCP",
   "DNSCryptUDP",
   "DNSCryptTCP",
   "DoT",
-  "DoH"};
+  "DoH",
+  "DoQ",
+  "DoH3"};
 
-static const std::vector<std::string> prettyNames = {
+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(Protocol::typeenum protocol) :
-  d_protocol(protocol)
-{
-  if (protocol >= names.size()) {
-    throw std::runtime_error("Unknown protocol: '" + std::to_string(protocol) + "'");
-  }
-}
+  "DNS over HTTPS",
+  "DNS over QUIC",
+  "DNS over HTTP/3"};
 
 Protocol::Protocol(const std::string& s)
 {
-  const auto& it = std::find(names.begin(), names.end(), s);
-  if (it == names.end()) {
+  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(names.begin(), it);
+  auto index = std::distance(s_names.begin(), it);
   d_protocol = static_cast<Protocol::typeenum>(index);
 }
 
@@ -74,12 +70,26 @@ bool Protocol::operator!=(Protocol::typeenum type) const
 
 const std::string& Protocol::toString() const
 {
-  return names.at(static_cast<uint8_t>(d_protocol));
+  return s_names.at(static_cast<uint8_t>(d_protocol));
 }
 
 const std::string& Protocol::toPrettyString() const
 {
-  return prettyNames.at(static_cast<uint8_t>(d_protocol));
+  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);
+}
 }
index ddd74e3688e5856282386ca08a188ca424671c03..beb43ed3d71d218588432770cf5fd6a55c523098 100644 (file)
@@ -21,7 +21,8 @@
  */
 #pragma once
 
-#include <vector>
+#include <array>
+#include <cstdint>
 #include <string>
 
 namespace dnsdist
@@ -31,15 +32,24 @@ class Protocol
 public:
   enum typeenum : uint8_t
   {
-    DoUDP,
+    DoUDP = 0,
     DoTCP,
     DNSCryptUDP,
     DNSCryptTCP,
     DoT,
-    DoH
+    DoH,
+    DoQ,
+    DoH3
   };
 
-  Protocol(typeenum protocol = DoUDP);
+  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;
@@ -47,8 +57,15 @@ public:
 
   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 6b044273a4c1cc9bb3479790470001534fc6046f..b97b44e6459d305a30154c2aaf08b73239e3ff26 100644 (file)
@@ -48,8 +48,12 @@ void Rings::init()
   /* resize all the rings */
   for (auto& shard : d_shards) {
     shard = std::make_unique<Shard>();
-    shard->queryRing.lock()->set_capacity(d_capacity / d_numberOfShards);
-    shard->respRing.lock()->set_capacity(d_capacity / d_numberOfShards);
+    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 */
@@ -66,6 +70,16 @@ void Rings::setNumberOfLockRetries(size_t 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;
@@ -111,9 +125,9 @@ std::unordered_map<int, vector<boost::variant<string,double>>> Rings::getTopBand
               });
   std::unordered_map<int, vector<boost::variant<string,double>>> ret;
   uint64_t rest = 0;
-  unsigned int count = 1;
+  int count = 1;
   for(const auto& rc : rcounts) {
-    if(count==numentries+1) {
+    if (count == static_cast<int>(numentries + 1)) {
       rest+=rc.first;
     }
     else {
@@ -200,3 +214,12 @@ size_t Rings::loadFromFile(const std::string& filepath, const struct timespec& n
 
   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;
+}
index f1b2f48709510ed2cb25bf6f6a8be172a4bd6b02..084044f0584fdd3c68b6974f028c64e278483f84 100644 (file)
@@ -32,6 +32,7 @@
 #include "lock.hh"
 #include "stat_t.hh"
 #include "dnsdist-protocols.hh"
+#include "dnsdist-mac-address.hh"
 
 struct Rings {
   struct Query
@@ -45,7 +46,7 @@ struct Rings {
     // incoming protocol
     dnsdist::Protocol protocol;
 #if defined(DNSDIST_RINGS_WITH_MACADDRESS)
-    char macaddress[6];
+    dnsdist::MacAddress macaddress;
     bool hasmac{false};
 #endif
   };
@@ -57,10 +58,12 @@ struct Rings {
     struct timespec when;
     struct dnsheader dh;
     unsigned int usec;
-    unsigned int size;
+    uint16_t size;
     uint16_t qtype;
     // outgoing protocol
     dnsdist::Protocol protocol;
+
+    bool isACacheHit() const;
   };
 
   struct Shard
@@ -82,6 +85,8 @@ struct Rings {
   void init();
 
   void setNumberOfLockRetries(size_t retries);
+  void setRecordQueries(bool);
+  void setRecordResponses(bool);
 
   size_t getNumberOfShards() const
   {
@@ -101,9 +106,9 @@ struct Rings {
   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)
-    char macaddress[6];
+    dnsdist::MacAddress macaddress;
     bool hasmac{false};
-    if (getMACAddress(requestor, macaddress, sizeof(macaddress)) == 0) {
+    if (dnsdist::MacAddressesCache::get(requestor, macaddress.data(), macaddress.size()) == 0) {
       hasmac = true;
     }
 #endif
@@ -112,7 +117,7 @@ struct Rings {
       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, sizeof(macaddress), hasmac);
+        insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol, macaddress, hasmac);
 #else
         insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol);
 #endif
@@ -130,7 +135,7 @@ struct Rings {
     auto& shard = getOneShard();
     auto lock = shard->queryRing.lock();
 #if defined(DNSDIST_RINGS_WITH_MACADDRESS)
-    insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol, macaddress, sizeof(macaddress), hasmac);
+    insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol, macaddress, hasmac);
 #else
     insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol);
 #endif
@@ -186,6 +191,16 @@ struct Rings {
      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;
@@ -204,7 +219,7 @@ private:
   }
 
 #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 char* macaddress, size_t maclen, const bool hasmac)
+  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
@@ -213,9 +228,9 @@ private:
       d_nbQueryEntries++;
     }
 #if defined(DNSDIST_RINGS_WITH_MACADDRESS)
-    Rings::Query query({requestor, name, when, dh, size, qtype, protocol, "", hasmac});
+    Rings::Query query{requestor, name, when, dh, size, qtype, protocol, dnsdist::MacAddress{""}, hasmac};
     if (hasmac) {
-      memcpy(query.macaddress, macaddress, maclen);
+      memcpy(query.macaddress.data(), macaddress.data(), macaddress.size());
     }
     ring.push_back(std::move(query));
 #else
@@ -223,7 +238,7 @@ private:
 #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)
+  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++;
@@ -240,6 +255,8 @@ private:
   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;
index 1f102e2408881d92be6a18f2a7679d14a2b431ab..856fccb111d1bacd82bb1b296668bd6ac53dda3b 100644 (file)
@@ -1,5 +1,6 @@
 
 #include "dnsdist-snmp.hh"
+#include "dnsdist-metrics.hh"
 #include "dolog.hh"
 
 bool g_snmpEnabled{false};
@@ -55,7 +56,7 @@ 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;
+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.  */
@@ -80,7 +81,7 @@ static int handleCounter64Stats(netsnmp_mib_handler* handler,
     return SNMP_ERR_GENERR;
   }
 
-  if (const auto& val = boost::get<pdns::stat_t*>(&it->second)) {
+  if (const auto& val = std::get_if<pdns::stat_t*>(&it->second)) {
     return DNSDistSNMPAgent::setCounter64Value(requests, (*val)->load());
   }
 
@@ -125,7 +126,7 @@ static int handleFloatStats(netsnmp_mib_handler* handler,
     return SNMP_ERR_GENERR;
   }
 
-  if (const auto& val = boost::get<double*>(&it->second)) {
+  if (const auto& val = std::get_if<double*>(&it->second)) {
     std::string str(std::to_string(**val));
     snmp_set_var_typed_value(requests->requestvb,
                              ASN_OCTET_STR,
@@ -176,11 +177,11 @@ static int handleGauge64Stats(netsnmp_mib_handler* handler,
   }
 
   std::string str;
-  uint64_t value = (*boost::get<DNSDistStats::statfunction_t>(&it->second))(str);
+  uint64_t value = (*std::get_if<dnsdist::metrics::Stats::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)
+static void registerGauge64Stat(const char* name, const oid statOID[], size_t statOIDLength, dnsdist::metrics::Stats::statfunction_t ptr)
 {
   if (statOIDLength != OID_LENGTH(queriesOID)) {
     errlog("Invalid OID for SNMP Gauge64 statistic %s", name);
@@ -303,7 +304,7 @@ static int backendStatTable_handler(netsnmp_mib_handler* handler,
         break;
       case COLUMN_BACKENDLATENCY:
         DNSDistSNMPAgent::setCounter64Value(request,
-                                            server->latencyUsec/1000.0);
+                                            server->getRelevantLatencyUsec() / 1000.0);
         break;
       case COLUMN_BACKENDWEIGHT:
         DNSDistSNMPAgent::setCounter64Value(request,
@@ -375,16 +376,16 @@ static int backendStatTable_handler(netsnmp_mib_handler* handler,
 }
 #endif /* HAVE_NET_SNMP */
 
-bool DNSDistSNMPAgent::sendBackendStatusChangeTrap(const std::shared_ptr<DownstreamState>& dss)
+bool DNSDistSNMPAgent::sendBackendStatusChangeTrap(const DownstreamState& dss)
 {
 #ifdef HAVE_NET_SNMP
-  const string backendAddress = dss->d_config.remote.toStringWithPort();
-  const string backendStatus = dss->getStatus();
+  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,
+                            snmpTrapOID.data(),
+                            snmpTrapOID.size(),
                             ASN_OBJECT_ID,
                             backendStatusChangeTrapOID,
                             OID_LENGTH(backendStatusChangeTrapOID)  * sizeof(oid));
@@ -394,8 +395,8 @@ bool DNSDistSNMPAgent::sendBackendStatusChangeTrap(const std::shared_ptr<Downstr
                             backendNameOID,
                             OID_LENGTH(backendNameOID),
                             ASN_OCTET_STR,
-                            dss->getName().c_str(),
-                            dss->getName().size());
+                            dss.getName().c_str(),
+                            dss.getName().size());
 
   snmp_varlist_add_variable(&varList,
                             backendAddressOID,
@@ -411,7 +412,7 @@ bool DNSDistSNMPAgent::sendBackendStatusChangeTrap(const std::shared_ptr<Downstr
                             backendStatus.c_str(),
                             backendStatus.size());
 
-  return sendTrap(d_trapPipe[1], varList);
+  return sendTrap(d_sender, varList);
 #else
   return true;
 #endif /* HAVE_NET_SNMP */
@@ -423,8 +424,8 @@ bool DNSDistSNMPAgent::sendCustomTrap(const std::string& reason)
   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));
@@ -436,7 +437,7 @@ bool DNSDistSNMPAgent::sendCustomTrap(const std::string& reason)
                             reason.c_str(),
                             reason.size());
 
-  return sendTrap(d_trapPipe[1], varList);
+  return sendTrap(d_sender, varList);
 #else
   return true;
 #endif /* HAVE_NET_SNMP */
@@ -445,22 +446,22 @@ bool DNSDistSNMPAgent::sendCustomTrap(const std::string& reason)
 bool DNSDistSNMPAgent::sendDNSTrap(const DNSQuestion& dq, const std::string& reason)
 {
 #ifdef HAVE_NET_SNMP
-  std::string local = dq.local->toString();
-  std::string remote = dq.remote->toString();
-  std::string qname = dq.qname->toStringNoDot();
-  const uint32_t socketFamily = dq.remote->isIPv4() ? 1 : 2;
+  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.qtype;
-  const uint32_t qClass = (uint32_t) dq.qclass;
+  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,
+                            snmpTrapOID.data(),
+                            snmpTrapOID.size(),
                             ASN_OBJECT_ID,
                             actionTrapOID,
                             OID_LENGTH(actionTrapOID)  * sizeof(oid));
@@ -542,7 +543,7 @@ bool DNSDistSNMPAgent::sendDNSTrap(const DNSQuestion& dq, const std::string& rea
                             reason.c_str(),
                             reason.size());
 
-  return sendTrap(d_trapPipe[1], varList);
+  return sendTrap(d_sender, varList);
 #else
   return true;
 #endif /* HAVE_NET_SNMP */
@@ -552,44 +553,44 @@ DNSDistSNMPAgent::DNSDistSNMPAgent(const std::string& name, const std::string& d
 {
 #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);
+  registerCounter64Stat("queries", queriesOID, OID_LENGTH(queriesOID), &dnsdist::metrics::g_stats.queries);
+  registerCounter64Stat("responses", responsesOID, OID_LENGTH(responsesOID), &dnsdist::metrics::g_stats.responses);
+  registerCounter64Stat("servfailResponses", servfailResponsesOID, OID_LENGTH(servfailResponsesOID), &dnsdist::metrics::g_stats.servfailResponses);
+  registerCounter64Stat("aclDrops", aclDropsOID, OID_LENGTH(aclDropsOID), &dnsdist::metrics::g_stats.aclDrops);
+  registerCounter64Stat("ruleDrop", ruleDropOID, OID_LENGTH(ruleDropOID), &dnsdist::metrics::g_stats.ruleDrop);
+  registerCounter64Stat("ruleNXDomain", ruleNXDomainOID, OID_LENGTH(ruleNXDomainOID), &dnsdist::metrics::g_stats.ruleNXDomain);
+  registerCounter64Stat("ruleRefused", ruleRefusedOID, OID_LENGTH(ruleRefusedOID), &dnsdist::metrics::g_stats.ruleRefused);
+  registerCounter64Stat("ruleServFail", ruleServFailOID, OID_LENGTH(ruleServFailOID), &dnsdist::metrics::g_stats.ruleServFail);
+  registerCounter64Stat("ruleTruncated", ruleTruncatedOID, OID_LENGTH(ruleTruncatedOID), &dnsdist::metrics::g_stats.ruleTruncated);
+  registerCounter64Stat("selfAnswered", selfAnsweredOID, OID_LENGTH(selfAnsweredOID), &dnsdist::metrics::g_stats.selfAnswered);
+  registerCounter64Stat("downstreamTimeouts", downstreamTimeoutsOID, OID_LENGTH(downstreamTimeoutsOID), &dnsdist::metrics::g_stats.downstreamTimeouts);
+  registerCounter64Stat("downstreamSendErrors", downstreamSendErrorsOID, OID_LENGTH(downstreamSendErrorsOID), &dnsdist::metrics::g_stats.downstreamSendErrors);
+  registerCounter64Stat("truncFail", truncFailOID, OID_LENGTH(truncFailOID), &dnsdist::metrics::g_stats.truncFail);
+  registerCounter64Stat("noPolicy", noPolicyOID, OID_LENGTH(noPolicyOID), &dnsdist::metrics::g_stats.noPolicy);
+  registerCounter64Stat("latency0_1", latency0_1OID, OID_LENGTH(latency0_1OID), &dnsdist::metrics::g_stats.latency0_1);
+  registerCounter64Stat("latency1_10", latency1_10OID, OID_LENGTH(latency1_10OID), &dnsdist::metrics::g_stats.latency1_10);
+  registerCounter64Stat("latency10_50", latency10_50OID, OID_LENGTH(latency10_50OID), &dnsdist::metrics::g_stats.latency10_50);
+  registerCounter64Stat("latency50_100", latency50_100OID, OID_LENGTH(latency50_100OID), &dnsdist::metrics::g_stats.latency50_100);
+  registerCounter64Stat("latency100_1000", latency100_1000OID, OID_LENGTH(latency100_1000OID), &dnsdist::metrics::g_stats.latency100_1000);
+  registerCounter64Stat("latencySlow", latencySlowOID, OID_LENGTH(latencySlowOID), &dnsdist::metrics::g_stats.latencySlow);
+  registerCounter64Stat("nonCompliantQueries", nonCompliantQueriesOID, OID_LENGTH(nonCompliantQueriesOID), &dnsdist::metrics::g_stats.nonCompliantQueries);
+  registerCounter64Stat("nonCompliantResponses", nonCompliantResponsesOID, OID_LENGTH(nonCompliantResponsesOID), &dnsdist::metrics::g_stats.nonCompliantResponses);
+  registerCounter64Stat("rdQueries", rdQueriesOID, OID_LENGTH(rdQueriesOID), &dnsdist::metrics::g_stats.rdQueries);
+  registerCounter64Stat("emptyQueries", emptyQueriesOID, OID_LENGTH(emptyQueriesOID), &dnsdist::metrics::g_stats.emptyQueries);
+  registerCounter64Stat("cacheHits", cacheHitsOID, OID_LENGTH(cacheHitsOID), &dnsdist::metrics::g_stats.cacheHits);
+  registerCounter64Stat("cacheMisses", cacheMissesOID, OID_LENGTH(cacheMissesOID), &dnsdist::metrics::g_stats.cacheMisses);
+  registerCounter64Stat("dynBlocked", dynBlockedOID, OID_LENGTH(dynBlockedOID), &dnsdist::metrics::g_stats.dynBlocked);
+  registerFloatStat("latencyAvg100", latencyAvg100OID, OID_LENGTH(latencyAvg100OID), &dnsdist::metrics::g_stats.latencyAvg100);
+  registerFloatStat("latencyAvg1000", latencyAvg1000OID, OID_LENGTH(latencyAvg1000OID), &dnsdist::metrics::g_stats.latencyAvg1000);
+  registerFloatStat("latencyAvg10000", latencyAvg10000OID, OID_LENGTH(latencyAvg10000OID), &dnsdist::metrics::g_stats.latencyAvg10000);
+  registerFloatStat("latencyAvg1000000", latencyAvg1000000OID, OID_LENGTH(latencyAvg1000000OID), &dnsdist::metrics::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("securityStatus", securityStatusOID, OID_LENGTH(securityStatusOID), [](const std::string&) { return dnsdist::metrics::g_stats.securityStatus.load(); });
   registerGauge64Stat("realMemoryUsage", realMemoryUsageOID, OID_LENGTH(realMemoryUsageOID), &getRealMemoryUsage);
 
 
index 3ccde7802b23307a5369d49fc9642f63a7163b3a..283d43c6a5794335e851e5d5cded1cbf175ba30a 100644 (file)
@@ -31,7 +31,7 @@ class DNSDistSNMPAgent: public SNMPAgent
 {
 public:
   DNSDistSNMPAgent(const std::string& name, const std::string& daemonSocket);
-  bool sendBackendStatusChangeTrap(const std::shared_ptr<DownstreamState>&);
+  bool sendBackendStatusChangeTrap(const DownstreamState&);
   bool sendCustomTrap(const std::string& reason);
   bool sendDNSTrap(const DNSQuestion&, const std::string& reason="");
 };
index ada447c3de3fafe972877ef110bb2840ed424271..e3eb68e1152c4e50cb5f5db069f5b89ba590501a 100644 (file)
 #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"
    Let's start naively.
 */
 
-static LockGuarded<std::map<ComboAddress,size_t,ComboAddress::addressOnlyLessThan>> s_tcpClientsCount;
-
 size_t g_maxTCPQueriesPerConn{0};
 size_t g_maxTCPConnectionDuration{0};
-size_t g_maxTCPConnectionsPerClient{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};
+size_t g_tcpInternalPipeBufferSize{1048576U};
 uint64_t g_maxTCPQueuedConnections{10000};
 #else
 size_t g_tcpInternalPipeBufferSize{0};
@@ -75,27 +77,19 @@ int g_tcpRecvTimeout{2};
 int g_tcpSendTimeout{2};
 std::atomic<uint64_t> g_tcpStatesDumpRequested{0};
 
-static void decrementTCPClientCount(const ComboAddress& client)
-{
-  if (g_maxTCPConnectionsPerClient) {
-    auto tcpClientsCount = s_tcpClientsCount.lock();
-    tcpClientsCount->at(client)--;
-    if (tcpClientsCount->at(client) == 0) {
-      tcpClientsCount->erase(client);
-    }
-  }
-}
+LockGuarded<std::map<ComboAddress, size_t, ComboAddress::addressOnlyLessThan>> dnsdist::IncomingConcurrentTCPConnectionsManager::s_tcpClientsConcurrentConnectionsCount;
+size_t dnsdist::IncomingConcurrentTCPConnectionsManager::s_maxTCPConnectionsPerClient = 0;
 
 IncomingTCPConnectionState::~IncomingTCPConnectionState()
 {
-  decrementTCPClientCount(d_ci.remote);
+  dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(d_ci.remote);
 
   if (d_ci.cs != nullptr) {
-    struct timeval now;
+    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);
+    d_ci.cs->updateTCPMetrics(d_queriesCount, diff.tv_sec * 1000 + diff.tv_usec / 1000);
   }
 
   // would have been done when the object is destroyed anyway,
@@ -104,21 +98,30 @@ IncomingTCPConnectionState::~IncomingTCPConnectionState()
   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>& ds, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs, const struct timeval& now)
+std::shared_ptr<TCPConnectionToBackend> IncomingTCPConnectionState::getDownstreamConnection(std::shared_ptr<DownstreamState>& backend, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs, const struct timeval& now)
 {
-  std::shared_ptr<TCPConnectionToBackend> downstream{nullptr};
-
-  downstream = getOwnedDownstreamConnection(ds, tlvs);
+  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, ds, now, std::string());
-    if (ds->d_config.useProxyProtocol) {
+    downstream = t_downstreamTCPConnectionsManager.getConnectionToDownstream(d_threadData.mplexer, backend, now, std::string());
+    if (backend->d_config.useProxyProtocol) {
       registerOwnedDownstreamConnection(downstream);
     }
   }
@@ -126,94 +129,49 @@ std::shared_ptr<TCPConnectionToBackend> IncomingTCPConnectionState::getDownstrea
   return downstream;
 }
 
-static void tcpClientThread(int pipefd, int crossProtocolQueriesPipeFD, int crossProtocolResponsesListenPipeFD, int crossProtocolResponsesWritePipeFD);
+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): d_tcpclientthreads(maxThreads), d_maxthreads(maxThreads)
+TCPClientCollection::TCPClientCollection(size_t maxThreads, std::vector<ClientState*> tcpAcceptStates) :
+  d_tcpclientthreads(maxThreads), d_maxthreads(maxThreads)
 {
   for (size_t idx = 0; idx < maxThreads; idx++) {
-    addTCPClientThread();
+    addTCPClientThread(tcpAcceptStates);
   }
 }
 
-void TCPClientCollection::addTCPClientThread()
+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;
-    }
+  try {
+    auto [queryChannelSender, queryChannelReceiver] = pdns::channel::createObjectQueue<ConnectionInfo>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, g_tcpInternalPipeBufferSize);
 
-    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;
-    }
+    auto [crossProtocolQueryChannelSender, crossProtocolQueryChannelReceiver] = pdns::channel::createObjectQueue<CrossProtocolQuery>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, g_tcpInternalPipeBufferSize);
 
-    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;
-    }
+    auto [crossProtocolResponseChannelSender, crossProtocolResponseChannelReceiver] = pdns::channel::createObjectQueue<TCPCrossProtocolResponse>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, g_tcpInternalPipeBufferSize);
 
-    if (g_tcpInternalPipeBufferSize > 0 && getPipeBufferSize(fds[0]) < g_tcpInternalPipeBufferSize) {
-      setPipeBufferSize(fds[0], g_tcpInternalPipeBufferSize);
-    }
+    vinfolog("Adding TCP Client thread");
 
-    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]);
+    TCPWorkerThread worker(std::move(queryChannelSender), std::move(crossProtocolQueryChannelSender));
+
     try {
-      std::thread t1(tcpClientThread, pipefds[0], crossProtocolQueriesFDs[0], crossProtocolResponsesFDs[0], crossProtocolResponsesFDs[1]);
-      t1.detach();
+      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) {
-      /* 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;
   }
+  catch (const std::exception& e) {
+    errlog("Error creating TCP worker: %", e.what());
+  }
 }
 
 std::unique_ptr<TCPClientCollection> g_tcpclientthreads;
@@ -223,11 +181,11 @@ static IOState sendQueuedResponses(std::shared_ptr<IncomingTCPConnectionState>&
   IOState result = IOState::Done;
 
   while (state->active() && !state->d_queuedResponses.empty()) {
-    DEBUGLOG("queue size is "<<state->d_queuedResponses.size()<<", sending the next one");
+    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));
+    result = state->sendResponse(now, std::move(resp));
     if (result != IOState::Done) {
       return result;
     }
@@ -237,29 +195,33 @@ static IOState sendQueuedResponses(std::shared_ptr<IncomingTCPConnectionState>&
   return IOState::Done;
 }
 
-static void updateTCPLatency(const std::shared_ptr<DownstreamState>& ds, double udiff)
-{
-  ds->latencyUsecTCP = (127.0 * ds->latencyUsecTCP / 128.0) + udiff/128.0;
-}
-
-static void handleResponseSent(std::shared_ptr<IncomingTCPConnectionState>& state, const TCPResponse& currentResponse)
+void IncomingTCPConnectionState::handleResponseSent(TCPResponse& currentResponse)
 {
   if (currentResponse.d_idstate.qtype == QType::AXFR || currentResponse.d_idstate.qtype == QType::IXFR) {
     return;
   }
 
-  --state->d_currentQueriesCount;
+  --d_currentQueriesCount;
 
-  if (currentResponse.d_selfGenerated == false && currentResponse.d_connection && currentResponse.d_connection->getDS()) {
-    const auto& ds = currentResponse.d_connection->getDS();
+  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.sentTime.udiff();
-    vinfolog("Got answer from %s, relayed to %s (%s, %d bytes), took %f usec", ds->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), (state->d_handler.isTLS() ? "DoT" : "TCP"), currentResponse.d_buffer.size(), udiff);
-
-    ::handleResponseSent(ids, udiff, state->d_ci.remote, ds->d_config.remote, static_cast<unsigned int>(currentResponse.d_buffer.size()), currentResponse.d_cleartextDH, ds->getProtocol());
+    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);
 
-    updateTCPLatency(ds, 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)
@@ -269,11 +231,11 @@ static void prependSizeToTCPQuery(PacketBuffer& buffer, size_t proxyProtocolPayl
   }
 
   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) };
+  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() + proxyProtocolPayloadSize, sizeBytes, sizeBytes + 2);
+  buffer.insert(buffer.begin() + static_cast<PacketBuffer::iterator::difference_type>(proxyProtocolPayloadSize), sizeBytes.begin(), sizeBytes.end());
 }
 
 bool IncomingTCPConnectionState::canAcceptNewQueries(const struct timeval& now)
@@ -283,12 +245,13 @@ bool IncomingTCPConnectionState::canAcceptNewQueries(const struct timeval& now)
     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);
+  // 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 && d_queriesCount > g_maxTCPQueriesPerConn) {
+  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;
   }
@@ -303,27 +266,27 @@ bool IncomingTCPConnectionState::canAcceptNewQueries(const struct timeval& now)
 
 void IncomingTCPConnectionState::resetForNewQuery()
 {
-  d_buffer.resize(sizeof(uint16_t));
+  d_buffer.clear();
   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)
+std::shared_ptr<TCPConnectionToBackend> IncomingTCPConnectionState::getOwnedDownstreamConnection(const std::shared_ptr<DownstreamState>& backend, 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());
+  auto connIt = d_ownedConnectionsToBackend.find(backend);
+  if (connIt == d_ownedConnectionsToBackend.end()) {
+    DEBUGLOG("no owned connection found for " << backend->getName());
     return nullptr;
   }
 
-  for (auto& conn : it->second) {
+  for (auto& conn : connIt->second) {
     if (conn->canBeReused(true) && conn->matchesTLVs(tlvs)) {
-      DEBUGLOG("Got one owned connection accepting more for "<<ds->getName());
+      DEBUGLOG("Got one owned connection accepting more for " << backend->getName());
       conn->setReused();
       return conn;
     }
-    DEBUGLOG("not accepting more for "<<ds->getName());
+    DEBUGLOG("not accepting more for " << backend->getName());
   }
 
   return nullptr;
@@ -335,37 +298,36 @@ void IncomingTCPConnectionState::registerOwnedDownstreamConnection(std::shared_p
 }
 
 /* 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)
+IOState IncomingTCPConnectionState::sendResponse(const struct timeval& now, TCPResponse&& response)
 {
-  state->d_state = IncomingTCPConnectionState::State::sendingResponse;
+  d_state = 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) };
+  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, sizeBytes + 2);
-  state->d_currentPos = 0;
-  state->d_currentResponse = std::move(response);
+  response.d_buffer.insert(response.d_buffer.begin(), sizeBytes.begin(), sizeBytes.end());
+  d_currentPos = 0;
+  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());
+    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(state, state->d_currentResponse);
-      return iostate;
-    } else {
-      state->d_lastIOBlocked = true;
-      DEBUGLOG("partial write");
+      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", state->d_ci.remote.toStringWithPort(), e.what());
-    DEBUGLOG("Closing TCP client connection: "<<e.what());
-    ++state->d_ci.cs->tcpDiedSendingResponse;
+    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;
 
-    state->terminateClientConnection();
+    terminateClientConnection();
 
     return IOState::Done;
   }
@@ -399,50 +361,59 @@ void IncomingTCPConnectionState::terminateClientConnection()
     /* 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) {
+    for (const auto desc : afds) {
       try {
-        state->d_threadData.mplexer->addReadFD(fd, handleAsyncReady, state);
+        state->d_threadData.mplexer->addReadFD(desc, handleAsyncReady, state);
       }
       catch (...) {
       }
     }
-
   }
 }
 
-void IncomingTCPConnectionState::queueResponse(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now, TCPResponse&& response)
+void IncomingTCPConnectionState::queueResponse(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now, TCPResponse&& response, bool fromBackend)
 {
   // 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());
+  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 == IncomingTCPConnectionState::State::idle ||
-      state->d_state == IncomingTCPConnectionState::State::waitingForQuery) {
+  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 = IncomingTCPConnectionState::State::waitingForQuery;
+        state->d_state = State::waitingForQuery;
         iostate = IOState::NeedRead;
       }
       else {
-        state->d_state = IncomingTCPConnectionState::State::idle;
+        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(int fd, FDMultiplexer::funcparam_t& param)
+void IncomingTCPConnectionState::handleAsyncReady([[maybe_unused]] int desc, FDMultiplexer::funcparam_t& param)
 {
   auto state = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(param);
 
@@ -459,9 +430,7 @@ void IncomingTCPConnectionState::handleAsyncReady(int fd, FDMultiplexer::funcpar
 
   if (state->active()) {
     /* and now we restart our own I/O state machine */
-    struct timeval now;
-    gettimeofday(&now, nullptr);
-    handleIO(state, now);
+    state->handleIO();
   }
   else {
     /* we were only waiting for the engine to come back,
@@ -474,8 +443,8 @@ void IncomingTCPConnectionState::updateIO(std::shared_ptr<IncomingTCPConnectionS
 {
   if (newState == IOState::Async) {
     auto fds = state->d_handler.getAsyncFDs();
-    for (const auto fd : fds) {
-      state->d_threadData.mplexer->addReadFD(fd, handleAsyncReady, state);
+    for (const auto desc : fds) {
+      state->d_threadData.mplexer->addReadFD(desc, handleAsyncReady, state);
     }
     state->d_ioState->update(IOState::Done, handleIOCallback, state);
   }
@@ -487,9 +456,14 @@ void IncomingTCPConnectionState::updateIO(std::shared_ptr<IncomingTCPConnectionS
 /* 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.d_connection && response.d_connection->getDS() && response.d_connection->getDS()->d_config.useProxyProtocol) {
+  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
@@ -518,566 +492,700 @@ void IncomingTCPConnectionState::handleResponse(const struct timeval& now, TCPRe
     return;
   }
 
-  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->getRemote(), qnameWireLength)) {
-      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 (response.d_connection->getDS()) {
-      ++response.d_connection->getDS()->responses;
-    }
+      if (backend != nullptr) {
+        ++backend->responses;
+      }
+
+      DNSResponse dnsResponse(ids, response.d_buffer, backend);
+      dnsResponse.d_incomingTCPState = state;
 
-    DNSResponse dr = makeDNSResponseFromIDState(ids, response.d_buffer);
+      memcpy(&response.d_cleartextDH, dnsResponse.getHeader().get(), sizeof(response.d_cleartextDH));
 
-    memcpy(&response.d_cleartextDH, dr.getHeader(), sizeof(response.d_cleartextDH));
+      if (!processResponse(response.d_buffer, *state->d_threadData.localRespRuleActions, *state->d_threadData.localCacheInsertedRespRuleActions, dnsResponse, false)) {
+        state->terminateClientConnection();
+        return;
+      }
 
-    if (!processResponse(response.d_buffer, state->d_threadData.localRespRuleActions, dr, false, false)) {
+      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;
     }
   }
-  catch (const std::exception& e) {
-    vinfolog("Unexpected exception while handling response from backend: %s", e.what());
-    state->terminateClientConnection();
-    return;
-  }
 
-  ++g_stats.responses;
+  ++dnsdist::metrics::g_stats.responses;
   ++state->d_ci.cs->responses;
 
-  queueResponse(state, now, std::move(response));
+  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(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 TCPCrossProtocolQuerySender : public TCPQuerySender
+class TCPCrossProtocolQuery : public CrossProtocolQuery
 {
 public:
-  TCPCrossProtocolQuerySender(std::shared_ptr<IncomingTCPConnectionState>& state, int responseDescriptor): d_state(state), d_responseDesc(responseDescriptor)
-  {
-  }
-
-  bool active() const override
-  {
-    return d_state->active();
-  }
-
-  const ClientState* getClientState() const override
+  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))
   {
-    return d_state->getClientState();
   }
+  TCPCrossProtocolQuery(const TCPCrossProtocolQuery&) = delete;
+  TCPCrossProtocolQuery& operator=(const TCPCrossProtocolQuery&) = delete;
+  TCPCrossProtocolQuery(TCPCrossProtocolQuery&&) = delete;
+  TCPCrossProtocolQuery& operator=(TCPCrossProtocolQuery&&) = delete;
+  ~TCPCrossProtocolQuery() override = default;
 
-  void handleResponse(const struct timeval& now, TCPResponse&& response) override
+  std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
   {
-    if (d_responseDesc == -1) {
-      throw std::runtime_error("Invalid pipe descriptor in TCP Cross Protocol Query Sender");
-    }
-
-    auto ptr = new TCPCrossProtocolResponse(std::move(response), d_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_responseDesc, &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;
-    }
+    return d_sender;
   }
 
-  void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override
+  DNSQuestion getDQ() override
   {
-    handleResponse(now, std::move(response));
+    auto& ids = query.d_idstate;
+    DNSQuestion dnsQuestion(ids, query.d_buffer);
+    dnsQuestion.d_incomingTCPState = d_sender;
+    return dnsQuestion;
   }
 
-  void notifyIOError(IDState&& query, const struct timeval& now) override
+  DNSResponse getDR() override
   {
-    TCPResponse response(PacketBuffer(), std::move(query), nullptr);
-    handleResponse(now, std::move(response));
+    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_state;
-  int d_responseDesc{-1};
+  std::shared_ptr<IncomingTCPConnectionState> d_sender;
 };
 
-class TCPCrossProtocolQuery : public CrossProtocolQuery
+std::unique_ptr<CrossProtocolQuery> IncomingTCPConnectionState::getCrossProtocolQuery(PacketBuffer&& query, InternalQueryState&& state, const std::shared_ptr<DownstreamState>& backend)
 {
-public:
-  TCPCrossProtocolQuery(PacketBuffer&& buffer, IDState&& ids, std::shared_ptr<DownstreamState>& ds, std::shared_ptr<TCPCrossProtocolQuerySender>& sender): d_sender(sender)
-  {
-    query = InternalQuery(std::move(buffer), std::move(ids));
-    downstream = ds;
-    proxyProtocolPayloadSize = 0;
-  }
+  return std::make_unique<TCPCrossProtocolQuery>(std::move(query), std::move(state), backend, shared_from_this());
+}
 
-  ~TCPCrossProtocolQuery()
-  {
+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");
   }
 
-  std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
-  {
-    return d_sender;
-  }
+  dnsQuestion.ids.origID = dnsQuestion.getHeader()->id;
+  return std::make_unique<TCPCrossProtocolQuery>(std::move(dnsQuestion.getMutableData()), std::move(dnsQuestion.ids), nullptr, std::move(state));
+}
 
-private:
-  std::shared_ptr<TCPCrossProtocolQuerySender> d_sender;
-};
+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());
+  }
+}
 
-static void handleQuery(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now)
+IncomingTCPConnectionState::QueryProcessingResult IncomingTCPConnectionState::handleQuery(PacketBuffer&& queryIn, const struct timeval& now, std::optional<int32_t> streamID)
 {
-  if (state->d_querySize < sizeof(dnsheader)) {
-    ++g_stats.nonCompliantQueries;
-    state->terminateClientConnection();
-    return;
+  auto query = std::move(queryIn);
+  if (query.size() < sizeof(dnsheader)) {
+    ++dnsdist::metrics::g_stats.nonCompliantQueries;
+    ++d_ci.cs->nonCompliantQueries;
+    return QueryProcessingResult::TooSmall;
   }
 
-  ++state->d_queriesCount;
-  ++state->d_ci.cs->queries;
-  ++g_stats.queries;
+  ++d_queriesCount;
+  ++d_ci.cs->queries;
+  ++dnsdist::metrics::g_stats.queries;
 
-  if (state->d_handler.isTLS()) {
-    auto tlsVersion = state->d_handler.getTLSVersion();
+  if (d_handler.isTLS()) {
+    auto tlsVersion = d_handler.getTLSVersion();
     switch (tlsVersion) {
     case LibsslTLSVersion::TLS10:
-      ++state->d_ci.cs->tls10queries;
+      ++d_ci.cs->tls10queries;
       break;
     case LibsslTLSVersion::TLS11:
-      ++state->d_ci.cs->tls11queries;
+      ++d_ci.cs->tls11queries;
       break;
     case LibsslTLSVersion::TLS12:
-      ++state->d_ci.cs->tls12queries;
+      ++d_ci.cs->tls12queries;
       break;
     case LibsslTLSVersion::TLS13:
-      ++state->d_ci.cs->tls13queries;
+      ++d_ci.cs->tls13queries;
       break;
     default:
-      ++state->d_ci.cs->tlsUnknownqueries;
+      ++d_ci.cs->tlsUnknownqueries;
     }
   }
 
-  /* 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 queryRealTime;
-  gettime(&queryRealTime, true);
+  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;
+  }
 
-  std::unique_ptr<DNSCryptQuery> dnsCryptQuery{nullptr};
-  auto dnsCryptResponse = checkDNSCryptQuery(*state->d_ci.cs, state->d_buffer, dnsCryptQuery, queryRealTime.tv_sec, true);
+  auto dnsCryptResponse = checkDNSCryptQuery(*d_ci.cs, query, 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;
+    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! */
-    auto* dh = reinterpret_cast<dnsheader*>(state->d_buffer.data());
-    if (!checkQueryHeaders(dh)) {
-      state->terminateClientConnection();
-      return;
+    const dnsheader_aligned dnsHeader(query.data());
+    if (!checkQueryHeaders(*dnsHeader, *d_ci.cs)) {
+      return QueryProcessingResult::InvalidHeaders;
     }
 
-    if (dh->qdcount == 0) {
+    if (dnsHeader->qdcount == 0) {
       TCPResponse response;
-      dh->rcode = RCode::NotImp;
-      dh->qr = true;
-      response.d_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;
+      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;
     }
   }
 
-  uint16_t qtype, qclass;
-  unsigned int qnameWireLength = 0;
-  DNSName qname(reinterpret_cast<const char*>(state->d_buffer.data()), state->d_buffer.size(), sizeof(dnsheader), false, &qtype, &qclass, &qnameWireLength);
-  dnsdist::Protocol protocol = dnsdist::Protocol::DoTCP;
-  if (dnsCryptQuery) {
-    protocol = dnsdist::Protocol::DNSCryptTCP;
-  }
-  else if (state->d_handler.isTLS()) {
-    protocol = dnsdist::Protocol::DoT;
+  // 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 dq(&qname, qtype, qclass, &state->d_proxiedDestination, &state->d_proxiedRemote, state->d_buffer, protocol, &queryRealTime);
-  dq.dnsCryptQuery = std::move(dnsCryptQuery);
-  dq.sni = state->d_handler.getServerNameIndication();
-  if (state->d_proxyProtocolValues) {
+  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 */
-    dq.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(*state->d_proxyProtocolValues);
+    dnsQuestion.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(*d_proxyProtocolValues);
   }
 
-  if (dq.qtype == QType::AXFR || dq.qtype == QType::IXFR) {
-    dq.skipCache = true;
+  if (dnsQuestion.ids.qtype == QType::AXFR || dnsQuestion.ids.qtype == QType::IXFR) {
+    dnsQuestion.ids.skipCache = true;
   }
 
-  std::shared_ptr<DownstreamState> ds;
-  auto result = processQuery(dq, *state->d_ci.cs, state->d_threadData.holders, ds);
+  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) {
-    state->terminateClientConnection();
-    return;
+    return QueryProcessingResult::Dropped;
   }
 
   // the buffer might have been invalidated by now
-  const dnsheader* dh = dq.getHeader();
+  uint16_t queryID{0};
+  {
+    const auto dnsHeader = dnsQuestion.getHeader();
+    queryID = dnsHeader->id;
+  }
+
   if (result == ProcessQueryResult::SendAnswer) {
     TCPResponse response;
-    response.d_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;
+    {
+      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 || ds == nullptr) {
-    state->terminateClientConnection();
-    return;
+  if (result != ProcessQueryResult::PassToBackend || backend == nullptr) {
+    return QueryProcessingResult::NoBackend;
   }
 
-  IDState ids;
-  setIDStateFromDNSQuestion(ids, dq, std::move(qname));
-  ids.origID = dh->id;
-  ids.cs = state->d_ci.cs;
+  dnsQuestion.ids.origID = queryID;
 
-  ++state->d_currentQueriesCount;
+  ++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->getName());
+  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 (ds->d_config.useProxyProtocol) {
-      proxyProtocolPayload = getProxyProtocolPayload(dq);
+    if (backend->d_config.useProxyProtocol) {
+      proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
     }
 
-    auto incoming = std::make_shared<TCPCrossProtocolQuerySender>(state, state->d_threadData.crossProtocolResponsesPipe);
-    auto cpq = std::make_unique<TCPCrossProtocolQuery>(std::move(state->d_buffer), std::move(ids), ds, incoming);
+    auto cpq = std::make_unique<TCPCrossProtocolQuery>(std::move(query), std::move(ids), backend, state);
     cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
 
-    ds->passCrossProtocolQuery(std::move(cpq));
-    return;
+    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(state->d_buffer, 0);
+  prependSizeToTCPQuery(query, 0);
 
-  auto downstreamConnection = state->getDownstreamConnection(ds, dq.proxyProtocolValues, now);
+  auto downstreamConnection = getDownstreamConnection(backend, dnsQuestion.proxyProtocolValues, now);
 
-  if (ds->d_config.useProxyProtocol) {
+  if (backend->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();
+    if (!d_proxyProtocolPayloadHasTLV) {
+      d_proxyProtocolPayloadHasTLV = dnsQuestion.proxyProtocolValues && !dnsQuestion.proxyProtocolValues->empty();
     }
 
-    proxyProtocolPayload = getProxyProtocolPayload(dq);
+    proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
   }
 
-  if (dq.proxyProtocolValues) {
-    downstreamConnection->setProxyProtocolValuesSent(std::move(dq.proxyProtocolValues));
+  if (dnsQuestion.proxyProtocolValues) {
+    downstreamConnection->setProxyProtocolValuesSent(std::move(dnsQuestion.proxyProtocolValues));
   }
 
-  TCPQuery query(std::move(state->d_buffer), std::move(ids));
-  query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
+  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", 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->getName());
+  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(query));
+  downstreamConnection->queueQuery(incoming, std::move(tcpquery));
+  return QueryProcessingResult::Forwarded;
 }
 
-void IncomingTCPConnectionState::handleIOCallback(int fd, FDMultiplexer::funcparam_t& param)
+void IncomingTCPConnectionState::handleIOCallback(int desc, 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()));
+  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()));
   }
 
-  struct timeval now;
-  gettimeofday(&now, nullptr);
-  handleIO(conn, now);
+  conn->handleIO();
 }
 
-void IncomingTCPConnectionState::handleIO(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now)
+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(state->d_ioState);
+    IOStateGuard ioGuard(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());
+    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);
+      // handleNewIOState(state, IOState::Done, fd, handleIOCallback);
       return;
     }
 
-    state->d_lastIOBlocked = false;
+    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;
-          }
+      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 {
-          state->d_lastIOBlocked = true;
+          d_state = State::doingHandshake;
         }
       }
 
-      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 (d_state == State::doingHandshake) {
+        iostate = handleHandshake(now);
       }
 
-      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;
+      if (!d_lastIOBlocked && d_state == State::readingProxyProtocolHeader) {
+        auto status = handleProxyProtocolPayload();
+        if (status == ProxyProtocolResult::Done) {
+          if (isProxyPayloadOutsideTLS()) {
+            d_state = State::doingHandshake;
+            iostate = handleHandshake(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;
+          else {
+            d_state = State::readingQuerySize;
+            d_buffer.resize(sizeof(uint16_t));
+            d_currentPos = 0;
+            d_proxyProtocolNeed = 0;
           }
-
-          /* 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 if (status == ProxyProtocolResult::Error) {
+          iostate = IOState::Done;
         }
         else {
-          state->d_lastIOBlocked = true;
+          iostate = IOState::NeedRead;
         }
       }
 
-      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 (!d_lastIOBlocked && (d_state == State::waitingForQuery || d_state == State::readingQuerySize || d_state == State::readingQuery)) {
+        if (readIncomingQuery(now, iostate)) {
+          return;
         }
       }
 
-      if (!state->d_lastIOBlocked && state->d_state == IncomingTCPConnectionState::State::sendingResponse) {
+      if (!d_lastIOBlocked && d_state == State::sendingResponse) {
         DEBUGLOG("sending response");
-        iostate = state->d_handler.tryWrite(state->d_currentResponse.d_buffer, state->d_currentPos, state->d_currentResponse.d_buffer.size());
+        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(state, state->d_currentResponse);
-          state->d_state = IncomingTCPConnectionState::State::idle;
+          DEBUGLOG("response sent from " << __PRETTY_FUNCTION__);
+          handleResponseSent(d_currentResponse);
+          d_state = State::idle;
         }
         else {
-          state->d_lastIOBlocked = true;
+          d_lastIOBlocked = true;
         }
       }
 
-      if (state->active() &&
-          !state->d_lastIOBlocked &&
-          iostate == IOState::Done &&
-          (state->d_state == IncomingTCPConnectionState::State::idle ||
-           state->d_state == IncomingTCPConnectionState::State::waitingForQuery))
-      {
+      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 (!state->d_lastIOBlocked && state->active() && iostate == IOState::Done) {
+        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 (state->canAcceptNewQueries(now)) {
-            state->resetForNewQuery();
+          if (canAcceptNewQueries(now)) {
+            resetForNewQuery();
             iostate = IOState::NeedRead;
           }
           else {
-            state->d_state = IncomingTCPConnectionState::State::idle;
+            d_state = 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));
+      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& e) {
+    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
       */
-      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();
+      handleExceptionDuringIO(exp);
     }
 
-    if (!state->active()) {
+    if (!active()) {
       DEBUGLOG("state is no longer active");
       return;
     }
 
+    auto state = shared_from_this();
     if (iostate == IOState::Done) {
-      state->d_ioState->update(iostate, handleIOCallback, state);
+      d_ioState->update(iostate, handleIOCallback, state);
     }
     else {
       updateIO(state, iostate, now);
     }
     ioGuard.release();
-  }
-  while ((iostate == IOState::NeedRead || iostate == IOState::NeedWrite) && !state->d_lastIOBlocked);
+  } while ((iostate == IOState::NeedRead || iostate == IOState::NeedWrite) && !d_lastIOBlocked);
 }
 
-void IncomingTCPConnectionState::notifyIOError(IDState&& query, const struct timeval& now)
+void IncomingTCPConnectionState::notifyIOError(const struct timeval& now, TCPResponse&& response)
 {
-  std::shared_ptr<IncomingTCPConnectionState> state = shared_from_this();
+  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;
 
@@ -1091,7 +1199,7 @@ void IncomingTCPConnectionState::notifyIOError(IDState&& query, const struct tim
 
       if (state->active() && iostate != IOState::Done) {
         // we need to update the state right away, nobody will do that for us
-       updateIO(state, iostate, now);
+        updateIO(state, iostate, now);
       }
     }
     catch (const std::exception& e) {
@@ -1106,15 +1214,20 @@ void IncomingTCPConnectionState::notifyIOError(IDState&& query, const struct tim
 
 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));
+  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");
+  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;
@@ -1123,120 +1236,102 @@ void IncomingTCPConnectionState::handleTimeout(std::shared_ptr<IncomingTCPConnec
   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_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);
+  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) {
+  std::unique_ptr<ConnectionInfo> citmp{nullptr};
+  try {
+    auto tmp = threadData->queryReceiver.receive();
+    if (!tmp) {
       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());
+    citmp = std::move(*tmp);
   }
-  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");
+  catch (const std::exception& e) {
+    throw std::runtime_error("Error while reading from the TCP query channel: " + std::string(e.what()));
   }
 
-  try {
-    g_tcpclientthreads->decrementQueuedCount();
+  g_tcpclientthreads->decrementQueuedCount();
 
-    struct timeval now;
-    gettimeofday(&now, nullptr);
-    auto state = std::make_shared<IncomingTCPConnectionState>(std::move(*citmp), *threadData, now);
-    delete citmp;
-    citmp = nullptr;
+  timeval now{};
+  gettimeofday(&now, nullptr);
 
-    IncomingTCPConnectionState::handleIO(state, now);
+  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 */
   }
-  catch (...) {
-    delete citmp;
-    citmp = nullptr;
-    throw;
+  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);
-  CrossProtocolQuery* tmp{nullptr};
+  auto* threadData = boost::any_cast<TCPClientThreadData*>(param);
 
-  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) {
+  std::unique_ptr<CrossProtocolQuery> cpq{nullptr};
+  try {
+    auto tmp = threadData->crossProtocolQueryReceiver.receive();
+    if (!tmp) {
       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());
+    cpq = std::move(*tmp);
   }
-  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");
+  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 {
-    struct timeval now;
-    gettimeofday(&now, nullptr);
+    auto downstream = t_downstreamTCPConnectionsManager.getConnectionToDownstream(threadData->mplexer, downstreamServer, now, std::string());
 
-    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;
+    prependSizeToTCPQuery(query.d_buffer, query.d_idstate.d_proxyProtocolPayloadSize);
 
-    try {
-      auto downstream = t_downstreamTCPConnectionsManager.getConnectionToDownstream(threadData->mplexer, downstreamServer, now, std::string());
+    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());
 
-      prependSizeToTCPQuery(query.d_buffer, proxyProtocolPayloadSize);
-      downstream->queueQuery(tqs, std::move(query));
-    }
-    catch (...) {
-      tqs->notifyIOError(std::move(query.d_idstate), now);
-    }
+    downstream->queueQuery(tqs, std::move(query));
   }
   catch (...) {
-    delete tmp;
-    tmp = nullptr;
+    tqs->notifyIOError(now, std::move(query));
   }
 }
 
 static void handleCrossProtocolResponse(int pipefd, FDMultiplexer::funcparam_t& param)
 {
-  TCPCrossProtocolResponse* tmp{nullptr};
+  auto* threadData = boost::any_cast<TCPClientThreadData*>(param);
 
-  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) {
+  std::unique_ptr<TCPCrossProtocolResponse> cpr{nullptr};
+  try {
+    auto tmp = threadData->crossProtocolResponseReceiver.receive();
+    if (!tmp) {
       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());
+    cpr = std::move(*tmp);
   }
-  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");
+  catch (const std::exception& e) {
+    throw std::runtime_error("Error while reading from the TCP cross-protocol response: " + std::string(e.what()));
   }
 
-  auto response = std::move(*tmp);
-  delete tmp;
-  tmp = nullptr;
+  auto& response = *cpr;
 
   try {
     if (response.d_response.d_buffer.empty()) {
-      response.d_state->notifyIOError(std::move(response.d_response.d_idstate), response.d_now);
+      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));
@@ -1250,7 +1345,116 @@ static void handleCrossProtocolResponse(int pipefd, FDMultiplexer::funcparam_t&
   }
 }
 
-static void tcpClientThread(int pipefd, int crossProtocolQueriesPipeFD, int crossProtocolResponsesListenPipeFD, int crossProtocolResponsesWritePipeFD)
+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 */
@@ -1259,13 +1463,38 @@ static void tcpClientThread(int pipefd, int crossProtocolQueriesPipeFD, int cros
 
   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);
+    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);
+    };
 
-    struct timeval now;
+    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;
 
@@ -1277,76 +1506,15 @@ static void tcpClientThread(int pipefd, int crossProtocolQueriesPipeFD, int cros
 
         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);
-            }
-          }
+          scanForTimeouts(data, now);
 
           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());
-            }
+            dumpTCPStates(data);
           }
         }
       }
       catch (const std::exception& e) {
-        errlog("Error in TCP worker thread: %s", e.what());
+        warnlog("Error in TCP worker thread: %s", e.what());
       }
     }
   }
@@ -1355,86 +1523,144 @@ static void tcpClientThread(int pipefd, int crossProtocolQueriesPipeFD, int cros
   }
 }
 
-/* spawn as many of these as required, they call Accept on a socket on which they will accept queries, and
-   they will hand off to worker threads & spawn more of them if required
-*/
-void tcpAcceptorThread(ClientState* cs)
+static void acceptNewConnection(const TCPAcceptorParam& param, TCPClientThreadData* threadData)
 {
-  setThreadName("dnsdist/tcpAcce");
-
+  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 = cs->local.sin4.sin_family;
+  remote.sin4.sin_family = param.local.sin4.sin_family;
 
-  auto acl = g_ACL.getLocal();
-  for(;;) {
-    std::unique_ptr<ConnectionInfo> ci;
-    tcpClientCountIncremented = false;
-    try {
-      socklen_t remlen = remote.getSocklen();
-      ci = std::make_unique<ConnectionInfo>(cs);
+  tcpClientCountIncremented = false;
+  try {
+    socklen_t remlen = remote.getSocklen();
+    ConnectionInfo connInfo(&clientState);
 #ifdef HAVE_ACCEPT4
-      ci->fd = accept4(cs->tcpFD, reinterpret_cast<struct sockaddr*>(&remote), &remlen, SOCK_NONBLOCK);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    connInfo.fd = accept4(socket, reinterpret_cast<struct sockaddr*>(&remote), &remlen, SOCK_NONBLOCK);
 #else
-      ci->fd = accept(cs->tcpFD, reinterpret_cast<struct sockaddr*>(&remote), &remlen);
+    // 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 = ++cs->tcpCurrentConnections;
-      if (cs->d_tcpConcurrentConnectionsLimit > 0 && concurrentConnections > cs->d_tcpConcurrentConnectionsLimit) {
-        continue;
-      }
+    // will be decremented when the ConnectionInfo object is destroyed, no matter the reason
+    auto concurrentConnections = ++clientState.tcpCurrentConnections;
 
-      if (concurrentConnections > cs->tcpMaxConcurrentConnections.load()) {
-        cs->tcpMaxConcurrentConnections.store(concurrentConnections);
-      }
+    if (connInfo.fd < 0) {
+      throw std::runtime_error((boost::format("accepting new connection on socket: %s") % stringerror()).str());
+    }
 
-      if (ci->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 (!acl->match(remote)) {
-       ++g_stats.aclDrops;
-       vinfolog("Dropped TCP connection from %s because of ACL", remote.toStringWithPort());
-       continue;
-      }
+    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(ci->fd)) {
-        continue;
-      }
+    if (!setNonBlocking(connInfo.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());
-        continue;
-      }
 
-      if (g_maxTCPConnectionsPerClient) {
-        auto tcpClientsCount = s_tcpClientsCount.lock();
+    setTCPNoDelay(connInfo.fd); // disable NAGLE
 
-        if ((*tcpClientsCount)[remote] >= g_maxTCPConnectionsPerClient) {
-          vinfolog("Dropping TCP connection from %s because we have too many from this client already", remote.toStringWithPort());
-          continue;
-        }
-        (*tcpClientsCount)[remote]++;
-        tcpClientCountIncremented = true;
-      }
+    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());
+    vinfolog("Got TCP connection from %s", remote.toStringWithPort());
 
-      ci->remote = remote;
-      if (!g_tcpclientthreads->passConnectionToThread(std::move(ci))) {
+    connInfo.remote = remote;
+
+    if (threadData == nullptr) {
+      if (!g_tcpclientthreads->passConnectionToThread(std::make_unique<ConnectionInfo>(std::move(connInfo)))) {
         if (tcpClientCountIncremented) {
-          decrementTCPClientCount(remote);
+          dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(remote);
         }
       }
     }
-    catch (const std::exception& e) {
-      errlog("While reading a TCP question: %s", e.what());
-      if (tcpClientCountIncremented) {
-        decrementTCPClientCount(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 (...){}
+  }
+  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 7398cf019dd078d8731797e1c6548a6bfa623852..066b5c177f5475ef3689a83315161f6804fcdbab 100644 (file)
@@ -34,7 +34,9 @@
 #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-web.hh"
 #include "dolog.hh"
 #include "gettime.hh"
@@ -52,6 +54,8 @@ struct WebserverConfig
   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};
 };
 
@@ -79,6 +83,8 @@ std::string getWebserverConfig()
     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;
@@ -129,74 +135,102 @@ private:
 };
 
 #ifndef DISABLE_PROMETHEUS
-static const MetricDefinitionStorage s_metricDefinitions;
-
-const 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")},
-  { "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") },
-  { "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-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") },
+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) {
-    errlog("Not writing content to %s since the API is read-only", filebasename);
+    warnlog("Not writing content to %s since the API is read-only", filebasename);
     return false;
   }
 
@@ -219,15 +253,14 @@ static bool apiWriteConfigFile(const string& filebasename, const string& content
 
 static void apiSaveACL(const NetmaskGroup& nmg)
 {
-  vector<string> vec;
-  nmg.toStringVector(&vec);
+  auto aclEntries = nmg.toStringVector();
 
   string acl;
-  for(const auto& s : vec) {
+  for (const auto& entry : aclEntries) {
     if (!acl.empty()) {
       acl += ", ";
     }
-    acl += "\"" + s + "\"";
+    acl += "\"" + entry + "\"";
   }
 
   string content = "setACL({" + acl + "})";
@@ -249,8 +282,12 @@ static bool checkAPIKey(const YaHTTP::Request& req, const std::unique_ptr<Creden
   return false;
 }
 
-static bool checkWebPassword(const YaHTTP::Request& req, const std::unique_ptr<CredentialsHolder>& password)
+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");
@@ -297,21 +334,21 @@ static bool handleAuthorization(const YaHTTP::Request& req)
   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);
+      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 (checkAPIKey(req, config->apiKey)) {
+    if (!config->apiRequiresAuthentication  || checkAPIKey(req, config->apiKey)) {
       return true;
     }
 
-    return isAnAPIRequestAllowedWithWebAuth(req) && checkWebPassword(req, config->password);
+    return isAnAPIRequestAllowedWithWebAuth(req) && checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
   }
 
-  return checkWebPassword(req, config->password);
+  return checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
 }
 
 static bool isMethodAllowed(const YaHTTP::Request& req)
@@ -324,6 +361,13 @@ static bool isMethodAllowed(const YaHTTP::Request& req)
       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;
 }
 
@@ -395,17 +439,17 @@ static json11::Json::array someResponseRulesToJson(GlobalStateHolder<vector<T>>*
   Json::array responseRules;
   int num=0;
   auto localResponseRules = someResponseRules->getLocal();
-  for(const auto& a : *localResponseRules) {
-    Json::object rule{
-      {"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()},
-    };
-    responseRules.push_back(rule);
+  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;
 }
@@ -427,111 +471,144 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
   resp.status = 200;
 
   std::ostringstream output;
-  static const std::set<std::string> metricBlacklist = { "latency-count", "latency-sum" };
-  for (const auto& e : g_stats.entries) {
-    if (e.first == "special-memory-usage")
-      continue; // Too expensive for get-all
-    std::string metricName = std::get<0>(e);
+  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;
 
-    // Prometheus suggest using '_' instead of '-'
-    std::string prometheusMetricName = "dnsdist_" + boost::replace_all_copy(metricName, "-", "_");
-    if (metricBlacklist.count(metricName) != 0) {
-      continue;
-    }
+      if (metricBlacklist.count(metricName) != 0) {
+        continue;
+      }
 
-    MetricDefinition metricDetails;
-    if (!s_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
-      vinfolog("Do not have metric details for %s", metricName);
-      continue;
-    }
+      MetricDefinition metricDetails;
+      if (!s_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
+        vinfolog("Do not have metric details for %s", metricName);
+        continue;
+      }
 
-    std::string prometheusTypeName = s_metricDefinitions.getPrometheusStringMetricType(metricDetails.prometheusType);
+      const std::string prometheusTypeName = s_metricDefinitions.getPrometheusStringMetricType(metricDetails.prometheusType);
+      if (prometheusTypeName.empty()) {
+        vinfolog("Unknown Prometheus type for %s", metricName);
+        continue;
+      }
 
-    if (prometheusTypeName == "") {
-      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 << " ";
+      // 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 = boost::get<pdns::stat_t*>(&std::get<1>(e)))
-      output << (*val)->load();
-    else if (const auto& dval = boost::get<double*>(&std::get<1>(e)))
-      output << **dval;
-    else
-      output << (*boost::get<DNSDistStats::statfunction_t>(&std::get<1>(e)))(std::get<0>(e));
+      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";
+      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;
+  uint64_t latency_amounts = dnsdist::metrics::g_stats.latency0_1;
   output << "dnsdist_latency_bucket{le=\"1\"} " << latency_amounts << "\n";
-  latency_amounts += g_stats.latency1_10;
+  latency_amounts += dnsdist::metrics::g_stats.latency1_10;
   output << "dnsdist_latency_bucket{le=\"10\"} " << latency_amounts << "\n";
-  latency_amounts += g_stats.latency10_50;
+  latency_amounts += dnsdist::metrics::g_stats.latency10_50;
   output << "dnsdist_latency_bucket{le=\"50\"} " << latency_amounts << "\n";
-  latency_amounts += g_stats.latency50_100;
+  latency_amounts += dnsdist::metrics::g_stats.latency50_100;
   output << "dnsdist_latency_bucket{le=\"100\"} " << latency_amounts << "\n";
-  latency_amounts += g_stats.latency100_1000;
+  latency_amounts += dnsdist::metrics::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
+  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 " << g_stats.latencySum << "\n";
-  output << "dnsdist_latency_count " << g_stats.latencyCount << "\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_";
 
-  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 << "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 << "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 << "tlsersumptions "              << "counter"                                                           << "\n";
+  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;
@@ -548,34 +625,46 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
     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 << "drops"                        << label << " " << state->reuseds.load()              << "\n";
-    if (state->isUp())
-        output << statesbase << "latency"                  << label << " " << state->latencyUsec/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 << "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 << "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";
@@ -584,8 +673,8 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
   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 << "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";
@@ -606,7 +695,6 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
   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";
 
@@ -619,7 +707,7 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
     const string proto = front->getType();
     const string fullName = frontName + "_" + proto;
     uint64_t threadNumber = 0;
-    auto dupPair = frontendDuplicates.insert({fullName, 1});
+    auto dupPair = frontendDuplicates.emplace(fullName, 1);
     if (!dupPair.second) {
       threadNumber = dupPair.first->second;
       ++(dupPair.first->second);
@@ -628,12 +716,13 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
                                          % 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 << "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";
@@ -656,7 +745,7 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
           errorCounters = &front->tlsFrontend->d_tlsCounters;
         }
         else if (front->dohFrontend != nullptr) {
-          errorCounters = &front->dohFrontend->d_tlsCounters;
+          errorCounters = &front->dohFrontend->d_tlsContext.d_tlsCounters;
         }
 
         if (errorCounters != nullptr) {
@@ -694,9 +783,9 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
 #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();
+    const string frontName = doh->d_tlsContext.d_addr.toStringWithPort();
     uint64_t threadNumber = 0;
-    auto dupPair = frontendDuplicates.insert({frontName, 1});
+    auto dupPair = frontendDuplicates.emplace(frontName, 1);
     if (!dupPair.second) {
       threadNumber = dupPair.first->second;
       ++(dupPair.first->second);
@@ -757,6 +846,8 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
   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;
@@ -781,6 +872,7 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
       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";
     }
   }
 
@@ -789,8 +881,10 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
   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();
@@ -808,6 +902,7 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
       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";
@@ -820,6 +915,25 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
 
 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)
 {
@@ -842,54 +956,56 @@ static void handleJSONStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
       { "server-policy", g_policy.getLocal()->getName()}
     };
 
-    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.insert({e.first, (double)(*val)->load()});
-      else if (const auto& dval = boost::get<double*>(&e.second))
-        obj.insert({e.first, (**dval)});
-      else
-        obj.insert({e.first, (double)(*boost::get<DNSDistStats::statfunction_t>(&e.second))(e.first)});
-    }
+    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.insert({e.first.toString(), thing});
+    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) {
-        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.insert({dom, thing});
+      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";
@@ -907,7 +1023,24 @@ static void handleJSONStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
             {"seconds", (double)(std::get<2>(entry).tv_sec - now.tv_sec)},
             {"blocks", (double)(std::get<1>(entry))}
           };
-        obj.insert({std::get<0>(entry).toString(), thing });
+        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 */
@@ -935,6 +1068,7 @@ static void addServerToJSON(Json::array& servers, int id, const std::shared_ptr<
   }
 
   Json::array pools;
+  pools.reserve(a->d_config.pools.size());
   for (const auto& p: a->d_config.pools) {
     pools.push_back(p);
   }
@@ -944,16 +1078,18 @@ static void addServerToJSON(Json::array& servers, int id, const std::shared_ptr<
     {"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", pools},
+    {"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},
@@ -963,17 +1099,26 @@ static void addServerToJSON(Json::array& servers, int id, const std::shared_ptr<
     {"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)},
+    {"healthCheckFailures", (double)(a->d_healthCheckMetrics.d_failures)},
+    {"healthCheckFailuresParsing", (double)(a->d_healthCheckMetrics.d_parseErrors)},
+    {"healthCheckFailuresTimeout", (double)(a->d_healthCheckMetrics.d_timeOuts)},
+    {"healthCheckFailuresNetwork", (double)(a->d_healthCheckMetrics.d_networkErrors)},
+    {"healthCheckFailuresMismatch", (double)(a->d_healthCheckMetrics.d_mismatchErrors)},
+    {"healthCheckFailuresInvalid", (double)(a->d_healthCheckMetrics.d_invalidResponseErrors)},
     {"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));
@@ -984,25 +1129,31 @@ static void handleStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
   handleCORS(req, resp);
   resp.status = 200;
 
-  Json::array servers;
-  auto localServers = g_dstates.getLocal();
   int num = 0;
-  for (const auto& a : *localServers) {
-    addServerToJSON(servers, num++, a);
+
+  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;
-  for(const auto& front : g_frontends) {
+  frontends.reserve(g_frontends.size());
+  for (const auto& front : g_frontends) {
     if (front->udpFD == -1 && front->tcpFD == -1)
       continue;
-    Json::object frontend{
+    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() },
@@ -1028,7 +1179,7 @@ static void handleStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
       errorCounters = &front->tlsFrontend->d_tlsCounters;
     }
     else if (front->dohFrontend != nullptr) {
-      errorCounters = &front->dohFrontend->d_tlsCounters;
+      errorCounters = &front->dohFrontend->d_tlsContext.d_tlsCounters;
     }
     if (errorCounters != nullptr) {
       frontend["tlsHandshakeFailuresDHKeyTooSmall"] = (double)errorCounters->d_dhKeyTooSmall;
@@ -1040,17 +1191,18 @@ static void handleStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
       frontend["tlsHandshakeFailuresUnsupportedEC"] = (double)errorCounters->d_unsupportedEC;
       frontend["tlsHandshakeFailuresUnsupportedProtocol"] = (double)errorCounters->d_unsupportedProtocol;
     }
-    frontends.push_back(frontend);
+    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) {
-      Json::object obj{
+    for (const auto& doh : g_dohlocals) {
+      dohs.emplace_back(Json::object{
         { "id", num++ },
-        { "address", doh->d_local.toStringWithPort() },
+        { "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 },
@@ -1072,93 +1224,111 @@ static void handleStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
         { "error-responses", (double) doh->d_errorresponses },
         { "redirect-responses", (double) doh->d_redirectresponses },
         { "valid-responses", (double) doh->d_validresponses }
-      };
-      dohs.push_back(obj);
+      });
     }
   }
 #endif /* HAVE_DNS_OVER_HTTPS */
 
   Json::array pools;
-  auto localPools = g_pools.getLocal();
-  num = 0;
-  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) }
-    };
-    pools.push_back(entry);
+  {
+    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;
-  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(rule);
+  {
+    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)},
+        {"name", a.d_name},
+        {"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;
+  {
+    auto aclEntries = g_ACL.getLocal()->toStringVector();
 
-  vector<string> vec;
-  g_ACL.getLocal()->toStringVector(&vec);
-
-  for(const auto& s : vec) {
-    if(!acl.empty()) acl += ", ";
-    acl+=s;
+    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 += ", ";
+  {
+    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;
     }
-    localaddressesStr += addr;
   }
 
-  Json my_json = Json::object {
+  Json::object stats;
+  addStatsToJSONObject(stats);
+
+  Json responseObject(Json::object({
     { "daemon_type", "dnsdist" },
-    { "version", VERSION},
-    { "servers", servers},
-    { "frontends", frontends },
-    { "pools", pools },
-    { "rules", rules},
-    { "response-rules", responseRules},
-    { "cache-hit-response-rules", cacheHitResponseRules},
-    { "self-answered-response-rules", selfAnsweredResponseRules},
-    { "acl", acl},
-    { "local", localaddressesStr},
-    { "dohFrontends", dohs }
-  };
+    { "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 = my_json.dump();
+  resp.body = responseObject.dump();
 }
 
 static void handlePoolStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
@@ -1193,7 +1363,8 @@ static void handlePoolStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
     { "cacheDeferredLookups", (double) (cache ? cache->getDeferredLookups() : 0) },
     { "cacheLookupCollisions", (double) (cache ? cache->getLookupCollisions() : 0) },
     { "cacheInsertCollisions", (double) (cache ? cache->getInsertCollisions() : 0) },
-    { "cacheTTLTooShorts", (double) (cache ? cache->getTTLTooShorts() : 0) }
+    { "cacheTTLTooShorts", (double) (cache ? cache->getTTLTooShorts() : 0) },
+    { "cacheCleanupCount", (double) (cache ? cache->getCleanupCount() : 0) }
   };
 
   Json::array servers;
@@ -1218,30 +1389,41 @@ static void handleStatsOnly(const YaHTTP::Request& req, YaHTTP::Response& 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
+  {
+    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 = 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& dval = boost::get<double*>(&item.second)) {
-      doc.push_back(Json::object {
-          { "type", "StatisticItem" },
-          { "name", item.first },
-          { "value", (**dval) }
-        });
-    }
-    else {
-      doc.push_back(Json::object {
-          { "type", "StatisticItem" },
-          { "name", item.first },
-          { "value", (double)(*boost::get<DNSDistStats::statfunction_t>(&item.second))(item.first) }
-        });
+      if (const auto& val = std::get_if<pdns::stat_t*>(&item.d_value)) {
+        doc.push_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.push_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.push_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.push_back(Json::object {
+            { "type", "StatisticItem" },
+            { "name", item.d_name },
+            { "value", (double)(*func)(item.d_name) }
+          });
+      }
     }
   }
   Json my_json = doc;
@@ -1318,7 +1500,7 @@ static void handleAllowFrom(const YaHTTP::Request& req, YaHTTP::Response& resp)
       auto aclList = doc["value"];
       if (aclList.is_array()) {
 
-        for (auto value : aclList.array_items()) {
+        for (const auto& value : aclList.array_items()) {
           try {
             nmg.addMask(value.string_value());
           } catch (NetmaskException &e) {
@@ -1342,18 +1524,12 @@ static void handleAllowFrom(const YaHTTP::Request& req, YaHTTP::Response& resp)
     }
   }
   if (resp.status == 200) {
-    Json::array acl;
-    vector<string> vec;
-    g_ACL.getLocal()->toStringVector(&vec);
-
-    for(const auto& s : vec) {
-      acl.push_back(s);
-    }
+    auto aclEntries = g_ACL.getLocal()->toStringVector();
 
     Json::object obj{
       { "type", "ConfigSetting" },
       { "name", "allow-from" },
-      { "value", acl }
+      { "value", aclEntries }
     };
     Json my_json = obj;
     resp.body = my_json.dump();
@@ -1361,13 +1537,196 @@ static void handleAllowFrom(const YaHTTP::Request& req, YaHTTP::Response& resp)
 }
 #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)
+    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.push_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] = handler;
+  s_webHandlers[endpoint] = std::move(handler);
 }
 
 void clearWebHandlers()
@@ -1425,10 +1784,14 @@ void registerBuiltInWebHandlers()
   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);
 
@@ -1489,7 +1852,7 @@ static void connectionThread(WebClientConnection&& conn)
     else if (!handleAuthorization(req)) {
       YaHTTP::strstr_map_t::iterator header = req.headers.find("authorization");
       if (header != req.headers.end()) {
-        errlog("HTTP Request \"%s\" from %s: Web Authentication failed", req.url.path, conn.getClient().toStringWithPort());
+        vinfolog("HTTP Request \"%s\" from %s: Web Authentication failed", req.url.path, conn.getClient().toStringWithPort());
       }
       resp.status = 401;
       resp.body = "<h1>Unauthorized</h1>";
@@ -1517,10 +1880,10 @@ static void connectionThread(WebClientConnection&& conn)
     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());
+    vinfolog("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());
+    vinfolog("Webserver thread died with exception while processing a request from %s", conn.getClient().toStringWithPort());
   }
 }
 
@@ -1558,6 +1921,16 @@ 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);
@@ -1566,13 +1939,16 @@ void setWebserverMaxConcurrentConnections(size_t max)
 void dnsdistWebserverThread(int sock, const ComboAddress& local)
 {
   setThreadName("dnsdist/webserv");
-  warnlog("Webserver launched on %s", local.toStringWithPort());
+  infolog("Webserver launched on %s", local.toStringWithPort());
 
-  if (!g_webserverConfig.lock()->password) {
-    warnlog("Webserver launched on %s without a password set!", local.toStringWithPort());
+  {
+    auto config = g_webserverConfig.lock();
+    if (!config->password && config->dashboardRequiresAuthentication) {
+      warnlog("Webserver launched on %s without a password set!", local.toStringWithPort());
+    }
   }
 
-  for(;;) {
+  for (;;) {
     try {
       ComboAddress remote(local);
       int fd = SAccept(sock, remote);
@@ -1590,7 +1966,7 @@ void dnsdistWebserverThread(int sock, const ComboAddress& local)
       t.detach();
     }
     catch (const std::exception& e) {
-      errlog("Had an error accepting new webserver connection: %s", e.what());
+      vinfolog("Had an error accepting new webserver connection: %s", e.what());
     }
   }
 }
index b02ad621761ad9a5ddfa273bd46e3ed0041242ad..eb2ba5785571ccee55431742b320a6e468c24e38 100644 (file)
 
 #include "dnsdist-xpf.hh"
 
+#include "dnsdist-dnsparser.hh"
 #include "dnsparser.hh"
 #include "xpf.hh"
 
 bool addXPF(DNSQuestion& dq, uint16_t optionCode)
 {
-  std::string payload = generateXPFPayload(dq.overTCP(), *dq.remote, *dq.local);
+  std::string payload = generateXPFPayload(dq.overTCP(), dq.ids.origRemote, dq.ids.origDest);
   uint8_t root = '\0';
   dnsrecordheader drh;
   drh.d_type = htons(optionCode);
@@ -54,7 +55,9 @@ bool addXPF(DNSQuestion& dq, uint16_t optionCode)
   pos += payload.size();
   (void) pos;
 
-  dq.getHeader()->arcount = htons(ntohs(dq.getHeader()->arcount) + 1);
-
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.getMutableData(), [](dnsheader& header) {
+    header.arcount = htons(ntohs(header.arcount) + 1);
+    return true;
+  });
   return true;
 }
index b3b01859591367d58c8942bab21a62d263b14ae8..6f0d4080ab8aa9426e27886314d4255606b65f93 100644 (file)
 
 #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__
@@ -38,6 +41,7 @@
 #else
 #include <editline/readline.h>
 #endif
+#endif /* HAVE_LIBEDIT */
 
 #include "dnsdist-systemd.hh"
 #ifdef HAVE_SYSTEMD
 #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-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"
@@ -71,9 +84,9 @@
 #include "gettime.hh"
 #include "lock.hh"
 #include "misc.hh"
-#include "sodcrypto.hh"
 #include "sstuff.hh"
 #include "threadname.hh"
+#include "xsk.hh"
 
 /* Known sins:
 
 using std::thread;
 bool g_verbose;
 
-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;
@@ -103,6 +112,8 @@ 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};
@@ -111,7 +122,7 @@ 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.
@@ -130,15 +141,13 @@ size_t g_udpVectorSize{1};
 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;
-GlobalStateHolder<NetmaskTree<DynBlock, AddressAndPortRange>> g_dynblockNMG;
-GlobalStateHolder<SuffixMatchTree<DynBlock>> g_dynblockSMT;
-DNSAction::Action g_dynBlockAction = DNSAction::Action::Drop;
 
 bool g_servFailOnNoPolicy{false};
 bool g_truncateTC{false};
@@ -149,9 +158,38 @@ 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;
+// 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& 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
@@ -165,8 +203,12 @@ static void truncateTC(PacketBuffer& packet, size_t maximumSize, unsigned int qn
     }
 
     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;
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [](dnsheader& header) {
+      header.ancount = 0;
+      header.arcount = 0;
+      header.nscount = 0;
+      return true;
+    });
 
     if (hadEDNS) {
       addEDNS(packet, maximumSize, z & EDNS_HEADER_FLAG_DO, payloadSize, 0);
@@ -174,10 +216,11 @@ static void truncateTC(PacketBuffer& packet, size_t maximumSize, unsigned int qn
   }
   catch(...)
   {
-    ++g_stats.truncFail;
+    ++dnsdist::metrics::g_stats.truncFail;
   }
 }
 
+#ifndef DISABLE_DELAY_PIPE
 struct DelayedPacket
 {
   int fd;
@@ -186,13 +229,7 @@ struct DelayedPacket
   ComboAddress origDest;
   void operator()()
   {
-    ssize_t res;
-    if(origDest.sin4.sin_family == 0) {
-      res = sendto(fd, packet.data(), packet.size(), 0, (struct sockaddr*)&destination, destination.getSocklen());
-    }
-    else {
-      res = sendfromto(fd, packet.data(), packet.size(), 0, origDest, destination);
-    }
+    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));
@@ -200,12 +237,14 @@ struct DelayedPacket
   }
 };
 
-DelayPipe<DelayedPacket>* g_delay = nullptr;
+static std::unique_ptr<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());
+  // 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());
   return std::string(message + messageLen, this->getData().size() - messageLen);
 }
 
@@ -223,36 +262,92 @@ bool DNSQuestion::setTrailingData(const std::string& tail)
   return true;
 }
 
-void doLatencyStats(double udiff)
+bool DNSQuestion::editHeader(const std::function<bool(dnsheader&)>& editFunction)
 {
-  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;
+  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);
+}
 
-  auto doAvg = [](double& var, double n, double weight) {
+static void doLatencyStats(dnsdist::Protocol protocol, double udiff)
+{
+  constexpr auto doAvg = [](double& var, double n, double weight) {
     var = (weight -1) * var/weight + n/weight;
   };
 
-  doAvg(g_stats.latencyAvg100,     udiff,     100);
-  doAvg(g_stats.latencyAvg1000,    udiff,    1000);
-  doAvg(g_stats.latencyAvg10000,   udiff,   10000);
-  doAvg(g_stats.latencyAvg1000000, udiff, 1000000);
+  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 += 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 ComboAddress& 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)
 {
   if (response.size() < sizeof(dnsheader)) {
     return false;
   }
 
-  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(response.data());
+  const dnsheader_aligned dh(response.data());
   if (dh->qr == 0) {
-    ++g_stats.nonCompliantResponses;
+    ++dnsdist::metrics::g_stats.nonCompliantResponses;
+    if (remote) {
+      ++remote->nonCompliantResponses;
+    }
     return false;
   }
 
@@ -261,7 +356,10 @@ bool responseContentMatches(const PacketBuffer& response, const DNSName& qname,
       return true;
     }
     else {
-      ++g_stats.nonCompliantResponses;
+      ++dnsdist::metrics::g_stats.nonCompliantResponses;
+      if (remote) {
+        ++remote->nonCompliantResponses;
+      }
       return false;
     }
   }
@@ -269,13 +367,17 @@ bool responseContentMatches(const PacketBuffer& response, const DNSName& qname,
   uint16_t rqtype, rqclass;
   DNSName rqname;
   try {
-    rqname = DNSName(reinterpret_cast<const char*>(response.data()), response.size(), sizeof(dnsheader), false, &rqtype, &rqclass, &qnameWireLength);
+    // 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(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.toStringWithPort(), ntohs(dh->id), e.what());
+    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());
+    }
+    ++dnsdist::metrics::g_stats.nonCompliantResponses;
+    if (remote) {
+      ++remote->nonCompliantResponses;
     }
-    ++g_stats.nonCompliantResponses;
     return false;
   }
 
@@ -300,11 +402,14 @@ static void restoreFlags(struct dnsheader* dh, uint16_t origFlags)
   *flags |= origFlags;
 }
 
-static bool fixUpQueryTurnedResponse(DNSQuestion& dq, const uint16_t origFlags)
+static bool fixUpQueryTurnedResponse(DNSQuestion& dnsQuestion, const uint16_t origFlags)
 {
-  restoreFlags(dq.getHeader(), origFlags);
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [origFlags](dnsheader& header) {
+    restoreFlags(&header, origFlags);
+    return true;
+  });
 
-  return addEDNSToQueryTurnedResponse(dq);
+  return addEDNSToQueryTurnedResponse(dnsQuestion);
 }
 
 static bool fixUpResponse(PacketBuffer& response, const DNSName& qname, uint16_t origFlags, bool ednsAdded, bool ecsAdded, bool* zeroScope)
@@ -313,8 +418,10 @@ static bool fixUpResponse(PacketBuffer& response, const DNSName& qname, uint16_t
     return false;
   }
 
-  struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(response.data());
-  restoreFlags(dh, origFlags);
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(response, [origFlags](dnsheader& header) {
+    restoreFlags(&header, origFlags);
+    return true;
+  });
 
   if (response.size() == sizeof(dnsheader)) {
     return true;
@@ -352,10 +459,12 @@ static bool fixUpResponse(PacketBuffer& response, const DNSName& qname, uint16_t
         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);
+          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 */
@@ -410,15 +519,15 @@ static bool encryptResponse(PacketBuffer& response, size_t maximumSize, bool tcp
 }
 #endif /* HAVE_DNSCRYPT */
 
-static bool applyRulesToResponse(LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr)
+static bool applyRulesToResponse(const std::vector<DNSDistResponseRuleAction>& respRuleActions, DNSResponse& dr)
 {
-  DNSResponseAction::Action action=DNSResponseAction::Action::None;
+  DNSResponseAction::Action action = DNSResponseAction::Action::None;
   std::string ruleresult;
-  for(const auto& lr : *localRespRuleActions) {
-    if(lr.d_rule->matches(&dr)) {
-      lr.d_rule->d_matches++;
-      action=(*lr.d_action)(&dr, &ruleresult);
-      switch(action) {
+  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;
@@ -429,12 +538,27 @@ static bool applyRulesToResponse(LocalStateHolder<vector<DNSDistResponseRuleActi
         return true;
         break;
       case DNSResponseAction::Action::ServFail:
-        dr.getHeader()->rcode = RCode::ServFail;
+        dnsdist::PacketMangling::editDNSHeaderFromPacket(dr.getMutableData(), [](dnsheader& header) {
+          header.rcode = RCode::ServFail;
+          return true;
+        });
         return true;
         break;
+      case DNSResponseAction::Action::Truncate:
+        if (!dr.overTCP()) {
+          dnsdist::PacketMangling::editDNSHeaderFromPacket(dr.getMutableData(), [](dnsheader& header) {
+            header.tc = true;
+            header.qr = true;
+            return true;
+          });
+          truncateTC(dr.getMutableData(), dr.getMaximumSize(), dr.ids.qname.wirelength());
+          ++dnsdist::metrics::g_stats.ruleTruncated;
+          return true;
+        }
+        break;
         /* non-terminal actions follow */
       case DNSResponseAction::Action::Delay:
-        pdns::checked_stoi_into(dr.delayMsec, ruleresult); // sorry
+        pdns::checked_stoi_into(dr.ids.delayMsec, ruleresult); // sorry
         break;
       case DNSResponseAction::Action::None:
         break;
@@ -445,21 +569,15 @@ static bool applyRulesToResponse(LocalStateHolder<vector<DNSDistResponseRuleActi
   return true;
 }
 
-// whether the query was received over TCP or not (for rules, dnstap, protobuf, ...) will be taken from the DNSResponse, but receivedOverUDP is used to insert into the cache,
-// so that answers received over UDP for DoH are still cached with UDP answers.
-bool processResponse(PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted, bool receivedOverUDP)
+bool processResponseAfterRules(PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dr, bool muted)
 {
-  if (!applyRulesToResponse(localRespRuleActions, dr)) {
-    return false;
-  }
-
   bool zeroScope = false;
-  if (!fixUpResponse(response, *dr.qname, dr.origFlags, dr.ednsAdded, dr.ecsAdded, dr.useZeroScope ? &zeroScope : nullptr)) {
+  if (!fixUpResponse(response, dr.ids.qname, dr.ids.origFlags, dr.ids.ednsAdded, dr.ids.ecsAdded, dr.ids.useZeroScope ? &zeroScope : nullptr)) {
     return false;
   }
 
-  if (dr.packetCache && !dr.skipCache && response.size() <= s_maxPacketCacheEntrySize) {
-    if (!dr.useZeroScope) {
+  if (dr.ids.packetCache && !dr.ids.selfGenerated && !dr.ids.skipCache && (!dr.ids.forwardedOverUDP || response.size() <= s_maxUDPResponsePacketSize)) {
+    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
@@ -470,21 +588,35 @@ bool processResponse(PacketBuffer& response, LocalStateHolder<vector<DNSDistResp
       */
       zeroScope = false;
     }
-    uint32_t cacheKey = dr.cacheKey;
-    if (dr.protocol == dnsdist::Protocol::DoH && receivedOverUDP) {
-      cacheKey = dr.cacheKeyUDP;
+    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.cacheKeyNoECS;
+      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);
+  }
 
-    dr.packetCache->insert(cacheKey, zeroScope ? boost::none : dr.subnet, dr.cacheFlags, dr.dnssecOK, *dr.qname, dr.qtype, dr.qclass, response, receivedOverUDP, dr.getHeader()->rcode, dr.tempFailureTTL);
+  if (dr.ids.d_extendedError) {
+    dnsdist::edns::addExtendedDNSError(dr.getMutableData(), dr.getMaximumSize(), dr.ids.d_extendedError->infoCode, dr.ids.d_extendedError->extraText);
   }
 
 #ifdef HAVE_DNSCRYPT
   if (!muted) {
-    if (!encryptResponse(response, dr.getMaximumSize(), dr.overTCP(), dr.dnsCryptQuery)) {
+    if (!encryptResponse(response, dr.getMaximumSize(), dr.overTCP(), dr.ids.dnsCryptQuery)) {
       return false;
     }
   }
@@ -493,11 +625,24 @@ bool processResponse(PacketBuffer& response, LocalStateHolder<vector<DNSDistResp
   return true;
 }
 
-static size_t getInitialUDPPacketBufferSize()
+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(bool expectProxyProtocol)
 {
   static_assert(s_udpIncomingBufferSize <= s_initialUDPPacketBufferSize, "The incoming buffer size should not be larger than s_initialUDPPacketBufferSize");
 
-  if (g_proxyProtocolACL.empty()) {
+  if (!expectProxyProtocol || g_proxyProtocolACL.empty()) {
     return s_initialUDPPacketBufferSize;
   }
 
@@ -507,57 +652,162 @@ static size_t getInitialUDPPacketBufferSize()
 static size_t getMaximumIncomingPacketSize(const ClientState& cs)
 {
   if (cs.dnscryptCtx) {
-    return getInitialUDPPacketBufferSize();
+    return getInitialUDPPacketBufferSize(cs.d_enableProxyProtocol);
   }
 
-  if (g_proxyProtocolACL.empty()) {
+  if (!cs.d_enableProxyProtocol || g_proxyProtocolACL.empty()) {
     return s_udpIncomingBufferSize;
   }
 
   return s_udpIncomingBufferSize + g_proxyProtocolMaximumSize;
 }
 
-static bool sendUDPResponse(int origFD, const PacketBuffer& response, const int delayMsec, const ComboAddress& origDest, const ComboAddress& origRemote)
+bool sendUDPResponse(int origFD, const PacketBuffer& response, const int delayMsec, const ComboAddress& origDest, const ComboAddress& origRemote)
 {
-  if(delayMsec && g_delay) {
+#ifndef DISABLE_DELAY_PIPE
+  if (delayMsec > 0 && g_delay != nullptr) {
     DelayedPacket dp{origFD, response, origRemote, origDest};
     g_delay->submit(dp, delayMsec);
+    return true;
   }
-  else {
-    ssize_t res;
-    if (origDest.sin4.sin_family == 0) {
-      res = sendto(origFD, response.data(), response.size(), 0, reinterpret_cast<const struct sockaddr*>(&origRemote), origRemote.getSocklen());
-    }
-    else {
-      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));
-    }
+#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 IDState& ids, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol protocol)
+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)
 {
-  struct timespec ts;
-  gettime(&ts);
-  g_rings.insertResponse(ts, client, ids.qname, ids.qtype, static_cast<unsigned int>(udiff), size, cleartextDH, backend, protocol);
+  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;
+    ++dnsdist::metrics::g_stats.frontendNXDomain;
     break;
   case RCode::ServFail:
-    ++g_stats.servfailResponses;
-    ++g_stats.frontendServFail;
+    if (fromBackend) {
+      ++dnsdist::metrics::g_stats.servfailResponses;
+    }
+    ++dnsdist::metrics::g_stats.frontendServFail;
     break;
   case RCode::NoError:
-    ++g_stats.frontendNoError;
+    ++dnsdist::metrics::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());
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dr.getMutableData(), [](dnsheader& header) {
+      header.tc = true;
+      return 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().get(), sizeof(cleartextDH));
+
+  if (!isAsync) {
+    if (!processResponse(response, respRuleActions, cacheInsertedRespRuleActions, dr, ids.cs && ids.cs->muted)) {
+      return;
+    }
+
+    if (dr.isAsynchronous()) {
+      return;
+    }
+  }
+
+  ++dnsdist::metrics::g_stats.responses;
+  if (ids.cs) {
+    ++ids.cs->responses;
+  }
+
+  bool muted = true;
+  if (ids.cs != nullptr && !ids.cs->muted && !ids.isXSK()) {
+    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 {
+      if (!ids.isXSK()) {
+        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);
+      }
+      else {
+        vinfolog("Got answer from %s, relayed to %s (UDP via XSK), 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);
+  }
+}
+
+bool processResponderPacket(std::shared_ptr<DownstreamState>& dss, PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& localRespRuleActions, const std::vector<DNSDistResponseRuleAction>& 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
@@ -566,150 +816,97 @@ void responderThread(std::shared_ptr<DownstreamState> dss)
   try {
   setThreadName("dnsdist/respond");
   auto localRespRuleActions = g_respruleactions.getLocal();
-  const size_t initialBufferSize = getInitialUDPPacketBufferSize();
-  PacketBuffer response(initialBufferSize);
-
-  /* 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;
+  auto localCacheInsertedRespRuleActions = g_cacheInsertedRespRuleActions.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(;;) {
+  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& fd : sockets) {
-        response.resize(initialBufferSize);
+        /* 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(fd, response.data(), response.size(), 0);
 
         if (got == 0 && dss->isStopped()) {
           break;
         }
 
-        if (got < 0 || static_cast<size_t>(got) < sizeof(dnsheader)) {
+        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));
-        dnsheader* dh = reinterpret_cast<struct dnsheader*>(response.data());
-        queryId = dh->id;
-
-        IDState* ids = dss->getExistingState(queryId);
-        if (ids == nullptr) {
-          continue;
-        }
-
-        int64_t usageIndicator = ids->usageIndicator;
-
-        if (!IDState::isInUse(usageIndicator)) {
-          /* the corresponding state is marked as not in use, meaning that:
-             - it was already cleaned up by another thread and the state is gone ;
-             - we already got a response for this query and this one is a duplicate.
-             Either way, we don't touch it.
-          */
-          continue;
-        }
-
-        /* read the potential DOHUnit state as soon as possible, but don't use it
-           until we have confirmed that we own this state by updating usageIndicator */
-        auto du = DOHUnitUniquePtr(ids->du, DOHUnit::release);
-        /* setting age to 0 to prevent the maintainer thread from
-           cleaning this IDS while we process the response.
-        */
-        ids->age = 0;
-        int origFD = ids->origFD;
-
-        unsigned int qnameWireLength = 0;
-        if (fd != ids->backendFD || !responseContentMatches(response, ids->qname, ids->qtype, ids->qclass, dss->d_config.remote, qnameWireLength)) {
-          continue;
-        }
-
-        /* atomically mark the state as available, but only if it has not been altered
-           in the meantime */
-        if (ids->tryMarkUnused(usageIndicator)) {
-          /* clear the potential DOHUnit asap, it's ours now
-           and since we just marked the state as unused,
-           someone could overwrite it. */
-          ids->du = nullptr;
-          /* we only decrement the outstanding counter if the value was not
-             altered in the meantime, which would mean that the state has been actively reused
-             and the other thread has not incremented the outstanding counter, so we don't
-             want it to be decremented twice. */
-          --dss->outstanding;  // you'd think an attacker could game this, but we're using connected socket
-        } else {
-          /* someone updated the state in the meantime, we can't touch the existing pointer */
-          du.release();
-          /* since the state has been updated, we can't safely access it so let's just drop
-             this response */
-          continue;
-        }
-
-        dh->id = ids->origID;
-        ++dss->responses;
+        const dnsheader_aligned dnsHeader(response.data());
+        queryId = dnsHeader->id;
 
-        /* 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
-          dss->releaseState(queryId);
+        auto ids = dss->getState(queryId);
+        if (!ids) {
           continue;
         }
 
-        DNSResponse dr = makeDNSResponseFromIDState(*ids, response);
-        if (dh->tc && g_truncateTC) {
-          truncateTC(response, dr.getMaximumSize(), qnameWireLength);
-        }
-        memcpy(&cleartextDH, dr.getHeader(), sizeof(cleartextDH));
-
-        if (!processResponse(response, localRespRuleActions, dr, ids->cs && ids->cs->muted, true)) {
-          dss->releaseState(queryId);
+        if (!ids->isXSK() && fd != ids->backendFD) {
+          dss->restoreState(queryId, std::move(*ids));
           continue;
         }
 
-        ++g_stats.responses;
-        if (ids->cs) {
-          ++ids->cs->responses;
-        }
-
-        if (ids->cs && !ids->cs->muted) {
-          ComboAddress empty;
-          empty.sin4.sin_family = 0;
-          sendUDPResponse(origFD, response, dr.delayMsec, ids->hopLocal, ids->hopRemote);
+        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 */
         }
-
-        double udiff = ids->sentTime.udiff();
-        vinfolog("Got answer from %s, relayed to %s, took %f usec", dss->d_config.remote.toStringWithPort(), ids->origRemote.toStringWithPort(), udiff);
-
-        handleResponseSent(*ids, udiff, *dr.remote, dss->d_config.remote, static_cast<unsigned int>(got), cleartextDH, dss->getProtocol());
-        dss->releaseState(queryId);
-
-        dss->latencyUsec = (127.0 * dss->latencyUsec / 128.0) + udiff/128.0;
-
-        doLatencyStats(udiff);
       }
     }
-    catch (const std::exception& e){
+    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)
-{
+catch (const std::exception& e) {
   errlog("UDP responder thread died because of exception: %s", e.what());
 }
-catch (const PDNSException& e)
-{
+catch (const PDNSException& e) {
   errlog("UDP responder thread died because of PowerDNS exception: %s", e.reason);
 }
-catch (...)
-{
+catch (...) {
   errlog("UDP responder thread died because of an exception: %s", "unknown");
 }
 }
@@ -725,8 +922,8 @@ static void spoofResponseFromString(DNSQuestion& dq, const string& spoofContent,
   if (raw) {
     std::vector<std::string> raws;
     stringtok(raws, spoofContent, ",");
-    SpoofAction sa(raws);
-    sa(&dq, &result);
+    SpoofAction tempSpoofAction(raws, std::nullopt);
+    tempSpoofAction(&dq, &result);
   }
   else {
     std::vector<std::string> addrs;
@@ -735,13 +932,13 @@ static void spoofResponseFromString(DNSQuestion& dq, const string& spoofContent,
     if (addrs.size() == 1) {
       try {
         ComboAddress spoofAddr(spoofContent);
-        SpoofAction sa({spoofAddr});
-        sa(&dq, &result);
+        SpoofAction tempSpoofAction({spoofAddr});
+        tempSpoofAction(&dq, &result);
       }
       catch(const PDNSException &e) {
         DNSName cname(spoofContent);
-        SpoofAction sa(cname); // CNAME then
-        sa(&dq, &result);
+        SpoofAction tempSpoofAction(cname); // CNAME then
+        tempSpoofAction(&dq, &result);
       }
     } else {
       std::vector<ComboAddress> cas;
@@ -752,8 +949,8 @@ static void spoofResponseFromString(DNSQuestion& dq, const string& spoofContent,
         catch (...) {
         }
       }
-      SpoofAction sa(cas);
-      sa(&dq, &result);
+      SpoofAction tempSpoofAction(cas);
+      tempSpoofAction(&dq, &result);
     }
   }
 }
@@ -762,37 +959,43 @@ static void spoofPacketFromString(DNSQuestion& dq, const string& spoofContent)
 {
   string result;
 
-  SpoofAction sa(spoofContent.c_str(), spoofContent.size());
-  sa(&dq, &result);
+  SpoofAction tempSpoofAction(spoofContent.c_str(), spoofContent.size());
+  tempSpoofAction(&dq, &result);
 }
 
 bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::string& ruleresult, bool& drop)
 {
-  switch(action) {
+  if (dq.isAsynchronous()) {
+    return false;
+  }
+
+  auto setRCode = [&dq](uint8_t rcode) {
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.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:
-    ++g_stats.ruleDrop;
+    ++dnsdist::metrics::g_stats.ruleDrop;
     drop = true;
     return true;
     break;
   case DNSAction::Action::Nxdomain:
-    dq.getHeader()->rcode = RCode::NXDomain;
-    dq.getHeader()->qr=true;
-    ++g_stats.ruleNXDomain;
+    setRCode(RCode::NXDomain);
     return true;
     break;
   case DNSAction::Action::Refused:
-    dq.getHeader()->rcode = RCode::Refused;
-    dq.getHeader()->qr=true;
-    ++g_stats.ruleRefused;
+    setRCode(RCode::Refused);
     return true;
     break;
   case DNSAction::Action::ServFail:
-    dq.getHeader()->rcode = RCode::ServFail;
-    dq.getHeader()->qr=true;
-    ++g_stats.ruleServFail;
+    setRCode(RCode::ServFail);
     return true;
     break;
   case DNSAction::Action::Spoof:
@@ -809,12 +1012,15 @@ bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::s
     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;
+      dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.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;
@@ -824,16 +1030,19 @@ bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::s
   case DNSAction::Action::Pool:
     /* we need to keep this because a custom Lua action can return
        DNSAction.Spoof, 'poolname' */
-    dq.poolname = ruleresult;
+    dq.ids.poolName = ruleresult;
     return true;
     break;
   case DNSAction::Action::NoRecurse:
-    dq.getHeader()->rd = false;
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.getMutableData(), [](dnsheader& header) {
+      header.rd = false;
+      return true;
+    });
     return true;
     break;
     /* non-terminal actions follow */
   case DNSAction::Action::Delay:
-    pdns::checked_stoi_into(dq.delayMsec, ruleresult); // sorry
+    pdns::checked_stoi_into(dq.ids.delayMsec, ruleresult); // sorry
     break;
   case DNSAction::Action::None:
     /* fall-through */
@@ -848,10 +1057,12 @@ bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::s
 
 static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, const struct timespec& now)
 {
-  g_rings.insertQuery(now, *dq.remote, *dq.qname, dq.qtype, dq.getData().size(), *dq.getHeader(), dq.getProtocol());
+  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.qname).toLogString();
+    string qname = dq.ids.qname.toLogString();
     bool countQuery{true};
     if (g_qcount.filter) {
       auto lock = g_lua.lock();
@@ -867,10 +1078,19 @@ static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, const stru
     }
   }
 
+#ifndef DISABLE_DYNBLOCKS
+  auto setRCode = [&dq](uint8_t rcode) {
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.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(*dq.remote, dq.remote->isIPv4() ? 32 : 128, 16))) {
+  if (auto got = holders.dynNMGBlock->lookup(AddressAndPortRange(dq.ids.origRemote, dq.ids.origRemote.isIPv4() ? 32 : 128, 16))) {
     auto updateBlockStats = [&got]() {
-      ++g_stats.dynBlocked;
+      ++dnsdist::metrics::g_stats.dynBlocked;
       got->second.blocks++;
     };
 
@@ -879,58 +1099,63 @@ static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, const stru
       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.remote->toStringWithPort());
+        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;
+        setRCode(RCode::NXDomain);
         return true;
 
       case DNSAction::Action::Refused:
-        vinfolog("Query from %s refused because of dynamic block", dq.remote->toStringWithPort());
+        vinfolog("Query from %s refused because of dynamic block", dq.ids.origRemote.toStringWithPort());
         updateBlockStats();
 
-        dq.getHeader()->rcode = RCode::Refused;
-        dq.getHeader()->qr = true;
+        setRCode(RCode::Refused);
         return true;
 
       case DNSAction::Action::Truncate:
         if (!dq.overTCP()) {
           updateBlockStats();
-          vinfolog("Query from %s truncated because of dynamic block", dq.remote->toStringWithPort());
-          dq.getHeader()->tc = true;
-          dq.getHeader()->qr = true;
-          dq.getHeader()->ra = dq.getHeader()->rd;
-          dq.getHeader()->aa = false;
-          dq.getHeader()->ad = false;
+          vinfolog("Query from %s truncated because of dynamic block", dq.ids.origRemote.toStringWithPort());
+          dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.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", dq.remote->toStringWithPort(), dq.qname->toLogString());
+          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.remote->toStringWithPort());
-        dq.getHeader()->rd = false;
+        vinfolog("Query from %s setting rd=0 because of dynamic block", dq.ids.origRemote.toStringWithPort());
+        dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.getMutableData(), [](dnsheader& header) {
+          header.rd = false;
+          return true;
+        });
         return true;
       default:
         updateBlockStats();
-        vinfolog("Query from %s dropped because of dynamic block", dq.remote->toStringWithPort());
+        vinfolog("Query from %s dropped because of dynamic block", dq.ids.origRemote.toStringWithPort());
         return false;
       }
     }
   }
 
-  if (auto got = holders.dynSMTBlock->lookup(*dq.qname)) {
+  if (auto got = holders.dynSMTBlock->lookup(dq.ids.qname)) {
     auto updateBlockStats = [&got]() {
-      ++g_stats.dynBlocked;
+      ++dnsdist::metrics::g_stats.dynBlocked;
       got->blocks++;
     };
 
@@ -944,55 +1169,60 @@ static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, const stru
         /* do nothing */
         break;
       case DNSAction::Action::Nxdomain:
-        vinfolog("Query from %s for %s turned into NXDomain because of dynamic block", dq.remote->toStringWithPort(), dq.qname->toLogString());
+        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;
+        setRCode(RCode::NXDomain);
         return true;
       case DNSAction::Action::Refused:
-        vinfolog("Query from %s for %s refused because of dynamic block", dq.remote->toStringWithPort(), dq.qname->toLogString());
+        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;
+        setRCode(RCode::Refused);
         return true;
       case DNSAction::Action::Truncate:
         if (!dq.overTCP()) {
           updateBlockStats();
 
-          vinfolog("Query from %s for %s truncated because of dynamic block", dq.remote->toStringWithPort(), dq.qname->toLogString());
-          dq.getHeader()->tc = true;
-          dq.getHeader()->qr = true;
-          dq.getHeader()->ra = dq.getHeader()->rd;
-          dq.getHeader()->aa = false;
-          dq.getHeader()->ad = false;
+          vinfolog("Query from %s for %s truncated because of dynamic block", dq.ids.origRemote.toStringWithPort(), dq.ids.qname.toLogString());
+          dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.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", dq.remote->toStringWithPort(), dq.qname->toLogString());
+          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.remote->toStringWithPort());
-        dq.getHeader()->rd = false;
+        vinfolog("Query from %s setting rd=0 because of dynamic block", dq.ids.origRemote.toStringWithPort());
+        dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.getMutableData(), [](dnsheader& header) {
+          header.rd = false;
+          return true;
+        });
         return true;
       default:
         updateBlockStats();
-        vinfolog("Query from %s for %s dropped because of dynamic block", dq.remote->toStringWithPort(), dq.qname->toLogString());
+        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;
+  DNSAction::Action action = DNSAction::Action::None;
   string ruleresult;
   bool drop = false;
-  for(const auto& lr : *holders.ruleactions) {
-    if(lr.d_rule->matches(&dq)) {
+  for (const auto& lr : *holders.ruleactions) {
+    if (lr.d_rule->matches(&dq)) {
       lr.d_rule->d_matches++;
-      action=(*lr.d_action)(&dq, &ruleresult);
+      action = (*lr.d_action)(&dq, &ruleresult);
       if (processRulesResult(action, dq, ruleresult, drop)) {
         break;
       }
@@ -1025,14 +1255,14 @@ ssize_t udpClientSendRequestToBackend(const std::shared_ptr<DownstreamState>& ss
 
   if (result == -1) {
     int savederrno = errno;
-    vinfolog("Error sending request to backend %s: %d", ss->d_config.remote.toStringWithPort(), savederrno);
+    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)) {
+    if (!healthCheck && (savederrno == EINVAL || savederrno == ENODEV || savederrno == ENETUNREACH || savederrno == EBADF)) {
       ss->reconnect();
     }
   }
@@ -1045,27 +1275,45 @@ static bool isUDPQueryAcceptable(ClientState& cs, LocalHolders& holders, const s
   if (msgh->msg_flags & MSG_TRUNC) {
     /* message was too large for our buffer */
     vinfolog("Dropping message too large for our buffer");
-    ++g_stats.nonCompliantQueries;
+    ++cs.nonCompliantQueries;
+    ++dnsdist::metrics::g_stats.nonCompliantQueries;
     return false;
   }
 
-  expectProxyProtocol = expectProxyProtocolFrom(remote);
+  expectProxyProtocol = cs.d_enableProxyProtocol && expectProxyProtocolFrom(remote);
   if (!holders.acl->match(remote) && !expectProxyProtocol) {
     vinfolog("Query from %s dropped because of ACL", remote.toStringWithPort());
-    ++g_stats.aclDrops;
+    ++dnsdist::metrics::g_stats.aclDrops;
     return false;
   }
 
   if (HarvestDestinationAddress(msgh, &dest)) {
-    /* we don't get the port, only the address */
-    dest.sin4.sin_port = cs.local.sin4.sin_port;
+    /* 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;
+  ++cs.queries;
+  ++dnsdist::metrics::g_stats.queries;
 
   return true;
 }
@@ -1091,22 +1339,23 @@ bool checkDNSCryptQuery(const ClientState& cs, PacketBuffer& query, std::unique_
   return false;
 }
 
-bool checkQueryHeaders(const struct dnsheader* dh)
+bool checkQueryHeaders(const struct dnsheader& dnsHeader, ClientState& clientState)
 {
-  if (dh->qr) {   // don't respond to responses
-    ++g_stats.nonCompliantQueries;
+  if (dnsHeader.qr) {   // don't respond to responses
+    ++dnsdist::metrics::g_stats.nonCompliantQueries;
+    ++clientState.nonCompliantQueries;
     return false;
   }
 
-  if (dh->qdcount == 0) {
-    ++g_stats.emptyQueries;
+  if (dnsHeader.qdcount == 0) {
+    ++dnsdist::metrics::g_stats.emptyQueries;
     if (g_dropEmptyQueries) {
       return false;
     }
   }
 
-  if (dh->rd) {
-    ++g_stats.rdQueries;
+  if (dnsHeader.rd) {
+    ++dnsdist::metrics::g_stats.rdQueries;
   }
 
   return true;
@@ -1130,158 +1379,177 @@ static void queueResponse(const ClientState& cs, const PacketBuffer& response, c
 #endif /* DISABLE_RECVMMSG */
 
 /* self-generated responses or cache hits */
-static bool prepareOutgoingResponse(LocalHolders& holders, ClientState& cs, DNSQuestion& dq, bool cacheHit)
+static bool prepareOutgoingResponse(LocalHolders& holders, const ClientState& cs, DNSQuestion& dq, bool cacheHit)
 {
-  DNSResponse dr(dq.qname, dq.qtype, dq.qclass, dq.local, dq.remote, dq.getMutableData(), dq.protocol, dq.queryTime);
-
-  dr.uniqueId = dq.uniqueId;
-  dr.qTag = std::move(dq.qTag);
-  dr.delayMsec = dq.delayMsec;
+  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)) {
+  if (!applyRulesToResponse(cacheHit ? *holders.cacheHitRespRuleactions : *holders.selfAnsweredRespRuleactions, dr)) {
     return false;
   }
 
-  /* in case a rule changed it */
-  dq.delayMsec = dr.delayMsec;
+  if (dr.ids.ttlCap > 0) {
+    std::string result;
+    LimitTTLResponseAction ac(0, dr.ids.ttlCap, {});
+    ac(&dr, &result);
+  }
 
-#ifdef HAVE_DNSCRYPT
-  if (!cs.muted) {
-    if (!encryptResponse(dq.getMutableData(), dq.getMaximumSize(), dq.overTCP(), dq.dnsCryptQuery)) {
-      return false;
-    }
+  if (dr.ids.d_extendedError) {
+    dnsdist::edns::addExtendedDNSError(dr.getMutableData(), dr.getMaximumSize(), dr.ids.d_extendedError->infoCode, dr.ids.d_extendedError->extraText);
   }
-#endif /* HAVE_DNSCRYPT */
 
   if (cacheHit) {
-    ++g_stats.cacheHits;
+    ++dnsdist::metrics::g_stats.cacheHits;
   }
 
-  switch (dr.getHeader()->rcode) {
-  case RCode::NXDomain:
-    ++g_stats.frontendNXDomain;
-    break;
-  case RCode::ServFail:
-    ++g_stats.frontendServFail;
-    break;
-  case RCode::NoError:
-    ++g_stats.frontendNoError;
-    break;
+  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 */
 
-  doLatencyStats(0);  // we're not going to measure this
   return true;
 }
 
-ProcessQueryResult processQuery(DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)
+ProcessQueryResult processQueryAfterRules(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.getHeader()->qr) { // something turned it into a response
-      fixUpQueryTurnedResponse(dq, dq.origFlags);
+      fixUpQueryTurnedResponse(dq, dq.ids.origFlags);
 
-      if (!prepareOutgoingResponse(holders, cs, dq, false)) {
+      if (!prepareOutgoingResponse(holders, *dq.ids.cs, dq, false)) {
         return ProcessQueryResult::Drop;
       }
 
-      ++g_stats.selfAnswered;
-      ++cs.responses;
+      const auto rcode = dq.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;
+      ++dq.ids.cs->responses;
       return ProcessQueryResult::SendAnswer;
     }
-
-    std::shared_ptr<ServerPool> serverPool = getPool(*holders.pools, dq.poolname);
+    std::shared_ptr<ServerPool> serverPool = getPool(*holders.pools, dq.ids.poolName);
     std::shared_ptr<ServerPolicy> poolPolicy = serverPool->policy;
-    dq.packetCache = serverPool->packetCache;
+    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.packetCache && !dq.skipCache) {
-      dq.dnssecOK = (getEDNSZ(dq) & EDNS_HEADER_FLAG_DO);
+    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.packetCache && !dq.skipCache && (!selectedBackend || !selectedBackend->d_config.disableZeroScope) && dq.packetCache->isECSParsingEnabled()) {
-        if (dq.packetCache->get(dq, dq.getHeader()->id, &dq.cacheKeyNoECS, dq.subnet, dq.dnssecOK, !dq.overTCP(), allowExpired)) {
+      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, cs, dq, true)) {
+          if (!prepareOutgoingResponse(holders, *dq.ids.cs, dq, true)) {
             return ProcessQueryResult::Drop;
           }
 
+          ++dnsdist::metrics::g_stats.responses;
+          ++dq.ids.cs->responses;
           return ProcessQueryResult::SendAnswer;
         }
 
-        if (!dq.subnet) {
+        if (!dq.ids.subnet) {
           /* there was no existing ECS on the query, enable the zero-scope feature */
-          dq.useZeroScope = true;
+          dq.ids.useZeroScope = true;
         }
       }
 
-      if (!handleEDNSClientSubnet(dq, dq.ednsAdded, dq.ecsAdded)) {
-        vinfolog("Dropping query from %s because we couldn't insert the ECS value", dq.remote->toStringWithPort());
+      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.packetCache && !dq.skipCache) {
-      if (dq.packetCache->get(dq, dq.getHeader()->id, &dq.cacheKey, dq.subnet, dq.dnssecOK, !dq.overTCP(), allowExpired)) {
+    if (dq.ids.packetCache && !dq.ids.skipCache) {
+      bool forwardedOverUDP = !dq.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 (dq.ids.packetCache->get(dq, dq.getHeader()->id, &dq.ids.cacheKey, dq.ids.subnet, dq.ids.dnssecOK, forwardedOverUDP, allowExpired, false, true, dq.ids.protocol != dnsdist::Protocol::DoH || forwardedOverUDP)) {
+
+        dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.getMutableData(), [flags=dq.ids.origFlags](dnsheader& header) {
+          restoreFlags(&header, flags);
+          return true;
+        });
 
-        restoreFlags(dq.getHeader(), dq.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, cs, dq, true)) {
+        if (!prepareOutgoingResponse(holders, *dq.ids.cs, dq, true)) {
           return ProcessQueryResult::Drop;
         }
 
+        ++dnsdist::metrics::g_stats.responses;
+        ++dq.ids.cs->responses;
         return ProcessQueryResult::SendAnswer;
       }
-      else if (dq.protocol == dnsdist::Protocol::DoH) {
-        /* do a second-lookup for UDP responses */
-        /* we need to do a copy to be able to restore the query on a TC=1 cached answer */
-        PacketBuffer initialQuery(dq.getData());
-        if (dq.packetCache->get(dq, dq.getHeader()->id, &dq.cacheKeyUDP, dq.subnet, dq.dnssecOK, true, allowExpired)) {
-          if (dq.getHeader()->tc == 0) {
-            if (!prepareOutgoingResponse(holders, cs, dq, true)) {
-              return ProcessQueryResult::Drop;
-            }
-
-            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, true)) {
+          if (!prepareOutgoingResponse(holders, *dq.ids.cs, dq, true)) {
+            return ProcessQueryResult::Drop;
           }
-          dq.getMutableData() = std::move(initialQuery);
+
+          ++dnsdist::metrics::g_stats.responses;
+          ++dq.ids.cs->responses;
+          return ProcessQueryResult::SendAnswer;
         }
       }
 
-      ++g_stats.cacheMisses;
+      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());
+
+      ++dnsdist::metrics::g_stats.cacheMisses;
     }
 
     if (!selectedBackend) {
-      ++g_stats.noPolicy;
+      ++dnsdist::metrics::g_stats.noPolicy;
 
-      vinfolog("%s query for %s|%s from %s, no policy applied", g_servFailOnNoPolicy ? "ServFailed" : "Dropped", dq.qname->toLogString(), QType(dq.qtype).toString(), dq.remote->toStringWithPort());
+      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;
+        dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.getMutableData(), [](dnsheader& header) {
+          header.rcode = RCode::ServFail;
+          header.qr = true;
+          return true;
+        });
 
-        fixUpQueryTurnedResponse(dq, dq.origFlags);
+        fixUpQueryTurnedResponse(dq, dq.ids.origFlags);
 
-        if (!prepareOutgoingResponse(holders, cs, dq, false)) {
+        if (!prepareOutgoingResponse(holders, *dq.ids.cs, dq, false)) {
           return ProcessQueryResult::Drop;
         }
+        ++dnsdist::metrics::g_stats.responses;
+        ++dq.ids.cs->responses;
         // no response-only statistics counter to update.
         return ProcessQueryResult::SendAnswer;
       }
@@ -1290,17 +1558,24 @@ ProcessQueryResult processQuery(DNSQuestion& dq, ClientState& cs, LocalHolders&
     }
 
     /* save the DNS flags as sent to the backend so we can cache the answer with the right flags later */
-    dq.cacheFlags = *getFlagsFromDNSHeader(dq.getHeader());
+    dq.ids.cacheFlags = *getFlagsFromDNSHeader(dq.getHeader().get());
 
     if (dq.addXPF && selectedBackend->d_config.xpfRRCode != 0) {
       addXPF(dq, selectedBackend->d_config.xpfRRCode);
     }
 
+    if (selectedBackend->d_config.useProxyProtocol && dq.getProtocol().isEncrypted() && selectedBackend->d_config.d_proxyProtocolAdvertiseTLS) {
+      if (!dq.proxyProtocolValues) {
+        dq.proxyProtocolValues = std::make_unique<std::vector<ProxyProtocolValue>>();
+      }
+      dq.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 from %s, id %d: %s", (dq.overTCP() ? "TCP" : "UDP"), dq.remote->toStringWithPort(), queryId, e.what());
+    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;
 }
@@ -1308,7 +1583,7 @@ ProcessQueryResult processQuery(DNSQuestion& dq, ClientState& cs, LocalHolders&
 class UDPTCPCrossQuerySender : public TCPQuerySender
 {
 public:
-  UDPTCPCrossQuerySender(const ClientState& cs, std::shared_ptr<DownstreamState>& ds, uint16_t payloadSize): d_cs(cs), d_ds(ds), d_payloadSize(payloadSize)
+  UDPTCPCrossQuerySender()
   {
   }
 
@@ -1321,53 +1596,18 @@ public:
     return true;
   }
 
-  const ClientState* getClientState() const override
-  {
-    return &d_cs;
-  }
-
   void handleResponse(const struct timeval& now, TCPResponse&& response) override
   {
-    if (!d_ds) {
+    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();
-    DNSResponse dr = makeDNSResponseFromIDState(ids, response.d_buffer);
-    if (response.d_buffer.size() > d_payloadSize) {
-      vinfolog("Got a response of size %d over TCP, while the initial UDP payload size was %d, truncating", response.d_buffer.size(), d_payloadSize);
-      truncateTC(dr.getMutableData(), dr.getMaximumSize(), dr.qname->wirelength());
-      dr.getHeader()->tc = true;
-    }
+    static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localCacheInsertedRespRuleActions = g_cacheInsertedRespRuleActions.getLocal();
 
-    dnsheader cleartextDH;
-    memcpy(&cleartextDH, dr.getHeader(), sizeof(cleartextDH));
-
-    if (!processResponse(response.d_buffer, localRespRuleActions, dr, false, true)) {
-      return;
-    }
-
-    ++g_stats.responses;
-    if (ids.cs) {
-      ++ids.cs->responses;
-    }
-
-    if (ids.cs && !ids.cs->muted) {
-      ComboAddress empty;
-      empty.sin4.sin_family = 0;
-      sendUDPResponse(ids.origFD, response.d_buffer, dr.delayMsec, ids.hopLocal, ids.hopRemote);
-    }
-
-    double udiff = ids.sentTime.udiff();
-    vinfolog("Got answer from %s, relayed to %s (UDP), took %f usec", d_ds->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), udiff);
-
-    handleResponseSent(ids, udiff, *dr.remote, d_ds->d_config.remote, response.d_buffer.size(), cleartextDH, d_ds->getProtocol());
-
-    d_ds->latencyUsec = (127.0 * d_ds->latencyUsec / 128.0) + udiff/128.0;
-
-    doLatencyStats(udiff);
+    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
@@ -1375,29 +1615,27 @@ public:
     return handleResponse(now, std::move(response));
   }
 
-  void notifyIOError(IDState&& query, const struct timeval& now) override
+  void notifyIOError(const struct timeval&, TCPResponse&&) override
   {
     // nothing to do
   }
-private:
-  const ClientState& d_cs;
-  std::shared_ptr<DownstreamState> d_ds{nullptr};
-  uint16_t d_payloadSize{0};
 };
 
 class UDPCrossProtocolQuery : public CrossProtocolQuery
 {
 public:
-  UDPCrossProtocolQuery(PacketBuffer&& buffer, IDState&& ids, std::shared_ptr<DownstreamState>& ds): d_cs(*ids.cs)
+  UDPCrossProtocolQuery(PacketBuffer&& buffer_, InternalQueryState&& ids_, std::shared_ptr<DownstreamState> ds): CrossProtocolQuery(InternalQuery(std::move(buffer_), std::move(ids_)), ds)
   {
-    uint16_t z = 0;
-    getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(buffer.data()), buffer.size(), &d_payloadSize, &z);
-    if (d_payloadSize < 512) {
-      d_payloadSize = 512;
+    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;
+      }
     }
-    query = InternalQuery(std::move(buffer), std::move(ids));
-    downstream = ds;
-    proxyProtocolPayloadSize = 0;
   }
 
   ~UDPCrossProtocolQuery()
@@ -1406,21 +1644,123 @@ public:
 
   std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
   {
-    auto sender = std::make_shared<UDPTCPCrossQuerySender>(d_cs, downstream, d_payloadSize);
-    return sender;
+    return s_sender;
   }
-
 private:
-  const ClientState& d_cs;
-  uint16_t d_payloadSize{0};
+  static std::shared_ptr<UDPTCPCrossQuerySender> s_sender;
 };
 
-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)
+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>& 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& 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;
-  ComboAddress proxiedRemote = remote;
-  ComboAddress proxiedDestination = dest;
+  InternalQueryState ids;
+  ids.cs = &cs;
+  ids.origRemote = remote;
+  ids.hopRemote = remote;
+  ids.protocol = dnsdist::Protocol::DoUDP;
 
   try {
     bool expectProxyProtocol = false;
@@ -1428,21 +1768,28 @@ static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct
       return;
     }
     /* dest might have been updated, if we managed to harvest the destination address */
-    proxiedDestination = dest;
+    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, proxiedRemote, proxiedDestination, proxyProtocolValues)) {
+    if (expectProxyProtocol && !handleProxyProtocol(remote, false, *holders.acl, query, ids.origRemote, ids.origDest, proxyProtocolValues)) {
       return;
     }
 
-    /* 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 queryRealTime;
-    gettime(&queryRealTime, true);
+    ids.queryRealTime.start();
 
-    std::unique_ptr<DNSCryptQuery> dnsCryptQuery = nullptr;
-    auto dnsCryptResponse = checkDNSCryptQuery(cs, query, dnsCryptQuery, queryRealTime.tv_sec, false);
+    auto dnsCryptResponse = checkDNSCryptQuery(cs, query, ids.dnsCryptQuery, ids.queryRealTime.d_start.tv_sec, false);
     if (dnsCryptResponse) {
       sendUDPResponse(cs.udpFD, query, 0, dest, remote);
       return;
@@ -1450,52 +1797,61 @@ static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct
 
     {
       /* 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);
+      const dnsheader_aligned dnsHeader(query.data());
+      queryId = ntohs(dnsHeader->id);
 
-      if (!checkQueryHeaders(dh)) {
+      if (!checkQueryHeaders(*dnsHeader, cs)) {
         return;
       }
 
-      if (dh->qdcount == 0) {
-        dh->rcode = RCode::NotImp;
-        dh->qr = true;
+      if (dnsHeader->qdcount == 0) {
+        dnsdist::PacketMangling::editDNSHeaderFromPacket(query, [](dnsheader& header) {
+          header.rcode = RCode::NotImp;
+          header.qr = true;
+          return true;
+        });
+
         sendUDPResponse(cs.udpFD, query, 0, dest, remote);
         return;
       }
     }
 
-    uint16_t qtype, qclass;
-    unsigned int qnameWireLength = 0;
-    DNSName qname(reinterpret_cast<const char*>(query.data()), query.size(), sizeof(dnsheader), false, &qtype, &qclass, &qnameWireLength);
-    DNSQuestion dq(&qname, qtype, qclass, proxiedDestination.sin4.sin_family != 0 ? &proxiedDestination : &cs.local, &proxiedRemote, query, dnsCryptQuery ? dnsdist::Protocol::DNSCryptUDP : dnsdist::Protocol::DoUDP, &queryRealTime);
-    dq.dnsCryptQuery = std::move(dnsCryptQuery);
+    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().get());
+    ids.origFlags = *flags;
+
     if (!proxyProtocolValues.empty()) {
       dq.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(std::move(proxyProtocolValues));
     }
-    dq.hopRemote = &remote;
-    dq.hopLocal = &dest;
+
     std::shared_ptr<DownstreamState> ss{nullptr};
-    auto result = processQuery(dq, cs, holders, ss);
+    auto result = processQuery(dq, holders, ss);
 
-    if (result == ProcessQueryResult::Drop) {
+    if (result == ProcessQueryResult::Drop || result == ProcessQueryResult::Asynchronous) {
       return;
     }
 
     // the buffer might have been invalidated by now (resized)
-    struct dnsheader* dh = dq.getHeader();
+    const auto dh = dq.getHeader();
     if (result == ProcessQueryResult::SendAnswer) {
 #ifndef DISABLE_RECVMMSG
 #if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
-      if (dq.delayMsec == 0 && responsesVect != nullptr) {
+      if (dq.ids.delayMsec == 0 && responsesVect != nullptr) {
         queueResponse(cs, query, dest, remote, responsesVect[*queuedResponses], respIOV, respCBuf);
         (*queuedResponses)++;
+        handleResponseSent(dq.ids.qname, dq.ids.qtype, 0., remote, ComboAddress(), query.size(), *dh, 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(cs.udpFD, query, dq.delayMsec, dest, remote);
+      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;
     }
 
@@ -1511,17 +1867,7 @@ static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct
         proxyProtocolPayload = getProxyProtocolPayload(dq);
       }
 
-      IDState ids;
-      ids.cs = &cs;
-      ids.origFD = cs.udpFD;
       ids.origID = dh->id;
-      setIDStateFromDNSQuestion(ids, dq, std::move(qname));
-      if (dest.sin4.sin_family != 0) {
-        ids.origDest = dest;
-      }
-      else {
-        ids.origDest = cs.local;
-      }
       auto cpq = std::make_unique<UDPCrossProtocolQuery>(std::move(query), std::move(ids), ss);
       cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
 
@@ -1529,45 +1875,142 @@ static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct
       return;
     }
 
-    unsigned int idOffset = 0;
-    int64_t generation;
-    IDState* ids = ss->getIDState(idOffset, generation);
+    assignOutgoingUDPQueryToBackend(ss, dh->id, dq, 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());
+  }
+}
 
-    ids->cs = &cs;
-    ids->origFD = cs.udpFD;
-    ids->origID = dh->id;
-    setIDStateFromDNSQuestion(*ids, dq, std::move(qname));
+#ifdef HAVE_XSK
+namespace dnsdist::xsk
+{
+bool XskProcessQuery(ClientState& cs, LocalHolders& holders, XskPacket& packet)
+{
+  uint16_t queryId = 0;
+  const auto& remote = packet.getFromAddr();
+  const auto& dest = packet.getToAddr();
+  InternalQueryState ids;
+  ids.cs = &cs;
+  ids.origRemote = remote;
+  ids.hopRemote = remote;
+  ids.origDest = dest;
+  ids.hopLocal = dest;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  ids.xskPacketHeader = packet.cloneHeaderToPacketBuffer();
 
-    if (dest.sin4.sin_family != 0) {
-      ids->origDest = dest;
+  try {
+    bool expectProxyProtocol = false;
+    if (!XskIsQueryAcceptable(packet, cs, holders, expectProxyProtocol)) {
+      return false;
     }
-    else {
-      ids->origDest = cs.local;
+
+    auto query = packet.clonePacketBuffer();
+    std::vector<ProxyProtocolValue> proxyProtocolValues;
+    if (expectProxyProtocol && !handleProxyProtocol(remote, false, *holders.acl, query, ids.origRemote, ids.origDest, proxyProtocolValues)) {
+      return false;
     }
 
-    dh = dq.getHeader();
-    dh->id = idOffset;
+    ids.queryRealTime.start();
 
-    if (ss->d_config.useProxyProtocol) {
-      addProxyProtocol(dq);
+    auto dnsCryptResponse = checkDNSCryptQuery(cs, 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.get(), cs)) {
+        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;
+      }
+    }
+
+    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 = cs.local;
+    }
+    if (ids.dnsCryptQuery) {
+      ids.protocol = dnsdist::Protocol::DNSCryptUDP;
     }
+    DNSQuestion dq(ids, query);
+    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);
 
-    int fd = ss->pickSocketForSending();
-    ids->backendFD = fd;
-    ssize_t ret = udpClientSendRequestToBackend(ss, fd, query);
+    if (result == ProcessQueryResult::Drop) {
+      return false;
+    }
 
-    if(ret < 0) {
-      ++ss->sendErrors;
-      ++g_stats.downstreamSendErrors;
+    if (result == ProcessQueryResult::SendAnswer) {
+      packet.setPayload(query);
+      if (dq.ids.delayMsec > 0) {
+        packet.addDelay(dq.ids.delayMsec);
+      }
+      const auto dh = dq.getHeader();
+      handleResponseSent(ids.qname, ids.qtype, 0., remote, ComboAddress(), query.size(), *dh, dnsdist::Protocol::DoUDP, dnsdist::Protocol::DoUDP, false);
+      return true;
     }
 
-    vinfolog("Got query for %s|%s from %s, relayed to %s", ids->qname.toLogString(), QType(ids->qtype).toString(), proxiedRemote.toStringWithPort(), ss->getName());
+    if (result != ProcessQueryResult::PassToBackend || ss == nullptr) {
+      return false;
+    }
+
+    // the buffer might have been invalidated by now (resized)
+    const auto dh = dq.getHeader();
+    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 false;
+    }
+
+    if (ss->d_xskInfos.empty()) {
+      assignOutgoingUDPQueryToBackend(ss, dh->id, dq, query, true);
+      return false;
+    }
+    else {
+      assignOutgoingUDPQueryToBackend(ss, dh->id, dq, query, false);
+      auto sourceAddr = ss->pickSourceAddressForSending();
+      packet.setAddr(sourceAddr, ss->d_config.sourceMACAddr, ss->d_config.remote, ss->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", proxiedRemote.toStringWithPort(), queryId, e.what());
+  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* cs, LocalHolders& holders)
@@ -1583,6 +2026,10 @@ static void MultipleMessagesUDPClientThread(ClientState* cs, LocalHolders& holde
   };
   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);
@@ -1592,7 +2039,7 @@ static void MultipleMessagesUDPClientThread(ClientState* cs, LocalHolders& holde
      - 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 initialBufferSize = getInitialUDPPacketBufferSize(cs->d_enableProxyProtocol);
   const size_t maxIncomingPacketSize = getMaximumIncomingPacketSize(*cs);
 
   /* initialize the structures needed to receive our messages */
@@ -1631,7 +2078,8 @@ static void MultipleMessagesUDPClientThread(ClientState* cs, LocalHolders& holde
       const ComboAddress& remote = recvData[msgIdx].remote;
 
       if (static_cast<size_t>(got) < sizeof(struct dnsheader)) {
-        ++g_stats.nonCompliantQueries;
+        ++dnsdist::metrics::g_stats.nonCompliantQueries;
+        ++cs->nonCompliantQueries;
         continue;
       }
 
@@ -1656,7 +2104,7 @@ static void MultipleMessagesUDPClientThread(ClientState* cs, LocalHolders& holde
 #endif /* DISABLE_RECVMMSG */
 
 // listens to incoming queries, sends out to downstream servers, noting the intended return path
-static void udpClientThread(ClientState* cs)
+static void udpClientThread(std::vector<ClientState*> states)
 {
   try {
     setThreadName("dnsdist/udpClie");
@@ -1664,7 +2112,7 @@ static void udpClientThread(ClientState* cs)
 #ifndef DISABLE_RECVMMSG
 #if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
     if (g_udpVectorSize > 1) {
-      MultipleMessagesUDPClientThread(cs, holders);
+      MultipleMessagesUDPClientThread(states.at(0), holders);
     }
     else
 #endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
@@ -1675,48 +2123,100 @@ static void udpClientThread(ClientState* cs)
          - 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);
+      struct UDPStateParam
+      {
+        ClientState* cs{nullptr};
+        size_t maxIncomingPacketSize{0};
+        int socket{-1};
+      };
+      const size_t initialBufferSize = getInitialUDPPacketBufferSize(true);
       PacketBuffer packet(initialBufferSize);
 
       struct msghdr msgh;
       struct iovec iov;
-      /* used by HarvestDestinationAddress */
-      cmsgbuf_aligned cbuf;
-
       ComboAddress remote;
       ComboAddress dest;
-      remote.sin4.sin_family = cs->local.sin4.sin_family;
-      fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), reinterpret_cast<char*>(&packet.at(0)), maxIncomingPacketSize, &remote);
 
-      for(;;) {
+      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(cs->udpFD, &msgh, 0);
+        ssize_t got = recvmsg(param.socket, &msgh, 0);
 
         if (got < 0 || static_cast<size_t>(got) < sizeof(struct dnsheader)) {
-          ++g_stats.nonCompliantQueries;
-          continue;
+          ++dnsdist::metrics::g_stats.nonCompliantQueries;
+          ++param.cs->nonCompliantQueries;
+          return;
         }
 
         packet.resize(static_cast<size_t>(got));
 
-        processUDPQuery(*cs, holders, &msgh, remote, dest, packet, nullptr, nullptr, nullptr, nullptr);
+        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;
+        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)
-  {
+  catch (const std::exception &e) {
     errlog("UDP client thread died because of exception: %s", e.what());
   }
-  catch(const PDNSException &e)
-  {
+  catch (const PDNSException &e) {
     errlog("UDP client thread died because of PowerDNS exception: %s", e.reason);
   }
-  catch(...)
-  {
+  catch (...) {
     errlog("UDP client thread died because of an exception: %s", "unknown");
   }
 }
@@ -1728,28 +2228,29 @@ pdns::stat16_t g_cacheCleaningPercentage{100};
 static void maintThread()
 {
   setThreadName("dnsdist/main");
-  int interval = 1;
+  constexpr int interval = 1;
   size_t counter = 0;
   int32_t secondsToWaitLog = 0;
 
   for (;;) {
-    sleep(interval);
+    std::this_thread::sleep_for(std::chrono::seconds(interval));
 
     {
       auto lua = g_lua.lock();
-      auto f = lua->readVariable<boost::optional<std::function<void()> > >("maintenance");
-      if (f) {
-        try {
-          (*f)();
-          secondsToWaitLog = 0;
+      try {
+        auto maintenanceCallback = lua->readVariable<boost::optional<std::function<void()> > >("maintenance");
+        if (maintenanceCallback) {
+          (*maintenanceCallback)();
         }
-        catch(const std::exception &e) {
-          if (secondsToWaitLog <= 0) {
-            infolog("Error during execution of maintenance function: %s", e.what());
-            secondsToWaitLog = 61;
-          }
-          secondsToWaitLog -= interval;
+        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;
       }
     }
 
@@ -1763,7 +2264,7 @@ static void maintThread()
          if something prevents us from cleaning the expired entries */
       auto localPools = g_pools.getLocal();
       for (const auto& entry : *localPools) {
-        auto& pool = entry.second;
+        const auto& pool = entry.second;
 
         auto packetCache = pool->packetCache;
         if (!packetCache) {
@@ -1775,7 +2276,7 @@ static void maintThread()
         /* 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) {
+        if (packetCache->keepStaleData() && !iter->second) {
           /* so far all pools had at least one backend up */
           if (pool->countServers(true) == 0) {
             iter->second = true;
@@ -1784,12 +2285,12 @@ static void maintThread()
       }
 
       const time_t now = time(nullptr);
-      for (auto pair : caches) {
+      for (const auto& pair : caches) {
         /* shall we keep expired entries ? */
-        if (pair.second == true) {
+        if (pair.second) {
           continue;
         }
-        auto& packetCache = pair.first;
+        const auto& packetCache = pair.first;
         size_t upTo = (packetCache->getMaxEntries()* (100 - g_cacheCleaningPercentage)) / 100;
         packetCache->purgeExpired(upTo, now);
       }
@@ -1798,12 +2299,14 @@ static void maintThread()
   }
 }
 
+#ifndef DISABLE_DYNBLOCKS
 static void dynBlockMaintenanceThread()
 {
   setThreadName("dnsdist/dynBloc");
 
   DynBlockMaintenance::run();
 }
+#endif
 
 #ifndef DISABLE_SECPOLL
 static void secPollThread()
@@ -1816,6 +2319,7 @@ static void secPollThread()
     }
     catch(...) {
     }
+    // coverity[store_truncates_time_t]
     sleep(g_secPollInterval);
   }
 }
@@ -1825,37 +2329,47 @@ static void healthChecksThread()
 {
   setThreadName("dnsdist/healthC");
 
-  static const int interval = 1;
+  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(;;) {
-    sleep(interval);
+  for (;;) {
+    struct timeval now;
+    gettimeofday(&now, nullptr);
+    auto elapsedTimeUsec = uSec(now - lastRound);
+    if (elapsedTimeUsec < intervalUsec) {
+      usleep(intervalUsec - elapsedTimeUsec);
+      gettimeofday(&lastRound, nullptr);
+    }
+    else {
+      lastRound = now;
+    }
 
-    auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent());
-    auto states = g_dstates.getLocal(); // this points to the actual shared_ptrs!
+    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->updateStatisticsInfo();
 
-      dss->handleTimeouts();
+      dss->handleUDPTimeouts();
 
-      if (dss->d_nextCheck > 1) {
-        --dss->d_nextCheck;
+      if (!dss->healthCheckRequired()) {
         continue;
       }
 
-      dss->d_nextCheck = dss->d_config.checkInterval;
+      if (!mplexer) {
+        mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(states->size()));
+      }
 
-      if (dss->d_config.availability == DownstreamState::Availability::Auto) {
-        if (!queueHealthCheck(mplexer, dss)) {
-          updateHealthCheckResult(dss, false, false);
-        }
+      if (!queueHealthCheck(mplexer, dss)) {
+        dss->submitHealthCheckResult(false, false);
       }
     }
 
-    handleQueuedHealthChecks(*mplexer);
+    if (mplexer) {
+      handleQueuedHealthChecks(*mplexer);
+    }
   }
 }
 
@@ -1886,9 +2400,9 @@ static void bindAny(int af, int sock)
 
 static void dropGroupPrivs(gid_t gid)
 {
-  if (gid) {
+  if (gid != 0) {
     if (setgid(gid) == 0) {
-      if (setgroups(0, NULL) < 0) {
+      if (setgroups(0, nullptr) < 0) {
         warnlog("Warning: Unable to drop supplementary gids: %s", stringerror());
       }
     }
@@ -1900,8 +2414,8 @@ static void dropGroupPrivs(gid_t gid)
 
 static void dropUserPrivs(uid_t uid)
 {
-  if(uid) {
-    if(setuid(uid) < 0) {
+  if (uid != 0) {
+    if (setuid(uid) < 0) {
       warnlog("Warning: Unable to set user ID to %d: %s", uid, stringerror());
     }
   }
@@ -1910,7 +2424,7 @@ static void dropUserPrivs(uid_t uid)
 static void checkFileDescriptorsLimits(size_t udpBindsCount, size_t tcpBindsCount)
 {
   /* stdin, stdout, stderr */
-  size_t requiredFDsCount = 3;
+  rlim_t requiredFDsCount = 3;
   auto backends = g_dstates.getLocal();
   /* UDP sockets to backends */
   size_t backendUDPSocketsCount = 0;
@@ -1959,142 +2473,205 @@ static void checkFileDescriptorsLimits(size_t udpBindsCount, size_t tcpBindsCoun
 
 static bool g_warned_ipv6_recvpktinfo = false;
 
-static void setUpLocalBind(std::unique_ptr<ClientState>& cs)
+static void setupLocalSocket(ClientState& clientState, const ComboAddress& addr, int& socket, bool tcp, bool warn)
 {
-  /* skip some warnings if there is an identical UDP context */
-  bool warn = cs->tcp == false || cs->tlsFrontend != nullptr || cs->dohFrontend != nullptr;
-  int& fd = cs->tcp == false ? cs->udpFD : cs->tcpFD;
   (void) warn;
+  socket = SSocket(addr.sin4.sin_family, !tcp ? SOCK_DGRAM : SOCK_STREAM, 0);
 
-  fd = SSocket(cs->local.sin4.sin_family, cs->tcp == false ? SOCK_DGRAM : SOCK_STREAM, 0);
-
-  if (cs->tcp) {
-    SSetsockopt(fd, SOL_SOCKET, SO_REUSEADDR, 1);
+  if (tcp) {
+    SSetsockopt(socket, SOL_SOCKET, SO_REUSEADDR, 1);
 #ifdef TCP_DEFER_ACCEPT
-    SSetsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, 1);
+    SSetsockopt(socket, IPPROTO_TCP, TCP_DEFER_ACCEPT, 1);
 #endif
-    if (cs->fastOpenQueueSize > 0) {
+    if (clientState.fastOpenQueueSize > 0) {
 #ifdef TCP_FASTOPEN
-      SSetsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, cs->fastOpenQueueSize);
-#else
+      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", cs->local.toStringWithPort());
+        warnlog("TCP Fast Open has been configured on local address '%s' but is not supported", addr.toStringWithPort());
       }
-#endif
+#endif /* TCP_FASTOPEN */
     }
   }
 
-  if(cs->local.sin4.sin_family == AF_INET6) {
-    SSetsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, 1);
+  if (addr.sin4.sin_family == AF_INET6) {
+    SSetsockopt(socket, IPPROTO_IPV6, IPV6_V6ONLY, 1);
   }
 
-  bindAny(cs->local.sin4.sin_family, fd);
+  bindAny(addr.sin4.sin_family, socket);
 
-  if(!cs->tcp && IsAnyAddress(cs->local)) {
-    int one=1;
-    (void)setsockopt(fd, IPPROTO_IP, GEN_IP_PKTINFO, &one, sizeof(one)); // linux supports this, so why not - might fail on other systems
+  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 (cs->local.isIPv6() && setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)) < 0 &&
+    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;
+      warnlog("Warning: IPV6_RECVPKTINFO setsockopt failed: %s", stringerror());
+      g_warned_ipv6_recvpktinfo = true;
     }
 #endif
   }
 
-  if (cs->reuseport) {
-    if (!setReusePort(fd)) {
+  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", cs->local.toStringWithPort());
+        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 (!cs->tcp && !cs->dnscryptCtx) {
+  const bool isQUIC = clientState.doqFrontend != nullptr || clientState.doh3Frontend != nullptr;
+  if (isQUIC) {
+    /* disable fragmentation and force PMTU discovery for QUIC-enabled sockets */
     try {
-      setSocketIgnorePMTU(cs->udpFD, cs->local.sin4.sin_family);
+      setSocketForcePMTU(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", cs->local.toStringWithPort(), e.what());
+    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 (!cs->tcp) {
+  if (!tcp) {
     if (g_socketUDPSendBuffer > 0) {
       try {
-        setSocketSendBuffer(cs->udpFD, g_socketUDPSendBuffer);
+        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(cs->udpFD, g_socketUDPRecvBuffer);
+        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 = cs->interface;
+  const std::string& itf = clientState.interface;
   if (!itf.empty()) {
 #ifdef SO_BINDTODEVICE
-    int res = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, itf.c_str(), itf.length());
+    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", cs->local.toStringWithPort(), stringerror());
+      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", cs->local.toStringWithPort());
+      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);
-    vinfolog("Attaching default BPF Filter to %s frontend %s", (!cs->tcp ? "UDP" : "TCP"), cs->local.toStringWithPort());
+    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 */
 
-  if (cs->tlsFrontend != nullptr) {
-    if (!cs->tlsFrontend->setupTLS()) {
-      errlog("Error while setting up TLS on local address '%s', exiting", cs->local.toStringWithPort());
-      _exit(EXIT_FAILURE);
-    }
-  }
-
-  if (cs->dohFrontend != nullptr) {
-    cs->dohFrontend->setup();
-  }
+  SBind(socket, addr);
 
-  SBind(fd, cs->local);
+  if (tcp) {
+    SListen(socket, clientState.tcpListenQueueSize);
 
-  if (cs->tcp) {
-    SListen(cs->tcpFD, cs->tcpListenQueueSize);
-
-    if (cs->tlsFrontend != nullptr) {
-      warnlog("Listening on %s for TLS", cs->local.toStringWithPort());
+    if (clientState.tlsFrontend != nullptr) {
+      infolog("Listening on %s for TLS", addr.toStringWithPort());
     }
-    else if (cs->dohFrontend != nullptr) {
-      warnlog("Listening on %s for DoH", cs->local.toStringWithPort());
+    else if (clientState.dohFrontend != nullptr) {
+      infolog("Listening on %s for DoH", addr.toStringWithPort());
     }
-    else if (cs->dnscryptCtx != nullptr) {
-      warnlog("Listening on %s for DNSCrypt", cs->local.toStringWithPort());
+    else if (clientState.dnscryptCtx != nullptr) {
+      infolog("Listening on %s for DNSCrypt", addr.toStringWithPort());
     }
     else {
-      warnlog("Listening on %s", cs->local.toStringWithPort());
+      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();
   }
 
-  cs->ready = true;
+  cstate->ready = true;
 }
 
 struct
@@ -2124,7 +2701,7 @@ static void usage()
   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
+#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";
@@ -2150,231 +2727,512 @@ static void usage()
 #ifdef COVERAGE
 static void cleanupLuaObjects()
 {
-  /* when our coverage mode is enabled, we need to make
-     that the Lua objects destroyed before the Lua contexts. */
+  /* when our coverage mode is enabled, we need to make sure
+     that the Lua objects are destroyed before the Lua contexts. */
   g_ruleactions.setState({});
   g_respruleactions.setState({});
   g_cachehitrespruleactions.setState({});
   g_selfansweredrespruleactions.setState({});
   g_dstates.setState({});
   g_policy.setState(ServerPolicy());
+  g_pools.setState({});
   clearWebHandlers();
+  dnsdist::lua::hooks::clearMaintenanceHooks();
 }
 
-static void sighandler(int sig)
+static void sigTermHandler(int)
 {
   cleanupLuaObjects();
-  exit(EXIT_SUCCESS);
+  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
-
-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);
-#ifdef COVERAGE
-    signal(SIGTERM, sighandler);
 #endif
 
-    openlog("dnsdist", LOG_PID|LOG_NDELAY, LOG_DAEMON);
+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 (dnsdist::logging::LoggingConfiguration::getSyslog()) {
+    syslog(LOG_INFO, "Exiting on user request");
+  }
+  std::cout<<"Exiting on user request"<<std::endl;
+#endif /* __SANITIZE_THREAD__ */
 
-#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);
+  _exit(EXIT_SUCCESS);
+}
+#endif /* COVERAGE */
 
-    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':
+static void reportFeatures()
+{
 #ifdef LUAJIT_VERSION
-        cout<<"dnsdist "<<VERSION<<" ("<<LUA_RELEASE<<" ["<<LUAJIT_VERSION<<"])"<<endl;
+  cout<<"dnsdist "<<VERSION<<" ("<<LUA_RELEASE<<" ["<<LUAJIT_VERSION<<"])"<<endl;
 #else
-        cout<<"dnsdist "<<VERSION<<" ("<<LUA_RELEASE<<")"<<endl;
+  cout<<"dnsdist "<<VERSION<<" ("<<LUA_RELEASE<<")"<<endl;
+#endif
+  cout<<"Enabled features: ";
+#ifdef HAVE_XSK
+  cout<<"AF_XDP ";
 #endif
-        cout<<"Enabled features: ";
 #ifdef HAVE_CDB
-        cout<<"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(";
+  cout<<"dns-over-tls(";
 #ifdef HAVE_GNUTLS
-        cout<<"gnutls";
+  cout<<"gnutls";
 #ifdef HAVE_LIBSSL
-        cout<<" ";
-#endif
+  cout<<" ";
 #endif
+#endif /* HAVE_GNUTLS */
 #ifdef HAVE_LIBSSL
-        cout<<"openssl";
-#endif
-        cout<<") ";
+  cout<<"openssl";
 #endif
+  cout<<") ";
+#endif /* HAVE_DNS_OVER_TLS */
 #ifdef HAVE_DNS_OVER_HTTPS
-        cout<<"dns-over-https(DOH) ";
-#endif
+  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 ";
+  cout<<"dnscrypt ";
 #endif
 #ifdef HAVE_EBPF
-        cout<<"ebpf ";
+  cout<<"ebpf ";
 #endif
 #ifdef HAVE_FSTRM
-        cout<<"fstrm ";
+  cout<<"fstrm ";
 #endif
 #ifdef HAVE_IPCIPHER
-        cout<<"ipcipher ";
+  cout<<"ipcipher ";
 #endif
 #ifdef HAVE_LIBEDIT
-        cout<<"libeditr ";
+  cout<<"libedit ";
 #endif
 #ifdef HAVE_LIBSODIUM
-        cout<<"libsodium ";
+  cout<<"libsodium ";
 #endif
 #ifdef HAVE_LMDB
-        cout<<"lmdb ";
-#endif
-#ifdef HAVE_NGHTTP2
-        cout<<"outgoing-dns-over-https(nghttp2) ";
+  cout<<"lmdb ";
 #endif
 #ifndef DISABLE_PROTOBUF
-        cout<<"protobuf ";
+  cout<<"protobuf ";
 #endif
 #ifdef HAVE_RE2
-        cout<<"re2 ";
+  cout<<"re2 ";
 #endif
 #ifndef DISABLE_RECVMMSG
 #if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
-        cout<<"recvmmsg/sendmmsg ";
+  cout<<"recvmmsg/sendmmsg ";
 #endif
 #endif /* DISABLE_RECVMMSG */
 #ifdef HAVE_NET_SNMP
-        cout<<"snmp ";
+  cout<<"snmp ";
 #endif
 #ifdef HAVE_SYSTEMD
-        cout<<"systemd";
+  cout<<"systemd";
 #endif
-        cout<<endl;
-        exit(EXIT_SUCCESS);
-        break;
-      case '?':
-        //getopt_long printed an error message.
-        usage();
+  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);
-        break;
+      }
+#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;
       }
     }
 
-    argc -= optind;
-    argv += optind;
-    (void) argc;
+    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 */
 
-    for(auto p = argv; *p; ++p) {
-      if(g_cmdLine.beClient) {
-        clientAddress = ComboAddress(*p, 5199);
-      } else {
-        g_cmdLine.remotes.push_back(*p);
+    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()) {
+    if (g_cmdLine.beClient || !g_cmdLine.command.empty()) {
       setupLua(*(g_lua.lock()), true, false, g_cmdLine.config);
-      if (clientAddress != ComboAddress())
+      if (clientAddress != ComboAddress()) {
         g_serverControl = clientAddress;
+      }
       doClient(g_serverControl, g_cmdLine.command);
 #ifdef COVERAGE
       exit(EXIT_SUCCESS);
@@ -2384,9 +3242,10 @@ int main(int argc, char** argv)
     }
 
     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"})
+    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);
     }
 
@@ -2409,69 +3268,24 @@ int main(int argc, char** argv)
 #endif
     }
 
-    auto todo = setupLua(*(g_lua.lock()), false, false, g_cmdLine.config);
+    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);
 
-    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();
-        }
-      }
-    }
+    dnsdist::g_asyncHolder = std::make_unique<dnsdist::AsynchronousHolder>();
 
-    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;
-        }
-      }
+    auto todo = setupLua(*(g_lua.lock()), false, false, g_cmdLine.config);
 
-      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, "", {})));
-      }
-    }
+    setupPools();
 
-    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, "", {})));
-    }
+    initFrontends();
 
     g_configurationDone = true;
 
     g_rings.init();
 
-    for(auto& frontend : g_frontends) {
+    for (auto& frontend : g_frontends) {
       setUpLocalBind(frontend);
 
-      if (frontend->tcp == false) {
+      if (!frontend->tcp) {
         ++udpBindsCount;
       }
       else {
@@ -2479,76 +3293,43 @@ int main(int argc, char** argv)
       }
     }
 
-    warnlog("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);
-
-    vector<string> vec;
-    std::string acls;
-    g_ACL.getLocal()->toStringVector(&vec);
-    for(const auto& s : vec) {
-      if (!acls.empty())
-        acls += ", ";
-      acls += s;
+    {
+      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);
     }
-    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 += ", ";
+    {
+      std::string acls;
+      auto aclEntries = g_consoleACL.getLocal()->toStringVector();
+      for (const auto& entry : aclEntries) {
+        if (!acls.empty()) {
+          acls += ", ";
+        }
+        acls += entry;
       }
-      acls += entry;
+      infolog("Console ACL allowing connections from: %s", acls.c_str());
     }
-    infolog("Console ACL allowing connections from: %s", acls.c_str());
 
-#ifdef HAVE_LIBSODIUM
+#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
 
-    uid_t newgid=getegid();
-    gid_t newuid=geteuid();
-
-    if(!g_cmdLine.gid.empty())
-      newgid = strToGID(g_cmdLine.gid.c_str());
-
-    if(!g_cmdLine.uid.empty())
-      newuid = strToUID(g_cmdLine.uid.c_str());
-
-    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);
-    }
-
-    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());
-    }
+    dropPrivileges();
 
     /* this need to be done _after_ dropping privileges */
-    g_delay = new DelayPipe<DelayedPacket>();
+#ifndef DISABLE_DELAY_PIPE
+    g_delay = std::make_unique<DelayPipe<DelayedPacket>>();
+#endif /* DISABLE_DELAY_PIPE */
 
-    if (g_snmpAgent) {
+    if (g_snmpAgent != nullptr) {
       g_snmpAgent->run();
     }
 
@@ -2563,18 +3344,22 @@ int main(int argc, char** argv)
     /* we need to create the TCP worker threads before the
        acceptor ones, otherwise we might crash when processing
        the first TCP query */
-    g_tcpclientthreads = std::make_unique<TCPClientCollection>(*g_maxTCPClientThreads);
+#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& t : todo) {
-      t();
+    for (auto& todoItem : todo) {
+      todoItem();
     }
 
-    localPools = g_pools.getCopy();
+    auto localPools = g_pools.getCopy();
     /* create the default pool no matter what */
     createPoolIfNotExists(localPools, "");
-    if (g_cmdLine.remotes.size()) {
+    if (!g_cmdLine.remotes.empty()) {
       for (const auto& address : g_cmdLine.remotes) {
         DownstreamState::Config config;
         config.remote = ComboAddress(address, 53);
@@ -2593,50 +3378,31 @@ int main(int argc, char** argv)
 
     checkFileDescriptorsLimits(udpBindsCount, tcpBindsCount);
 
-    auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent());
-    for (auto& dss : g_dstates.getCopy()) { // it is a copy, but the internal shared_ptrs are the real deal
-      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);
+    {
+      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) {
 
-    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) {
-        thread t1(udpClientThread, cs.get());
-        if (!cs->cpus.empty()) {
-          mapThreadToCPUList(t1.native_handle(), cs->cpus);
-        }
-        t1.detach();
-      }
-      else if (cs->tcpFD >= 0) {
-        thread t1(tcpAcceptorThread, cs.get());
-        if (!cs->cpus.empty()) {
-          mapThreadToCPUList(t1.native_handle(), cs->cpus);
+        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());
+          }
         }
-        t1.detach();
       }
+      handleQueuedHealthChecks(*mplexer, true);
     }
 
+    dnsdist::startFrontends();
+
     dnsdist::ServiceDiscovery::run();
 
 #ifndef DISABLE_CARBON
-    thread carbonthread(carbonDumpThread);
-    carbonthread.detach();
+    dnsdist::Carbon::run();
 #endif /* DISABLE_CARBON */
 
     thread stattid(maintThread);
@@ -2644,8 +3410,10 @@ int main(int argc, char** argv)
 
     thread healththread(healthChecksThread);
 
+#ifndef DISABLE_DYNBLOCKS
     thread dynBlockMaintThread(dynBlockMaintenanceThread);
     dynBlockMaintThread.detach();
+#endif /* DISABLE_DYNBLOCKS */
 
 #ifndef DISABLE_SECPOLL
     if (!g_secPollSuffix.empty()) {
@@ -2683,6 +3451,7 @@ int main(int argc, char** argv)
       errlog("Fatal pdns error: %s", ae.reason);
     }
 #ifdef COVERAGE
+    cleanupLuaObjects();
     exit(EXIT_FAILURE);
 #else
     _exit(EXIT_FAILURE);
@@ -2692,6 +3461,7 @@ int main(int argc, char** argv)
   {
     errlog("Fatal error: %s", e.what());
 #ifdef COVERAGE
+    cleanupLuaObjects();
     exit(EXIT_FAILURE);
 #else
     _exit(EXIT_FAILURE);
@@ -2701,6 +3471,7 @@ int main(int argc, char** argv)
   {
     errlog("Fatal pdns error: %s", ae.reason);
 #ifdef COVERAGE
+    cleanupLuaObjects();
     exit(EXIT_FAILURE);
 #else
     _exit(EXIT_FAILURE);
index e644fe36e719e9582dad20f96ae803bae51cada7..19eb3c715a464698f87cb24817c9bf8f66ddf4f9 100644 (file)
  * 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 <boost/variant.hpp>
 
-#include "capabilities.hh"
 #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 "dnsdist-doh-common.hh"
+#include "doq.hh"
+#include "doh3.hh"
 #include "ednsoptions.hh"
 #include "iputils.hh"
 #include "misc.hh"
@@ -60,16 +65,19 @@ extern bool g_ECSOverride;
 
 using QTag = std::unordered_map<string, string>;
 
+class IncomingTCPConnectionState;
+
+struct ClientState;
+
 struct DNSQuestion
 {
-  DNSQuestion(const DNSName* name, uint16_t type, uint16_t class_, const ComboAddress* lc, const ComboAddress* rem, PacketBuffer& data_, dnsdist::Protocol proto, const struct timespec* queryTime_):
-    data(data_), qname(name), local(lc), remote(rem), queryTime(queryTime_), tempFailureTTL(boost::none), qtype(type), qclass(class_), ecsPrefixLength(rem->sin4.sin_family == AF_INET ? g_ECSSourcePrefixV4 : g_ECSSourcePrefixV6), protocol(proto), ecsOverride(g_ECSOverride) {
-    const uint16_t* flags = getFlagsFromDNSHeader(getHeader());
-    origFlags = *flags;
+  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&);
@@ -82,20 +90,26 @@ struct DNSQuestion
     return data;
   }
 
-  dnsheader* getHeader()
+  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");
     }
-    return reinterpret_cast<dnsheader*>(&data.at(0));
+    dnsheader_aligned dh(data.data());
+    return dh;
   }
 
-  const dnsheader* getHeader() const
+  /* 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");
     }
-    return reinterpret_cast<const dnsheader*>(&data.at(0));
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    return reinterpret_cast<dnsheader*>(data.data());
   }
 
   bool hasRoomFor(size_t more) const
@@ -113,83 +127,84 @@ struct DNSQuestion
 
   dnsdist::Protocol getProtocol() const
   {
-    return protocol;
+    return ids.protocol;
   }
 
   bool overTCP() const
   {
-    return !(protocol == dnsdist::Protocol::DoUDP || protocol == dnsdist::Protocol::DNSCryptUDP);
+    return !(ids.protocol == dnsdist::Protocol::DoUDP || ids.protocol == dnsdist::Protocol::DNSCryptUDP);
   }
 
-  void setTag(const std::string& key, std::string&& value) {
-    if (!qTag) {
-      qTag = std::make_unique<QTag>();
+  void setTag(std::string&& key, std::string&& value) {
+    if (!ids.qTag) {
+      ids.qTag = std::make_unique<QTag>();
     }
-    qTag->insert_or_assign(key, std::move(value));
+    ids.qTag->insert_or_assign(std::move(key), std::move(value));
   }
 
   void setTag(const std::string& key, const std::string& value) {
-    if (!qTag) {
-      qTag = std::make_unique<QTag>();
+    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>();
     }
-    qTag->insert_or_assign(key, value);
+    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:
-  boost::optional<boost::uuids::uuid> uniqueId;
-  Netmask ecs;
-  boost::optional<Netmask> subnet;
+  InternalQueryState& ids;
+  std::unique_ptr<Netmask> ecs{nullptr};
   std::string sni; /* Server Name Indication, if any (DoT or DoH) */
-  std::string poolname;
-  mutable std::shared_ptr<std::map<uint16_t, EDNSOptionView> > ednsOptions;
-  std::shared_ptr<DNSDistPacketCache> packetCache{nullptr};
-  const DNSName* qname{nullptr};
-  const ComboAddress* local{nullptr};
-  const ComboAddress* remote{nullptr};
-  /* this is the address dnsdist received the packet on,
-     which might not match local when support for incoming proxy protocol
-     is enabled */
-  const ComboAddress* hopLocal{nullptr};  /* the address dnsdist received the packet from, see above */
-  const ComboAddress* hopRemote{nullptr};
-  std::unique_ptr<QTag> qTag{nullptr};
+  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};
-  std::unique_ptr<DNSCryptQuery> dnsCryptQuery{nullptr};
-  const struct timespec* queryTime{nullptr};
-  struct DOHUnit* du{nullptr};
-  int delayMsec{0};
-  boost::optional<uint32_t> tempFailureTTL;
-  uint32_t cacheKeyNoECS{0};
-  uint32_t cacheKey{0};
-  /* for DoH */
-  uint32_t cacheKeyUDP{0};
-  const uint16_t qtype;
-  const uint16_t qclass;
   uint16_t ecsPrefixLength;
-  uint16_t origFlags;
-  uint16_t cacheFlags{0}; /* DNS flags as sent to the backend */
-  const dnsdist::Protocol protocol;
   uint8_t ednsRCode{0};
-  bool skipCache{false};
   bool ecsOverride;
   bool useECS{true};
   bool addXPF{true};
-  bool ecsSet{false};
-  bool ecsAdded{false};
-  bool ednsAdded{false};
-  bool useZeroScope{false};
-  bool dnssecOK{false};
+  bool asynchronous{false};
 };
 
+struct DownstreamState;
+
 struct DNSResponse : DNSQuestion
 {
-  DNSResponse(const DNSName* name, uint16_t type, uint16_t class_, const ComboAddress* lc, const ComboAddress* rem, PacketBuffer& data_, dnsdist::Protocol proto, const struct timespec* queryTime_):
-    DNSQuestion(name, type, class_, lc, rem, data_, proto, queryTime_) { }
+  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:
@@ -259,7 +274,7 @@ public:
 class DNSResponseAction
 {
 public:
-  enum class Action : uint8_t { Allow, Delay, Drop, HeaderModify, ServFail, None };
+  enum class Action : uint8_t { Allow, Delay, Drop, HeaderModify, ServFail, Truncate, None };
   virtual Action operator()(DNSResponse*, string* ruleresult) const =0;
   virtual ~DNSResponseAction()
   {
@@ -331,119 +346,6 @@ 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};
-  typedef std::function<uint64_t(const std::string&)> statfunction_t;
-  typedef boost::variant<stat_t*, 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},
-    {"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},
-  };
-};
-
-extern struct DNSDistStats g_stats;
-void doLatencyStats(double udiff);
-
-#include "dnsdist-idstate.hh"
-
 class BasicQPSLimiter
 {
 public:
@@ -569,13 +471,18 @@ struct QueryCount {
 
 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_): cpus(cpus_), interface(itfName), local(local_), fastOpenQueueSize(fastOpenQueue), tcp(isTCP_), reuseport(doReusePort)
+  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};
@@ -601,10 +508,14 @@ struct ClientState
   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};
@@ -614,6 +525,7 @@ struct ClientState
   bool muted{false};
   bool tcp;
   bool reuseport;
+  bool d_enableProxyProtocol{true}; // the global proxy protocol ACL still applies
   bool ready{false};
 
   int getSocket() const
@@ -641,11 +553,56 @@ struct ClientState
     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 (dohFrontend) {
+    if (doqFrontend) {
+      result += " (DNS over QUIC)";
+    }
+    else if (doh3Frontend) {
+      result += " (DNS over HTTP/3)";
+    }
+    else if (dohFrontend) {
       if (dohFrontend->isHTTPS()) {
         result += " (DNS over HTTPS)";
       }
@@ -663,19 +620,48 @@ struct ClientState
     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) {
-      d_filter->removeSocket(getSocket());
+      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)
+  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;
   }
 
@@ -696,7 +682,8 @@ struct DownstreamState: public std::enable_shared_from_this<DownstreamState>
   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};
+  enum class Availability : uint8_t { Up, Down, Auto, Lazy };
+  enum class LazyHealthCheckMode : uint8_t { TimeoutOnly, TimeoutOrServFail };
 
   struct Config
   {
@@ -720,6 +707,10 @@ struct DownstreamState: public std::enable_shared_from_this<DownstreamState>
     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};
@@ -736,6 +727,12 @@ struct DownstreamState: public std::enable_shared_from_this<DownstreamState>
     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};
@@ -743,6 +740,7 @@ struct DownstreamState: public std::enable_shared_from_this<DownstreamState>
     bool mustResolve{false};
     bool useECS{false};
     bool useProxyProtocol{false};
+    bool d_proxyProtocolAdvertiseTLS{false};
     bool setCD{false};
     bool disableZeroScope{false};
     bool tcpFastOpen{false};
@@ -751,6 +749,18 @@ struct DownstreamState: public std::enable_shared_from_this<DownstreamState>
     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);
@@ -761,11 +771,13 @@ struct DownstreamState: public std::enable_shared_from_this<DownstreamState>
   ~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};
@@ -781,6 +793,8 @@ struct DownstreamState: public std::enable_shared_from_this<DownstreamState>
   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};
@@ -795,31 +809,61 @@ struct DownstreamState: public std::enable_shared_from_this<DownstreamState>
 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};
-  uint8_t currentCheckFailures{0};
-  uint8_t consecutiveSuccessfulChecks{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::thread tid;
   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
@@ -842,16 +886,24 @@ public:
     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;
   }
@@ -879,7 +931,8 @@ public:
     return status;
   }
 
-  bool reconnect();
+  bool reconnect(bool initialAttempt = false);
+  void waitUntilConnected();
   void hash();
   void setId(const boost::uuids::uuid& newId);
   void setWeight(int newWeight);
@@ -899,6 +952,11 @@ public:
     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;
@@ -925,10 +983,19 @@ public:
   bool passCrossProtocolQuery(std::unique_ptr<CrossProtocolQuery>&& cpq);
   int pickSocketForSending();
   void pickSocketsReadyForReceiving(std::vector<int>& ready);
-  void handleTimeouts();
-  IDState* getIDState(unsigned int& id, int64_t& generation);
-  IDState* getExistingState(unsigned int id);
-  void releaseState(unsigned int id);
+  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
   {
@@ -944,11 +1011,17 @@ public:
     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 handleTimeout(IDState& ids);
 };
 using servers_t = vector<std::shared_ptr<DownstreamState>>;
 
@@ -962,14 +1035,14 @@ public:
   virtual ~DNSRule ()
   {
   }
-  virtual bool matches(const DNSQuestion* dq) const =0;
+  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<ServerPolicy::NumberedServerVector>())
+  ServerPool(): d_servers(std::make_shared<const ServerPolicy::NumberedServerVector>())
   {
   }
 
@@ -994,12 +1067,12 @@ struct ServerPool
 
   size_t poolLoad();
   size_t countServers(bool upOnly);
-  const std::shared_ptr<ServerPolicy::NumberedServerVector> getServers();
+  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<ServerPolicy::NumberedServerVector>> d_servers;
+  SharedLockGuarded<std::shared_ptr<const ServerPolicy::NumberedServerVector>> d_servers;
   bool d_useECS{false};
 };
 
@@ -1036,12 +1109,15 @@ 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<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;
@@ -1053,7 +1129,6 @@ 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_maxTCPConnectionsPerClient;
 extern size_t g_tcpInternalPipeBufferSize;
 extern pdns::stat16_t g_cacheCleaningDelay;
 extern pdns::stat16_t g_cacheCleaningPercentage;
@@ -1071,7 +1146,7 @@ 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()), selfAnsweredRespRuleactions(g_selfansweredrespruleactions.getLocal()), servers(g_dstates.getLocal()), dynNMGBlock(g_dynblockNMG.getLocal()), dynSMTBlock(g_dynblockSMT.getLocal()), pools(g_pools.getLocal())
+  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())
   {
   }
 
@@ -1079,6 +1154,7 @@ struct LocalHolders
   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;
@@ -1086,24 +1162,16 @@ struct LocalHolders
   LocalStateHolder<pools_t> pools;
 };
 
-vector<std::function<void(void)>> setupLua(bool client, const std::string& config);
-
-void tcpAcceptorThread(ClientState* p);
-
-#ifdef HAVE_DNS_OVER_HTTPS
-void dohThread(ClientState* cs);
-#endif /* HAVE_DNS_OVER_HTTPS */
+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 ComboAddress& remote, unsigned int& qnameWireLength);
-bool processResponse(PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted, bool receivedOverUDP);
-bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::string& ruleresult, bool& drop);
+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* dh);
+bool checkQueryHeaders(const struct dnsheader& dnsHeader, ClientState& clientState);
 
 extern std::vector<std::shared_ptr<DNSCryptContext>> g_dnsCryptLocals;
 int handleDNSCryptQuery(PacketBuffer& packet, DNSCryptQuery& query, bool tcp, time_t now, PacketBuffer& response);
@@ -1118,14 +1186,18 @@ 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 };
-ProcessQueryResult processQuery(DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend);
+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 processResponderPacket(std::shared_ptr<DownstreamState>& dss, PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& localRespRuleActions, const std::vector<DNSDistResponseRuleAction>& cacheInsertedRespRuleActions, InternalQueryState&& ids);
 
-DNSResponse makeDNSResponseFromIDState(IDState& ids, PacketBuffer& data);
-void setIDStateFromDNSQuestion(IDState& ids, DNSQuestion& dq, DNSName&& qname);
+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>& ss, const int sd, const PacketBuffer& request, bool healthCheck = false);
-void handleResponseSent(const IDState& ids, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol protocol);
-
+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 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 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 2a15da9086e34b04480d55d577bb89969e37ce5e..68ab167dcfbd9ba951713ec59e982191c8eb3c43 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 = \
@@ -21,6 +22,8 @@ CLEANFILES = \
        htmlfiles.h \
        dnsdist-lua-ffi-interface.inc
 
+sysconf_DATA = dnsdist.conf-dist
+
 dnslabeltext.cc: dnslabeltext.rl
        $(AM_V_GEN)$(RAGEL) $< -o dnslabeltext.cc
 
@@ -32,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))
@@ -77,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
@@ -84,7 +91,7 @@ endif
 
 EXTRA_DIST=COPYING \
           dnslabeltext.rl \
-          dnsdistconf.lua \
+          dnsdist.conf-dist \
           dnsmessage.proto \
           dnstap.proto \
           README.md \
@@ -103,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
@@ -111,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:
@@ -123,51 +131,71 @@ dnsdist-web.$(OBJEXT): htmlfiles.h
 dnsdist-lua-ffi.$(OBJEXT): dnsdist-lua-ffi-interface.inc
 
 dnsdist_SOURCES = \
-       ascii.hh \
        base64.hh \
        bpf-filter.cc bpf-filter.hh \
+       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.cc dnsdist-idstate.hh \
+       dnsdist-idstate.hh \
+       dnsdist-internal-queries.cc dnsdist-internal-queries.hh \
        dnsdist-kvs.hh dnsdist-kvs.cc \
        dnsdist-lbpolicies.cc dnsdist-lbpolicies.hh \
        dnsdist-lua-actions.cc \
        dnsdist-lua-bindings-dnscrypt.cc \
+       dnsdist-lua-bindings-dnsparser.cc \
        dnsdist-lua-bindings-dnsquestion.cc \
        dnsdist-lua-bindings-kvs.cc \
+       dnsdist-lua-bindings-network.cc \
        dnsdist-lua-bindings-packetcache.cc \
        dnsdist-lua-bindings-protobuf.cc \
+       dnsdist-lua-bindings-rings.cc \
        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 \
        dnsdist-lua-vars.cc \
        dnsdist-lua-web.cc \
        dnsdist-lua.cc dnsdist-lua.hh \
-       dnsdist-nghttp2.cc dnsdist-nghttp2.hh \
+       dnsdist-mac-address.cc dnsdist-mac-address.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-rules.cc dnsdist-rules.hh \
        dnsdist-secpoll.cc dnsdist-secpoll.hh \
@@ -180,15 +208,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 \
@@ -201,6 +234,7 @@ dnsdist_SOURCES = \
        iputils.cc iputils.hh \
        libssl.cc libssl.hh \
        lock.hh \
+       logging.hh \
        misc.cc misc.hh \
        mplexer.hh \
        namespaces.hh \
@@ -214,7 +248,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 \
@@ -223,23 +256,33 @@ dnsdist_SOURCES = \
        tcpiohandler.cc tcpiohandler.hh \
        threadname.hh threadname.cc \
        uuid-utils.hh uuid-utils.cc \
-       xpf.cc xpf.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 \
        dns.cc dns.hh \
        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-idstate.cc dnsdist-idstate.hh \
+       dnsdist-edns.cc dnsdist-edns.hh \
+       dnsdist-idstate.hh \
        dnsdist-kvs.cc dnsdist-kvs.hh \
        dnsdist-lbpolicies.cc dnsdist-lbpolicies.hh \
        dnsdist-lua-bindings-dnsquestion.cc \
@@ -247,11 +290,16 @@ testrunner_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-network.cc dnsdist-lua-network.hh \
        dnsdist-lua-vars.cc \
-       dnsdist-nghttp2.cc dnsdist-nghttp2.hh \
+       dnsdist-mac-address.cc dnsdist-mac-address.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-rules.cc dnsdist-rules.hh \
        dnsdist-session-cache.cc dnsdist-session-cache.hh \
@@ -259,13 +307,15 @@ testrunner_SOURCES = \
        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 \
@@ -279,22 +329,29 @@ 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 \
        test-dnscrypt_cc.cc \
        test-dnsdist-connections-cache.cc \
+       test-dnsdist-dnsparser.cc \
+       test-dnsdist-lua-ffi.cc \
        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-dnsdistnghttp2_cc.cc \
+       test-dnsdistluanetwork.cc \
+       test-dnsdistnghttp2_common.hh \
        test-dnsdistpacketcache_cc.cc \
        test-dnsdistrings_cc.cc \
        test-dnsdistrules_cc.cc \
@@ -308,12 +365,13 @@ 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) \
        $(PROGRAM_LDFLAGS) \
-       -pthread 
+       -pthread
 
 dnsdist_LDADD = \
        $(LUA_LIBS) \
@@ -325,7 +383,8 @@ dnsdist_LDADD = \
        $(SYSTEMD_LIBS) \
        $(NET_SNMP_LIBS) \
        $(LIBCAP_LIBS) \
-       $(IPCRYPT_LIBS)
+       $(IPCRYPT_LIBS) \
+       $(ARC4RANDOM_LIBS)
 
 testrunner_LDFLAGS = \
        $(AM_LDFLAGS) \
@@ -339,7 +398,8 @@ testrunner_LDADD = \
        $(LIBSODIUM_LIBS) \
        $(LUA_LIBS) \
        $(RT_LIBS) \
-       $(LIBCAP_LIBS)
+       $(LIBCAP_LIBS) \
+       $(ARC4RANDOM_LIBS)
 
 if HAVE_CDB
 dnsdist_LDADD += $(CDB_LDFLAGS) $(CDB_LIBS)
@@ -356,6 +416,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)
@@ -377,17 +444,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
@@ -412,13 +504,88 @@ endif
 
 if HAVE_SOLARIS
 dnsdist_SOURCES += \
-        devpollmplexer.cc \
-        portsmplexer.cc
+       devpollmplexer.cc \
+       portsmplexer.cc
 testrunner_SOURCES += \
-        devpollmplexer.cc \
-        portsmplexer.cc
+       devpollmplexer.cc \
+       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)
@@ -524,6 +691,15 @@ endif
 if !HAVE_SYSTEMD_SYSTEM_CALL_FILTER
        $(AM_V_GEN)perl -ni -e 'print unless /^SystemCallFilter/' $@
 endif
+if !HAVE_SYSTEMD_PROTECT_PROC
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectProc/' $@
+endif
+if !HAVE_SYSTEMD_PRIVATE_IPC
+       $(AM_V_GEN)perl -ni -e 'print unless /^PrivateIPC/' $@
+endif
+if !HAVE_SYSTEMD_REMOVE_IPC
+       $(AM_V_GEN)perl -ni -e 'print unless /^RemoveIPC/' $@
+endif
 
 dnsdist@.service: dnsdist.service
        $(AM_V_GEN)sed -e 's!/dnsdist !&--config $(sysconfdir)/dnsdist-%i.conf !' \
diff --git a/pdns/dnsdistdist/ascii.hh b/pdns/dnsdistdist/ascii.hh
deleted file mode 120000 (symlink)
index 6d5e3eb..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../ascii.hh
\ No newline at end of file
diff --git a/pdns/dnsdistdist/burtle.hh b/pdns/dnsdistdist/burtle.hh
new file mode 120000 (symlink)
index 0000000..0a00339
--- /dev/null
@@ -0,0 +1 @@
+../burtle.hh
\ No newline at end of file
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 b5757f088d5a14654ef509c43abc67037f47335b..81e39c8bc983e366fc8ecd402c565d2624b2642b 100644 (file)
@@ -9,8 +9,6 @@ AC_PROG_CC
 AC_PROG_CXX
 AC_LANG([C++])
 
-PDNS_CHECK_TIME_T
-
 AC_DEFINE([DNSDIST], [1],
   [This is dnsdist]
 )
@@ -22,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
@@ -32,13 +31,16 @@ PTHREAD_SET_NAME
 PDNS_CHECK_NETWORK_LIBS
 PDNS_CHECK_PTHREAD_NP
 PDNS_CHECK_SECURE_MEMSET
+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
 
@@ -50,10 +52,14 @@ 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])
+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"], [
@@ -63,40 +69,62 @@ 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
 
+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
@@ -120,7 +148,11 @@ AS_IF([test "x$enable_hardening" != "xno"], [
   AC_LD_RELRO
 ])
 
+PDNS_INIT_AUTO_VARS
+
 PDNS_ENABLE_SANITIZERS
+PDNS_ENABLE_LTO
+PDNS_ENABLE_COVERAGE
 
 PDNS_CHECK_PYTHON_VENV
 
@@ -136,6 +168,9 @@ LDFLAGS="$RELRO_LDFLAGS $LDFLAGS"
 CFLAGS="$SANITIZER_FLAGS $PIE_CFLAGS $CFLAGS"
 CXXFLAGS="$SANITIZER_FLAGS $PIE_CFLAGS $CXXFLAGS"
 
+CCVERSION=`$CC --version | head -1`
+CXXVERSION=`$CXX --version | head -1`
+
 PROGRAM_LDFLAGS="$PIE_LDFLAGS $PROGRAM_LDFLAGS"
 AC_SUBST([PROGRAM_LDFLAGS])
 
@@ -149,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])
@@ -165,8 +201,8 @@ AS_IF([test "x$ac_configure_args" != "x"],
 )
 AC_MSG_NOTICE([dnsdist configured with: $summary_conf_opts])
 AC_MSG_NOTICE([])
-AC_MSG_NOTICE([CC: $CC])
-AC_MSG_NOTICE([CXX: $CXX])
+AC_MSG_NOTICE([CC: $CC ($CCVERSION)])
+AC_MSG_NOTICE([CXX: $CXX ($CXXVERSION)])
 AC_MSG_NOTICE([LD: $LD])
 AC_MSG_NOTICE([CFLAGS: $CFLAGS])
 AC_MSG_NOTICE([CPPFLAGS: $CPPFLAGS])
@@ -183,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])]
@@ -203,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])]
@@ -219,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])],
@@ -231,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
diff --git a/pdns/dnsdistdist/dnsdist-async.cc b/pdns/dnsdistdist/dnsdist-async.cc
new file mode 100644 (file)
index 0000000..9cb96d8
--- /dev/null
@@ -0,0 +1,396 @@
+/*
+ * 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-async.hh"
+#include "dnsdist-internal-queries.hh"
+#include "dolog.hh"
+#include "threadname.hh"
+
+namespace dnsdist
+{
+
+AsynchronousHolder::Data::Data(bool failOpen) :
+  d_failOpen(failOpen)
+{
+  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();
+}
+
+AsynchronousHolder::~AsynchronousHolder()
+{
+  try {
+    stop();
+  }
+  catch (...) {
+  }
+}
+
+bool AsynchronousHolder::notify() const
+{
+  return d_data->d_notifier.notify();
+}
+
+bool AsynchronousHolder::wait(AsynchronousHolder::Data& data, FDMultiplexer& mplexer, std::vector<int>& readyFDs, int atMostMs)
+{
+  readyFDs.clear();
+  mplexer.getAvailableFDs(readyFDs, atMostMs);
+  if (readyFDs.empty()) {
+    /* timeout */
+    return true;
+  }
+
+  data.d_waiter.clear();
+  return false;
+}
+
+void AsynchronousHolder::stop()
+{
+  {
+    auto content = d_data->d_content.lock();
+    d_data->d_done = true;
+  }
+
+  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
+  {
+  };
+  std::list<std::pair<uint16_t, std::unique_ptr<CrossProtocolQuery>>> expiredEvents;
+
+  auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(1));
+  mplexer->addReadFD(data->d_waiter.getDescriptor(), [](int, FDMultiplexer::funcparam_t&) {});
+  std::vector<int> readyFDs;
+
+  while (true) {
+    bool shouldWait = true;
+    int timeout = -1;
+    {
+      auto content = data->d_content.lock();
+      if (data->d_done) {
+        return;
+      }
+
+      if (!content->empty()) {
+        gettimeofday(&now, nullptr);
+        struct timeval next = getNextTTD(*content);
+        if (next <= now) {
+          pickupExpired(*content, now, expiredEvents);
+          shouldWait = false;
+        }
+        else {
+          auto remainingUsec = uSec(next - now);
+          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;
+          }
+        }
+      }
+    }
+
+    if (shouldWait) {
+      auto timedOut = wait(*data, *mplexer, readyFDs, timeout);
+      if (timedOut) {
+        auto content = data->d_content.lock();
+        gettimeofday(&now, nullptr);
+        pickupExpired(*content, now, expiredEvents);
+      }
+    }
+
+    while (!expiredEvents.empty()) {
+      auto [queryID, query] = std::move(expiredEvents.front());
+      expiredEvents.pop_front();
+      if (!data->d_failOpen) {
+        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) {
+          TCPResponse tresponse(std::move(query->query));
+          sender->notifyIOError(now, std::move(tresponse));
+        }
+      }
+      else {
+        vinfolog("Asynchronous query %d has expired at %d.%d, resuming", queryID, now.tv_sec, now.tv_usec);
+        resumeQuery(std::move(query));
+      }
+    }
+  }
+}
+
+void AsynchronousHolder::push(uint16_t asyncID, uint16_t queryID, const struct timeval& ttd, std::unique_ptr<CrossProtocolQuery>&& query)
+{
+  bool needNotify = false;
+  {
+    auto content = d_data->d_content.lock();
+    if (!content->empty()) {
+      /* the thread is already waiting on a TTD expiry in addition to notifications,
+         let's not wake it unless our TTD comes before the current one */
+      const struct timeval next = getNextTTD(*content);
+      if (ttd < next) {
+        needNotify = true;
+      }
+    }
+    else {
+      /* the thread is currently only waiting for a notify */
+      needNotify = true;
+    }
+    content->insert({std::move(query), ttd, asyncID, queryID});
+  }
+
+  if (needNotify) {
+    notify();
+  }
+}
+
+std::unique_ptr<CrossProtocolQuery> AsynchronousHolder::get(uint16_t asyncID, uint16_t queryID)
+{
+  /* 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 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(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 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);
+  }
+}
+
+struct timeval AsynchronousHolder::getNextTTD(const content_t& content)
+{
+  if (content.empty()) {
+    throw std::runtime_error("AsynchronousHolder::getNextTTD() called on an empty holder");
+  }
+
+  return content.get<TTDTag>().begin()->d_ttd;
+}
+
+bool AsynchronousHolder::empty()
+{
+  return d_data->d_content.read_only_lock()->empty();
+}
+
+static bool resumeResponse(std::unique_ptr<CrossProtocolQuery>&& response)
+{
+  try {
+    auto& ids = response->query.d_idstate;
+    DNSResponse dnsResponse = response->getDR();
+
+    LocalHolders holders;
+    auto result = processResponseAfterRules(response->query.d_buffer, *holders.cacheInsertedRespRuleActions, dnsResponse, ids.cs->muted);
+    if (!result) {
+      /* easy */
+      return true;
+    }
+
+    auto sender = response->getTCPQuerySender();
+    if (sender) {
+      struct timeval now
+      {
+      };
+      gettimeofday(&now, nullptr);
+
+      TCPResponse resp(std::move(response->query.d_buffer), std::move(response->query.d_idstate), nullptr, response->downstream);
+      resp.d_async = true;
+      sender->handleResponse(now, std::move(resp));
+    }
+  }
+  catch (const std::exception& e) {
+    vinfolog("Got exception while resuming cross-protocol response: %s", e.what());
+    return false;
+  }
+
+  return true;
+}
+
+static LockGuarded<std::deque<std::unique_ptr<CrossProtocolQuery>>> s_asynchronousEventsQueue;
+
+bool queueQueryResumptionEvent(std::unique_ptr<CrossProtocolQuery>&& query)
+{
+  s_asynchronousEventsQueue.lock()->push_back(std::move(query));
+  return true;
+}
+
+void handleQueuedAsynchronousEvents()
+{
+  while (true) {
+    std::unique_ptr<CrossProtocolQuery> query;
+    {
+      // we do not want to hold the lock while resuming
+      auto queue = s_asynchronousEventsQueue.lock();
+      if (queue->empty()) {
+        return;
+      }
+
+      query = std::move(queue->front());
+      queue->pop_front();
+    }
+    if (query && !resumeQuery(std::move(query))) {
+      vinfolog("Unable to resume asynchronous query event");
+    }
+  }
+}
+
+bool resumeQuery(std::unique_ptr<CrossProtocolQuery>&& query)
+{
+  if (query->d_isResponse) {
+    return resumeResponse(std::move(query));
+  }
+
+  DNSQuestion dnsQuestion = query->getDQ();
+  LocalHolders holders;
+
+  auto result = processQueryAfterRules(dnsQuestion, holders, query->downstream);
+  if (result == ProcessQueryResult::Drop) {
+    /* easy */
+    return true;
+  }
+  if (result == ProcessQueryResult::PassToBackend) {
+    if (query->downstream == nullptr) {
+      return false;
+    }
+
+#ifdef HAVE_DNS_OVER_HTTPS
+    if (dnsQuestion.ids.du != nullptr) {
+      dnsQuestion.ids.du->downstream = query->downstream;
+    }
+#endif
+
+    if (query->downstream->isTCPOnly() || !(dnsQuestion.getProtocol().isUDP() || dnsQuestion.getProtocol() == dnsdist::Protocol::DoH)) {
+      query->downstream->passCrossProtocolQuery(std::move(query));
+      return true;
+    }
+
+    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, dnsQuestion, query->query.d_buffer);
+  }
+  if (result == ProcessQueryResult::SendAnswer) {
+    auto sender = query->getTCPQuerySender();
+    if (!sender) {
+      return false;
+    }
+
+    struct timeval now
+    {
+    };
+    gettimeofday(&now, nullptr);
+
+    TCPResponse response(std::move(query->query.d_buffer), std::move(query->query.d_idstate), nullptr, query->downstream);
+    response.d_async = true;
+    response.d_idstate.selfGenerated = true;
+
+    try {
+      sender->handleResponse(now, std::move(response));
+      return true;
+    }
+    catch (const std::exception& e) {
+      vinfolog("Got exception while resuming cross-protocol self-answered query: %s", e.what());
+      return false;
+    }
+  }
+  if (result == ProcessQueryResult::Asynchronous) {
+    /* nope */
+    errlog("processQueryAfterRules returned 'asynchronous' while trying to resume an already asynchronous query");
+    return false;
+  }
+
+  return false;
+}
+
+bool suspendQuery(DNSQuestion& dnsQuestion, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)
+{
+  if (!g_asyncHolder) {
+    return false;
+  }
+
+  struct timeval now
+  {
+  };
+  gettimeofday(&now, nullptr);
+  struct timeval ttd = now;
+  ttd.tv_sec += timeoutMs / 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(dnsQuestion, false);
+
+  g_asyncHolder->push(asyncID, queryID, ttd, std::move(query));
+  return true;
+}
+
+bool suspendResponse(DNSResponse& dnsResponse, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)
+{
+  if (!g_asyncHolder) {
+    return false;
+  }
+
+  struct timeval now
+  {
+  };
+  gettimeofday(&now, nullptr);
+  struct timeval ttd = now;
+  ttd.tv_sec += timeoutMs / 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(dnsResponse, true);
+  query->d_isResponse = true;
+  query->downstream = dnsResponse.d_downstream;
+
+  g_asyncHolder->push(asyncID, queryID, ttd, std::move(query));
+  return true;
+}
+
+std::unique_ptr<AsynchronousHolder> g_asyncHolder;
+}
diff --git a/pdns/dnsdistdist/dnsdist-async.hh b/pdns/dnsdistdist/dnsdist-async.hh
new file mode 100644 (file)
index 0000000..c0b8453
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * 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 <thread>
+
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/key_extractors.hpp>
+
+#include "channel.hh"
+#include "dnsdist-tcp.hh"
+
+namespace dnsdist
+{
+class AsynchronousHolder
+{
+public:
+  AsynchronousHolder(bool failOpen = true);
+  ~AsynchronousHolder();
+  void push(uint16_t asyncID, uint16_t queryID, const struct timeval& ttd, std::unique_ptr<CrossProtocolQuery>&& query);
+  std::unique_ptr<CrossProtocolQuery> get(uint16_t asyncID, uint16_t queryID);
+  bool empty();
+  void stop();
+
+private:
+  struct TTDTag
+  {
+  };
+  struct IDTag
+  {
+  };
+
+  struct Entry
+  {
+    /* not used by any of the indexes, so mutable */
+    mutable std::unique_ptr<CrossProtocolQuery> d_query;
+    struct timeval d_ttd;
+    uint16_t d_asyncID;
+    uint16_t d_queryID;
+  };
+
+  typedef multi_index_container<
+    Entry,
+    indexed_by<
+      ordered_unique<tag<IDTag>,
+                     composite_key<
+                       Entry,
+                       member<Entry, uint16_t, &Entry::d_queryID>,
+                       member<Entry, uint16_t, &Entry::d_asyncID>>>,
+      ordered_non_unique<tag<TTDTag>,
+                         member<Entry, struct timeval, &Entry::d_ttd>>>>
+    content_t;
+
+  static void pickupExpired(content_t&, const struct timeval& now, std::list<std::pair<uint16_t, std::unique_ptr<CrossProtocolQuery>>>& expiredEvents);
+  static struct timeval getNextTTD(const content_t&);
+
+  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;
+    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(Data& data, FDMultiplexer& mplexer, std::vector<int>& readyFDs, int atMostMs);
+  bool notify() const;
+};
+
+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();
+
+extern std::unique_ptr<AsynchronousHolder> g_asyncHolder;
+}
index 25dd110345f0a2384cd595e59e603f1c8a5d890b..9c5da43e563554c4c8a950d9f4ba789fc2d8234a 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()) {
@@ -45,51 +83,89 @@ bool DownstreamState::reconnect()
     return false;
   }
 
+  if (IsAnyAddress(d_config.remote)) {
+    return true;
+  }
+
   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);
       fd = -1;
     }
-    if (!IsAnyAddress(d_config.remote)) {
-      fd = SSocket(d_config.remote.sin4.sin_family, SOCK_DGRAM, 0);
-      if (!IsAnyAddress(d_config.sourceAddr)) {
-        SSetsockopt(fd, SOL_SOCKET, SO_REUSEADDR, 1);
-        if (!d_config.sourceItfName.empty()) {
+    fd = SSocket(d_config.remote.sin4.sin_family, SOCK_DGRAM, 0);
+
 #ifdef SO_BINDTODEVICE
-          int res = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, d_config.sourceItfName.c_str(), d_config.sourceItfName.length());
-          if (res != 0) {
-            infolog("Error setting up the interface on backend socket '%s': %s", d_config.remote.toStringWithPort(), stringerror());
-          }
+    if (!d_config.sourceItfName.empty()) {
+      int res = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, d_config.sourceItfName.c_str(), d_config.sourceItfName.length());
+      if (res != 0) {
+        infolog("Error setting up the interface on backend socket '%s': %s", d_config.remote.toStringWithPort(), stringerror());
+      }
+    }
 #endif
-        }
 
-        SBind(fd, d_config.sourceAddr);
+    if (!IsAnyAddress(d_config.sourceAddr)) {
+#ifdef IP_BIND_ADDRESS_NO_PORT
+      if (d_config.ipBindAddrNoPort) {
+        SSetsockopt(fd, SOL_IP, IP_BIND_ADDRESS_NO_PORT, 1);
       }
-      try {
-        SConnect(fd, d_config.remote);
-        if (sockets.size() > 1) {
-          (*mplexer.lock())->addReadFD(fd, [](int, boost::any) {});
-        }
-        connected = true;
+#endif
+      SBind(fd, d_config.sourceAddr);
+    }
+
+    try {
+      SConnect(fd, d_config.remote);
+      if (sockets.size() > 1) {
+        (*mplexer.lock())->addReadFD(fd, [](int, boost::any) {});
       }
-      catch(const std::runtime_error& error) {
+#ifdef HAVE_XSK
+      if (!d_xskInfos.empty()) {
+        addXSKDestination(fd);
+      }
+#endif /* HAVE_XSK */
+      connected = true;
+    }
+    catch (const std::runtime_error& error) {
+      if (initialAttempt || g_verbose) {
         infolog("Error connecting to new server with address %s: %s", d_config.remote.toStringWithPort(), error.what());
-        connected = false;
-        break;
       }
+      connected = false;
+      break;
     }
   }
 
   /* 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);
@@ -107,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) {
@@ -190,6 +293,11 @@ DownstreamState::DownstreamState(DownstreamState::Config&& config, std::shared_p
     setWeight(d_config.d_weight);
   }
 
+  if (d_config.availability == Availability::Lazy && d_config.d_lazyHealthCheckSampleSize > 0) {
+    d_lazyHealthCheckStats.lock()->d_lastResults.set_capacity(d_config.d_lazyHealthCheckSampleSize);
+    setUpStatus(true);
+  }
+
   setName(d_config.name);
 
   if (d_tlsCtx) {
@@ -224,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();
   }
 }
@@ -245,14 +361,14 @@ void DownstreamState::connectUDPSockets()
   sockets.resize(d_config.d_numberOfSockets);
 
   if (sockets.size() > 1) {
-    *(mplexer.lock()) = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent());
+    *(mplexer.lock()) = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(sockets.size()));
   }
 
   for (auto& fd : sockets) {
     fd = -1;
   }
 
-  reconnect();
+  reconnect(true);
 }
 
 DownstreamState::~DownstreamState()
@@ -307,189 +423,533 @@ bool DownstreamState::s_randomizeSockets{false};
 bool DownstreamState::s_randomizeIDs{false};
 int DownstreamState::s_udpTimeout{2};
 
-static bool isIDSExpired(IDState& ids)
+static bool isIDSExpired(const IDState& ids)
 {
-  auto age = ids.age++;
+  auto age = ids.age.load();
   return age > DownstreamState::s_udpTimeout;
 }
 
-void DownstreamState::handleTimeout(IDState& ids)
+void DownstreamState::handleUDPTimeout(IDState& ids)
 {
-  /* We mark the state as unused as soon as possible
-     to limit the risk of racing with the
-     responder thread.
-  */
-  auto oldDU = ids.du;
-
-  ids.du = nullptr;
-  handleDOHTimeout(DOHUnitUniquePtr(oldDU, DOHUnit::release));
-  oldDU = nullptr;
   ids.age = 0;
-  reuseds++;
+  ids.inUse = false;
+  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.qname.toLogString(), QType(ids.qtype).toString(), ids.origRemote.toStringWithPort());
+           ids.internal.qname.toLogString(), QType(ids.internal.qtype).toString(), ids.internal.origRemote.toStringWithPort());
+
+  if (g_rings.shouldRecordResponses()) {
+    struct timespec ts;
+    gettime(&ts);
+
+    struct dnsheader fake;
+    memset(&fake, 0, sizeof(fake));
+    fake.id = ids.internal.origID;
+    uint16_t* flags = getFlagsFromDNSHeader(&fake);
+    *flags = ids.internal.origFlags;
 
-  struct timespec ts;
-  gettime(&ts);
+    g_rings.insertResponse(ts, ids.internal.origRemote, ids.internal.qname, ids.internal.qtype, std::numeric_limits<unsigned int>::max(), 0, fake, d_config.remote, getProtocol());
+  }
 
-  struct dnsheader fake;
-  memset(&fake, 0, sizeof(fake));
-  fake.id = ids.origID;
+  reportTimeoutOrError();
+}
 
-  g_rings.insertResponse(ts, ids.origRemote, ids.qname, ids.qtype, std::numeric_limits<unsigned int>::max(), 0, fake, d_config.remote, getProtocol());
+void DownstreamState::reportResponse(uint8_t rcode)
+{
+  if (d_config.availability == Availability::Lazy && d_config.d_lazyHealthCheckSampleSize > 0) {
+    bool failure = d_config.d_lazyHealthCheckMode == LazyHealthCheckMode::TimeoutOrServFail ? rcode == RCode::ServFail : false;
+    d_lazyHealthCheckStats.lock()->d_lastResults.push_back(failure);
+  }
+}
+
+void DownstreamState::reportTimeoutOrError()
+{
+  if (d_config.availability == Availability::Lazy && d_config.d_lazyHealthCheckSampleSize > 0) {
+    d_lazyHealthCheckStats.lock()->d_lastResults.push_back(true);
+  }
 }
 
-void DownstreamState::handleTimeouts()
+void DownstreamState::handleUDPTimeouts()
 {
+  if (getProtocol() != dnsdist::Protocol::DoUDP) {
+    return;
+  }
+
   if (s_randomizeIDs) {
     auto map = d_idStatesMap.lock();
     for (auto it = map->begin(); it != map->end(); ) {
       auto& ids = it->second;
       if (isIDSExpired(ids)) {
-        handleTimeout(ids);
+        handleUDPTimeout(ids);
         it = map->erase(it);
         continue;
       }
+      ++ids.age;
       ++it;
     }
   }
   else {
-    for (IDState& ids : idStates) {
-      int64_t usageIndicator = ids.usageIndicator;
-      if (IDState::isInUse(usageIndicator) && isIDSExpired(ids)) {
-        if (!ids.tryMarkUnused(usageIndicator)) {
-          /* this state has been altered in the meantime,
-             don't go anywhere near it */
+    if (outstanding.load() > 0) {
+      for (IDState& ids : idStates) {
+        if (!ids.isInUse()) {
           continue;
         }
-
-        handleTimeout(ids);
+        if (!isIDSExpired(ids)) {
+          ++ids.age;
+          continue;
+        }
+        auto guard = ids.acquire();
+        if (!guard) {
+          continue;
+        }
+        /* check again, now that we have locked this state */
+        if (ids.isInUse() && isIDSExpired(ids)) {
+          handleUDPTimeout(ids);
+        }
       }
     }
   }
 }
 
-IDState* DownstreamState::getExistingState(unsigned int stateId)
+uint16_t DownstreamState::saveState(InternalQueryState&& state)
 {
   if (s_randomizeIDs) {
+    /* if the state is already in use we will retry,
+       up to 5 five times. The last selected one is used
+       even if it was already in use */
+    size_t remainingAttempts = 5;
     auto map = d_idStatesMap.lock();
-    auto it = map->find(stateId);
-    if (it == map->end()) {
-      return nullptr;
+
+    do {
+      uint16_t selectedID = dnsdist::getRandomValue(std::numeric_limits<uint16_t>::max());
+      auto [it, inserted] = map->emplace(selectedID, IDState());
+
+      if (!inserted) {
+        remainingAttempts--;
+        if (remainingAttempts > 0) {
+          continue;
+        }
+
+        auto oldDU = std::move(it->second.internal.du);
+        ++reuseds;
+        ++dnsdist::metrics::g_stats.downstreamTimeouts;
+        DOHUnitInterface::handleTimeout(std::move(oldDU));
+      }
+      else {
+        ++outstanding;
+      }
+
+      it->second.internal = std::move(state);
+      it->second.age.store(0);
+
+      return it->first;
     }
-    return &it->second;
+    while (true);
   }
-  else {
-    if (stateId >= idStates.size()) {
-      return nullptr;
+
+  do {
+    uint16_t selectedID = (idOffset++) % idStates.size();
+    IDState& ids = idStates[selectedID];
+    auto guard = ids.acquire();
+    if (!guard) {
+      continue;
+    }
+    if (ids.isInUse()) {
+      /* we are reusing a state, no change in outstanding but if there was an existing DOHUnit we need
+         to handle it because it's about to be overwritten. */
+      auto oldDU = std::move(ids.internal.du);
+      ++reuseds;
+      ++dnsdist::metrics::g_stats.downstreamTimeouts;
+      DOHUnitInterface::handleTimeout(std::move(oldDU));
+    }
+    else {
+      ++outstanding;
     }
-    return &idStates[stateId];
+    ids.internal = std::move(state);
+    ids.age.store(0);
+    ids.inUse = true;
+    return selectedID;
   }
+  while (true);
 }
 
-void DownstreamState::releaseState(unsigned int stateId)
+void DownstreamState::restoreState(uint16_t id, InternalQueryState&& state)
 {
   if (s_randomizeIDs) {
     auto map = d_idStatesMap.lock();
-    auto it = map->find(stateId);
-    if (it == map->end()) {
-      return;
+
+    auto [it, inserted] = map->emplace(id, IDState());
+    if (!inserted) {
+      /* already used */
+      ++reuseds;
+      ++dnsdist::metrics::g_stats.downstreamTimeouts;
+      DOHUnitInterface::handleTimeout(std::move(state.du));
     }
-    if (it->second.isInUse()) {
-      return;
+    else {
+      it->second.internal = std::move(state);
+      ++outstanding;
     }
-    map->erase(it);
+    return;
+  }
+
+  auto& ids = idStates[id];
+  auto guard = ids.acquire();
+  if (!guard) {
+    /* already used */
+    ++reuseds;
+    ++dnsdist::metrics::g_stats.downstreamTimeouts;
+    DOHUnitInterface::handleTimeout(std::move(state.du));
+    return;
   }
+  if (ids.isInUse()) {
+    /* already used */
+    ++reuseds;
+    ++dnsdist::metrics::g_stats.downstreamTimeouts;
+    DOHUnitInterface::handleTimeout(std::move(state.du));
+    return;
+  }
+  ids.internal = std::move(state);
+  ids.inUse = true;
+  ++outstanding;
 }
 
-IDState* DownstreamState::getIDState(unsigned int& selectedID, int64_t& generation)
+std::optional<InternalQueryState> DownstreamState::getState(uint16_t id)
 {
-  DOHUnitUniquePtr du(nullptr, DOHUnit::release);
-  IDState* ids = nullptr;
+  std::optional<InternalQueryState> result = std::nullopt;
+
   if (s_randomizeIDs) {
-    /* if the state is already in use we will retry,
-       up to 5 five times. The last selected one is used
-       even if it was already in use */
-    size_t remainingAttempts = 5;
     auto map = d_idStatesMap.lock();
 
-    bool done = false;
-    do {
-      selectedID = dnsdist::getRandomValue(std::numeric_limits<uint16_t>::max());
-      auto [it, inserted] = map->insert({selectedID, IDState()});
-      ids = &it->second;
-      if (inserted) {
-        done = true;
+    auto it = map->find(id);
+    if (it == map->end()) {
+      return result;
+    }
+
+    result = std::move(it->second.internal);
+    map->erase(it);
+    --outstanding;
+    return result;
+  }
+
+  if (id > idStates.size()) {
+    return result;
+  }
+
+  auto& ids = idStates[id];
+  auto guard = ids.acquire();
+  if (!guard) {
+    return result;
+  }
+
+  if (ids.isInUse()) {
+    result = std::move(ids.internal);
+    --outstanding;
+  }
+  ids.inUse = false;
+  return result;
+}
+
+bool DownstreamState::healthCheckRequired(std::optional<time_t> currentTime)
+{
+  if (d_config.availability == DownstreamState::Availability::Lazy) {
+    auto stats = d_lazyHealthCheckStats.lock();
+    if (stats->d_status == LazyHealthCheckStats::LazyStatus::PotentialFailure) {
+      vinfolog("Sending health-check query for %s which is still in the Potential Failure state", getNameWithAddr());
+      return true;
+    }
+    if (stats->d_status == LazyHealthCheckStats::LazyStatus::Failed) {
+      auto now = currentTime ? *currentTime : time(nullptr);
+      if (stats->d_nextCheck <= now) {
+        /* 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 */
+        vinfolog("Sending health-check query for %s which is still in the Failed state", getNameWithAddr());
+        updateNextLazyHealthCheck(*stats, true, now);
+        return true;
       }
-      else {
-        remainingAttempts--;
+      return false;
+    }
+    if (stats->d_status == LazyHealthCheckStats::LazyStatus::Healthy) {
+      auto& lastResults = stats->d_lastResults;
+      size_t totalCount = lastResults.size();
+      if (totalCount < d_config.d_lazyHealthCheckMinSampleCount) {
+        return false;
       }
+
+      size_t failures = 0;
+      for (const auto& result : lastResults) {
+        if (result) {
+          ++failures;
+        }
+      }
+
+      const auto maxFailureRate = static_cast<float>(d_config.d_lazyHealthCheckThreshold);
+      auto current = (100.0 * failures) / totalCount;
+      if (current >= maxFailureRate) {
+        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;
+        /* 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 */
+        updateNextLazyHealthCheck(*stats, true);
+        return true;
+      }
+    }
+
+    return false;
+  }
+  else if (d_config.availability == DownstreamState::Availability::Auto) {
+
+    if (d_nextCheck > 1) {
+      --d_nextCheck;
+      return false;
+    }
+
+    d_nextCheck = d_config.checkInterval;
+    return true;
+  }
+
+  return false;
+}
+
+time_t DownstreamState::getNextLazyHealthCheck()
+{
+  auto stats = d_lazyHealthCheckStats.lock();
+  return stats->d_nextCheck;
+}
+
+void DownstreamState::updateNextLazyHealthCheck(LazyHealthCheckStats& stats, bool checkScheduled, std::optional<time_t> currentTime)
+{
+  auto now = currentTime ? * currentTime : time(nullptr);
+  if (d_config.d_lazyHealthCheckUseExponentialBackOff) {
+    if (stats.d_status == DownstreamState::LazyHealthCheckStats::LazyStatus::PotentialFailure) {
+      /* we are still in the "up" state, we need to send the next query quickly to
+         determine if the backend is really down */
+      stats.d_nextCheck = now + d_config.checkInterval;
+      vinfolog("Backend %s is in potential failure state, next check in %d seconds", getNameWithAddr(), d_config.checkInterval);
+    }
+    else if (consecutiveSuccessfulChecks > 0) {
+      /* we are in 'Failed' state, but just had one (or more) successful check,
+         so we want the next one to happen quite quickly as the backend might
+         be available again. */
+      stats.d_nextCheck = now + d_config.d_lazyHealthCheckFailedInterval;
+      if (!checkScheduled) {
+        vinfolog("Backend %s is in failed state but had %d consecutive successful checks, next check in %d seconds", getNameWithAddr(), std::to_string(consecutiveSuccessfulChecks), d_config.d_lazyHealthCheckFailedInterval);
+      }
+    }
+    else {
+      uint16_t failedTests = currentCheckFailures;
+      if (checkScheduled) {
+        /* we are planning the check after that one, which will only
+           occur if there is a failure */
+        failedTests++;
+      }
+
+      time_t backOff = d_config.d_lazyHealthCheckMaxBackOff;
+      const ExponentialBackOffTimer backOffTimer(d_config.d_lazyHealthCheckMaxBackOff);
+      auto backOffCoeffTmp = backOffTimer.get(failedTests);
+      /* 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;
+        }
+      }
+
+      stats.d_nextCheck = now + backOff;
+      vinfolog("Backend %s is in failed state and has failed %d consecutive checks, next check in %d seconds", getNameWithAddr(), failedTests, backOff);
     }
-    while (!done && remainingAttempts > 0);
   }
   else {
-    selectedID = (idOffset++) % idStates.size();
-    ids = &idStates[selectedID];
+    stats.d_nextCheck = now + d_config.d_lazyHealthCheckFailedInterval;
+    vinfolog("Backend %s is in %s state, next check in %d seconds", getNameWithAddr(), (stats.d_status == DownstreamState::LazyHealthCheckStats::LazyStatus::PotentialFailure ? "potential failure" : "failed"), d_config.d_lazyHealthCheckFailedInterval);
   }
+}
 
-  ids->age = 0;
+void DownstreamState::submitHealthCheckResult(bool initial, bool newResult)
+{
+  if (!newResult) {
+    ++d_healthCheckMetrics.d_failures;
+  }
 
-  /* that means that the state was in use, possibly with an allocated
-     DOHUnit that we will need to handle, but we can't touch it before
-     confirming that we now own this state */
-  if (ids->isInUse()) {
-    du = DOHUnitUniquePtr(ids->du, DOHUnit::release);
+  if (initial) {
+    /* if this is the initial health-check, at startup, we do not care
+       about the minimum number of failed/successful health-checks */
+    if (!IsAnyAddress(d_config.remote)) {
+      infolog("Marking downstream %s as '%s'", getNameWithAddr(), newResult ? "up" : "down");
+    }
+    setUpStatus(newResult);
+    if (newResult == false) {
+      currentCheckFailures++;
+      if (d_config.availability == DownstreamState::Availability::Lazy) {
+        auto stats = d_lazyHealthCheckStats.lock();
+        stats->d_status = LazyHealthCheckStats::LazyStatus::Failed;
+        updateNextLazyHealthCheck(*stats, false);
+      }
+    }
+    return;
   }
 
-  /* we atomically replace the value, we now own this state */
-  generation = ids->generation++;
-  if (!ids->markAsUsed(generation)) {
-    /* the state was not in use.
-       we reset 'du' because it might have still been in use when we read it. */
-    du.release();
-    ++outstanding;
+  bool newState = newResult;
+
+  if (newResult) {
+    /* check succeeded */
+    currentCheckFailures = 0;
+
+    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 */
+        newState = false;
+
+        if (d_config.availability == DownstreamState::Availability::Lazy) {
+          auto stats = d_lazyHealthCheckStats.lock();
+          updateNextLazyHealthCheck(*stats, false);
+        }
+      }
+    }
+
+    if (newState) {
+      if (d_config.availability == DownstreamState::Availability::Lazy) {
+        auto stats = d_lazyHealthCheckStats.lock();
+        vinfolog("Backend %s had %d successful checks, moving to Healthy", getNameWithAddr(), std::to_string(consecutiveSuccessfulChecks));
+        stats->d_status = LazyHealthCheckStats::LazyStatus::Healthy;
+        stats->d_lastResults.clear();
+      }
+    }
   }
   else {
-    /* we are reusing a state, no change in outstanding but if there was an existing DOHUnit we need
-       to handle it because it's about to be overwritten. */
-    ids->du = nullptr;
-    ++reuseds;
-    ++g_stats.downstreamTimeouts;
-    handleDOHTimeout(std::move(du));
+    /* check failed */
+    consecutiveSuccessfulChecks = 0;
+
+    currentCheckFailures++;
+
+    if (upStatus) {
+      /* we were previously marked as "up" and failed a health-check,
+         let's see if this is enough to move to the "down" state or if
+         need more failed checks for that */
+      if (currentCheckFailures < d_config.maxCheckFailures) {
+        /* we need more than one failure to be marked as down,
+           and we did not reach the threshold yet, let's stay up */
+        newState = true;
+      }
+      else if (d_config.availability == DownstreamState::Availability::Lazy) {
+        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;
+        updateNextLazyHealthCheck(*stats, false);
+      }
+    }
   }
 
-  return ids;
+  if (newState != upStatus) {
+    /* we are actually moving to a new state */
+    if (!IsAnyAddress(d_config.remote)) {
+      infolog("Marking downstream %s as '%s'", getNameWithAddr(), newState ? "up" : "down");
+    }
+
+    if (newState && !isTCPOnly() && (!connected || d_config.reconnectOnUp)) {
+      newState = reconnect();
+    }
+
+    setUpStatus(newState);
+    if (g_snmpAgent && g_snmpTrapsEnabled) {
+      g_snmpAgent->sendBackendStatusChangeTrap(*this);
+    }
+  }
+}
+
+#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;
+  {
+    auto lock = d_servers.read_lock();
+    servers = *lock;
+  }
+
   size_t count = 0;
-  auto servers = d_servers.read_lock();
-  for (const auto& server : **servers) {
+  for (const auto& server : *servers) {
     if (!upOnly || std::get<1>(server)->isUp() ) {
       count++;
     }
   }
+
   return count;
 }
 
 size_t ServerPool::poolLoad()
 {
+  std::shared_ptr<const ServerPolicy::NumberedServerVector> servers = nullptr;
+  {
+    auto lock = d_servers.read_lock();
+    servers = *lock;
+  }
+
   size_t load = 0;
-  auto servers = d_servers.read_lock();
-  for (const auto& server : **servers) {
+  for (const auto& server : *servers) {
     size_t serverOutstanding = std::get<1>(server)->outstanding.load();
     load += serverOutstanding;
   }
   return load;
 }
 
-const std::shared_ptr<ServerPolicy::NumberedServerVector> ServerPool::getServers()
+const std::shared_ptr<const ServerPolicy::NumberedServerVector> ServerPool::getServers()
 {
-  std::shared_ptr<ServerPolicy::NumberedServerVector> result;
+  std::shared_ptr<const ServerPolicy::NumberedServerVector> result;
   {
     result = *(d_servers.read_lock());
   }
@@ -502,18 +962,18 @@ void ServerPool::addServer(shared_ptr<DownstreamState>& server)
   /* we can't update the content of the shared pointer directly even when holding the lock,
      as other threads might hold a copy. We can however update the pointer as long as we hold the lock. */
   unsigned int count = static_cast<unsigned int>((*servers)->size());
-  auto newServers = std::make_shared<ServerPolicy::NumberedServerVector>(*(*servers));
-  newServers->emplace_back(++count, server);
+  auto newServers = ServerPolicy::NumberedServerVector(*(*servers));
+  newServers.emplace_back(++count, server);
   /* we need to reorder based on the server 'order' */
-  std::stable_sort(newServers->begin(), newServers->end(), [](const std::pair<unsigned int,std::shared_ptr<DownstreamState> >& a, const std::pair<unsigned int,std::shared_ptr<DownstreamState> >& b) {
+  std::stable_sort(newServers.begin(), newServers.end(), [](const std::pair<unsigned int,std::shared_ptr<DownstreamState> >& a, const std::pair<unsigned int,std::shared_ptr<DownstreamState> >& b) {
       return a.second->d_config.order < b.second->d_config.order;
     });
   /* and now we need to renumber for Lua (custom policies) */
   size_t idx = 1;
-  for (auto& serv : *newServers) {
+  for (auto& serv : newServers) {
     serv.first = idx++;
   }
-  *servers = std::move(newServers);
+  *servers = std::make_shared<const ServerPolicy::NumberedServerVector>(std::move(newServers));
 }
 
 void ServerPool::removeServer(shared_ptr<DownstreamState>& server)
diff --git a/pdns/dnsdistdist/dnsdist-backoff.hh b/pdns/dnsdistdist/dnsdist-backoff.hh
new file mode 100644 (file)
index 0000000..3e3081f
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+class ExponentialBackOffTimer
+{
+public:
+  ExponentialBackOffTimer(unsigned int maxBackOff) :
+    d_maxBackOff(maxBackOff)
+  {
+  }
+
+  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;
+  }
+
+private:
+  const unsigned int d_maxBackOff;
+};
index 5b3fbbce0b98ed5c3327125f56e842085e8588b4..0fb04d4f03352d346e4dd38cbb4ff7c4e697bd21 100644 (file)
 #include "config.h"
 
 #ifndef DISABLE_CARBON
-#include "sholder.hh"
+
+#include <thread>
 #include "iputils.hh"
+#include "lock.hh"
 
-struct CarbonConfig
+namespace dnsdist
+{
+class Carbon
 {
-  ComboAddress server;
-  std::string namespace_name;
-  std::string ourname;
-  std::string instance_name;
-  unsigned int interval;
+public:
+  struct Endpoint
+  {
+    ComboAddress server;
+    std::string namespace_name;
+    std::string ourname;
+    std::string instance_name;
+    unsigned int interval;
+  };
+
+  static bool addEndpoint(Endpoint&& endpoint);
+  static void run();
+
+private:
+  struct Config
+  {
+    std::vector<Endpoint> d_endpoints;
+    bool d_running{false};
+  };
+
+  static LockGuarded<Config> s_config;
 };
 
-extern GlobalStateHolder<std::vector<CarbonConfig>> g_carbon;
-void carbonDumpThread();
+}
+
 #endif /* DISABLE_CARBON */
diff --git a/pdns/dnsdistdist/dnsdist-concurrent-connections.hh b/pdns/dnsdistdist/dnsdist-concurrent-connections.hh
new file mode 100644 (file)
index 0000000..3cf55d5
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * 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 "iputils.hh"
+#include "lock.hh"
+
+namespace dnsdist
+{
+class IncomingConcurrentTCPConnectionsManager
+{
+public:
+  static bool accountNewTCPConnection(const ComboAddress& from)
+  {
+    if (s_maxTCPConnectionsPerClient == 0) {
+      return true;
+    }
+    auto db = s_tcpClientsConcurrentConnectionsCount.lock();
+    auto& count = (*db)[from];
+    if (count >= s_maxTCPConnectionsPerClient) {
+      return false;
+    }
+    ++count;
+    return true;
+  }
+
+  static void accountClosedTCPConnection(const ComboAddress& from)
+  {
+    if (s_maxTCPConnectionsPerClient == 0) {
+      return;
+    }
+    auto db = s_tcpClientsConcurrentConnectionsCount.lock();
+    auto& count = db->at(from);
+    count--;
+    if (count == 0) {
+      db->erase(from);
+    }
+  }
+
+  static void setMaxTCPConnectionsPerClient(size_t max)
+  {
+    s_maxTCPConnectionsPerClient = max;
+  }
+
+private:
+  static LockGuarded<std::map<ComboAddress, size_t, ComboAddress::addressOnlyLessThan>> s_tcpClientsConcurrentConnectionsCount;
+  static size_t s_maxTCPConnectionsPerClient;
+};
+
+}
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 5cca42b15f2c25e40689e599b10108173706292e..bba713bb6f43170689c860cc4649c75f9837a7b7 100644 (file)
@@ -27,6 +27,7 @@
 #include "dnsparser.hh"
 #include "dolog.hh"
 #include "sstuff.hh"
+#include "threadname.hh"
 
 namespace dnsdist
 {
@@ -37,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(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;
 }
 
@@ -50,13 +51,9 @@ struct DesignatedResolvers
 
 static bool parseSVCParams(const PacketBuffer& answer, std::map<uint16_t, DesignatedResolvers>& resolvers)
 {
-  if (answer.size() <= sizeof(struct dnsheader)) {
-    throw std::runtime_error("Looking for SVC records in a packet smaller than a DNS header");
-  }
-
   std::map<DNSName, std::vector<ComboAddress>> hints;
-  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(answer.data());
-  PacketReader pr(pdns_string_view(reinterpret_cast<const char*>(answer.data()), answer.size()));
+  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);
   uint16_t nscount = ntohs(dh->nscount);
@@ -150,6 +147,7 @@ static bool handleSVCResult(const PacketBuffer& answer, const ComboAddress& exis
   }
 
   for (const auto& [priority, resolver] : resolvers) {
+    (void)priority;
     /* do not compare the ports */
     std::set<ComboAddress, ComboAddress::addressOnlyLessThan> tentativeAddresses;
     ServiceDiscovery::DiscoveredResolverConfig tempConfig;
@@ -228,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;
   }
 
@@ -254,6 +252,13 @@ bool ServiceDiscovery::getDiscoveredConfig(const UpgradeableBackend& upgradeable
 
     Socket sock(addr.sin4.sin_family, SOCK_STREAM);
     sock.setNonBlocking();
+
+#ifdef SO_BINDTODEVICE
+    if (!backend->d_config.sourceItfName.empty()) {
+      setsockopt(sock.getHandle(), SOL_SOCKET, SO_BINDTODEVICE, backend->d_config.sourceItfName.c_str(), backend->d_config.sourceItfName.length());
+    }
+#endif
+
     if (!IsAnyAddress(backend->d_config.sourceAddr)) {
       sock.setReuseAddr();
 #ifdef IP_BIND_ADDRESS_NO_PORT
@@ -261,21 +266,16 @@ bool ServiceDiscovery::getDiscoveredConfig(const UpgradeableBackend& upgradeable
         SSetsockopt(sock.getHandle(), SOL_IP, IP_BIND_ADDRESS_NO_PORT, 1);
       }
 #endif
-
-      if (!backend->d_config.sourceItfName.empty()) {
-#ifdef SO_BINDTODEVICE
-        setsockopt(sock.getHandle(), SOL_SOCKET, SO_BINDTODEVICE, backend->d_config.sourceItfName.c_str(), backend->d_config.sourceItfName.length());
-#endif
-      }
       sock.bind(backend->d_config.sourceAddr);
     }
     sock.connect(addr, backend->d_config.tcpConnectTimeout);
 
     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);
       }
@@ -284,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);
       }
@@ -337,10 +337,10 @@ bool ServiceDiscovery::getDiscoveredConfig(const UpgradeableBackend& upgradeable
     return handleSVCResult(packet, addr, upgradeableBackend.d_dohKey, config);
   }
   catch (const std::exception& e) {
-    errlog("Error while trying to discover backend upgrade for %s: %s", addr.toStringWithPort(), e.what());
+    warnlog("Error while trying to discover backend upgrade for %s: %s", addr.toStringWithPort(), e.what());
   }
   catch (...) {
-    errlog("Error while trying to discover backend upgrade for %s", addr.toStringWithPort());
+    warnlog("Error while trying to discover backend upgrade for %s", addr.toStringWithPort());
   }
 
   return false;
@@ -368,8 +368,7 @@ static bool checkBackendUsability(std::shared_ptr<DownstreamState>& ds)
       sock.bind(ds->d_config.sourceAddr);
     }
 
-    time_t now = time(nullptr);
-    auto handler = 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, now);
+    auto handler = 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);
     handler->connect(ds->d_config.tcpFastOpen, ds->d_config.remote, timeval{ds->d_config.checkTimeout, 0});
     return true;
   }
@@ -398,15 +397,18 @@ bool ServiceDiscovery::tryToUpgradeBackend(const UpgradeableBackend& backend)
 
   DownstreamState::Config config(backend.d_ds->d_config);
   config.remote = discoveredConfig.d_addr;
-  if (discoveredConfig.d_port != 0) {
-    config.remote.setPort(discoveredConfig.d_port);
-  }
-  else {
-    if (discoveredConfig.d_protocol == dnsdist::Protocol::DoT) {
-      config.remote.setPort(853);
+  config.remote.setPort(discoveredConfig.d_port);
+
+  if (backend.keepAfterUpgrade && config.availability == DownstreamState::Availability::Up) {
+    /* it's OK to keep the forced state if we replace the initial
+       backend, but if we are adding a new backend, it should not
+       inherit that setting, especially since DoX backends are much
+       more likely to fail (certificate errors, ...) */
+    if (config.d_upgradeToLazyHealthChecks) {
+      config.availability = DownstreamState::Availability::Lazy;
     }
-    else if (discoveredConfig.d_protocol == dnsdist::Protocol::DoH) {
-      config.remote.setPort(443);
+    else {
+      config.availability = DownstreamState::Availability::Auto;
     }
   }
 
@@ -496,6 +498,7 @@ bool ServiceDiscovery::tryToUpgradeBackend(const UpgradeableBackend& backend)
 
 void ServiceDiscovery::worker()
 {
+  setThreadName("dnsdist/discove");
   while (true) {
     time_t now = time(nullptr);
 
@@ -505,14 +508,14 @@ void ServiceDiscovery::worker()
     for (auto backendIt = upgradeables.begin(); backendIt != upgradeables.end();) {
       auto& backend = *backendIt;
       try {
-        if (backend.d_nextCheck > now) {
+        if (backend->d_nextCheck > now) {
           ++backendIt;
           continue;
         }
 
-        auto upgraded = tryToUpgradeBackend(backend);
+        auto upgraded = tryToUpgradeBackend(*backend);
         if (upgraded) {
-          upgradedBackends.insert(backend.d_ds);
+          upgradedBackends.insert(backend->d_ds);
           backendIt = upgradeables.erase(backendIt);
           continue;
         }
@@ -524,14 +527,14 @@ void ServiceDiscovery::worker()
         vinfolog("Exception in the Service Discovery thread");
       }
 
-      backend.d_nextCheck = now + backend.d_interval;
+      backend->d_nextCheck = now + backend->d_interval;
       ++backendIt;
     }
 
     {
       auto backends = s_upgradeableBackends.lock();
       for (auto it = backends->begin(); it != backends->end();) {
-        if (upgradedBackends.count(it->d_ds) != 0) {
+        if (upgradedBackends.count((*it)->d_ds) != 0) {
           it = backends->erase(it);
         }
         else {
@@ -555,6 +558,6 @@ bool ServiceDiscovery::run()
   return true;
 }
 
-LockGuarded<std::vector<ServiceDiscovery::UpgradeableBackend>> ServiceDiscovery::s_upgradeableBackends;
+LockGuarded<std::vector<std::shared_ptr<ServiceDiscovery::UpgradeableBackend>>> ServiceDiscovery::s_upgradeableBackends;
 std::thread ServiceDiscovery::s_thread;
 }
index f516a9524c75a07674eb6d92fa2c17b3a73450cc..ceb2d2cafe09c8ba0ae39006da4d6de3635a25c3 100644 (file)
@@ -71,7 +71,7 @@ private:
 
   static void worker();
 
-  static LockGuarded<std::vector<UpgradeableBackend>> s_upgradeableBackends;
+  static LockGuarded<std::vector<std::shared_ptr<UpgradeableBackend>>> s_upgradeableBackends;
   static std::thread s_thread;
 };
 
diff --git a/pdns/dnsdistdist/dnsdist-dnsparser.cc b/pdns/dnsdistdist/dnsdist-dnsparser.cc
new file mode 100644 (file)
index 0000000..a15f2d5
--- /dev/null
@@ -0,0 +1,217 @@
+/*
+ * 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 "dnsparser.hh"
+
+namespace dnsdist
+{
+DNSPacketOverlay::DNSPacketOverlay(const std::string_view& packet)
+{
+  if (packet.size() < sizeof(dnsheader)) {
+    throw std::runtime_error("Packet is too small for a DNS packet");
+  }
+
+  memcpy(&d_header, packet.data(), sizeof(dnsheader));
+  uint64_t numRecords = ntohs(d_header.ancount) + ntohs(d_header.nscount) + ntohs(d_header.arcount);
+  d_records.reserve(numRecords);
+
+  try {
+    PacketReader reader(std::string_view(reinterpret_cast<const char*>(packet.data()), packet.size()));
+
+    for (uint16_t n = 0; n < ntohs(d_header.qdcount); ++n) {
+      reader.xfrName(d_qname);
+      reader.xfrType(d_qtype);
+      reader.xfrType(d_qclass);
+    }
+
+    for (uint64_t n = 0; n < numRecords; ++n) {
+      Record rec;
+      reader.xfrName(rec.d_name);
+      rec.d_place = n < ntohs(d_header.ancount) ? DNSResourceRecord::ANSWER : (n < (ntohs(d_header.ancount) + ntohs(d_header.nscount)) ? DNSResourceRecord::AUTHORITY : DNSResourceRecord::ADDITIONAL);
+      reader.xfrType(rec.d_type);
+      reader.xfrType(rec.d_class);
+      reader.xfr32BitInt(rec.d_ttl);
+      reader.xfr16BitInt(rec.d_contentLength);
+      rec.d_contentOffset = reader.getPosition();
+      reader.skip(rec.d_contentLength);
+      d_records.push_back(std::move(rec));
+    }
+  }
+  catch (const std::exception& e) {
+    throw std::runtime_error("Unable to parse DNS packet: " + std::string(e.what()));
+  }
+  catch (...) {
+    throw std::runtime_error("Unable to parse DNS packet");
+  }
+}
+
+bool changeNameInDNSPacket(PacketBuffer& initialPacket, const DNSName& from, const DNSName& to)
+{
+  if (initialPacket.size() < sizeof(dnsheader)) {
+    return false;
+  }
+
+  PacketReader pr(std::string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()));
+
+  dnsheader dh;
+  memcpy(&dh, initialPacket.data(), sizeof(dh));
+  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;
+
+  size_t recordsCount = ancount + nscount + arcount;
+  struct dnsrecordheader ah;
+
+  rrname = pr.getName();
+  if (rrname == from) {
+    rrname = to;
+  }
+
+  rrtype = pr.get16BitInt();
+  rrclass = pr.get16BitInt();
+
+  PacketBuffer newContent;
+  newContent.reserve(initialPacket.size());
+  GenericDNSPacketWriter<PacketBuffer> pw(newContent, rrname, rrtype, rrclass, dh.opcode);
+  /* we want to copy the flags and ID but not the counts since we recreate the records below */
+  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, but do not copy it */
+  for (idx = 1; idx < qdcount; idx++) {
+    rrname = pr.getName();
+    (void)pr.get16BitInt();
+    (void)pr.get16BitInt();
+  }
+
+  static const std::unordered_set<QType> nameOnlyTypes{QType::NS, QType::PTR, QType::CNAME, QType::DNAME};
+  static const std::unordered_set<QType> noNameTypes{QType::A, QType::AAAA, QType::DHCID, QType::TXT, QType::OPT, QType::HINFO, QType::DNSKEY, QType::CDNSKEY, QType::DS, QType::CDS, QType::DLV, QType::SSHFP, QType::KEY, QType::CERT, QType::TLSA, QType::SMIMEA, QType::OPENPGPKEY, QType::NSEC, QType::NSEC3, QType::CSYNC, QType::NSEC3PARAM, QType::LOC, QType::NID, QType::L32, QType::L64, QType::EUI48, QType::EUI64, QType::URI, QType::CAA};
+
+  /* copy AN, NS and AR */
+  for (idx = 0; idx < recordsCount; idx++) {
+    rrname = pr.getName();
+    if (rrname == from) {
+      rrname = to;
+    }
+    pr.getDnsrecordheader(ah);
+
+    auto place = idx < ancount ? DNSResourceRecord::ANSWER : (idx < (ancount + nscount) ? DNSResourceRecord::AUTHORITY : DNSResourceRecord::ADDITIONAL);
+    pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, place, true);
+    if (nameOnlyTypes.count(ah.d_type)) {
+      rrname = pr.getName();
+      pw.xfrName(rrname);
+    }
+    else if (noNameTypes.count(ah.d_type)) {
+      pr.xfrBlob(blob);
+      pw.xfrBlob(blob);
+    }
+    else if (ah.d_type == QType::RRSIG) {
+      /* good luck */
+      pr.xfrBlob(blob);
+      pw.xfrBlob(blob);
+    }
+    else if (ah.d_type == QType::MX) {
+      auto prio = pr.get16BitInt();
+      rrname = pr.getName();
+      pw.xfr16BitInt(prio);
+      pw.xfrName(rrname);
+    }
+    else if (ah.d_type == QType::SOA) {
+      auto mname = pr.getName();
+      pw.xfrName(mname);
+      auto rname = pr.getName();
+      pw.xfrName(rname);
+      /* serial */
+      pw.xfr32BitInt(pr.get32BitInt());
+      /* refresh */
+      pw.xfr32BitInt(pr.get32BitInt());
+      /* retry */
+      pw.xfr32BitInt(pr.get32BitInt());
+      /* expire */
+      pw.xfr32BitInt(pr.get32BitInt());
+      /* minimal */
+      pw.xfr32BitInt(pr.get32BitInt());
+    }
+    else if (ah.d_type == QType::SRV) {
+      /* preference */
+      pw.xfr16BitInt(pr.get16BitInt());
+      /* weight */
+      pw.xfr16BitInt(pr.get16BitInt());
+      /* port */
+      pw.xfr16BitInt(pr.get16BitInt());
+      auto target = pr.getName();
+      pw.xfrName(target);
+    }
+    else {
+      /* sorry, unsafe type */
+      return false;
+    }
+  }
+
+  pw.commit();
+  initialPacket = std::move(newContent);
+
+  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;
+  }
+}
+}
diff --git a/pdns/dnsdistdist/dnsdist-dnsparser.hh b/pdns/dnsdistdist/dnsdist-dnsparser.hh
new file mode 100644 (file)
index 0000000..4f7cdad
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * 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 "dnsparser.hh"
+
+namespace dnsdist
+{
+class DNSPacketOverlay
+{
+public:
+  DNSPacketOverlay(const std::string_view& packet);
+
+  struct Record
+  {
+    DNSName d_name;
+    uint32_t d_ttl;
+    uint16_t d_type;
+    uint16_t d_class;
+    uint16_t d_contentLength;
+    uint16_t d_contentOffset;
+    DNSResourceRecord::Place d_place;
+  };
+
+  DNSName d_qname;
+  std::vector<Record> d_records;
+  uint16_t d_qtype;
+  uint16_t d_qclass;
+  dnsheader d_header;
+};
+
+/* Rewrite, if they are exactly equal to 'from', the qname and owner name of any record
+ * to 'to'. Since that might break DNS name pointers, the whole payload is rewritten,
+ * and the operation may fail if there is at least one unsupported record in the payload,
+ * 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 120000 (symlink)
index 0000000..5692084
--- /dev/null
@@ -0,0 +1 @@
+../dnsdist-doh-common.hh
\ No newline at end of file
diff --git a/pdns/dnsdistdist/dnsdist-downstream-connection.hh b/pdns/dnsdistdist/dnsdist-downstream-connection.hh
new file mode 100644 (file)
index 0000000..f13cbcf
--- /dev/null
@@ -0,0 +1,316 @@
+#pragma once
+
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+#include <boost/multi_index/key_extractors.hpp>
+
+#include "tcpiohandler-mplexer.hh"
+#include "dnsdist-tcp.hh"
+
+template <class T>
+class DownstreamConnectionsManager
+{
+  struct SequencedTag
+  {
+  };
+  struct OrderedTag
+  {
+  };
+
+  typedef multi_index_container<
+    std::shared_ptr<T>,
+    indexed_by<
+      ordered_unique<tag<OrderedTag>,
+                     identity<std::shared_ptr<T>>>,
+      /* new elements are added to the front of the sequence */
+      sequenced<tag<SequencedTag>>>>
+    list_t;
+  struct ConnectionLists
+  {
+    list_t d_actives;
+    list_t d_idles;
+  };
+
+public:
+  static void setMaxIdleConnectionsPerDownstream(size_t max)
+  {
+    s_maxIdleConnectionsPerDownstream = max;
+  }
+
+  static void setCleanupInterval(uint16_t interval)
+  {
+    s_cleanupInterval = interval;
+  }
+
+  static void setMaxIdleTime(uint16_t max)
+  {
+    s_maxIdleTime = max;
+  }
+
+  std::shared_ptr<T> getConnectionToDownstream(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& ds, const struct timeval& now, std::string&& proxyProtocolPayload)
+  {
+    struct timeval freshCutOff = now;
+    freshCutOff.tv_sec -= 1;
+
+    auto backendId = ds->getID();
+
+    cleanupClosedConnections(now);
+
+    const bool haveProxyProtocol = ds->d_config.useProxyProtocol || !proxyProtocolPayload.empty();
+    if (!haveProxyProtocol) {
+      const auto& it = d_downstreamConnections.find(backendId);
+      if (it != d_downstreamConnections.end()) {
+        /* first scan idle connections, more recent first */
+        auto entry = findUsableConnectionInList(now, freshCutOff, it->second.d_idles, true);
+        if (entry) {
+          ++ds->tcpReusedConnections;
+          it->second.d_actives.insert(entry);
+          return entry;
+        }
+
+        /* then scan actives ones, more recent first as well */
+        entry = findUsableConnectionInList(now, freshCutOff, it->second.d_actives, false);
+        if (entry) {
+          ++ds->tcpReusedConnections;
+          return entry;
+        }
+      }
+    }
+
+    if (ds->d_config.d_tcpConcurrentConnectionsLimit > 0 && ds->tcpCurrentConnections.load() >= ds->d_config.d_tcpConcurrentConnectionsLimit) {
+      ++ds->tcpTooManyConcurrentConnections;
+      throw std::runtime_error("Maximum number of TCP connections to " + ds->getNameWithAddr() + " reached, not creating a new one");
+    }
+
+    auto newConnection = std::make_shared<T>(ds, mplexer, now, std::move(proxyProtocolPayload));
+    // might make sense to check whether max in flight > 0?
+    if (!haveProxyProtocol) {
+      auto& list = d_downstreamConnections[backendId].d_actives;
+      list.template get<SequencedTag>().push_front(newConnection);
+    }
+
+    return newConnection;
+  }
+
+  void cleanupClosedConnections(const struct timeval& now)
+  {
+    if (s_cleanupInterval == 0 || (d_nextCleanup != 0 && d_nextCleanup > now.tv_sec)) {
+      return;
+    }
+
+    d_nextCleanup = now.tv_sec + s_cleanupInterval;
+
+    struct timeval freshCutOff = now;
+    freshCutOff.tv_sec -= 1;
+    struct timeval idleCutOff = now;
+    idleCutOff.tv_sec -= s_maxIdleTime;
+
+    for (auto dsIt = d_downstreamConnections.begin(); dsIt != d_downstreamConnections.end();) {
+      cleanUpList(dsIt->second.d_idles, now, freshCutOff, idleCutOff);
+      cleanUpList(dsIt->second.d_actives, now, freshCutOff, idleCutOff);
+
+      if (dsIt->second.d_idles.empty() && dsIt->second.d_actives.empty()) {
+        dsIt = d_downstreamConnections.erase(dsIt);
+      }
+      else {
+        ++dsIt;
+      }
+    }
+  }
+
+  size_t clear()
+  {
+    size_t count = 0;
+    for (const auto& downstream : d_downstreamConnections) {
+      count += downstream.second.d_actives.size();
+      for (auto& conn : downstream.second.d_actives) {
+        conn->stopIO();
+      }
+      count += downstream.second.d_idles.size();
+      for (auto& conn : downstream.second.d_idles) {
+        conn->stopIO();
+      }
+    }
+
+    d_downstreamConnections.clear();
+    return count;
+  }
+
+  size_t count() const
+  {
+    return getActiveCount() + getIdleCount();
+  }
+
+  size_t getActiveCount() const
+  {
+    size_t count = 0;
+    for (const auto& downstream : d_downstreamConnections) {
+      count += downstream.second.d_actives.size();
+    }
+    return count;
+  }
+
+  size_t getIdleCount() const
+  {
+    size_t count = 0;
+    for (const auto& downstream : d_downstreamConnections) {
+      count += downstream.second.d_idles.size();
+    }
+    return count;
+  }
+
+  bool removeDownstreamConnection(std::shared_ptr<T>& conn)
+  {
+    auto backendIt = d_downstreamConnections.find(conn->getDS()->getID());
+    if (backendIt == d_downstreamConnections.end()) {
+      return false;
+    }
+
+    /* idle list first */
+    {
+      auto it = backendIt->second.d_idles.find(conn);
+      if (it != backendIt->second.d_idles.end()) {
+        backendIt->second.d_idles.erase(it);
+        return true;
+      }
+    }
+    /* then active */
+    {
+      auto it = backendIt->second.d_actives.find(conn);
+      if (it != backendIt->second.d_actives.end()) {
+        backendIt->second.d_actives.erase(it);
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  bool moveToIdle(std::shared_ptr<T>& conn)
+  {
+    auto backendIt = d_downstreamConnections.find(conn->getDS()->getID());
+    if (backendIt == d_downstreamConnections.end()) {
+      return false;
+    }
+
+    auto it = backendIt->second.d_actives.find(conn);
+    if (it == backendIt->second.d_actives.end()) {
+      return false;
+    }
+
+    backendIt->second.d_actives.erase(it);
+
+    if (backendIt->second.d_idles.size() >= s_maxIdleConnectionsPerDownstream) {
+      auto old = backendIt->second.d_idles.template get<SequencedTag>().back();
+      old->release();
+      backendIt->second.d_idles.template get<SequencedTag>().pop_back();
+    }
+
+    backendIt->second.d_idles.template get<SequencedTag>().push_front(conn);
+    return true;
+  }
+
+protected:
+  void cleanUpList(list_t& list, const struct timeval& now, const struct timeval& freshCutOff, const struct timeval& idleCutOff)
+  {
+    auto& sidx = list.template get<SequencedTag>();
+    for (auto connIt = sidx.begin(); connIt != sidx.end();) {
+      if (!(*connIt)) {
+        connIt = sidx.erase(connIt);
+        continue;
+      }
+
+      auto& entry = *connIt;
+
+      /* don't bother checking freshly used connections */
+      if (freshCutOff < entry->getLastDataReceivedTime()) {
+        ++connIt;
+        continue;
+      }
+
+      if (entry->isIdle() && entry->getLastDataReceivedTime() < idleCutOff) {
+        /* idle for too long */
+        (*connIt)->release();
+        connIt = sidx.erase(connIt);
+        continue;
+      }
+
+      if (entry->isUsable()) {
+        ++connIt;
+        continue;
+      }
+
+      if (entry->isIdle()) {
+        (*connIt)->release();
+      }
+      connIt = sidx.erase(connIt);
+    }
+  }
+
+  std::shared_ptr<T> findUsableConnectionInList(const struct timeval& now, const struct timeval& freshCutOff, list_t& list, bool removeIfFound)
+  {
+    auto& sidx = list.template get<SequencedTag>();
+    for (auto listIt = sidx.begin(); listIt != sidx.end();) {
+      if (!(*listIt)) {
+        listIt = sidx.erase(listIt);
+        continue;
+      }
+
+      auto& entry = *listIt;
+      if (isConnectionUsable(entry, now, freshCutOff)) {
+        entry->setReused();
+        // make a copy since the iterator will be invalidated after erasing
+        auto result = entry;
+        if (removeIfFound) {
+          sidx.erase(listIt);
+        }
+        return result;
+      }
+
+      if (entry->willBeReusable(false)) {
+        ++listIt;
+        continue;
+      }
+
+      /* that connection will not be usable later, no need to keep it in that list */
+      listIt = sidx.erase(listIt);
+    }
+
+    return nullptr;
+  }
+
+  bool isConnectionUsable(const std::shared_ptr<T>& conn, const struct timeval& now, const struct timeval& freshCutOff)
+  {
+    if (!conn->canBeReused()) {
+      return false;
+    }
+
+    /* for connections that have not been used very recently,
+       check whether they have been closed in the meantime */
+    if (freshCutOff < conn->getLastDataReceivedTime()) {
+      /* used recently enough, skip the check */
+      return true;
+    }
+
+    return conn->isUsable();
+  }
+
+  static size_t s_maxIdleConnectionsPerDownstream;
+  static uint16_t s_cleanupInterval;
+  static uint16_t s_maxIdleTime;
+
+  std::map<boost::uuids::uuid, ConnectionLists> d_downstreamConnections;
+
+  time_t d_nextCleanup{0};
+};
+
+template <class T>
+size_t DownstreamConnectionsManager<T>::s_maxIdleConnectionsPerDownstream{10};
+template <class T>
+uint16_t DownstreamConnectionsManager<T>::s_cleanupInterval{60};
+template <class T>
+uint16_t DownstreamConnectionsManager<T>::s_maxIdleTime{300};
+
+using DownstreamTCPConnectionsManager = DownstreamConnectionsManager<TCPConnectionToBackend>;
+extern thread_local DownstreamTCPConnectionsManager t_downstreamTCPConnectionsManager;
index 3aa007830822d953f8485c53ffa8268894776e2a..b7baca2be7154118fd49b07e33b9becaccc03623 100644 (file)
@@ -1,7 +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;
@@ -23,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) {
@@ -48,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;
 
@@ -102,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));
+    }
   }
 }
 
@@ -167,47 +197,51 @@ 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);
 }
 
-void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange> >& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated, bool warning)
+/* return the actual action that will be taken by that block:
+   - either the one set on that block, if any
+   - or the one set with setDynBlocksAction in g_dynBlockAction
+*/
+static DNSAction::Action getActualAction(const DynBlock& block)
 {
-  /* network exclusions are address-based only (no port) */
-  if (d_excludedSubnets.match(requestor.getNetwork())) {
-    /* do not add a block for excluded subnets */
-    return;
+  if (block.action != DNSAction::Action::None) {
+    return block.action;
   }
+  return g_dynBlockAction;
+}
 
-  if (!blocks) {
-    blocks = g_dynblockNMG.getCopy();
-  }
-  struct timespec until = now;
-  until.tv_sec += rule.d_blockDuration;
+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)
+{
   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;
       }
     }
 
@@ -220,18 +254,16 @@ 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) {
-    if (g_defaultBPFFilter &&
-        ((requestor.isIPv4() && requestor.getBits() == 32) || (requestor.isIPv6() && requestor.getBits() == 128)) &&
-        (db.action == DNSAction::Action::Drop || db.action == 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 action = db.action == DNSAction::Action::Drop ? BPFFilter::MatchAction::Drop : BPFFilter::MatchAction::Truncate;
-        if (g_defaultBPFFilter->supportsMatchAction(action)) {
+        BPFFilter::MatchAction bpfAction = actualAction == DNSAction::Action::Drop ? BPFFilter::MatchAction::Drop : BPFFilter::MatchAction::Truncate;
+        if (g_defaultBPFFilter->supportsMatchAction(bpfAction)) {
           /* the current BPF filter implementation only supports full addresses (/32 or /128) and no port */
-          g_defaultBPFFilter->block(requestor.getNetwork(), action);
+          g_defaultBPFFilter->block(requestor.getNetwork(), bpfAction);
           bpf = true;
         }
       }
@@ -240,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) {
@@ -286,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 (!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;
+  }
 
-  if (!d_beQuiet && (!got || expired)) {
-    warnlog("Inserting dynamic block for %s for %d seconds: %s", name, rule.d_blockDuration, rule.d_blockReason);
+  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)
@@ -311,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];
         }
       }
     }
@@ -353,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;
@@ -370,34 +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) {
-        root.submit(c.name, ((c.dh.rcode == 0 && c.usec == std::numeric_limits<unsigned int>::max()) ? -1 : c.dh.rcode), c.size, 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);
       }
     }
   }
@@ -405,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;
@@ -412,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 {
-            g_defaultBPFFilter->unblock(entry.first.getNetwork());
+            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(network);
           }
           catch (const std::exception& e) {
             vinfolog("Error while removing eBPF dynamic block for %s: %s", entry.first.toString(), e.what());
@@ -427,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;
     }
   }
 
@@ -471,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);
     }
   }
 
@@ -498,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);
     }
   });
 
@@ -510,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};
 };
 
@@ -541,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) {
@@ -561,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) {
@@ -581,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);
         }
       }
     }
@@ -621,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) {
@@ -646,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);
         }
       }
     }
@@ -690,7 +780,8 @@ void DynBlockMaintenance::run()
     sleepDelay = std::min(sleepDelay, (nextMetricsCollect - now));
     sleepDelay = std::min(sleepDelay, (nextMetricsGeneration - now));
 
-    sleep(sleepDelay);
+    // coverity[store_truncates_time_t]
+    std::this_thread::sleep_for(std::chrono::seconds(sleepDelay));
 
     try {
       now = time(nullptr);
@@ -712,7 +803,9 @@ void DynBlockMaintenance::run()
       }
 
       if (s_expiredDynBlocksPurgeInterval > 0 && now >= nextExpiredPurge) {
-        struct timespec tspec;
+        struct timespec tspec
+        {
+        };
         gettime(&tspec);
         purgeExpired(tspec);
 
@@ -737,3 +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 */
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 73%
rename from pdns/rec-snmp.hh
rename to pdns/dnsdistdist/dnsdist-edns.hh
index 7f067211c42044ae245909d1b4e8533b7bd7ab02..8e60e5b049dc1162465fbe1e20e355628ec74045 100644 (file)
  */
 #pragma once
 
-#include "snmp-agent.hh"
+#include <optional>
+#include <string>
+#include <utility>
 
-class RecursorSNMPAgent;
+#include "noinitvector.hh"
 
-class RecursorSNMPAgent : public SNMPAgent
+namespace dnsdist::edns
 {
-public:
-  RecursorSNMPAgent(const std::string& name, const std::string& daemonSocket);
-  bool sendCustomTrap(const std::string& reason);
-};
-
-extern std::shared_ptr<RecursorSNMPAgent> g_snmpAgent;
+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 64efdf534182e42e8efcef867753f794772613e8..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;
@@ -55,123 +64,74 @@ struct HealthCheckData
   bool d_initial{false};
 };
 
-void updateHealthCheckResult(const std::shared_ptr<DownstreamState>& dss, bool initial, bool newState)
-{
-  if (initial) {
-    warnlog("Marking downstream %s as '%s'", dss->getNameWithAddr(), newState ? "up" : "down");
-    dss->setUpStatus(newState);
-    return;
-  }
-
-  if (newState) {
-    /* check succeeded */
-    dss->currentCheckFailures = 0;
-
-    if (!dss->upStatus) {
-      /* we were marked as down */
-      dss->consecutiveSuccessfulChecks++;
-      if (dss->consecutiveSuccessfulChecks < dss->d_config.minRiseSuccesses) {
-        /* if we need more than one successful check to rise
-           and we didn't reach the threshold yet,
-           let's stay down */
-        newState = false;
-      }
-    }
-  }
-  else {
-    /* check failed */
-    dss->consecutiveSuccessfulChecks = 0;
-
-    if (dss->upStatus) {
-      /* we are currently up */
-      dss->currentCheckFailures++;
-      if (dss->currentCheckFailures < dss->d_config.maxCheckFailures) {
-        /* we need more than one failure to be marked as down,
-           and we did not reach the threshold yet, let's stay up */
-        newState = true;
-      }
-    }
-  }
-
-  if (newState != dss->upStatus) {
-    warnlog("Marking downstream %s as '%s'", dss->getNameWithAddr(), newState ? "up" : "down");
-
-    if (newState && !dss->isTCPOnly() && (!dss->connected || dss->d_config.reconnectOnUp)) {
-      newState = dss->reconnect();
-      dss->start();
-    }
-
-    dss->setUpStatus(newState);
-    dss->currentCheckFailures = 0;
-    dss->consecutiveSuccessfulChecks = 0;
-    if (g_snmpAgent && g_snmpTrapsEnabled) {
-      g_snmpAgent->sendBackendStatusChangeTrap(dss);
-    }
-  }
-}
-
 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;
   }
@@ -182,28 +142,25 @@ 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;
   }
 
-  const ClientState* getClientState() const override
-  {
-    return nullptr;
-  }
-
   void handleResponse(const struct timeval& now, TCPResponse&& response) override
   {
     d_data->d_buffer = std::move(response.d_buffer);
-    updateHealthCheckResult(d_data->d_ds, d_data->d_initial, ::handleResponse(d_data));
+    d_data->d_ds->submitHealthCheckResult(d_data->d_initial, ::handleResponse(d_data));
   }
 
   void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override
@@ -211,46 +168,67 @@ public:
     throw std::runtime_error("Unexpected XFR reponse to a health check query");
   }
 
-  void notifyIOError(IDState&& query, const struct timeval& now) override
+  void notifyIOError(const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
   {
-    updateHealthCheckResult(d_data->d_ds, d_data->d_initial, false);
+    ++d_data->d_ds->d_healthCheckMetrics.d_networkErrors;
+    d_data->d_ds->submitHealthCheckResult(d_data->d_initial, false);
   }
 
 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;
     }
-    updateHealthCheckResult(data->d_ds, 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());
     }
-    updateHealthCheckResult(data->d_ds, data->d_initial, false);
+    ++data->d_ds->d_healthCheckMetrics.d_networkErrors;
+    data->d_ds->submitHealthCheckResult(data->d_initial, false);
     return;
   }
 
-  updateHealthCheckResult(data->d_ds, data->d_initial, handleResponse(data));
+  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);
 
@@ -271,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;
       }
@@ -281,7 +259,7 @@ static void healthCheckTCPCallback(int fd, FDMultiplexer::funcparam_t& param)
     if (data->d_tcpState == HealthCheckData::TCPState::ReadingResponse) {
       ioState = data->d_tcpHandler->tryRead(data->d_buffer, data->d_bufferPos, data->d_buffer.size());
       if (ioState == IOState::Done) {
-        updateHealthCheckResult(data->d_ds, data->d_initial, handleResponse(data));
+        data->d_ds->submitHealthCheckResult(data->d_initial, handleResponse(data));
       }
     }
 
@@ -308,41 +286,41 @@ static void healthCheckTCPCallback(int fd, FDMultiplexer::funcparam_t& param)
     ioGuard.release();
   }
   catch (const std::exception& e) {
-    updateHealthCheckResult(data->d_ds, data->d_initial, false);
+    ++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());
     }
   }
   catch (...) {
-    updateHealthCheckResult(data->d_ds, data->d_initial, false);
+    data->d_ds->submitHealthCheckResult(data->d_initial, false);
     if (g_verboseHealthChecks) {
       infolog("Unknown exception while checking the health of backend %s", data->d_ds->getNameWithAddr());
     }
   }
 }
 
-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);
@@ -357,88 +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();
-    if (!IsAnyAddress(ds->d_config.sourceAddr)) {
-      sock.setReuseAddr();
-#ifdef IP_BIND_ADDRESS_NO_PORT
-      if (ds->d_config.ipBindAddrNoPort) {
-        SSetsockopt(sock.getHandle(), SOL_IP, IP_BIND_ADDRESS_NO_PORT, 1);
-      }
-#endif
 
-      if (!ds->d_config.sourceItfName.empty()) {
 #ifdef SO_BINDTODEVICE
-        int res = setsockopt(sock.getHandle(), SOL_SOCKET, SO_BINDTODEVICE, ds->d_config.sourceItfName.c_str(), ds->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());
-        }
+    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", downstream->getNameWithAddr(), stringerror());
+      }
+    }
 #endif
+
+    if (!IsAnyAddress(downstream->d_config.sourceAddr)) {
+      if (downstream->doHealthcheckOverTCP()) {
+        sock.setReuseAddr();
+      }
+#ifdef IP_BIND_ADDRESS_NO_PORT
+      if (downstream->d_config.ipBindAddrNoPort) {
+        SSetsockopt(sock.getHandle(), SOL_IP, IP_BIND_ADDRESS_NO_PORT, 1);
       }
-      sock.bind(ds->d_config.sourceAddr);
+#endif
+      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 */
-    if (data->d_ttd.tv_usec > 1000000) {
-      ++data->d_ttd.tv_sec;
-      data->d_ttd.tv_usec -= 1000000;
-    }
+    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()) {
-      InternalQuery query(std::move(packet), IDState());
+#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)) {
-        updateHealthCheckResult(data->d_ds, data->d_initial, false);
+      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, now);
+      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());
@@ -454,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;
   }
@@ -473,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) {
@@ -481,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) {
@@ -492,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();
         }
@@ -499,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());
         }
 
-        updateHealthCheckResult(data->d_ds, initial, false);
+        ++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());
         }
       }
     }
@@ -523,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());
         }
 
-        updateHealthCheckResult(data->d_ds, initial, false);
+        ++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 04af75b0dbdfb0eed2418430bfcf3892b16b9aa8..e9da6c66de8b0eb56baadb0c0fc57e78461822d0 100644 (file)
@@ -27,7 +27,5 @@
 
 extern bool g_verboseHealthChecks;
 
-void updateHealthCheckResult(const std::shared_ptr<DownstreamState>& dss, bool initial, bool newState);
-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);
diff --git a/pdns/dnsdistdist/dnsdist-idstate.cc b/pdns/dnsdistdist/dnsdist-idstate.cc
deleted file mode 100644 (file)
index 286808c..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-
-#include "dnsdist.hh"
-
-DNSResponse makeDNSResponseFromIDState(IDState& ids, PacketBuffer& data)
-{
-  DNSResponse dr(&ids.qname, ids.qtype, ids.qclass, &ids.origDest, &ids.origRemote, data, ids.protocol, &ids.sentTime.d_start);
-  dr.origFlags = ids.origFlags;
-  dr.cacheFlags = ids.cacheFlags;
-  dr.ecsAdded = ids.ecsAdded;
-  dr.ednsAdded = ids.ednsAdded;
-  dr.useZeroScope = ids.useZeroScope;
-  dr.packetCache = std::move(ids.packetCache);
-  dr.delayMsec = ids.delayMsec;
-  dr.skipCache = ids.skipCache;
-  dr.cacheKey = ids.cacheKey;
-  dr.cacheKeyNoECS = ids.cacheKeyNoECS;
-  dr.cacheKeyUDP = ids.cacheKeyUDP;
-  dr.dnssecOK = ids.dnssecOK;
-  dr.tempFailureTTL = ids.tempFailureTTL;
-  dr.qTag = std::move(ids.qTag);
-  dr.subnet = std::move(ids.subnet);
-  dr.uniqueId = std::move(ids.uniqueId);
-
-  if (ids.dnsCryptQuery) {
-    dr.dnsCryptQuery = std::move(ids.dnsCryptQuery);
-  }
-
-  dr.hopRemote = &ids.hopRemote;
-  dr.hopLocal = &ids.hopLocal;
-
-  return dr;
-}
-
-void setIDStateFromDNSQuestion(IDState& ids, DNSQuestion& dq, DNSName&& qname)
-{
-  ids.origRemote = *dq.remote;
-  ids.origDest = *dq.local;
-  ids.sentTime.set(*dq.queryTime);
-  ids.qname = std::move(qname);
-  ids.qtype = dq.qtype;
-  ids.qclass = dq.qclass;
-  ids.protocol = dq.protocol;
-  ids.delayMsec = dq.delayMsec;
-  ids.tempFailureTTL = dq.tempFailureTTL;
-  ids.origFlags = dq.origFlags;
-  ids.cacheFlags = dq.cacheFlags;
-  ids.cacheKey = dq.cacheKey;
-  ids.cacheKeyNoECS = dq.cacheKeyNoECS;
-  ids.cacheKeyUDP = dq.cacheKeyUDP;
-  ids.subnet = dq.subnet;
-  ids.skipCache = dq.skipCache;
-  ids.packetCache = dq.packetCache;
-  ids.ednsAdded = dq.ednsAdded;
-  ids.ecsAdded = dq.ecsAdded;
-  ids.useZeroScope = dq.useZeroScope;
-  ids.qTag = std::move(dq.qTag);
-  ids.dnssecOK = dq.dnssecOK;
-  ids.uniqueId = std::move(dq.uniqueId);
-
-  if (dq.hopRemote) {
-    ids.hopRemote = *dq.hopRemote;
-  }
-  else {
-    ids.hopRemote.sin4.sin_family = 0;
-  }
-
-  if (dq.hopLocal) {
-    ids.hopLocal = *dq.hopLocal;
-  }
-  else {
-    ids.hopLocal.sin4.sin_family = 0;
-  }
-
-  ids.dnsCryptQuery = std::move(dq.dnsCryptQuery);
-}
diff --git a/pdns/dnsdistdist/dnsdist-internal-queries.cc b/pdns/dnsdistdist/dnsdist-internal-queries.cc
new file mode 100644 (file)
index 0000000..b707fef
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * 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-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)
+{
+  auto protocol = dq.getProtocol();
+  if (protocol == dnsdist::Protocol::DoUDP || protocol == dnsdist::Protocol::DNSCryptUDP) {
+    return getUDPCrossProtocolQueryFromDQ(dq);
+  }
+#ifdef HAVE_DNS_OVER_HTTPS
+  else if (protocol == dnsdist::Protocol::DoH) {
+#ifdef HAVE_LIBH2OEVLOOP
+    if (dq.ids.cs->dohFrontend->d_library == "h2o") {
+      return getDoHCrossProtocolQueryFromDQ(dq, isResponse);
+    }
+#endif /* HAVE_LIBH2OEVLOOP */
+    return getTCPCrossProtocolQueryFromDQ(dq);
+  }
+#endif
+#ifdef HAVE_DNS_OVER_QUIC
+  else if (protocol == dnsdist::Protocol::DoQ) {
+    return getDOQCrossProtocolQueryFromDQ(dq, isResponse);
+  }
+#endif
+#ifdef HAVE_DNS_OVER_HTTP3
+  else if (protocol == dnsdist::Protocol::DoH3) {
+    return getDOH3CrossProtocolQueryFromDQ(dq, isResponse);
+  }
+#endif
+  else {
+    return getTCPCrossProtocolQueryFromDQ(dq);
+  }
+}
+}
similarity index 85%
rename from pdns/secpoll-recursor.hh
rename to pdns/dnsdistdist/dnsdist-internal-queries.hh
index 3a06d20e1c1a24b27e6e51eb7f67efd1377d48e1..46634aa11a8b47a6a6239d07c97115428ed2c4fc 100644 (file)
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #pragma once
-#include <time.h>
-#include "namespaces.hh"
-#include <stdint.h>
 
-void doSecPoll(time_t*);
-extern uint32_t g_security_status;
-extern std::string g_security_message;
+#include <memory>
+#include "dnsdist.hh"
+
+namespace dnsdist
+{
+std::unique_ptr<CrossProtocolQuery> getInternalQueryFromDQ(DNSQuestion& dq, 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 9f9392cdc118bb2506afe718d65933de38677850..764a45c35c07532d2cae06b68681b06e426b248e 100644 (file)
@@ -44,7 +44,7 @@ public:
 
   std::vector<std::string> getKeys(const DNSQuestion& dq) override
   {
-    return getKeys(*dq.remote);
+    return getKeys(dq.ids.origRemote);
   }
 
   std::string toString() const override
@@ -75,7 +75,7 @@ public:
 
   std::vector<std::string> getKeys(const DNSQuestion& dq) override
   {
-    return getKeys(*dq.qname);
+    return getKeys(dq.ids.qname);
   }
 
   std::string toString() const override
@@ -101,7 +101,7 @@ public:
 
   std::vector<std::string> getKeys(const DNSQuestion& dq) override
   {
-    return getKeys(*dq.qname);
+    return getKeys(dq.ids.qname);
   }
 
   std::string toString() const override
@@ -126,9 +126,9 @@ public:
 
   std::vector<std::string> getKeys(const DNSQuestion& dq) override
   {
-    if (dq.qTag) {
-      const auto& it = dq.qTag->find(d_tag);
-      if (it != dq.qTag->end()) {
+    if (dq.ids.qTag) {
+      const auto& it = dq.ids.qTag->find(d_tag);
+      if (it != dq.ids.qTag->end()) {
         return { it->second };
       }
     }
index eb5a3aa3ae53dcddcc074c937beacb01dca0e80e..17f2f5238e4e15698cbbeab341bb754c62f5d014 100644 (file)
 #include "dnsdist-lua.hh"
 #include "dnsdist-lua-ffi.hh"
 #include "dolog.hh"
+#include "dns_random.hh"
 
 GlobalStateHolder<ServerPolicy> g_policy;
 bool g_roundrobinFailOnNoServer{false};
 
-// get server with least outstanding queries, and within those, with the lowest order, and within those: the fastest
-shared_ptr<DownstreamState> leastOutstanding(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq)
-{
-  if (servers.size() == 1 && servers[0].second->isUp()) {
-    return servers[0].second;
-  }
+static constexpr size_t s_staticArrayCutOff = 16;
+template <typename T> using DynamicIndexArray = std::vector<std::pair<T, size_t>>;
+template <typename T> using StaticIndexArray = std::array<std::pair<T, size_t>, s_staticArrayCutOff>;
 
-  vector<pair<std::tuple<int,int,double>, size_t>> poss;
+template <class T> static std::shared_ptr<DownstreamState> getLeastOutstanding(const ServerPolicy::NumberedServerVector& servers, T& poss)
+{
   /* so you might wonder, why do we go through this trouble? The data on which we sort could change during the sort,
      which would suck royally and could even lead to crashes. So first we snapshot on what we sort, and then we sort */
-  poss.reserve(servers.size());
-  size_t position = 0;
-  for(const auto& d : servers) {
-    if(d.second->isUp()) {
-      poss.emplace_back(std::make_tuple(d.second->outstanding.load(), d.second->d_config.order, d.second->latencyUsec), position);
+  size_t usableServers = 0;
+  for (const auto& d : servers) {
+    if (d.second->isUp()) {
+      poss.at(usableServers) = std::pair(std::tuple(d.second->outstanding.load(), d.second->d_config.order, d.second->getRelevantLatencyUsec()), d.first);
+      usableServers++;
     }
-    ++position;
   }
 
-  if (poss.empty()) {
+  if (usableServers == 0) {
     return shared_ptr<DownstreamState>();
   }
 
-  nth_element(poss.begin(), poss.begin(), poss.end(), [](const decltype(poss)::value_type& a, const decltype(poss)::value_type& b) { return a.first < b.first; });
-  return servers.at(poss.begin()->second).second;
+  std::nth_element(poss.begin(), poss.begin(), poss.begin() + usableServers, [](const typename T::value_type& a, const typename T::value_type& b) { return a.first < b.first; });
+  // minus 1 because the NumberedServerVector starts at 1 for Lua
+  return servers.at(poss.begin()->second - 1).second;
+}
+
+// get server with least outstanding queries, and within those, with the lowest order, and within those: the fastest
+shared_ptr<DownstreamState> leastOutstanding(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq)
+{
+  using LeastOutstandingType = std::tuple<int,int,double>;
+
+  if (servers.size() == 1 && servers[0].second->isUp()) {
+    return servers[0].second;
+  }
+
+  if (servers.size() <= s_staticArrayCutOff) {
+    StaticIndexArray<LeastOutstandingType> poss;
+    return getLeastOutstanding(servers, poss);
+  }
+
+  DynamicIndexArray<LeastOutstandingType> poss;
+  poss.resize(servers.size());
+  return getLeastOutstanding(servers, poss);
 }
 
 shared_ptr<DownstreamState> firstAvailable(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq)
 {
-  for(auto& d : servers) {
+  for (auto& d : servers) {
     if (d.second->isUp() && d.second->qps.checkOnly()) {
       return d.second;
     }
@@ -68,30 +86,12 @@ shared_ptr<DownstreamState> firstAvailable(const ServerPolicy::NumberedServerVec
 
 double g_weightedBalancingFactor = 0;
 
-static shared_ptr<DownstreamState> valrandom(unsigned int val, const ServerPolicy::NumberedServerVector& servers)
+template <class T> static std::shared_ptr<DownstreamState> getValRandom(const ServerPolicy::NumberedServerVector& servers, T& poss, const unsigned int val, const double targetLoad)
 {
-  vector<pair<int, size_t>> poss;
-  poss.reserve(servers.size());
+  constexpr int max = std::numeric_limits<int>::max();
   int sum = 0;
-  int max = std::numeric_limits<int>::max();
-  double targetLoad = std::numeric_limits<double>::max();
-
-  if (g_weightedBalancingFactor > 0) {
-    /* we start with one, representing the query we are currently handling */
-    double currentLoad = 1;
-    size_t totalWeight = 0;
-    for (const auto& pair : servers) {
-      if (pair.second->isUp()) {
-        currentLoad += pair.second->outstanding;
-        totalWeight += pair.second->d_config.d_weight;
-      }
-    }
-
-    if (totalWeight > 0) {
-      targetLoad = (currentLoad / totalWeight) * g_weightedBalancingFactor;
-    }
-  }
 
+  size_t usableServers = 0;
   for (const auto& d : servers) {      // w=1, w=10 -> 1, 11
     if (d.second->isUp() && (g_weightedBalancingFactor == 0 || (d.second->outstanding <= (targetLoad * d.second->d_config.d_weight)))) {
       // Don't overflow sum when adding high weights
@@ -101,27 +101,60 @@ static shared_ptr<DownstreamState> valrandom(unsigned int val, const ServerPolic
         sum += d.second->d_config.d_weight;
       }
 
-      poss.emplace_back(sum, d.first);
+      poss.at(usableServers) = std::pair(sum, d.first);
+      usableServers++;
     }
   }
 
-  // Catch poss & sum are empty to avoid SIGFPE
-  if (poss.empty() || sum == 0) {
+  // Catch the case where usableServers or sum are equal to 0 to avoid a SIGFPE
+  if (usableServers == 0 || sum == 0) {
     return shared_ptr<DownstreamState>();
   }
 
   int r = val % sum;
-  auto p = upper_bound(poss.begin(), poss.end(),r, [](int r_, const decltype(poss)::value_type& a) { return  r_ < a.first;});
-  if (p == poss.end()) {
+  auto p = std::upper_bound(poss.begin(), poss.begin() + usableServers, r, [](int r_, const typename T::value_type& a) { return  r_ < a.first;});
+  if (p == poss.begin() + usableServers) {
     return shared_ptr<DownstreamState>();
   }
 
+  // minus 1 because the NumberedServerVector starts at 1 for Lua
   return servers.at(p->second - 1).second;
 }
 
+static shared_ptr<DownstreamState> valrandom(const unsigned int val, const ServerPolicy::NumberedServerVector& servers)
+{
+  using ValRandomType = int;
+  double targetLoad = std::numeric_limits<double>::max();
+
+  if (g_weightedBalancingFactor > 0) {
+    /* we start with one, representing the query we are currently handling */
+    double currentLoad = 1;
+    size_t totalWeight = 0;
+    for (const auto& pair : servers) {
+      if (pair.second->isUp()) {
+        currentLoad += pair.second->outstanding;
+        totalWeight += pair.second->d_config.d_weight;
+      }
+    }
+
+    if (totalWeight > 0) {
+      targetLoad = (currentLoad / totalWeight) * g_weightedBalancingFactor;
+    }
+  }
+
+  if (servers.size() <= s_staticArrayCutOff) {
+    StaticIndexArray<ValRandomType> poss;
+    return getValRandom(servers, poss, val, targetLoad);
+  }
+
+  DynamicIndexArray<ValRandomType> poss;
+  poss.resize(servers.size());
+  return getValRandom(servers, poss, val, targetLoad);
+}
+
 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;
@@ -134,7 +167,7 @@ shared_ptr<DownstreamState> whashedFromHash(const ServerPolicy::NumberedServerVe
 
 shared_ptr<DownstreamState> whashed(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq)
 {
-  return whashedFromHash(servers, dq->qname->hash(g_hashperturb));
+  return whashedFromHash(servers, dq->ids.qname.hash(g_hashperturb));
 }
 
 shared_ptr<DownstreamState> chashedFromHash(const ServerPolicy::NumberedServerVector& servers, size_t qhash)
@@ -196,7 +229,7 @@ shared_ptr<DownstreamState> chashedFromHash(const ServerPolicy::NumberedServerVe
 
 shared_ptr<DownstreamState> chashed(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq)
 {
-  return chashedFromHash(servers, dq->qname->hash(g_hashperturb));
+  return chashedFromHash(servers, dq->ids.qname.hash(g_hashperturb));
 }
 
 shared_ptr<DownstreamState> roundrobin(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq)
@@ -227,7 +260,7 @@ shared_ptr<DownstreamState> roundrobin(const ServerPolicy::NumberedServerVector&
   return servers.at(candidates.at((counter++) % candidates.size()) - 1).second;
 }
 
-const std::shared_ptr<ServerPolicy::NumberedServerVector> getDownstreamCandidates(const pools_t& pools, const std::string& poolName)
+const std::shared_ptr<const ServerPolicy::NumberedServerVector> getDownstreamCandidates(const pools_t& pools, const std::string& poolName)
 {
   std::shared_ptr<ServerPool> pool = getPool(pools, poolName);
   return pool->getServers();
@@ -244,7 +277,7 @@ std::shared_ptr<ServerPool> createPoolIfNotExists(pools_t& pools, const string&
     if (!poolName.empty())
       vinfolog("Creating pool %s", poolName);
     pool = std::make_shared<ServerPool>();
-    pools.insert(std::pair<std::string,std::shared_ptr<ServerPool> >(poolName, pool));
+    pools.insert(std::pair<std::string, std::shared_ptr<ServerPool> >(poolName, pool));
   }
   return pool;
 }
@@ -257,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)
index fd3fb39e53f56bc6ef11a8b0b3dcfb44d87deedf..114eead045058cfd5b79493fdd0d6e5c3503a2bb 100644 (file)
@@ -56,7 +56,7 @@ void setupLuaBindingsDNSCrypt(LuaContext& luaCtx, bool client)
 
       if (ctx != nullptr) {
         size_t idx = 1;
-        for (auto pair : ctx->getCertificates()) {
+        for (const auto& pair : ctx->getCertificates()) {
           result.push_back({idx++, pair});
         }
       }
@@ -101,8 +101,8 @@ void setupLuaBindingsDNSCrypt(LuaContext& luaCtx, bool client)
         boost::format fmt("%1$-3d %|5t|%2$-8d %|10t|%3$-7d %|20t|%4$-21.21s %|41t|%5$-21.21s");
         ret << (fmt % "#" % "Serial" % "Version" % "From" % "To" ) << endl;
 
-        for (auto pair : ctx->getCertificates()) {
-          const auto cert = pair->cert;
+        for (const auto& pair : ctx->getCertificates()) {
+          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";
         }
     });
 
@@ -152,7 +152,7 @@ void setupLuaBindingsDNSCrypt(LuaContext& luaCtx, bool client)
     luaCtx.registerFunction<uint32_t(DNSCryptCert::*)()const>("getTSStart", [](const DNSCryptCert& cert) { return ntohl(cert.getTSStart()); });
     luaCtx.registerFunction<uint32_t(DNSCryptCert::*)()const>("getTSEnd", [](const DNSCryptCert& cert) { return ntohl(cert.getTSEnd()); });
 
-    luaCtx.writeFunction("generateDNSCryptCertificate", [client](const std::string& providerPrivateKeyFile, const std::string& certificateFile, const std::string privateKeyFile, uint32_t serial, time_t begin, time_t end, boost::optional<DNSCryptExchangeVersion> version) {
+    luaCtx.writeFunction("generateDNSCryptCertificate", [client](const std::string& providerPrivateKeyFile, const std::string& certificateFile, const std::string& privateKeyFile, uint32_t serial, time_t begin, time_t end, boost::optional<DNSCryptExchangeVersion> version) {
       setLuaNoSideEffect();
       if (client) {
         return;
@@ -167,12 +167,12 @@ 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";
       }
     });
 
-    luaCtx.writeFunction("generateDNSCryptProviderKeys", [client](const std::string& publicKeyFile, const std::string privateKeyFile) {
+    luaCtx.writeFunction("generateDNSCryptProviderKeys", [client](const std::string& publicKeyFile, const std::string& privateKeyFile) {
       setLuaNoSideEffect();
       if (client) {
         return;
@@ -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
diff --git a/pdns/dnsdistdist/dnsdist-lua-bindings-dnsparser.cc b/pdns/dnsdistdist/dnsdist-lua-bindings-dnsparser.cc
new file mode 100644 (file)
index 0000000..9605314
--- /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.
+ */
+#include "dnsdist.hh"
+#include "dnsdist-dnsparser.hh"
+#include "dnsdist-lua.hh"
+
+void setupLuaBindingsDNSParser(LuaContext& luaCtx)
+{
+#ifndef DISABLE_DNSPACKET_BINDINGS
+  luaCtx.writeFunction("newDNSPacketOverlay", [](const std::string& packet) {
+    dnsdist::DNSPacketOverlay dpo(packet);
+    return dpo;
+  });
+
+  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; });
+
+  luaCtx.registerFunction<uint16_t (dnsdist::DNSPacketOverlay::*)(uint8_t) const>("getRecordsCountInSection", [](const dnsdist::DNSPacketOverlay& overlay, uint8_t section) -> uint16_t {
+    if (section > DNSResourceRecord::ADDITIONAL) {
+      return 0;
+    }
+    uint16_t count = 0;
+    for (const auto& record : overlay.d_records) {
+      if (record.d_place == section) {
+        count++;
+      }
+    }
+
+    return count;
+  });
+
+  luaCtx.registerFunction<dnsdist::DNSPacketOverlay::Record (dnsdist::DNSPacketOverlay::*)(size_t) const>("getRecord", [](const dnsdist::DNSPacketOverlay& overlay, size_t idx) {
+    return overlay.d_records.at(idx);
+  });
+
+  luaCtx.registerMember<DNSName(dnsdist::DNSPacketOverlay::Record::*)>(std::string("name"), [](const dnsdist::DNSPacketOverlay::Record& record) { return record.d_name; });
+  luaCtx.registerMember<uint16_t(dnsdist::DNSPacketOverlay::Record::*)>(std::string("type"), [](const dnsdist::DNSPacketOverlay::Record& record) { return record.d_type; });
+  luaCtx.registerMember<uint16_t(dnsdist::DNSPacketOverlay::Record::*)>(std::string("class"), [](const dnsdist::DNSPacketOverlay::Record& record) { return record.d_class; });
+  luaCtx.registerMember<uint32_t(dnsdist::DNSPacketOverlay::Record::*)>(std::string("ttl"), [](const dnsdist::DNSPacketOverlay::Record& record) { return record.d_ttl; });
+  luaCtx.registerMember<uint8_t(dnsdist::DNSPacketOverlay::Record::*)>(std::string("place"), [](const dnsdist::DNSPacketOverlay::Record& record) { return record.d_place; });
+  luaCtx.registerMember<uint16_t(dnsdist::DNSPacketOverlay::Record::*)>(std::string("contentLength"), [](const dnsdist::DNSPacketOverlay::Record& record) { return record.d_contentLength; });
+  luaCtx.registerMember<uint16_t(dnsdist::DNSPacketOverlay::Record::*)>(std::string("contentOffset"), [](const dnsdist::DNSPacketOverlay::Record& record) { return record.d_contentOffset; });
+
+#endif /* DISABLE_DNSPACKET_BINDINGS */
+}
index 581fec203bbcc411d8d4315a2d1ba80da521550d..ba1e732344d73fadf5c133fbccee7fa9e6b27e68 100644 (file)
@@ -30,7 +30,7 @@ void setupLuaBindingsKVS(LuaContext& luaCtx, bool client)
     if (client) {
       return std::shared_ptr<KeyValueStore>(nullptr);
     }
-    return std::shared_ptr<KeyValueStore>(new LMDBKVStore(fname, dbName, noLock.get_value_or(false)));
+    return std::shared_ptr<KeyValueStore>(new LMDBKVStore(fname, dbName, noLock ? *noLock : false));
   });
 #endif /* HAVE_LMDB */
 
@@ -46,7 +46,7 @@ void setupLuaBindingsKVS(LuaContext& luaCtx, bool client)
 #if defined(HAVE_LMDB) || defined(HAVE_CDB)
   /* Key Value Store objects */
   luaCtx.writeFunction("KeyValueLookupKeySourceIP", [](boost::optional<uint8_t> v4Mask, boost::optional<uint8_t> v6Mask, boost::optional<bool> includePort) {
-    return std::shared_ptr<KeyValueLookupKey>(new KeyValueLookupKeySourceIP(v4Mask.get_value_or(32), v6Mask.get_value_or(128), includePort.get_value_or(false)));
+    return std::shared_ptr<KeyValueLookupKey>(new KeyValueLookupKeySourceIP(v4Mask ? *v4Mask : 32, v6Mask ? *v6Mask : 128, includePort ? *includePort : false));
   });
   luaCtx.writeFunction("KeyValueLookupKeyQName", [](boost::optional<bool> wireFormat) {
     return std::shared_ptr<KeyValueLookupKey>(new KeyValueLookupKeyQName(wireFormat ? *wireFormat : true));
diff --git a/pdns/dnsdistdist/dnsdist-lua-bindings-network.cc b/pdns/dnsdistdist/dnsdist-lua-bindings-network.cc
new file mode 100644 (file)
index 0000000..75d8447
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * 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-lua.hh"
+#include "dnsdist-lua-ffi.hh"
+#include "dnsdist-lua-network.hh"
+#include "dolog.hh"
+
+void setupLuaBindingsNetwork(LuaContext& luaCtx, bool client)
+{
+  luaCtx.writeFunction("newNetworkEndpoint", [client](const std::string& path) {
+    if (client) {
+      return std::shared_ptr<dnsdist::NetworkEndpoint>(nullptr);
+    }
+
+    try {
+      return std::make_shared<dnsdist::NetworkEndpoint>(path);
+    }
+    catch (const std::exception& e) {
+      warnlog("Error connecting to network endpoint: %s", e.what());
+    }
+    return std::shared_ptr<dnsdist::NetworkEndpoint>(nullptr);
+  });
+
+  luaCtx.registerFunction<bool (std::shared_ptr<dnsdist::NetworkEndpoint>::*)() const>("isValid", [](const std::shared_ptr<dnsdist::NetworkEndpoint>& endpoint) {
+    return endpoint != nullptr;
+  });
+
+  luaCtx.registerFunction<bool (std::shared_ptr<dnsdist::NetworkEndpoint>::*)(const std::string&) const>("send", [client](const std::shared_ptr<dnsdist::NetworkEndpoint>& endpoint, const std::string& payload) {
+    if (client || !endpoint || payload.empty()) {
+      return false;
+    }
+
+    return endpoint->send(payload);
+  });
+
+  luaCtx.writeFunction("newNetworkListener", [client]() {
+    if (client) {
+      return std::shared_ptr<dnsdist::NetworkListener>(nullptr);
+    }
+
+    return std::make_shared<dnsdist::NetworkListener>();
+  });
+
+  luaCtx.registerFunction<bool (std::shared_ptr<dnsdist::NetworkListener>::*)(const std::string&, uint16_t, std::function<void(uint16_t, std::string & dgram, const std::string& from)>)>("addUnixListeningEndpoint", [client](std::shared_ptr<dnsdist::NetworkListener>& listener, const std::string& path, uint16_t endpointID, std::function<void(uint16_t endpoint, std::string & dgram, const std::string& from)> cb) {
+    if (client || !cb) {
+      return false;
+    }
+
+    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);
+      }
+      dnsdist::handleQueuedAsynchronousEvents();
+    });
+  });
+
+  // if you make the dnsdist_ffi_network_message_t* in the function prototype const, LuaWrapper will stop treating it like a lightuserdata, messing everything up!!
+  luaCtx.registerFunction<bool (std::shared_ptr<dnsdist::NetworkListener>::*)(const std::string&, uint16_t, std::function<void(dnsdist_ffi_network_message_t*)>)>("addUnixListeningEndpointFFI", [client](std::shared_ptr<dnsdist::NetworkListener>& listener, const std::string& path, uint16_t endpointID, std::function<void(dnsdist_ffi_network_message_t*)> cb) {
+    if (client) {
+      return false;
+    }
+
+    return listener->addUnixListeningEndpoint(path, endpointID, [cb](dnsdist::NetworkListener::EndpointID endpoint, std::string&& dgram, const std::string& from) {
+      {
+        auto lock = g_lua.lock();
+        dnsdist_ffi_network_message_t msg(dgram, from, endpoint);
+        cb(&msg);
+      }
+      dnsdist::handleQueuedAsynchronousEvents();
+    });
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<dnsdist::NetworkListener>::*)()>("start", [client](std::shared_ptr<dnsdist::NetworkListener>& listener) {
+    if (client) {
+      return;
+    }
+
+    listener->start();
+  });
+
+  luaCtx.writeFunction("getResolvers", [](const std::string& resolvConfPath) -> LuaArray<std::string> {
+    auto resolvers = getResolvers(resolvConfPath);
+    LuaArray<std::string> result;
+    result.reserve(resolvers.size());
+    int counter = 1;
+    for (const auto& resolver : resolvers) {
+      result.emplace_back(counter, resolver.toString());
+      counter++;
+    }
+    return result;
+  });
+};
index b77cd7f204920e41fd7e3b59335fcbc8216f95f5..f71bf37516de58b4bbd36e8be4a0e9a62ae3cb07 100644 (file)
@@ -24,7 +24,6 @@
 #include <sys/types.h>
 
 #include "config.h"
-#include "dolog.hh"
 #include "dnsdist.hh"
 #include "dnsdist-lua.hh"
 
@@ -35,90 +34,67 @@ void setupLuaBindingsPacketCache(LuaContext& luaCtx, bool client)
   /* PacketCache */
   luaCtx.writeFunction("newPacketCache", [client](size_t maxEntries, boost::optional<LuaAssociativeTable<boost::variant<bool, size_t, LuaArray<uint16_t>>>> vars) {
 
-      bool keepStaleData = false;
-      size_t maxTTL = 86400;
-      size_t minTTL = 0;
-      size_t tempFailTTL = 60;
-      size_t maxNegativeTTL = 3600;
-      size_t staleTTL = 60;
-      size_t numberOfShards = 20;
-      bool dontAge = false;
-      bool deferrableInsertLock = true;
-      bool ecsParsing = false;
-      std::unordered_set<uint16_t> optionsToSkip{EDNSOptionCode::COOKIE};
-
-      if (vars) {
-
-        if (vars->count("deferrableInsertLock")) {
-          deferrableInsertLock = boost::get<bool>((*vars)["deferrableInsertLock"]);
-        }
-
-        if (vars->count("dontAge")) {
-          dontAge = boost::get<bool>((*vars)["dontAge"]);
-        }
-
-        if (vars->count("keepStaleData")) {
-          keepStaleData = boost::get<bool>((*vars)["keepStaleData"]);
-        }
-
-        if (vars->count("maxNegativeTTL")) {
-          maxNegativeTTL = boost::get<size_t>((*vars)["maxNegativeTTL"]);
-        }
-
-        if (vars->count("maxTTL")) {
-          maxTTL = boost::get<size_t>((*vars)["maxTTL"]);
-        }
-
-        if (vars->count("minTTL")) {
-          minTTL = boost::get<size_t>((*vars)["minTTL"]);
-        }
-
-        if (vars->count("numberOfShards")) {
-          numberOfShards = boost::get<size_t>((*vars)["numberOfShards"]);
-        }
-
-        if (vars->count("parseECS")) {
-          ecsParsing = boost::get<bool>((*vars)["parseECS"]);
-        }
-
-        if (vars->count("staleTTL")) {
-          staleTTL = boost::get<size_t>((*vars)["staleTTL"]);
-        }
+    bool keepStaleData = false;
+    size_t maxTTL = 86400;
+    size_t minTTL = 0;
+    size_t tempFailTTL = 60;
+    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;
+    bool cookieHashing = false;
+    LuaArray<uint16_t> skipOptions;
+    std::unordered_set<uint16_t> optionsToSkip{EDNSOptionCode::COOKIE};
+
+    getOptionalValue<bool>(vars, "deferrableInsertLock", deferrableInsertLock);
+    getOptionalValue<bool>(vars, "dontAge", dontAge);
+    getOptionalValue<bool>(vars, "keepStaleData", keepStaleData);
+    getOptionalValue<size_t>(vars, "maxNegativeTTL", maxNegativeTTL);
+    getOptionalValue<size_t>(vars, "maxTTL", maxTTL);
+    getOptionalValue<size_t>(vars, "minTTL", minTTL);
+    getOptionalValue<size_t>(vars, "numberOfShards", numberOfShards);
+    getOptionalValue<bool>(vars, "parseECS", ecsParsing);
+    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) {
+        optionsToSkip.insert(option.second);
+      }
+    }
 
-        if (vars->count("temporaryFailureTTL")) {
-          tempFailTTL = boost::get<size_t>((*vars)["temporaryFailureTTL"]);
-        }
+    if (cookieHashing) {
+      optionsToSkip.erase(EDNSOptionCode::COOKIE);
+    }
 
-        if (vars->count("cookieHashing")) {
-          if (boost::get<bool>((*vars)["cookieHashing"])) {
-            optionsToSkip.erase(EDNSOptionCode::COOKIE);
-          }
-        }
-        if (vars->count("skipOptions")) {
-          for (auto option: boost::get<LuaArray<uint16_t>>(vars->at("skipOptions"))) {
-            optionsToSkip.insert(option.second);
-          }
-        }
-      }
+    checkAllParametersConsumed("newPacketCache", vars);
 
-      if (maxEntries < numberOfShards) {
-        warnlog("The number of entries (%d) in the packet cache is smaller than the number of shards (%d), decreasing the number of shards to %d", maxEntries, numberOfShards, maxEntries);
-        g_outputBuffer += "The number of entries (" + std::to_string(maxEntries) + " in the packet cache is smaller than the number of shards (" + std::to_string(numberOfShards) + "), decreasing the number of shards to " + std::to_string(maxEntries);
-        numberOfShards = maxEntries;
-      }
+    if (maxEntries < numberOfShards) {
+      warnlog("The number of entries (%d) in the packet cache is smaller than the number of shards (%d), decreasing the number of shards to %d", maxEntries, numberOfShards, maxEntries);
+      g_outputBuffer += "The number of entries (" + std::to_string(maxEntries) + " in the packet cache is smaller than the number of shards (" + std::to_string(numberOfShards) + "), decreasing the number of shards to " + std::to_string(maxEntries);
+      numberOfShards = maxEntries;
+    }
 
-      if (client) {
-        maxEntries = 1;
-        numberOfShards = 1;
-      }
+    if (client) {
+      maxEntries = 1;
+      numberOfShards = 1;
+    }
 
-      auto res = std::make_shared<DNSDistPacketCache>(maxEntries, maxTTL, minTTL, tempFailTTL, maxNegativeTTL, staleTTL, dontAge, numberOfShards, deferrableInsertLock, ecsParsing);
+    auto res = std::make_shared<DNSDistPacketCache>(maxEntries, maxTTL, minTTL, tempFailTTL, maxNegativeTTL, staleTTL, dontAge, numberOfShards, deferrableInsertLock, ecsParsing);
 
-      res->setKeepStaleData(keepStaleData);
-      res->setSkippedOptions(optionsToSkip);
+    res->setKeepStaleData(keepStaleData);
+    res->setSkippedOptions(optionsToSkip);
+    if (maxEntrySize >= sizeof(dnsheader)) {
+      res->setMaximumEntrySize(maxEntrySize);
+    }
 
-      return res;
-    });
+    return res;
+  });
 
 #ifndef DISABLE_PACKETCACHE_BINDINGS
   luaCtx.registerFunction<std::string(std::shared_ptr<DNSDistPacketCache>::*)()const>("toString", [](const std::shared_ptr<DNSDistPacketCache>& cache) {
@@ -160,7 +136,7 @@ void setupLuaBindingsPacketCache(LuaContext& luaCtx, bool client)
                   qname = DNSName(boost::get<string>(dname));
                 }
                 if (cache) {
-                  g_outputBuffer="Expunged " + std::to_string(cache->expungeByName(qname, qtype ? *qtype : QType(QType::ANY).getCode(), suffixMatch ? *suffixMatch : false)) + " records\n";
+                  g_outputBuffer+="Expunged " + std::to_string(cache->expungeByName(qname, qtype ? *qtype : QType(QType::ANY).getCode(), suffixMatch ? *suffixMatch : false)) + " records\n";
                 }
     });
   luaCtx.registerFunction<void(std::shared_ptr<DNSDistPacketCache>::*)()const>("printStats", [](const std::shared_ptr<DNSDistPacketCache>& cache) {
@@ -173,6 +149,7 @@ void setupLuaBindingsPacketCache(LuaContext& luaCtx, bool client)
         g_outputBuffer+="Lookup Collisions: " + std::to_string(cache->getLookupCollisions()) + "\n";
         g_outputBuffer+="Insert Collisions: " + std::to_string(cache->getInsertCollisions()) + "\n";
         g_outputBuffer+="TTL Too Shorts: " + std::to_string(cache->getTTLTooShorts()) + "\n";
+        g_outputBuffer+="Cleanup Count: " + std::to_string(cache->getCleanupCount()) + "\n";
       }
     });
   luaCtx.registerFunction<LuaAssociativeTable<uint64_t>(std::shared_ptr<DNSDistPacketCache>::*)()const>("getStats", [](const std::shared_ptr<DNSDistPacketCache>& cache) {
@@ -187,9 +164,43 @@ void setupLuaBindingsPacketCache(LuaContext& luaCtx, bool client)
         stats["lookupCollisions"] = cache->getLookupCollisions();
         stats["insertCollisions"] = cache->getInsertCollisions();
         stats["ttlTooShorts"] = cache->getTTLTooShorts();
+        stats["cleanupCount"] = cache->getCleanupCount();
       }
       return stats;
     });
+
+  luaCtx.registerFunction<LuaArray<DNSName>(std::shared_ptr<DNSDistPacketCache>::*)(const ComboAddress& addr)const>("getDomainListByAddress", [](const std::shared_ptr<DNSDistPacketCache>& cache, const ComboAddress& addr) {
+      LuaArray<DNSName> results;
+      if (!cache) {
+        return results;
+      }
+
+      int counter = 1;
+      auto domains = cache->getDomainsContainingRecords(addr);
+      results.reserve(domains.size());
+      for (auto& domain : domains) {
+        results.emplace_back(counter, std::move(domain));
+        counter++;
+      }
+      return results;
+    });
+
+  luaCtx.registerFunction<LuaArray<ComboAddress>(std::shared_ptr<DNSDistPacketCache>::*)(const DNSName& domain)const>("getAddressListByDomain", [](const std::shared_ptr<DNSDistPacketCache>& cache, const DNSName& domain) {
+      LuaArray<ComboAddress> results;
+      if (!cache) {
+        return results;
+      }
+
+      int counter = 1;
+      auto addresses = cache->getRecordsForDomain(domain);
+      results.reserve(addresses.size());
+      for (auto& address : addresses) {
+        results.emplace_back(counter, std::move(address));
+        counter++;
+      }
+      return results;
+    });
+
   luaCtx.registerFunction<void(std::shared_ptr<DNSDistPacketCache>::*)(const std::string& fname)const>("dump", [](const std::shared_ptr<DNSDistPacketCache>& cache, const std::string& fname) {
       if (cache) {
 
index 6e470d34e8b7292f754d5ad7c8f3cf7906c2422f..e532a568280f620622b13020a5f0a7e83d5744da 100644 (file)
@@ -32,7 +32,7 @@
 #include "remote_logger.hh"
 
 #ifdef HAVE_FSTRM
-static void parseFSTRMOptions(const boost::optional<LuaAssociativeTable<unsigned int>>& params, LuaAssociativeTable<unsigned int>& options)
+static void parseFSTRMOptions(boost::optional<LuaAssociativeTable<unsigned int>>& params, LuaAssociativeTable<unsigned int>& options)
 {
   if (!params) {
     return;
@@ -41,9 +41,7 @@ static void parseFSTRMOptions(const boost::optional<LuaAssociativeTable<unsigned
   static std::vector<std::string> const potentialOptions = { "bufferHint", "flushTimeout", "inputQueueSize", "outputQueueSize", "queueNotifyThreshold", "reopenInterval" };
 
   for (const auto& potentialOption : potentialOptions) {
-    if (params->count(potentialOption)) {
-      options[potentialOption] = boost::get<unsigned int>(params->at(potentialOption));
-    }
+    getOptionalValue<unsigned int>(params, potentialOption, options[potentialOption]);
   }
 }
 #endif /* HAVE_FSTRM */
@@ -138,6 +136,7 @@ void setupLuaBindingsProtoBuf(LuaContext& luaCtx, bool client, bool configCheck)
 
       LuaAssociativeTable<unsigned int> options;
       parseFSTRMOptions(params, options);
+      checkAllParametersConsumed("newRemoteLogger", params);
       return std::shared_ptr<RemoteLoggerInterface>(new FrameStreamLogger(AF_UNIX, address, !client, options));
 #else
       throw std::runtime_error("fstrm support is required to build an AF_UNIX FrameStreamLogger");
@@ -152,6 +151,7 @@ void setupLuaBindingsProtoBuf(LuaContext& luaCtx, bool client, bool configCheck)
 
       LuaAssociativeTable<unsigned int> options;
       parseFSTRMOptions(params, options);
+      checkAllParametersConsumed("newFrameStreamTcpLogger", params);
       return std::shared_ptr<RemoteLoggerInterface>(new FrameStreamLogger(AF_INET, address, !client, options));
 #else
       throw std::runtime_error("fstrm with TCP support is required to build an AF_INET FrameStreamLogger");
diff --git a/pdns/dnsdistdist/dnsdist-lua-bindings-rings.cc b/pdns/dnsdistdist/dnsdist-lua-bindings-rings.cc
new file mode 100644 (file)
index 0000000..059bce7
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * 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-rings.hh"
+#include "dnsdist-lua.hh"
+
+#ifndef DISABLE_LUA_BINDINGS_RINGS
+struct LuaRingEntry
+{
+  DNSName qname;
+  ComboAddress requestor;
+  ComboAddress ds;
+  struct timespec when;
+  std::string macAddr;
+  struct dnsheader dh;
+  unsigned int usec;
+  unsigned int size;
+  uint16_t qtype;
+  dnsdist::Protocol protocol;
+  bool isResponse;
+};
+
+template <typename T>
+static void addRingEntryToList(LuaArray<LuaRingEntry>& list, const T& entry)
+{
+  constexpr bool response = std::is_same_v<T, Rings::Response>;
+  if constexpr (!response) {
+#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
+    list.emplace_back(list.size() + 1, LuaRingEntry{entry.name, entry.requestor, ComboAddress(), entry.when, entry.hasmac ? std::string(reinterpret_cast<const char*>(entry.macaddress.data()), entry.macaddress.size()) : std::string(), entry.dh, 0U, entry.size, entry.qtype, entry.protocol, false});
+#else
+    list.emplace_back(list.size() + 1, LuaRingEntry{entry.name, entry.requestor, ComboAddress(), entry.when, std::string(), entry.dh, 0U, entry.size, entry.qtype, entry.protocol, false});
+#endif
+  }
+  else {
+    list.emplace_back(list.size() + 1, LuaRingEntry{entry.name, entry.requestor, entry.ds, entry.when, std::string(), entry.dh, entry.usec, entry.size, entry.qtype, entry.protocol, true});
+  }
+}
+
+#endif /* DISABLE_LUA_BINDINGS_RINGS */
+
+void setupLuaBindingsRings(LuaContext& luaCtx, bool client)
+{
+#ifndef DISABLE_LUA_BINDINGS_RINGS
+  luaCtx.writeFunction("getRingEntries", [client]() {
+    LuaArray<LuaRingEntry> results;
+
+    if (client) {
+      return results;
+    }
+
+    for (const auto& shard : g_rings.d_shards) {
+      {
+        auto ql = shard->queryRing.lock();
+        for (const auto& entry : *ql) {
+          addRingEntryToList(results, entry);
+        }
+      }
+      {
+        auto rl = shard->respRing.lock();
+        for (const auto& entry : *rl) {
+          addRingEntryToList(results, entry);
+        }
+      }
+    }
+
+    return results;
+  });
+
+  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) -> const ComboAddress& {
+    return entry.requestor;
+  });
+
+  luaCtx.registerMember<ComboAddress(LuaRingEntry::*)>(std::string("backend"), [](const LuaRingEntry& entry) -> const ComboAddress& {
+    return entry.ds;
+  });
+
+  luaCtx.registerMember<timespec(LuaRingEntry::*)>(std::string("when"), [](const LuaRingEntry& entry) {
+    return entry.when;
+  });
+
+  luaCtx.registerMember<std::string(LuaRingEntry::*)>(std::string("macAddress"), [](const LuaRingEntry& entry) -> const std::string& {
+    return entry.macAddr;
+  });
+
+  luaCtx.registerMember<dnsheader(LuaRingEntry::*)>(std::string("dnsheader"), [](const LuaRingEntry& entry) {
+    return entry.dh;
+  });
+
+  luaCtx.registerMember<unsigned int(LuaRingEntry::*)>(std::string("usec"), [](const LuaRingEntry& entry) {
+    return entry.usec;
+  });
+
+  luaCtx.registerMember<unsigned int(LuaRingEntry::*)>(std::string("size"), [](const LuaRingEntry& entry) {
+    return entry.size;
+  });
+
+  luaCtx.registerMember<uint16_t(LuaRingEntry::*)>(std::string("qtype"), [](const LuaRingEntry& entry) {
+    return entry.qtype;
+  });
+
+  luaCtx.registerMember<std::string(LuaRingEntry::*)>(std::string("protocol"), [](const LuaRingEntry& entry) {
+    return entry.protocol.toString();
+  });
+
+  luaCtx.registerMember<bool(LuaRingEntry::*)>(std::string("isResponse"), [](const LuaRingEntry& entry) {
+    return entry.isResponse;
+  });
+
+  luaCtx.registerMember<int64_t(timespec::*)>(std::string("tv_sec"), [](const timespec& ts) {
+    return ts.tv_sec;
+  });
+
+  luaCtx.registerMember<uint64_t(timespec::*)>(std::string("tv_nsec"), [](const timespec& ts) {
+    return ts.tv_nsec;
+  });
+
+#endif /* DISABLE_LUA_BINDINGS_RINGS */
+}
index e4fbdd840d0e1181f5ce1c64e7d27b0ff024dfe9..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")));
@@ -67,6 +68,7 @@ void dnsdist_ffi_dnsquestion_get_qname_raw(const dnsdist_ffi_dnsquestion_t* dq,
 size_t dnsdist_ffi_dnsquestion_get_qname_hash(const dnsdist_ffi_dnsquestion_t* dq, size_t init) __attribute__ ((visibility ("default")));
 uint16_t dnsdist_ffi_dnsquestion_get_qtype(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
 uint16_t dnsdist_ffi_dnsquestion_get_qclass(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+uint16_t dnsdist_ffi_dnsquestion_get_id(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
 int dnsdist_ffi_dnsquestion_get_rcode(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
 void* dnsdist_ffi_dnsquestion_get_header(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
 uint16_t dnsdist_ffi_dnsquestion_get_len(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
@@ -85,11 +87,13 @@ uint32_t dnsdist_ffi_dnsquestion_get_temp_failure_ttl(const dnsdist_ffi_dnsquest
 bool dnsdist_ffi_dnsquestion_get_do(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
 void dnsdist_ffi_dnsquestion_get_sni(const dnsdist_ffi_dnsquestion_t* dq, const char** sni, size_t* sniSize) __attribute__ ((visibility ("default")));
 const char* dnsdist_ffi_dnsquestion_get_tag(const dnsdist_ffi_dnsquestion_t* dq, const char* label) __attribute__ ((visibility ("default")));
+size_t dnsdist_ffi_dnsquestion_get_tag_raw(const dnsdist_ffi_dnsquestion_t* dq, const char* label, char* buffer, size_t bufferSize) __attribute__ ((visibility ("default")));
 const char* dnsdist_ffi_dnsquestion_get_http_path(dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
 const char* dnsdist_ffi_dnsquestion_get_http_query_string(dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
 const char* dnsdist_ffi_dnsquestion_get_http_host(dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
 const char* dnsdist_ffi_dnsquestion_get_http_scheme(dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
 size_t dnsdist_ffi_dnsquestion_get_mac_addr(const dnsdist_ffi_dnsquestion_t* dq, void* buffer, size_t bufferSize) __attribute__ ((visibility ("default")));
+uint64_t dnsdist_ffi_dnsquestion_get_elapsed_us(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
 
 // returns the length of the resulting 'out' array. 'out' is not set if the length is 0
 size_t dnsdist_ffi_dnsquestion_get_edns_options(dnsdist_ffi_dnsquestion_t* ref, const dnsdist_ffi_ednsoption_t** out) __attribute__ ((visibility ("default")));
@@ -106,9 +110,16 @@ void dnsdist_ffi_dnsquestion_set_ecs_prefix_length(dnsdist_ffi_dnsquestion_t* dq
 void dnsdist_ffi_dnsquestion_set_temp_failure_ttl(dnsdist_ffi_dnsquestion_t* dq, uint32_t tempFailureTTL) __attribute__ ((visibility ("default")));
 void dnsdist_ffi_dnsquestion_unset_temp_failure_ttl(dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
 void dnsdist_ffi_dnsquestion_set_tag(dnsdist_ffi_dnsquestion_t* dq, const char* label, const char* value) __attribute__ ((visibility ("default")));
+void dnsdist_ffi_dnsquestion_set_tag_raw(dnsdist_ffi_dnsquestion_t* dq, const char* label, const char* value, size_t valueSize) __attribute__ ((visibility ("default")));
+
+void dnsdist_ffi_dnsquestion_set_requestor_id(dnsdist_ffi_dnsquestion_t* dq, const char* value, size_t valueSize) __attribute__ ((visibility ("default")));
+void dnsdist_ffi_dnsquestion_set_device_id(dnsdist_ffi_dnsquestion_t* dq, const char* value, size_t valueSize) __attribute__ ((visibility ("default")));
+void dnsdist_ffi_dnsquestion_set_device_name(dnsdist_ffi_dnsquestion_t* dq, const char* value, size_t valueSize) __attribute__ ((visibility ("default")));
 
 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")));
@@ -122,6 +133,10 @@ void dnsdist_ffi_dnsquestion_spoof_addrs(dnsdist_ffi_dnsquestion_t* dq, const dn
 // spoof raw response. will just replace qid to match question
 void dnsdist_ffi_dnsquestion_spoof_packet(dnsdist_ffi_dnsquestion_t* dq, const char* rawresponse, size_t len) __attribute__ ((visibility ("default")));
 
+/* decrease the returned TTL but _after_ inserting the original response into the packet cache */
+void dnsdist_ffi_dnsquestion_set_max_returned_ttl(dnsdist_ffi_dnsquestion_t* dq, uint32_t max) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_dnsquestion_set_restartable(dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+
 typedef struct dnsdist_ffi_servers_list_t dnsdist_ffi_servers_list_t;
 typedef struct dnsdist_ffi_server_t dnsdist_ffi_server_t;
 
@@ -141,7 +156,19 @@ double dnsdist_ffi_server_get_latency(const dnsdist_ffi_server_t* server) __attr
 void dnsdist_ffi_dnsresponse_set_min_ttl(dnsdist_ffi_dnsresponse_t* dr, uint32_t min) __attribute__ ((visibility ("default")));
 void dnsdist_ffi_dnsresponse_set_max_ttl(dnsdist_ffi_dnsresponse_t* dr, uint32_t max) __attribute__ ((visibility ("default")));
 void dnsdist_ffi_dnsresponse_limit_ttl(dnsdist_ffi_dnsresponse_t* dr, uint32_t min, uint32_t max) __attribute__ ((visibility ("default")));
+/* decrease the returned TTL but _after_ inserting the original response into the packet cache */
+void dnsdist_ffi_dnsresponse_set_max_returned_ttl(dnsdist_ffi_dnsresponse_t* dr, uint32_t max) __attribute__ ((visibility ("default")));
 void dnsdist_ffi_dnsresponse_clear_records_type(dnsdist_ffi_dnsresponse_t* dr, uint16_t qtype) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_dnsresponse_rebase(dnsdist_ffi_dnsresponse_t* dr, const char* initialName, size_t initialNameSize) __attribute__ ((visibility ("default")));
+
+bool dnsdist_ffi_dnsquestion_set_async(dnsdist_ffi_dnsquestion_t* dq, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_dnsresponse_set_async(dnsdist_ffi_dnsquestion_t* dq, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs) __attribute__ ((visibility ("default")));
+
+bool dnsdist_ffi_resume_from_async(uint16_t asyncID, uint16_t queryID, const char* tag, size_t tagSize, const char* tagValue, size_t tagValueSize, bool useCache) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_drop_from_async(uint16_t asyncID, uint16_t queryID) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_set_answer_from_async(uint16_t asyncID, uint16_t queryID, const char* raw, size_t rawSize) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_set_rcode_from_async(uint16_t asyncID, uint16_t queryID, uint8_t rcode, bool clearAnswers) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_resume_from_async_with_alternate_name(uint16_t asyncID, uint16_t queryID, const char* alternateName, size_t alternateNameSize, const char* tag, size_t tagSize, const char* tagValue, size_t tagValueSize, const char* formerNameTagName, size_t formerNameTagSize) __attribute__ ((visibility ("default")));
 
 typedef struct dnsdist_ffi_proxy_protocol_value {
   const char* value;
@@ -150,4 +177,110 @@ typedef struct dnsdist_ffi_proxy_protocol_value {
 } dnsdist_ffi_proxy_protocol_value_t;
 
 size_t dnsdist_ffi_generate_proxy_protocol_payload(size_t addrSize, const void* srcAddr, const void* dstAddr, uint16_t srcPort, uint16_t dstPort, bool tcp, size_t valuesCount, const dnsdist_ffi_proxy_protocol_value_t* values, void* out, size_t outSize) __attribute__ ((visibility ("default")));
-size_t dnsdist_ffi_dnsquestion_generate_proxy_protocol_payload(const dnsdist_ffi_dnsquestion_t* dq, const size_t valuesCount, const dnsdist_ffi_proxy_protocol_value_t* values, void* out, const size_t outSize);
+size_t dnsdist_ffi_dnsquestion_generate_proxy_protocol_payload(const dnsdist_ffi_dnsquestion_t* dq, const size_t valuesCount, const dnsdist_ffi_proxy_protocol_value_t* values, void* out, const size_t outSize) __attribute__ ((visibility ("default")));
+
+typedef struct dnsdist_ffi_domain_list_t dnsdist_ffi_domain_list_t;
+typedef struct dnsdist_ffi_address_list_t dnsdist_ffi_address_list_t;
+
+const char* dnsdist_ffi_address_list_get(const dnsdist_ffi_address_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+void dnsdist_ffi_address_list_free(dnsdist_ffi_address_list_t*) __attribute__ ((visibility ("default")));
+
+const char* dnsdist_ffi_domain_list_get(const dnsdist_ffi_domain_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+void dnsdist_ffi_domain_list_free(dnsdist_ffi_domain_list_t*) __attribute__ ((visibility ("default")));
+
+size_t dnsdist_ffi_packetcache_get_domain_list_by_addr(const char* poolName, const char* addr, dnsdist_ffi_domain_list_t** out) __attribute__ ((visibility ("default")));
+size_t dnsdist_ffi_packetcache_get_address_list_by_domain(const char* poolName, const char* domain, dnsdist_ffi_address_list_t** out) __attribute__ ((visibility ("default")));
+
+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")));
+
+void dnsdist_ffi_ring_entry_list_free(dnsdist_ffi_ring_entry_list_t*) __attribute__ ((visibility ("default")));
+
+size_t dnsdist_ffi_ring_get_entries(dnsdist_ffi_ring_entry_list_t** out) __attribute__ ((visibility ("default")));
+size_t dnsdist_ffi_ring_get_entries_by_addr(const char* addr, dnsdist_ffi_ring_entry_list_t** out) __attribute__ ((visibility ("default")));
+size_t dnsdist_ffi_ring_get_entries_by_mac(const char* addr, dnsdist_ffi_ring_entry_list_t** out) __attribute__ ((visibility ("default")));
+
+typedef struct dnsdist_ffi_network_endpoint_t dnsdist_ffi_network_endpoint_t;
+
+bool dnsdist_ffi_network_endpoint_new(const char* path, size_t pathSize, dnsdist_ffi_network_endpoint_t** out) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_network_endpoint_is_valid(const dnsdist_ffi_network_endpoint_t* endpoint) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_network_endpoint_send(const dnsdist_ffi_network_endpoint_t* endpoint, const char* payload, size_t payloadSize) __attribute__ ((visibility ("default")));
+void dnsdist_ffi_network_endpoint_free(dnsdist_ffi_network_endpoint_t* endpoint) __attribute__ ((visibility ("default")));
+
+typedef struct dnsdist_ffi_dnspacket_t dnsdist_ffi_dnspacket_t;
+
+bool dnsdist_ffi_dnspacket_parse(const char* packet, size_t packetSize, dnsdist_ffi_dnspacket_t** out) __attribute__ ((visibility ("default")));
+void dnsdist_ffi_dnspacket_get_qname_raw(const dnsdist_ffi_dnspacket_t* packet, const char** qname, size_t* qnameSize) __attribute__ ((visibility ("default")));
+uint16_t dnsdist_ffi_dnspacket_get_qtype(const dnsdist_ffi_dnspacket_t* packet) __attribute__ ((visibility ("default")));
+uint16_t dnsdist_ffi_dnspacket_get_qclass(const dnsdist_ffi_dnspacket_t* packet) __attribute__ ((visibility ("default")));
+uint16_t dnsdist_ffi_dnspacket_get_records_count_in_section(const dnsdist_ffi_dnspacket_t* packet, uint8_t section) __attribute__ ((visibility ("default")));
+void dnsdist_ffi_dnspacket_get_record_name_raw(const dnsdist_ffi_dnspacket_t* packet, size_t idx, const char** name, size_t* nameSize) __attribute__ ((visibility ("default")));
+uint16_t dnsdist_ffi_dnspacket_get_record_type(const dnsdist_ffi_dnspacket_t* packet, size_t idx) __attribute__ ((visibility ("default")));
+uint16_t dnsdist_ffi_dnspacket_get_record_class(const dnsdist_ffi_dnspacket_t* packet, size_t idx) __attribute__ ((visibility ("default")));
+uint32_t dnsdist_ffi_dnspacket_get_record_ttl(const dnsdist_ffi_dnspacket_t* packet, size_t idx) __attribute__ ((visibility ("default")));
+uint16_t dnsdist_ffi_dnspacket_get_record_content_length(const dnsdist_ffi_dnspacket_t* packet, size_t idx) __attribute__ ((visibility ("default")));
+uint16_t dnsdist_ffi_dnspacket_get_record_content_offset(const dnsdist_ffi_dnspacket_t* packet, size_t idx) __attribute__ ((visibility ("default")));
+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")));
+
+typedef struct dnsdist_ffi_network_message_t dnsdist_ffi_network_message_t;
+
+const char* dnsdist_ffi_network_message_get_payload(const dnsdist_ffi_network_message_t* msg) __attribute__ ((visibility ("default")));
+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 420e13f9330ec6d38325a098357286221f871a85..6c08cfc1bc2456b314cc980d1823ca78354671a7 100644 (file)
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
+#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"
+#include "dnsdist-rings.hh"
 #include "dolog.hh"
 
 uint16_t dnsdist_ffi_dnsquestion_get_qtype(const dnsdist_ffi_dnsquestion_t* dq)
 {
-  return dq->dq->qtype;
+  return dq->dq->ids.qtype;
 }
 
 uint16_t dnsdist_ffi_dnsquestion_get_qclass(const dnsdist_ffi_dnsquestion_t* dq)
 {
-  return dq->dq->qclass;
+  return dq->dq->ids.qclass;
+}
+
+uint16_t dnsdist_ffi_dnsquestion_get_id(const dnsdist_ffi_dnsquestion_t* dq)
+{
+  if (dq == nullptr) {
+    return 0;
+  }
+  return ntohs(dq->dq->getHeader()->id);
 }
 
 static void dnsdist_ffi_comboaddress_to_raw(const ComboAddress& ca, const void** addr, size_t* addrSize)
@@ -49,12 +65,21 @@ static void dnsdist_ffi_comboaddress_to_raw(const ComboAddress& ca, const void**
 
 void dnsdist_ffi_dnsquestion_get_localaddr(const dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize)
 {
-  dnsdist_ffi_comboaddress_to_raw(*dq->dq->local, addr, addrSize);
+  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->remote, addr, addrSize);
+  dnsdist_ffi_comboaddress_to_raw(dq->dq->ids.origRemote, addr, addrSize);
 }
 
 size_t dnsdist_ffi_dnsquestion_get_mac_addr(const dnsdist_ffi_dnsquestion_t* dq, void* buffer, size_t bufferSize)
@@ -62,8 +87,7 @@ size_t dnsdist_ffi_dnsquestion_get_mac_addr(const dnsdist_ffi_dnsquestion_t* dq,
   if (dq == nullptr) {
     return 0;
   }
-
-  auto ret = getMACAddress(*dq->dq->remote, reinterpret_cast<char*>(buffer), bufferSize);
+  auto ret = dnsdist::MacAddressesCache::get(dq->dq->ids.origRemote, reinterpret_cast<unsigned char*>(buffer), bufferSize);
   if (ret != 0) {
     return 0;
   }
@@ -71,32 +95,41 @@ size_t dnsdist_ffi_dnsquestion_get_mac_addr(const dnsdist_ffi_dnsquestion_t* dq,
   return 6;
 }
 
+uint64_t dnsdist_ffi_dnsquestion_get_elapsed_us(const dnsdist_ffi_dnsquestion_t* dq)
+{
+  if (dq == nullptr) {
+    return 0;
+  }
+
+  return static_cast<uint64_t>(std::round(dq->dq->ids.queryRealTime.udiff()));
+}
+
 void dnsdist_ffi_dnsquestion_get_masked_remoteaddr(dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize, uint8_t bits)
 {
-  dq->maskedRemote = Netmask(*dq->dq->remote, bits).getMaskedNetwork();
+  dq->maskedRemote = Netmask(dq->dq->ids.origRemote, bits).getMaskedNetwork();
   dnsdist_ffi_comboaddress_to_raw(dq->maskedRemote, addr, addrSize);
 }
 
 uint16_t dnsdist_ffi_dnsquestion_get_local_port(const dnsdist_ffi_dnsquestion_t* dq)
 {
-  return dq->dq->local->getPort();
+  return dq->dq->ids.origDest.getPort();
 }
 
 uint16_t dnsdist_ffi_dnsquestion_get_remote_port(const dnsdist_ffi_dnsquestion_t* dq)
 {
-  return dq->dq->remote->getPort();
+  return dq->dq->ids.origRemote.getPort();
 }
 
 void dnsdist_ffi_dnsquestion_get_qname_raw(const dnsdist_ffi_dnsquestion_t* dq, const char** qname, size_t* qnameSize)
 {
-  const auto& storage = dq->dq->qname->getStorage();
+  const auto& storage = dq->dq->ids.qname.getStorage();
   *qname = storage.data();
   *qnameSize = storage.size();
 }
 
 size_t dnsdist_ffi_dnsquestion_get_qname_hash(const dnsdist_ffi_dnsquestion_t* dq, size_t init)
 {
-  return dq->dq->qname->hash(init);
+  return dq->dq->ids.qname.hash(init);
 }
 
 int dnsdist_ffi_dnsquestion_get_rcode(const dnsdist_ffi_dnsquestion_t* dq)
@@ -106,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)
@@ -168,7 +201,7 @@ dnsdist_ffi_protocol_type dnsdist_ffi_dnsquestion_get_protocol(const dnsdist_ffi
 
 bool dnsdist_ffi_dnsquestion_get_skip_cache(const dnsdist_ffi_dnsquestion_t* dq)
 {
-  return dq->dq->skipCache;
+  return dq->dq->ids.skipCache;
 }
 
 bool dnsdist_ffi_dnsquestion_get_use_ecs(const dnsdist_ffi_dnsquestion_t* dq)
@@ -193,13 +226,13 @@ uint16_t dnsdist_ffi_dnsquestion_get_ecs_prefix_length(const dnsdist_ffi_dnsques
 
 bool dnsdist_ffi_dnsquestion_is_temp_failure_ttl_set(const dnsdist_ffi_dnsquestion_t* dq)
 {
-  return dq->dq->tempFailureTTL != boost::none;
+  return dq->dq->ids.tempFailureTTL != boost::none;
 }
 
 uint32_t dnsdist_ffi_dnsquestion_get_temp_failure_ttl(const dnsdist_ffi_dnsquestion_t* dq)
 {
-  if (dq->dq->tempFailureTTL) {
-    return *dq->dq->tempFailureTTL;
+  if (dq->dq->ids.tempFailureTTL) {
+    return *dq->dq->ids.tempFailureTTL;
   }
   return 0;
 }
@@ -219,9 +252,9 @@ const char* dnsdist_ffi_dnsquestion_get_tag(const dnsdist_ffi_dnsquestion_t* dq,
 {
   const char * result = nullptr;
 
-  if (dq->dq->qTag != nullptr) {
-    const auto it = dq->dq->qTag->find(label);
-    if (it != dq->dq->qTag->cend()) {
+  if (dq != nullptr && dq->dq != nullptr && dq->dq->ids.qTag != nullptr) {
+    const auto it = dq->dq->ids.qTag->find(label);
+    if (it != dq->dq->ids.qTag->cend()) {
       result = it->second.c_str();
     }
   }
@@ -229,14 +262,33 @@ const char* dnsdist_ffi_dnsquestion_get_tag(const dnsdist_ffi_dnsquestion_t* dq,
   return result;
 }
 
+size_t dnsdist_ffi_dnsquestion_get_tag_raw(const dnsdist_ffi_dnsquestion_t* dq, const char* label, char* buffer, size_t bufferSize)
+{
+  if (dq == nullptr || dq->dq == nullptr || dq->dq->ids.qTag == nullptr || label == nullptr || buffer == nullptr || bufferSize == 0) {
+    return 0;
+  }
+
+  const auto it = dq->dq->ids.qTag->find(label);
+  if (it == dq->dq->ids.qTag->cend()) {
+    return 0;
+  }
+
+  if (it->second.size() > bufferSize) {
+    return 0;
+  }
+
+  memcpy(buffer, it->second.c_str(), it->second.size());
+  return it->second.size();
+}
+
 const char* dnsdist_ffi_dnsquestion_get_http_path(dnsdist_ffi_dnsquestion_t* dq)
 {
   if (!dq->httpPath) {
-    if (dq->dq->du == nullptr) {
+    if (dq->dq->ids.du == nullptr) {
       return nullptr;
     }
 #ifdef HAVE_DNS_OVER_HTTPS
-    dq->httpPath = dq->dq->du->getHTTPPath();
+    dq->httpPath = dq->dq->ids.du->getHTTPPath();
 #endif /* HAVE_DNS_OVER_HTTPS */
   }
   if (dq->httpPath) {
@@ -248,11 +300,11 @@ const char* dnsdist_ffi_dnsquestion_get_http_path(dnsdist_ffi_dnsquestion_t* dq)
 const char* dnsdist_ffi_dnsquestion_get_http_query_string(dnsdist_ffi_dnsquestion_t* dq)
 {
   if (!dq->httpQueryString) {
-    if (dq->dq->du == nullptr) {
+    if (dq->dq->ids.du == nullptr) {
       return nullptr;
     }
 #ifdef HAVE_DNS_OVER_HTTPS
-    dq->httpQueryString = dq->dq->du->getHTTPQueryString();
+    dq->httpQueryString = dq->dq->ids.du->getHTTPQueryString();
 #endif /* HAVE_DNS_OVER_HTTPS */
   }
   if (dq->httpQueryString) {
@@ -264,11 +316,11 @@ const char* dnsdist_ffi_dnsquestion_get_http_query_string(dnsdist_ffi_dnsquestio
 const char* dnsdist_ffi_dnsquestion_get_http_host(dnsdist_ffi_dnsquestion_t* dq)
 {
   if (!dq->httpHost) {
-    if (dq->dq->du == nullptr) {
+    if (dq->dq->ids.du == nullptr) {
       return nullptr;
     }
 #ifdef HAVE_DNS_OVER_HTTPS
-    dq->httpHost = dq->dq->du->getHTTPHost();
+    dq->httpHost = dq->dq->ids.du->getHTTPHost();
 #endif /* HAVE_DNS_OVER_HTTPS */
   }
   if (dq->httpHost) {
@@ -280,11 +332,11 @@ const char* dnsdist_ffi_dnsquestion_get_http_host(dnsdist_ffi_dnsquestion_t* dq)
 const char* dnsdist_ffi_dnsquestion_get_http_scheme(dnsdist_ffi_dnsquestion_t* dq)
 {
   if (!dq->httpScheme) {
-    if (dq->dq->du == nullptr) {
+    if (dq->dq->ids.du == nullptr) {
       return nullptr;
     }
 #ifdef HAVE_DNS_OVER_HTTPS
-    dq->httpScheme = dq->dq->du->getHTTPScheme();
+    dq->httpScheme = dq->dq->ids.du->getHTTPScheme();
 #endif /* HAVE_DNS_OVER_HTTPS */
   }
   if (dq->httpScheme) {
@@ -319,19 +371,22 @@ size_t dnsdist_ffi_dnsquestion_get_edns_options(dnsdist_ffi_dnsquestion_t* dq, c
     totalCount += option.second.values.size();
   }
 
-  dq->ednsOptionsVect.clear();
-  dq->ednsOptionsVect.resize(totalCount);
+  if (!dq->ednsOptionsVect) {
+    dq->ednsOptionsVect = std::make_unique<std::vector<dnsdist_ffi_ednsoption_t>>();
+  }
+  dq->ednsOptionsVect->clear();
+  dq->ednsOptionsVect->resize(totalCount);
   size_t pos = 0;
   for (const auto& option : *dq->dq->ednsOptions) {
     for (const auto& entry : option.second.values) {
-      fill_edns_option(entry, dq->ednsOptionsVect.at(pos));
-      dq->ednsOptionsVect.at(pos).optionCode = option.first;
+      fill_edns_option(entry, dq->ednsOptionsVect->at(pos));
+      dq->ednsOptionsVect->at(pos).optionCode = option.first;
       pos++;
     }
   }
 
   if (totalCount > 0) {
-    *out = dq->ednsOptionsVect.data();
+    *out = dq->ednsOptionsVect->data();
   }
 
   return totalCount;
@@ -339,26 +394,33 @@ size_t dnsdist_ffi_dnsquestion_get_edns_options(dnsdist_ffi_dnsquestion_t* dq, c
 
 size_t dnsdist_ffi_dnsquestion_get_http_headers(dnsdist_ffi_dnsquestion_t* dq, const dnsdist_ffi_http_header_t** out)
 {
-  if (dq->dq->du == nullptr) {
+  if (dq->dq->ids.du == nullptr) {
     return 0;
   }
 
 #ifdef HAVE_DNS_OVER_HTTPS
-  dq->httpHeaders = dq->dq->du->getHTTPHeaders();
-  dq->httpHeadersVect.clear();
-  dq->httpHeadersVect.resize(dq->httpHeaders.size());
+  auto headers = dq->dq->ids.du->getHTTPHeaders();
+  if (headers.size() == 0) {
+    return 0;
+  }
+  dq->httpHeaders = std::make_unique<std::unordered_map<std::string, std::string>>(std::move(headers));
+  if (!dq->httpHeadersVect) {
+    dq->httpHeadersVect = std::make_unique<std::vector<dnsdist_ffi_http_header_t>>();
+  }
+  dq->httpHeadersVect->clear();
+  dq->httpHeadersVect->resize(dq->httpHeaders->size());
   size_t pos = 0;
-  for (const auto& header : dq->httpHeaders) {
-    dq->httpHeadersVect.at(pos).name = header.first.c_str();
-    dq->httpHeadersVect.at(pos).value = header.second.c_str();
+  for (const auto& header : *dq->httpHeaders) {
+    dq->httpHeadersVect->at(pos).name = header.first.c_str();
+    dq->httpHeadersVect->at(pos).value = header.second.c_str();
     ++pos;
   }
 
-  if (!dq->httpHeadersVect.empty()) {
-    *out = dq->httpHeadersVect.data();
+  if (!dq->httpHeadersVect->empty()) {
+    *out = dq->httpHeadersVect->data();
   }
 
-  return dq->httpHeadersVect.size();
+  return dq->httpHeadersVect->size();
 #else
   return 0;
 #endif
@@ -366,27 +428,30 @@ size_t dnsdist_ffi_dnsquestion_get_http_headers(dnsdist_ffi_dnsquestion_t* dq, c
 
 size_t dnsdist_ffi_dnsquestion_get_tag_array(dnsdist_ffi_dnsquestion_t* dq, const dnsdist_ffi_tag_t** out)
 {
-  if (dq->dq->qTag == nullptr || dq->dq->qTag->size() == 0) {
+  if (dq == nullptr || dq->dq == nullptr || dq->dq->ids.qTag == nullptr || dq->dq->ids.qTag->size() == 0) {
     return 0;
   }
 
-  dq->tagsVect.clear();
-  dq->tagsVect.resize(dq->dq->qTag->size());
+  if (!dq->tagsVect) {
+    dq->tagsVect = std::make_unique<std::vector<dnsdist_ffi_tag_t>>();
+  }
+  dq->tagsVect->clear();
+  dq->tagsVect->resize(dq->dq->ids.qTag->size());
   size_t pos = 0;
 
-  for (const auto& tag : *dq->dq->qTag) {
-    auto& entry = dq->tagsVect.at(pos);
+  for (const auto& tag : *dq->dq->ids.qTag) {
+    auto& entry = dq->tagsVect->at(pos);
     entry.name = tag.first.c_str();
     entry.value = tag.second.c_str();
     ++pos;
   }
 
 
-  if (!dq->tagsVect.empty()) {
-    *out = dq->tagsVect.data();
+  if (!dq->tagsVect->empty()) {
+    *out = dq->tagsVect->data();
   }
 
-  return dq->tagsVect.size();
+  return dq->tagsVect->size();
 }
 
 void dnsdist_ffi_dnsquestion_set_result(dnsdist_ffi_dnsquestion_t* dq, const char* str, size_t strSize)
@@ -396,21 +461,37 @@ void dnsdist_ffi_dnsquestion_set_result(dnsdist_ffi_dnsquestion_t* dq, const cha
 
 void dnsdist_ffi_dnsquestion_set_http_response(dnsdist_ffi_dnsquestion_t* dq, uint16_t statusCode, const char* body, size_t bodyLen, const char* contentType)
 {
-  if (dq->dq->du == nullptr) {
+  if (dq->dq->ids.du == nullptr) {
     return;
   }
 
 #ifdef HAVE_DNS_OVER_HTTPS
   PacketBuffer bodyVect(body, body + bodyLen);
-  dq->dq->du->setHTTPResponse(statusCode, std::move(bodyVect), contentType);
-  dq->dq->getHeader()->qr = true;
+  dq->dq->ids.du->setHTTPResponse(statusCode, std::move(bodyVect), contentType);
+  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)
@@ -420,7 +501,7 @@ void dnsdist_ffi_dnsquestion_set_len(dnsdist_ffi_dnsquestion_t* dq, uint16_t len
 
 void dnsdist_ffi_dnsquestion_set_skip_cache(dnsdist_ffi_dnsquestion_t* dq, bool skipCache)
 {
-  dq->dq->skipCache = skipCache;
+  dq->dq->ids.skipCache = skipCache;
 }
 
 void dnsdist_ffi_dnsquestion_set_use_ecs(dnsdist_ffi_dnsquestion_t* dq, bool useECS)
@@ -440,12 +521,12 @@ void dnsdist_ffi_dnsquestion_set_ecs_prefix_length(dnsdist_ffi_dnsquestion_t* dq
 
 void dnsdist_ffi_dnsquestion_set_temp_failure_ttl(dnsdist_ffi_dnsquestion_t* dq, uint32_t tempFailureTTL)
 {
-  dq->dq->tempFailureTTL = tempFailureTTL;
+  dq->dq->ids.tempFailureTTL = tempFailureTTL;
 }
 
 void dnsdist_ffi_dnsquestion_unset_temp_failure_ttl(dnsdist_ffi_dnsquestion_t* dq)
 {
-  dq->dq->tempFailureTTL = boost::none;
+  dq->dq->ids.tempFailureTTL = boost::none;
 }
 
 void dnsdist_ffi_dnsquestion_set_tag(dnsdist_ffi_dnsquestion_t* dq, const char* label, const char* value)
@@ -453,6 +534,44 @@ void dnsdist_ffi_dnsquestion_set_tag(dnsdist_ffi_dnsquestion_t* dq, const char*
   dq->dq->setTag(label, value);
 }
 
+void dnsdist_ffi_dnsquestion_set_tag_raw(dnsdist_ffi_dnsquestion_t* dq, const char* label, const char* value, size_t valueSize)
+{
+  dq->dq->setTag(label, std::string(value, valueSize));
+}
+
+void dnsdist_ffi_dnsquestion_set_requestor_id(dnsdist_ffi_dnsquestion_t* dq, const char* value, size_t valueSize)
+{
+  if (!dq || !dq->dq || !value) {
+    return;
+  }
+  if (!dq->dq->ids.d_protoBufData) {
+    dq->dq->ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
+  }
+  dq->dq->ids.d_protoBufData->d_requestorID = std::string(value, valueSize);
+}
+
+void dnsdist_ffi_dnsquestion_set_device_id(dnsdist_ffi_dnsquestion_t* dq, const char* value, size_t valueSize)
+{
+  if (!dq || !dq->dq || !value) {
+    return;
+  }
+  if (!dq->dq->ids.d_protoBufData) {
+    dq->dq->ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
+  }
+  dq->dq->ids.d_protoBufData->d_deviceID = std::string(value, valueSize);
+}
+
+void dnsdist_ffi_dnsquestion_set_device_name(dnsdist_ffi_dnsquestion_t* dq, const char* value, size_t valueSize)
+{
+  if (!dq || !dq->dq || !value) {
+    return;
+  }
+  if (!dq->dq->ids.d_protoBufData) {
+    dq->dq->ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
+  }
+  dq->dq->ids.d_protoBufData->d_deviceName = std::string(value, valueSize);
+}
+
 size_t dnsdist_ffi_dnsquestion_get_trailing_data(dnsdist_ffi_dnsquestion_t* dq, const char** out)
 {
   dq->trailingData = dq->dq->getTrailingData();
@@ -478,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)
@@ -492,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)
@@ -521,8 +640,25 @@ 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)
+{
+  if (dq != nullptr && dq->dq != nullptr) {
+    dq->dq->ids.ttlCap = max;
+  }
+}
+
+bool dnsdist_ffi_dnsquestion_set_restartable(dnsdist_ffi_dnsquestion_t* dq)
+{
+  if (dq == nullptr || dq->dq == nullptr) {
+    return false;
+  }
+
+  dq->dq->ids.d_packet = std::make_unique<PacketBuffer>(dq->dq->getData());
+  return true;
 }
 
 size_t dnsdist_ffi_servers_list_get_count(const dnsdist_ffi_servers_list_t* list)
@@ -574,7 +710,7 @@ int dnsdist_ffi_server_get_order(const dnsdist_ffi_server_t* server)
 
 double dnsdist_ffi_server_get_latency(const dnsdist_ffi_server_t* server)
 {
-  return server->server->latencyUsec;
+  return server->server->getRelevantLatencyUsec();
 }
 
 bool dnsdist_ffi_server_is_up(const dnsdist_ffi_server_t* server)
@@ -604,40 +740,276 @@ void dnsdist_ffi_dnsresponse_set_max_ttl(dnsdist_ffi_dnsresponse_t* dr, uint32_t
 
 void dnsdist_ffi_dnsresponse_limit_ttl(dnsdist_ffi_dnsresponse_t* dr, uint32_t min, uint32_t max)
 {
-  if (dr->dr != nullptr) {
+  if (dr != nullptr && dr->dr != nullptr) {
     std::string result;
     LimitTTLResponseAction ac(min, max);
     ac(dr->dr, &result);
   }
 }
 
+void dnsdist_ffi_dnsresponse_set_max_returned_ttl(dnsdist_ffi_dnsresponse_t* dr, uint32_t max)
+{
+  if (dr != nullptr && dr->dr != nullptr) {
+    dr->dr->ids.ttlCap = max;
+  }
+}
+
 void dnsdist_ffi_dnsresponse_clear_records_type(dnsdist_ffi_dnsresponse_t* dr, uint16_t qtype)
 {
-  if (dr->dr != nullptr) {
-    clearDNSPacketRecordTypes(dr->dr->getMutableData(), std::set<QType>{qtype});
+  if (dr != nullptr && dr->dr != nullptr) {
+    clearDNSPacketRecordTypes(dr->dr->getMutableData(), std::unordered_set<QType>{qtype});
   }
 }
 
-const std::string& getLuaFFIWrappers()
+bool dnsdist_ffi_dnsresponse_rebase(dnsdist_ffi_dnsresponse_t* dr, const char* initialName, size_t initialNameSize)
 {
-  static const std::string interface =
-#include "dnsdist-lua-ffi-interface.inc"
-    ;
-  static const std::string code = R"FFICodeContent(
+  if (dr == nullptr || dr->dr == nullptr || initialName == nullptr || initialNameSize == 0) {
+    return false;
+  }
+
+  try {
+    DNSName parsed(initialName, initialNameSize, 0, false);
+
+    if (!dnsdist::changeNameInDNSPacket(dr->dr->getMutableData(), dr->dr->ids.qname, parsed)) {
+      return false;
+    }
+
+    // set qname to new one
+    dr->dr->ids.qname = std::move(parsed);
+    dr->dr->ids.skipCache = true;
+  }
+  catch (const std::exception& e) {
+    vinfolog("Error rebasing packet on a new DNSName: %s", e.what());
+    return false;
+  }
+
+  return true;
+}
+
+bool dnsdist_ffi_dnsquestion_set_async(dnsdist_ffi_dnsquestion_t* dq, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)
+{
+  try {
+    dq->dq->asynchronous = true;
+    return dnsdist::suspendQuery(*dq->dq, asyncID, queryID, timeoutMs);
+  }
+  catch (const std::exception& e) {
+    vinfolog("Error in dnsdist_ffi_dnsquestion_set_async: %s", e.what());
+  }
+  catch (...) {
+    vinfolog("Exception in dnsdist_ffi_dnsquestion_set_async");
+  }
+
+  return false;
+}
+
+bool dnsdist_ffi_dnsresponse_set_async(dnsdist_ffi_dnsquestion_t* dq, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)
+{
+  try {
+    dq->dq->asynchronous = true;
+    auto dr = dynamic_cast<DNSResponse*>(dq->dq);
+    if (!dr) {
+      vinfolog("Passed a DNSQuestion instead of a DNSResponse to dnsdist_ffi_dnsresponse_set_async");
+      return false;
+    }
+
+    return dnsdist::suspendResponse(*dr, asyncID, queryID, timeoutMs);
+  }
+  catch (const std::exception& e) {
+    vinfolog("Error in dnsdist_ffi_dnsresponse_set_async: %s", e.what());
+  }
+  catch (...) {
+    vinfolog("Exception in dnsdist_ffi_dnsresponse_set_async");
+  }
+  return false;
+}
+
+bool dnsdist_ffi_resume_from_async(uint16_t asyncID, uint16_t queryID, const char* tag, size_t tagSize, const char* tagValue, size_t tagValueSize, bool useCache)
+{
+  if (!dnsdist::g_asyncHolder) {
+    vinfolog("Unable to resume, no asynchronous holder");
+    return false;
+  }
+
+  auto query = dnsdist::g_asyncHolder->get(asyncID, queryID);
+  if (!query) {
+    vinfolog("Unable to resume, no object found for asynchronous ID %d and query ID %d", asyncID, queryID);
+    return false;
+  }
+
+  auto& ids = query->query.d_idstate;
+  if (tag != nullptr && tagSize > 0) {
+    if (!ids.qTag) {
+      ids.qTag = std::make_unique<QTag>();
+    }
+    (*ids.qTag)[std::string(tag, tagSize)] = std::string(tagValue, tagValueSize);
+  }
+
+  ids.skipCache = !useCache;
+
+  return dnsdist::queueQueryResumptionEvent(std::move(query));
+}
+
+bool dnsdist_ffi_set_rcode_from_async(uint16_t asyncID, uint16_t queryID, uint8_t rcode, bool clearAnswers)
+{
+  if (!dnsdist::g_asyncHolder) {
+    return false;
+  }
+
+  auto query = dnsdist::g_asyncHolder->get(asyncID, queryID);
+  if (!query) {
+    vinfolog("Unable to resume with a custom response code, no object found for asynchronous ID %d and query ID %d", asyncID, queryID);
+    return false;
+  }
+
+  if (!dnsdist::setInternalQueryRCode(query->query.d_idstate, query->query.d_buffer, rcode, clearAnswers)) {
+    return false;
+  }
+
+  query->query.d_idstate.skipCache = true;
+
+  return dnsdist::queueQueryResumptionEvent(std::move(query));
+}
+
+bool dnsdist_ffi_resume_from_async_with_alternate_name(uint16_t asyncID, uint16_t queryID, const char* alternateName, size_t alternateNameSize, const char* tag, size_t tagSize, const char* tagValue, size_t tagValueSize, const char* formerNameTagName, size_t formerNameTagSize)
+{
+  if (!dnsdist::g_asyncHolder) {
+    return false;
+  }
+
+  auto query = dnsdist::g_asyncHolder->get(asyncID, queryID);
+  if (!query) {
+    vinfolog("Unable to resume with an alternate name, no object found for asynchronous ID %d and query ID %d", asyncID, queryID);
+    return false;
+  }
+
+  auto& ids = query->query.d_idstate;
+  DNSName originalName = ids.qname;
+
+  try {
+    DNSName parsed(alternateName, alternateNameSize, 0, false);
+
+    PacketBuffer initialPacket;
+    if (query->d_isResponse) {
+      if (!ids.d_packet) {
+        return false;
+      }
+      initialPacket = std::move(*ids.d_packet);
+    }
+    else {
+      initialPacket = std::move(query->query.d_buffer);
+    }
+
+    // edit qname in query packet
+    if (!dnsdist::changeNameInDNSPacket(initialPacket, originalName, parsed)) {
+      return false;
+    }
+    if (query->d_isResponse) {
+      query->d_isResponse = false;
+    }
+    query->query.d_buffer = std::move(initialPacket);
+    // set qname to new one
+    ids.qname = std::move(parsed);
+  }
+  catch (const std::exception& e) {
+    vinfolog("Error rebasing packet on a new DNSName: %s", e.what());
+    return false;
+  }
+
+  // save existing qname in tag
+  if (formerNameTagName != nullptr && formerNameTagSize > 0) {
+    if (!ids.qTag) {
+      ids.qTag = std::make_unique<QTag>();
+    }
+    (*ids.qTag)[std::string(formerNameTagName, formerNameTagSize)] = originalName.getStorage();
+  }
+
+  if (tag != nullptr && tagSize > 0) {
+    if (!ids.qTag) {
+      ids.qTag = std::make_unique<QTag>();
+    }
+    (*ids.qTag)[std::string(tag, tagSize)] = std::string(tagValue, tagValueSize);
+  }
+
+  ids.skipCache = true;
+
+  // resume as query
+  return dnsdist::queueQueryResumptionEvent(std::move(query));
+}
+
+bool dnsdist_ffi_drop_from_async(uint16_t asyncID, uint16_t queryID)
+{
+  if (!dnsdist::g_asyncHolder) {
+    return false;
+  }
+
+  auto query = dnsdist::g_asyncHolder->get(asyncID, queryID);
+  if (!query) {
+    vinfolog("Unable to drop, no object found for asynchronous ID %d and query ID %d", asyncID, queryID);
+    return false;
+  }
+
+  auto sender = query->getTCPQuerySender();
+  if (!sender) {
+    return false;
+  }
+
+  struct timeval now;
+  gettimeofday(&now, nullptr);
+  TCPResponse tresponse(std::move(query->query));
+  sender->notifyIOError(now, std::move(tresponse));
+
+  return true;
+}
+
+bool dnsdist_ffi_set_answer_from_async(uint16_t asyncID, uint16_t queryID, const char* raw, size_t rawSize)
+{
+  if (rawSize < sizeof(dnsheader)) {
+    return false;
+  }
+  if (!dnsdist::g_asyncHolder) {
+    return false;
+  }
+
+  auto query = dnsdist::g_asyncHolder->get(asyncID, queryID);
+  if (!query) {
+    vinfolog("Unable to resume with a custom answer, no object found for asynchronous ID %d and query ID %d", asyncID, queryID);
+    return false;
+  }
+
+  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);
+
+  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));
+}
+
+static constexpr char s_lua_ffi_code[] = R"FFICodeContent(
   local ffi = require("ffi")
   local C = ffi.C
 
   ffi.cdef[[
-)FFICodeContent" + interface + R"FFICodeContent(
+)FFICodeContent"
+#include "dnsdist-lua-ffi-interface.inc"
+R"FFICodeContent(
   ]]
 
 )FFICodeContent";
-  return code;
+
+const char* getLuaFFIWrappers()
+{
+  return s_lua_ffi_code;
 }
 
 void setupLuaLoadBalancingContext(LuaContext& luaCtx)
 {
-  setupLuaBindings(luaCtx, true);
+  setupLuaBindings(luaCtx, true, false);
   setupLuaBindingsDNSQuestion(luaCtx);
   setupLuaBindingsKVS(luaCtx, true);
   setupLuaVars(luaCtx);
@@ -706,7 +1078,7 @@ size_t dnsdist_ffi_dnsquestion_generate_proxy_protocol_payload(const dnsdist_ffi
     }
   }
 
-  std::string payload = makeProxyHeader(dq->dq->overTCP(), *dq->dq->remote, *dq->dq->local, valuesVect);
+  std::string payload = makeProxyHeader(dq->dq->overTCP(), dq->dq->ids.origRemote, dq->dq->ids.origDest, valuesVect);
   if (payload.size() > outSize) {
     return 0;
   }
@@ -715,3 +1087,932 @@ size_t dnsdist_ffi_dnsquestion_generate_proxy_protocol_payload(const dnsdist_ffi
 
   return payload.size();
 }
+
+struct dnsdist_ffi_domain_list_t
+{
+  std::vector<std::string> d_domains;
+};
+struct dnsdist_ffi_address_list_t {
+  std::vector<std::string> d_addresses;
+};
+
+const char* dnsdist_ffi_domain_list_get(const dnsdist_ffi_domain_list_t* list, size_t idx)
+{
+  if (list == nullptr || idx >= list->d_domains.size()) {
+    return nullptr;
+  }
+
+  return list->d_domains.at(idx).c_str();
+}
+
+void dnsdist_ffi_domain_list_free(dnsdist_ffi_domain_list_t* list)
+{
+  delete list;
+}
+
+const char* dnsdist_ffi_address_list_get(const dnsdist_ffi_address_list_t* list, size_t idx)
+{
+  if (list == nullptr || idx >= list->d_addresses.size()) {
+    return nullptr;
+  }
+
+  return list->d_addresses.at(idx).c_str();
+}
+
+void dnsdist_ffi_address_list_free(dnsdist_ffi_address_list_t* list)
+{
+  delete list;
+}
+
+size_t dnsdist_ffi_packetcache_get_domain_list_by_addr(const char* poolName, const char* addr, dnsdist_ffi_domain_list_t** out)
+{
+  if (poolName == nullptr || addr == nullptr || out == nullptr) {
+    return 0;
+  }
+
+  ComboAddress ca;
+  try {
+    ca = ComboAddress(addr);
+  }
+  catch (const std::exception& e) {
+    vinfolog("Error parsing address passed to dnsdist_ffi_packetcache_get_domain_list_by_addr: %s", e.what());
+    return 0;
+  }
+  catch (const PDNSException& e) {
+    vinfolog("Error parsing address passed to dnsdist_ffi_packetcache_get_domain_list_by_addr: %s", e.reason);
+    return 0;
+  }
+
+  const auto localPools = g_pools.getCopy();
+  auto it = localPools.find(poolName);
+  if (it == localPools.end()) {
+    return 0;
+  }
+
+  auto pool = it->second;
+  if (!pool->packetCache) {
+    return 0;
+  }
+
+  auto domains = pool->packetCache->getDomainsContainingRecords(ca);
+  if (domains.size() == 0) {
+    return 0;
+  }
+
+  auto list = std::make_unique<dnsdist_ffi_domain_list_t>();
+  list->d_domains.reserve(domains.size());
+  for (const auto& domain : domains) {
+    try {
+      list->d_domains.push_back(domain.toString());
+    }
+    catch (const std::exception& e) {
+      vinfolog("Error converting domain to string in dnsdist_ffi_packetcache_get_domain_list_by_addr: %s", e.what());
+    }
+  }
+
+  size_t count = list->d_domains.size();
+  if (count > 0) {
+    *out = list.release();
+  }
+  return count;
+}
+
+size_t dnsdist_ffi_packetcache_get_address_list_by_domain(const char* poolName, const char* domain, dnsdist_ffi_address_list_t** out)
+{
+  if (poolName == nullptr || domain == nullptr || out == nullptr) {
+    return 0;
+  }
+
+  DNSName name;
+  try {
+    name = DNSName(domain);
+  }
+  catch (const std::exception& e) {
+    vinfolog("Error parsing domain passed to dnsdist_ffi_packetcache_get_address_list_by_domain: %s", e.what());
+    return 0;
+  }
+
+  const auto localPools = g_pools.getCopy();
+  auto it = localPools.find(poolName);
+  if (it == localPools.end()) {
+    return 0;
+  }
+
+  auto pool = it->second;
+  if (!pool->packetCache) {
+    return 0;
+  }
+
+  auto addresses = pool->packetCache->getRecordsForDomain(name);
+  if (addresses.size() == 0) {
+    return 0;
+  }
+
+  auto list = std::make_unique<dnsdist_ffi_address_list_t>();
+  list->d_addresses.reserve(addresses.size());
+  for (const auto& addr : addresses) {
+    try {
+      list->d_addresses.push_back(addr.toString());
+    }
+    catch (const std::exception& e) {
+      vinfolog("Error converting address to string in dnsdist_ffi_packetcache_get_address_list_by_domain: %s", e.what());
+    }
+  }
+
+  size_t count = list->d_addresses.size();
+  if (count > 0) {
+    *out = list.release();
+  }
+  return count;
+}
+
+struct dnsdist_ffi_ring_entry_list_t
+{
+  struct entry
+  {
+    std::string qname;
+    std::string requestor;
+    std::string macAddr;
+    std::string ds;
+    dnsheader dh;
+    double age;
+    unsigned int latency;
+    uint16_t size;
+    uint16_t qtype;
+    dnsdist::Protocol protocol;
+    bool isResponse;
+  };
+
+  std::vector<entry> d_entries;
+};
+
+bool dnsdist_ffi_ring_entry_is_response(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).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()) {
+    return nullptr;
+  }
+
+  return list->d_entries.at(idx).qname.c_str();
+}
+
+uint16_t dnsdist_ffi_ring_entry_get_type(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).qtype;
+}
+
+const char* dnsdist_ffi_ring_entry_get_requestor(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).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()) {
+    return 0;
+  }
+
+  return list->d_entries.at(idx).protocol.toNumber();
+}
+
+uint16_t dnsdist_ffi_ring_entry_get_size(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).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)
+{
+  if (list == nullptr || idx >= list->d_entries.size()) {
+    return false;
+  }
+
+  return !list->d_entries.at(idx).macAddr.empty();
+}
+
+const char* dnsdist_ffi_ring_entry_get_mac_address(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).macAddr.data();
+}
+
+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 struct timespec& now, const T& entry)
+{
+  auto age = DiffTime(entry.when, now);
+
+  constexpr bool response = std::is_same_v<T, Rings::Response>;
+  if constexpr (!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.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));
+  }
+}
+
+size_t dnsdist_ffi_ring_get_entries(dnsdist_ffi_ring_entry_list_t** out)
+{
+  if (out == nullptr) {
+    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, now, entry);
+      }
+    }
+    {
+      auto rl = shard->respRing.lock();
+      for (const auto& entry : *rl) {
+        addRingEntryToList(list, now, entry);
+      }
+    }
+  }
+
+  auto count = list->d_entries.size();
+  if (count > 0) {
+    *out = list.release();
+  }
+  return count;
+}
+
+size_t dnsdist_ffi_ring_get_entries_by_addr(const char* addr, dnsdist_ffi_ring_entry_list_t** out)
+{
+  if (out == nullptr || addr == nullptr) {
+    return 0;
+  }
+  ComboAddress ca;
+  try {
+    ca = ComboAddress(addr);
+  }
+  catch (const std::exception& e) {
+    vinfolog("Unable to convert address in dnsdist_ffi_ring_get_entries_by_addr: %s", e.what());
+    return 0;
+  }
+  catch (const PDNSException& e) {
+    vinfolog("Unable to convert address in dnsdist_ffi_ring_get_entries_by_addr: %s", e.reason);
+    return 0;
+  }
+
+  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) {
+    {
+      auto ql = shard->queryRing.lock();
+      for (const auto& entry : *ql) {
+        if (!compare(entry.requestor, ca)) {
+          continue;
+        }
+
+        addRingEntryToList(list, now, entry);
+      }
+    }
+    {
+      auto rl = shard->respRing.lock();
+      for (const auto& entry : *rl) {
+        if (!compare(entry.requestor, ca)) {
+          continue;
+        }
+
+        addRingEntryToList(list, now, entry);
+      }
+    }
+  }
+
+  auto count = list->d_entries.size();
+  if (count > 0) {
+    *out = list.release();
+  }
+  return count;
+}
+
+size_t dnsdist_ffi_ring_get_entries_by_mac(const char* addr, dnsdist_ffi_ring_entry_list_t** out)
+{
+  if (out == nullptr || addr == nullptr) {
+    return 0;
+  }
+
+#if !defined(DNSDIST_RINGS_WITH_MACADDRESS)
+  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();
+    for (const auto& entry : *ql) {
+      if (memcmp(addr, entry.macaddress.data(), entry.macaddress.size()) != 0) {
+        continue;
+      }
+
+      addRingEntryToList(list, now, entry);
+    }
+  }
+
+  auto count = list->d_entries.size();
+  if (count > 0) {
+    *out = list.release();
+  }
+  return count;
+#endif
+}
+
+struct dnsdist_ffi_network_endpoint_t
+{
+  dnsdist::NetworkEndpoint d_endpoint;
+};
+
+bool dnsdist_ffi_network_endpoint_new(const char* path, size_t pathSize, dnsdist_ffi_network_endpoint_t** out)
+{
+  if (path == nullptr || pathSize == 0 || out == nullptr) {
+    return false;
+  }
+  try {
+    dnsdist::NetworkEndpoint endpoint(std::string(path, pathSize));
+    *out = new dnsdist_ffi_network_endpoint_t{std::move(endpoint)};
+    return true;
+  }
+  catch (const std::exception& e) {
+    vinfolog("Error creating a new network endpoint: %s", e.what());
+    return false;
+  }
+}
+
+bool dnsdist_ffi_network_endpoint_is_valid(const dnsdist_ffi_network_endpoint_t* endpoint)
+{
+  return endpoint != nullptr;
+}
+
+bool dnsdist_ffi_network_endpoint_send(const dnsdist_ffi_network_endpoint_t* endpoint, const char* payload, size_t payloadSize)
+{
+  if (endpoint != nullptr && payload != nullptr && payloadSize != 0) {
+    return endpoint->d_endpoint.send(std::string_view(payload, payloadSize));
+  }
+  return false;
+}
+
+void dnsdist_ffi_network_endpoint_free(dnsdist_ffi_network_endpoint_t* endpoint)
+{
+  delete endpoint;
+}
+
+struct dnsdist_ffi_dnspacket_t
+{
+  dnsdist::DNSPacketOverlay overlay;
+};
+
+bool dnsdist_ffi_dnspacket_parse(const char* packet, size_t packetSize, dnsdist_ffi_dnspacket_t** out)
+{
+  if (packet == nullptr || out == nullptr || packetSize < sizeof(dnsheader)) {
+    return false;
+  }
+
+  try {
+    dnsdist::DNSPacketOverlay overlay(std::string_view(packet, packetSize));
+    *out = new dnsdist_ffi_dnspacket_t{std::move(overlay)};
+    return true;
+  }
+  catch (const std::exception& e) {
+    vinfolog("Error in dnsdist_ffi_dnspacket_parse: %s", e.what());
+  }
+  catch (...) {
+    vinfolog("Error in dnsdist_ffi_dnspacket_parse");
+  }
+  return false;
+}
+
+void dnsdist_ffi_dnspacket_get_qname_raw(const dnsdist_ffi_dnspacket_t* packet, const char** qname, size_t* qnameSize)
+{
+  if (packet == nullptr || qname == nullptr || qnameSize == nullptr) {
+    return;
+  }
+  const auto& storage = packet->overlay.d_qname.getStorage();
+  *qname = storage.data();
+  *qnameSize = storage.size();
+}
+
+uint16_t dnsdist_ffi_dnspacket_get_qtype(const dnsdist_ffi_dnspacket_t* packet)
+{
+  if (packet != nullptr) {
+    return packet->overlay.d_qtype;
+  }
+  return 0;
+}
+
+uint16_t dnsdist_ffi_dnspacket_get_qclass(const dnsdist_ffi_dnspacket_t* packet)
+{
+  if (packet != nullptr) {
+    return packet->overlay.d_qclass;
+  }
+  return 0;
+}
+
+uint16_t dnsdist_ffi_dnspacket_get_records_count_in_section(const dnsdist_ffi_dnspacket_t* packet, uint8_t section)
+{
+  if (packet == nullptr || section > DNSResourceRecord::ADDITIONAL) {
+    return 0;
+  }
+
+  uint16_t count = 0;
+  for (const auto& record : packet->overlay.d_records) {
+    if (record.d_place == section) {
+      count++;
+    }
+  }
+
+  return count;
+}
+
+void dnsdist_ffi_dnspacket_get_record_name_raw(const dnsdist_ffi_dnspacket_t* packet, size_t idx, const char** name, size_t* nameSize)
+{
+  if (packet == nullptr || name == nullptr || nameSize == nullptr || idx >= packet->overlay.d_records.size()) {
+    return;
+  }
+  const auto& storage = packet->overlay.d_records.at(idx).d_name.getStorage();
+  *name = storage.data();
+  *nameSize = storage.size();
+}
+
+uint16_t dnsdist_ffi_dnspacket_get_record_type(const dnsdist_ffi_dnspacket_t* packet, size_t idx)
+{
+  if (packet == nullptr || idx >= packet->overlay.d_records.size()) {
+    return 0;
+  }
+  return packet->overlay.d_records.at(idx).d_type;
+}
+
+uint16_t dnsdist_ffi_dnspacket_get_record_class(const dnsdist_ffi_dnspacket_t* packet, size_t idx)
+{
+  if (packet == nullptr || idx >= packet->overlay.d_records.size()) {
+    return 0;
+  }
+  return packet->overlay.d_records.at(idx).d_class;
+}
+
+uint32_t dnsdist_ffi_dnspacket_get_record_ttl(const dnsdist_ffi_dnspacket_t* packet, size_t idx)
+{
+  if (packet == nullptr || idx >= packet->overlay.d_records.size()) {
+    return 0;
+  }
+  return packet->overlay.d_records.at(idx).d_ttl;
+}
+
+uint16_t dnsdist_ffi_dnspacket_get_record_content_length(const dnsdist_ffi_dnspacket_t* packet, size_t idx)
+{
+  if (packet == nullptr || idx >= packet->overlay.d_records.size()) {
+    return 0;
+  }
+  return packet->overlay.d_records.at(idx).d_contentLength;
+}
+
+uint16_t dnsdist_ffi_dnspacket_get_record_content_offset(const dnsdist_ffi_dnspacket_t* packet, size_t idx)
+{
+  if (packet == nullptr || idx >= packet->overlay.d_records.size()) {
+    return 0;
+  }
+  return packet->overlay.d_records.at(idx).d_contentOffset;
+}
+
+size_t dnsdist_ffi_dnspacket_get_name_at_offset_raw(const char* packet, size_t packetSize, size_t offset, char* name, size_t nameSize)
+{
+  if (packet == nullptr || name == nullptr || nameSize == 0 || offset >= packetSize) {
+    return 0;
+  }
+  try {
+    DNSName parsed(packet, packetSize, offset, true);
+    const auto& storage = parsed.getStorage();
+    if (nameSize < storage.size()) {
+      return 0;
+    }
+    memcpy(name, storage.data(), storage.size());
+    return storage.size();
+  }
+  catch (const std::exception& e) {
+    vinfolog("Error parsing DNSName via dnsdist_ffi_dnspacket_get_name_at_offset_raw: %s", e.what());
+  }
+  return 0;
+}
+
+void dnsdist_ffi_dnspacket_free(dnsdist_ffi_dnspacket_t* packet)
+{
+  if (packet != nullptr) {
+    delete 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 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 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 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 result = dnsdist::metrics::getCustomMetric(std::string_view(metricName, metricNameLen));
+  if (std::get_if<dnsdist::metrics::Error>(&result) != nullptr) {
+    return 0.;
+  }
+  return std::get<double>(result);
+}
+
+const char* dnsdist_ffi_network_message_get_payload(const dnsdist_ffi_network_message_t* msg)
+{
+  if (msg != nullptr) {
+    return msg->payload.c_str();
+  }
+  return nullptr;
+}
+
+size_t dnsdist_ffi_network_message_get_payload_size(const dnsdist_ffi_network_message_t* msg)
+{
+  if (msg != nullptr) {
+    return msg->payload.size();
+  }
+  return 0;
+}
+
+uint16_t dnsdist_ffi_network_message_get_endpoint_id(const dnsdist_ffi_network_message_t* msg)
+{
+  if (msg != nullptr) {
+    return msg->endpointID;
+  }
+  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 24ebe85d0b8eacf10a791005bf353379fe1a8e29..a91003b971ad0ae557d05665325ae4c000dc6a39 100644 (file)
@@ -46,17 +46,17 @@ struct dnsdist_ffi_dnsquestion_t
   }
 
   DNSQuestion* dq{nullptr};
-  std::vector<dnsdist_ffi_ednsoption_t> ednsOptionsVect;
-  std::vector<dnsdist_ffi_http_header_t> httpHeadersVect;
-  std::vector<dnsdist_ffi_tag_t> tagsVect;
-  std::unordered_map<std::string, std::string> httpHeaders;
-  std::string trailingData;
   ComboAddress maskedRemote;
-  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::string trailingData;
+  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;
+  std::unique_ptr<std::unordered_map<std::string, std::string>> httpHeaders;
 };
 
 // dnsdist_ffi_dnsresponse_t is a lightuserdata
@@ -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
@@ -128,5 +128,28 @@ struct dnsdist_ffi_servers_list_t
   const ServerPolicy::NumberedServerVector& servers;
 };
 
-const std::string& getLuaFFIWrappers();
+// dnsdist_ffi_network_message_t is a lightuserdata
+template<>
+struct LuaContext::Pusher<dnsdist_ffi_network_message_t*> {
+    static const int minSize = 1;
+    static const int maxSize = 1;
+
+    static PushedObject push(lua_State* state, dnsdist_ffi_network_message_t* ptr) noexcept {
+        lua_pushlightuserdata(state, ptr);
+        return PushedObject{state, 1};
+    }
+};
+
+struct dnsdist_ffi_network_message_t
+{
+  dnsdist_ffi_network_message_t(const std::string& payload_ ,const std::string& from_, uint16_t endpointID_): payload(payload_), from(from_), endpointID(endpointID_)
+  {
+  }
+
+  const std::string& payload;
+  const std::string& from;
+  uint16_t endpointID;
+};
+
+const char* getLuaFFIWrappers();
 void setupLuaFFIPerThreadContext(LuaContext& luaCtx);
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);
+  });
+}
+
+}
similarity index 76%
rename from pdns/ascii.hh
rename to pdns/dnsdistdist/dnsdist-lua-hooks.hh
index 09f42de6f167bfb944ac2b882aafc0f90703e9ed..03f1ac4ec41b653553b505f1809b15857634c68d 100644 (file)
  */
 #pragma once
 
-inline bool dns_isspace(char c)
-{
-  return c==' ' || c=='\t' || c=='\r' || c=='\n';
-}
+#include <functional>
 
-inline unsigned char dns_toupper(unsigned char c)
-{
-  if(c>='a' && c<='z')
-    c+='A'-'a';
-  return c;
-}
+class LuaContext;
 
-inline unsigned char dns_tolower(unsigned char c)
+namespace dnsdist::lua::hooks
 {
-  if(c>='A' && c<='Z')
-    c+='a'-'A';
-  return c;
+using MaintenanceCallback = std::function<void()>;
+void runMaintenanceHooks(const LuaContext& context);
+void addMaintenanceCallback(MaintenanceCallback callback);
+void clearMaintenanceHooks();
+void setupLuaHooks(LuaContext& luaCtx);
 }
index 13158faa035a8bba050f1cc15b7d05d33a267006..232d5f991923e2a47b37117d42be923e8595b560 100644 (file)
@@ -23,6 +23,7 @@
 #include "dnsdist.hh"
 #include "dnsdist-dynblocks.hh"
 
+#ifndef DISABLE_DYNBLOCKS
 uint64_t dnsdist_ffi_stat_node_get_queries_count(const dnsdist_ffi_stat_node_t* node)
 {
   return node->self.queries;
@@ -53,6 +54,11 @@ uint64_t dnsdist_ffi_stat_node_get_bytes(const dnsdist_ffi_stat_node_t* node)
   return node->self.bytes;
 }
 
+uint64_t dnsdist_ffi_stat_node_get_hits(const dnsdist_ffi_stat_node_t* node)
+{
+  return node->self.hits;
+}
+
 unsigned int dnsdist_ffi_stat_node_get_labels_count(const dnsdist_ffi_stat_node_t* node)
 {
   return node->node.labelsCount;
@@ -100,7 +106,18 @@ uint64_t dnsdist_ffi_stat_node_get_children_bytes_count(const dnsdist_ffi_stat_n
   return node->children.bytes;
 }
 
+uint64_t dnsdist_ffi_stat_node_get_children_hits(const dnsdist_ffi_stat_node_t* node)
+{
+  return node->children.hits;
+}
+
 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 05f087f..0000000
+++ /dev/null
@@ -1,45 +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")));
-  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")));
-
-  void dnsdist_ffi_state_node_set_reason(dnsdist_ffi_stat_node_t* node, const char* reason, size_t reasonSize) __attribute__ ((visibility ("default")));
-}
diff --git a/pdns/dnsdistdist/dnsdist-lua-network.cc b/pdns/dnsdistdist/dnsdist-lua-network.cc
new file mode 100644 (file)
index 0000000..56a58cd
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * 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 <sys/socket.h>
+#include <sys/un.h>
+
+#include "dnsdist-lua-network.hh"
+#include "dolog.hh"
+#include "threadname.hh"
+
+namespace dnsdist
+{
+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);
+  std::string packet;
+
+#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, nullptr);
+  if (peeked > 0) {
+    packet.resize(static_cast<size_t>(peeked));
+  }
+#endif
+  if (packet.empty()) {
+    packet.resize(65535);
+  }
+
+  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 {
+      cbData->d_cb(cbData->d_endpoint, std::move(packet), fromAddr);
+    }
+    catch (const std::exception& e) {
+      vinfolog("Exception in the read callback of a NetworkListener: %s", e.what());
+    }
+    catch (...) {
+      vinfolog("Exception in the read callback of a NetworkListener");
+    }
+  }
+}
+
+bool NetworkListener::addUnixListeningEndpoint(const std::string& path, NetworkListener::EndpointID endpointID, NetworkListener::NetworkDatagramCB callback)
+{
+  if (d_data->d_running) {
+    throw std::runtime_error("NetworkListener should not be altered at runtime");
+  }
+
+  sockaddr_un sun{};
+  if (makeUNsockaddr(path, &sun) != 0) {
+    throw std::runtime_error("Invalid Unix socket path '" + path + "'");
+  }
+
+  bool abstractPath = path.at(0) == '\0';
+  if (!abstractPath) {
+    int err = unlink(path.c_str());
+    if (err != 0) {
+      err = errno;
+      if (err != ENOENT) {
+        vinfolog("Error removing Unix socket to path '%s': %s", path, stringerror(err));
+      }
+    }
+  }
+
+  Socket sock(sun.sun_family, SOCK_DGRAM, 0);
+  socklen_t sunLength = sizeof(sun);
+  if (abstractPath) {
+    /* 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 (bind(sock.getHandle(), reinterpret_cast<const struct sockaddr*>(&sun), sunLength) != 0) {
+    std::string sanitizedPath(path);
+    if (abstractPath) {
+      sanitizedPath[0] = '@';
+    }
+    throw std::runtime_error("Error binding Unix socket to path '" + sanitizedPath + "': " + stringerror());
+  }
+
+  sock.setNonBlocking();
+
+  auto cbData = std::make_shared<CBData>();
+  cbData->d_endpoint = endpointID;
+  cbData->d_cb = std::move(callback);
+  d_data->d_mplexer->addReadFD(sock.getHandle(), readCB, cbData);
+
+  d_data->d_sockets.insert({path, std::move(sock)});
+  return true;
+}
+
+void NetworkListener::runOnce(ListenerData& data, timeval& now, uint32_t timeout)
+{
+  if (data.d_exiting) {
+    return;
+  }
+
+  data.d_running = true;
+  if (data.d_sockets.empty()) {
+    throw runtime_error("NetworkListener started with no sockets");
+  }
+
+  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(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");
+  timeval now{};
+
+  while (!data->d_exiting) {
+    runOnce(*data, now, -1);
+  }
+}
+
+void NetworkListener::start()
+{
+  std::thread main = std::thread([this] {
+    mainThread(d_data);
+  });
+  main.detach();
+}
+
+NetworkEndpoint::NetworkEndpoint(const std::string& path) :
+  d_socket(AF_UNIX, SOCK_DGRAM, 0)
+{
+  sockaddr_un sun{};
+  if (makeUNsockaddr(path, &sun) != 0) {
+    throw std::runtime_error("Invalid Unix socket path '" + path + "'");
+  }
+
+  socklen_t sunLength = sizeof(sun);
+  bool abstractPath = path.at(0) == '\0';
+
+  if (abstractPath) {
+    /* 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) {
+      sanitizedPath[0] = '@';
+    }
+    throw std::runtime_error("Error connecting Unix socket to path '" + sanitizedPath + "': " + stringerror());
+  }
+
+  d_socket.setNonBlocking();
+}
+
+bool NetworkEndpoint::send(const std::string_view& payload) const
+{
+  auto sent = ::send(d_socket.getHandle(), payload.data(), payload.size(), 0);
+  if (sent <= 0) {
+    return false;
+  }
+
+  return static_cast<size_t>(sent) == payload.size();
+}
+}
diff --git a/pdns/dnsdistdist/dnsdist-lua-network.hh b/pdns/dnsdistdist/dnsdist-lua-network.hh
new file mode 100644 (file)
index 0000000..3cd2f08
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * 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 <thread>
+#include <unordered_map>
+
+#include "lock.hh"
+#include "mplexer.hh"
+#include "sstuff.hh"
+
+namespace dnsdist
+{
+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 endpointID, NetworkDatagramCB callback);
+  void start();
+  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);
+  static void mainThread(std::shared_ptr<ListenerData>& data);
+  static void runOnce(ListenerData& data, timeval& now, uint32_t timeout);
+
+  struct CBData
+  {
+    NetworkDatagramCB d_cb;
+    EndpointID d_endpoint;
+  };
+
+  std::shared_ptr<ListenerData> d_data;
+};
+
+class NetworkEndpoint
+{
+public:
+  NetworkEndpoint(const std::string& path);
+  bool send(const std::string_view& payload) const;
+
+private:
+  Socket d_socket;
+};
+}
diff --git a/pdns/dnsdistdist/dnsdist-mac-address.cc b/pdns/dnsdistdist/dnsdist-mac-address.cc
new file mode 100644 (file)
index 0000000..20a7245
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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-mac-address.hh"
+#include "misc.hh"
+
+namespace dnsdist
+{
+LockGuarded<boost::circular_buffer<MacAddressesCache::Entry>> MacAddressesCache::s_cache;
+
+int MacAddressesCache::get(const ComboAddress& ca, unsigned char* dest, size_t destLen)
+{
+  if (dest == nullptr || destLen < sizeof(Entry::mac)) {
+    return EINVAL;
+  }
+
+  auto compare = ComboAddress::addressOnlyEqual();
+  time_t now = time(nullptr);
+
+  {
+    auto cache = s_cache.lock();
+    for (const auto& entry : *cache) {
+      if (entry.ttd >= now && compare(entry.ca, ca) == true) {
+        if (!entry.found) {
+          // negative entry
+          return ENOENT;
+        }
+        memcpy(dest, entry.mac.data(), entry.mac.size());
+        return 0;
+      }
+    }
+  }
+
+  auto res = getMACAddress(ca, reinterpret_cast<char*>(dest), destLen);
+  {
+    auto cache = s_cache.lock();
+    if (cache->capacity() == 0) {
+      cache->set_capacity(MacAddressesCache::s_cacheSize);
+    }
+    Entry entry;
+    entry.ca = ca;
+    if (res == 0) {
+      memcpy(entry.mac.data(), dest, entry.mac.size());
+      entry.found = true;
+    }
+    else {
+      memset(entry.mac.data(), 0, entry.mac.size());
+      entry.found = false;
+    }
+    entry.ttd = now + MacAddressesCache::s_cacheValiditySeconds;
+    cache->push_back(std::move(entry));
+  }
+
+  return res;
+}
+}
diff --git a/pdns/dnsdistdist/dnsdist-mac-address.hh b/pdns/dnsdistdist/dnsdist-mac-address.hh
new file mode 100644 (file)
index 0000000..fe2f1f0
--- /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.
+ */
+#pragma once
+
+#include "circular_buffer.hh"
+#include "iputils.hh"
+#include "lock.hh"
+
+namespace dnsdist
+{
+using MacAddress = std::array<uint8_t, 6>;
+
+class MacAddressesCache
+{
+public:
+  static int get(const ComboAddress& ca, unsigned char* dest, size_t len);
+
+private:
+  struct Entry
+  {
+    ComboAddress ca;
+    MacAddress mac;
+    time_t ttd;
+    bool found;
+  };
+
+  static constexpr size_t s_cacheSize{10};
+  static constexpr time_t s_cacheValiditySeconds{60};
+  static LockGuarded<boost::circular_buffer<Entry>> s_cache;
+};
+}
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..5fe0206
--- /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;
+    }
+
+#if 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)) {
+#if 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 573319a46ca03ae85321b03e04bfed2d3c0d2bb4..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"
@@ -42,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:
@@ -81,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:
@@ -94,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);
@@ -131,10 +130,31 @@ 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 {
-    request.d_sender->handleResponse(now, TCPResponse(std::move(request.d_buffer), std::move(request.d_query.d_idstate), shared_from_this()));
+    if (!d_healthCheckQuery) {
+      const double udiff = request.d_query.d_idstate.queryRealTime.udiff();
+      d_ds->updateTCPLatency(udiff);
+      if (request.d_buffer.size() >= sizeof(dnsheader)) {
+        dnsheader dh;
+        memcpy(&dh, request.d_buffer.data(), sizeof(dh));
+        d_ds->reportResponse(dh.rcode);
+      }
+      else {
+        d_ds->reportTimeoutOrError();
+      }
+    }
+
+    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());
@@ -144,7 +164,12 @@ void DoHConnectionToBackend::handleResponse(PendingRequest&& request)
 void DoHConnectionToBackend::handleResponseError(PendingRequest&& request, const struct timeval& now)
 {
   try {
-    request.d_sender->notifyIOError(std::move(request.d_query.d_idstate), now);
+    if (!d_healthCheckQuery) {
+      d_ds->reportTimeoutOrError();
+    }
+
+    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());
@@ -156,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);
@@ -203,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());
@@ -257,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);
       }
     }
   }
@@ -309,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()) {
@@ -350,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)
@@ -385,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;
 
@@ -460,20 +455,26 @@ void DoHConnectionToBackend::stopIO()
 {
   d_ioState->reset();
 
-  auto shared = std::dynamic_pointer_cast<DoHConnectionToBackend>(shared_from_this());
-  if (!willBeReusable(false)) {
-    /* remove ourselves from the connection cache, this might mean that our
-       reference count drops to zero after that, so we need to be careful */
-    t_downstreamDoHConnectionsManager.removeDownstreamConnection(shared);
-  }
-  else {
-    t_downstreamDoHConnectionsManager.moveToIdle(shared);
+  if (isIdle()) {
+    auto shared = std::dynamic_pointer_cast<DoHConnectionToBackend>(shared_from_this());
+    if (!willBeReusable(false)) {
+      /* remove ourselves from the connection cache, this might mean that our
+         reference count drops to zero after that, so we need to be careful */
+      t_downstreamDoHConnectionsManager.removeDownstreamConnection(shared);
+    }
+    else {
+      t_downstreamDoHConnectionsManager.moveToIdle(shared);
+    }
   }
 }
 
-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) {
@@ -495,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);
     }
   }
 }
@@ -510,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);
@@ -626,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);
@@ -678,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);
@@ -710,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);
@@ -836,60 +822,58 @@ 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;
 
     for (;;) {
-      data.mplexer->run(&now);
+      data.mplexer->run(&now, 1000);
 
       if (now.tv_sec > lastTimeoutScan) {
         lastTimeoutScan = now.tv_sec;
@@ -905,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());
         }
       }
     }
@@ -948,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
 {
@@ -956,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) :
@@ -1004,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;
   }
 
@@ -1019,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. */
@@ -1106,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)
@@ -1114,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) {
@@ -1143,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>)) {
@@ -1180,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 43bd6b3d50e0e7d235f0a9cba49d4be6e6809104..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: int {
+enum class PrometheusMetricType: uint8_t {
     counter = 1,
     gauge = 2
 };
 
 // Keeps additional information about metrics
 struct MetricDefinition {
-  MetricDefinition(PrometheusMetricType _prometheusType, const std::string& _description): description(_description), prometheusType(_prometheusType) {
+  MetricDefinition(PrometheusMetricType _prometheusType, const std::string& _description, const std::string& customName_ = ""): description(_description), customName(customName_), prometheusType(_prometheusType) {
   }
 
   MetricDefinition() = default;
 
   // Metric description
   std::string description;
+  // Custom name, if any
+  std::string customName;
   // Metric type for Prometheus
-  PrometheusMetricType prometheusType;
+  PrometheusMetricType prometheusType{PrometheusMetricType::counter};
 };
 
 struct MetricDefinitionStorage {
@@ -54,6 +67,19 @@ struct MetricDefinitionStorage {
     return true;
   };
 
+  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(def.type);
+    if (realtype == namesToTypes.end()) {
+      return false;
+    }
+    metrics.emplace(def.name, MetricDefinition{realtype->second, def.description, def.customName});
+    return true;
+  }
+
   // Return string representation of Prometheus metric type
   std::string getPrometheusStringMetricType(PrometheusMetricType metricType) const {
     switch (metricType) {
@@ -69,6 +95,6 @@ struct MetricDefinitionStorage {
     }
   };
 
-  static const std::map<std::string, MetricDefinition> metrics;
+  static std::map<std::string, MetricDefinition> metrics;
 };
 #endif /* DISABLE_PROMETHEUS */
index 6a3e97897704f1fb2a077b644c9e880341c52861..d19e87206e2f47249a97f4ec6e6a3efda17d2a5e 100644 (file)
@@ -21,6 +21,7 @@
  */
 
 #include "dnsdist-proxy-protocol.hh"
+#include "dnsdist-metrics.hh"
 #include "dolog.hh"
 
 NetmaskGroup g_proxyProtocolACL;
@@ -29,7 +30,7 @@ bool g_applyACLToProxiedClients = false;
 
 std::string getProxyProtocolPayload(const DNSQuestion& dq)
 {
-  return makeProxyHeader(dq.overTCP(), *dq.remote, *dq.local, dq.proxyProtocolValues ? *dq.proxyProtocolValues : std::vector<ProxyProtocolValue>());
+  return makeProxyHeader(dq.overTCP(), dq.ids.origRemote, dq.ids.origDest, dq.proxyProtocolValues ? *dq.proxyProtocolValues : std::vector<ProxyProtocolValue>());
 }
 
 bool addProxyProtocol(DNSQuestion& dq, const std::string& payload)
@@ -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;
     }
   }
index d06abcd0e6d55fc222153bc0eb01b1b2857f3477..01e3ba4d9c2f98fe0c40428dd1d47c3f4dc48e04 100644 (file)
 #include <openssl/rand.h>
 #endif /* HAVE_RAND_BYTES */
 
+#if defined(HAVE_GETRANDOM)
+#include <sys/random.h>
+#endif
+
 #include "dnsdist-random.hh"
 #include "dns_random.hh"
+#include "dolog.hh"
+#include "misc.hh"
 
 namespace dnsdist
 {
+#if !defined(HAVE_LIBSODIUM) && defined(HAVE_GETRANDOM)
+static bool s_useGetRandom{true};
+#endif
+
 void initRandom()
 {
 #ifdef HAVE_LIBSODIUM
@@ -43,6 +53,15 @@ void initRandom()
 #else
   {
     auto getSeed = []() {
+#if defined(HAVE_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) {
+        warnlog("getrandom() failed %s", stringerror());
+        s_useGetRandom = false;
+      }
+#endif /* HAVE_GETRANDOM */
 #ifdef HAVE_RAND_BYTES
       unsigned int seed;
       if (RAND_bytes(reinterpret_cast<unsigned char*>(&seed), sizeof(seed)) == 1) {
@@ -64,8 +83,24 @@ uint32_t getRandomValue(uint32_t upperBound)
 #ifdef HAVE_LIBSODIUM
   return randombytes_uniform(upperBound);
 #else /* HAVE_LIBSODIUM */
-  uint32_t result;
+  uint32_t result = 0;
   unsigned int min = pdns::random_minimum_acceptable_value(upperBound);
+
+#if defined(HAVE_GETRANDOM)
+  if (s_useGetRandom) {
+    do {
+      auto got = getrandom(&result, sizeof(result), 0);
+      if (got == -1 && errno == EINTR) {
+        continue;
+      }
+      if (got != sizeof(result)) {
+        throw std::runtime_error("Error getting a random value via getrandom(): " + stringerror());
+      }
+    } while (result < min);
+
+    return result % upperBound;
+  }
+#endif /* HAVE_GETRANDOM */
 #ifdef HAVE_RAND_BYTES
   do {
     if (RAND_bytes(reinterpret_cast<unsigned char*>(&result), sizeof(result)) != 1) {
similarity index 51%
rename from pdns/validate-recursor.hh
rename to pdns/dnsdistdist/dnsdist-resolver.cc
index 7e31c63c0b03a76450aa9bad207b6533ac29bbc4..8e3417dc8c3eade478c406a3a90f1053e8f59c04 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.
  */
-#pragma once
-#include "namespaces.hh"
-#include "validate.hh"
-#include "logger.hh"
+#include <vector>
 
-/* Off: 3.x behaviour, we do no DNSSEC, no EDNS
-   ProcessNoValidate: we gather DNSSEC records on all queries, but we will never validate
-   Process: we gather DNSSEC records on all queries, if you do ad=1, we'll validate for you (unless you set cd=1)
-   ValidateForLog: Process + validate all answers, but only log failures
-   ValidateAll: DNSSEC issue -> servfail
-*/
+#include "dnsdist-resolver.hh"
+#include "iputils.hh"
 
-enum class DNSSECMode
+namespace dnsdist::resolver
 {
-  Off,
-  Process,
-  ProcessNoValidate,
-  ValidateForLog,
-  ValidateAll
-};
-extern DNSSECMode g_dnssecmode;
-extern bool g_dnssecLogBogus;
-
-bool checkDNSSECDisabled();
-bool warnIfDNSSECDisabled(const string& msg);
-vState increaseDNSSECStateCounter(const vState& state);
-vState increaseXDNSSECStateCounter(const vState& state);
-bool updateTrustAnchorsFromFile(const std::string& fname, map<DNSName, dsmap_t>& dsAnchors);
+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);
+}
+}
similarity index 76%
rename from pdns/pubsuffix.hh
rename to pdns/dnsdistdist/dnsdist-resolver.hh
index cc5f7c4272ca529677bc23c92d000f5ce24cb6c0..44b16166699420032b29bc3c4ec1560cc760da17 100644 (file)
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #pragma once
+#include "iputils.hh"
 
-#include <string>
-#include <vector>
-
-extern std::vector<std::vector<std::string>> g_pubs;
-
-/* initialize the g_pubs variable with the public suffix list,
-   using the file passed in parameter if any, or the built-in
-   list otherwise.
-*/
-void initPublicSuffixList(const std::string& file);
+namespace dnsdist::resolver
+{
+void asynchronousResolver(const std::string& hostname, const std::function<void(const std::string& hostname, std::vector<ComboAddress>& ips)>& callback);
+}
index 92b1a150aa3e986deceba25f968beda4a97e7ac5..d0f2fcb70d417c37816c2f0ebc0c2137234cdc29 100644 (file)
 #include "dnsdist-lua-ffi.hh"
 #include "dolog.hh"
 #include "dnsparser.hh"
+#include "dns_random.hh"
 
 class MaxQPSIPRule : public DNSRule
 {
 public:
-  MaxQPSIPRule(unsigned int qps, unsigned int burst, unsigned int ipv4trunc=32, unsigned int ipv6trunc=64, unsigned int expiration=300, unsigned int cleanupDelay=60, unsigned int scanFraction=10):
-    d_qps(qps), d_burst(burst), d_ipv4trunc(ipv4trunc), d_ipv6trunc(ipv6trunc), d_cleanupDelay(cleanupDelay), d_expiration(expiration), d_scanFraction(scanFraction)
+  MaxQPSIPRule(unsigned int qps, unsigned int burst, unsigned int ipv4trunc=32, unsigned int ipv6trunc=64, unsigned int expiration=300, unsigned int cleanupDelay=60, unsigned int scanFraction=10, size_t shardsCount=10):
+    d_shards(shardsCount), d_qps(qps), d_burst(burst), d_ipv4trunc(ipv4trunc), d_ipv6trunc(ipv6trunc), d_cleanupDelay(cleanupDelay), d_expiration(expiration), d_scanFraction(scanFraction)
   {
+    d_cleaningUp.clear();
     gettime(&d_lastCleanup, true);
   }
 
   void clear()
   {
-    d_limits.lock()->clear();
+    for (auto& shard : d_shards) {
+      shard.lock()->clear();
+    }
   }
 
   size_t cleanup(const struct timespec& cutOff, size_t* scannedCount=nullptr) const
   {
-    auto limits = d_limits.lock();
-    size_t toLook = limits->size() / d_scanFraction + 1;
-    size_t lookedAt = 0;
-
     size_t removed = 0;
-    auto& sequence = limits->get<SequencedTag>();
-    for (auto entry = sequence.begin(); entry != sequence.end() && lookedAt < toLook; lookedAt++) {
-      if (entry->d_limiter.seenSince(cutOff)) {
-        /* entries are ordered from least recently seen to more recently
-           seen, as soon as we see one that has not expired yet, we are
-           done */
-        lookedAt++;
-        break;
-      }
-
-      entry = sequence.erase(entry);
-      removed++;
+    if (scannedCount != nullptr) {
+      *scannedCount = 0;
     }
 
-    if (scannedCount != nullptr) {
-      *scannedCount = lookedAt;
+    for (auto& shard : d_shards) {
+      auto limits = shard.lock();
+      const size_t toLook = std::round((1.0 * limits->size()) / d_scanFraction)+ 1;
+      size_t lookedAt = 0;
+
+      auto& sequence = limits->get<SequencedTag>();
+      for (auto entry = sequence.begin(); entry != sequence.end() && lookedAt < toLook; lookedAt++) {
+        if (entry->d_limiter.seenSince(cutOff)) {
+          /* entries are ordered from least recently seen to more recently
+             seen, as soon as we see one that has not expired yet, we are
+             done */
+          lookedAt++;
+          break;
+        }
+
+        entry = sequence.erase(entry);
+        removed++;
+      }
+
+      if (scannedCount != nullptr) {
+        *scannedCount += lookedAt;
+      }
     }
 
     return removed;
@@ -83,26 +93,38 @@ public:
       cutOff.tv_sec += d_cleanupDelay;
 
       if (cutOff < now) {
-        /* the QPS Limiter doesn't use realtime, be careful! */
-        gettime(&cutOff, false);
-        cutOff.tv_sec -= d_expiration;
-
-        cleanup(cutOff);
-
-        d_lastCleanup = now;
+        try {
+          if (d_cleaningUp.test_and_set()) {
+            return;
+          }
+
+          d_lastCleanup = now;
+          /* the QPS Limiter doesn't use realtime, be careful! */
+          gettime(&cutOff, false);
+          cutOff.tv_sec -= d_expiration;
+
+          cleanup(cutOff);
+          d_cleaningUp.clear();
+        }
+        catch (...) {
+          d_cleaningUp.clear();
+          throw;
+        }
       }
     }
   }
 
   bool matches(const DNSQuestion* dq) const override
   {
-    cleanupIfNeeded(*dq->queryTime);
+    cleanupIfNeeded(dq->getQueryRealTime());
 
-    ComboAddress zeroport(*dq->remote);
+    ComboAddress zeroport(dq->ids.origRemote);
     zeroport.sin4.sin_port=0;
     zeroport.truncate(zeroport.sin4.sin_family == AF_INET ? d_ipv4trunc : d_ipv6trunc);
+    auto hash = ComboAddress::addressOnlyHash()(zeroport);
+    auto& shard = d_shards[hash % d_shards.size()];
     {
-      auto limits = d_limits.lock();
+      auto limits = shard.lock();
       auto iter = limits->find(zeroport);
       if (iter == limits->end()) {
         Entry e(zeroport, QPSLimiter(d_qps, d_burst));
@@ -121,11 +143,20 @@ public:
 
   size_t getEntriesCount() const
   {
-    return d_limits.lock()->size();
+    size_t count = 0;
+    for (auto& shard : d_shards) {
+      count += shard.lock()->size();
+    }
+    return count;
+  }
+
+  size_t getNumberOfShards() const
+  {
+    return d_shards.size();
   }
 
 private:
-  struct OrderedTag {};
+  struct HashedTag {};
   struct SequencedTag {};
   struct Entry
   {
@@ -139,15 +170,16 @@ private:
   typedef multi_index_container<
     Entry,
     indexed_by <
-      ordered_unique<tag<OrderedTag>, member<Entry,ComboAddress,&Entry::d_addr>, ComboAddress::addressOnlyLessThan >,
+      hashed_unique<tag<HashedTag>, member<Entry,ComboAddress,&Entry::d_addr>, ComboAddress::addressOnlyHash >,
       sequenced<tag<SequencedTag> >
       >
   > qpsContainer_t;
 
-  mutable LockGuarded<qpsContainer_t> d_limits;
+  mutable std::vector<LockGuarded<qpsContainer_t>> d_shards;
   mutable struct timespec d_lastCleanup;
-  unsigned int d_qps, d_burst, d_ipv4trunc, d_ipv6trunc, d_cleanupDelay, d_expiration;
-  unsigned int d_scanFraction{10};
+  const unsigned int d_qps, d_burst, d_ipv4trunc, d_ipv6trunc, d_cleanupDelay, d_expiration;
+  const unsigned int d_scanFraction{10};
+  mutable std::atomic_flag d_cleaningUp;
 };
 
 class MaxQPSRule : public DNSRule
@@ -196,9 +228,9 @@ public:
   bool matches(const DNSQuestion* dq) const override
   {
     if(!d_src) {
-        return d_nmg.match(*dq->local);
+        return d_nmg.match(dq->ids.origDest);
     }
-    return d_nmg.match(*dq->remote);
+    return d_nmg.match(dq->ids.origRemote);
   }
 
   string toString() const override
@@ -242,16 +274,16 @@ public:
   }
   bool matches(const DNSQuestion* dq) const override
   {
-    if (dq->remote->sin4.sin_family == AF_INET) {
+    if (dq->ids.origRemote.sin4.sin_family == AF_INET) {
       auto ip4s = d_ip4s.read_lock();
-      auto fnd = ip4s->find(dq->remote->sin4.sin_addr.s_addr);
+      auto fnd = ip4s->find(dq->ids.origRemote.sin4.sin_addr.s_addr);
       if (fnd == ip4s->end()) {
         return false;
       }
       return time(nullptr) < fnd->second;
     } else {
       auto ip6s = d_ip6s.read_lock();
-      auto fnd = ip6s->find({*dq->remote});
+      auto fnd = ip6s->find({dq->ids.origRemote});
       if (fnd == ip6s->end()) {
         return false;
       }
@@ -271,6 +303,7 @@ public:
     else {
       auto res = d_ip6s.write_lock()->insert({{ca}, ttd});
       if (!res.second && (time_t)res.first->second < ttd) {
+        // coverity[store_truncates_time_t]
         res.first->second = (uint32_t)ttd;
       }
     }
@@ -394,70 +427,72 @@ public:
 class AndRule : public DNSRule
 {
 public:
-  AndRule(const vector<pair<int, shared_ptr<DNSRule> > >& rules)
+  AndRule(const std::vector<pair<int, std::shared_ptr<DNSRule> > >& rules)
   {
-    for(const auto& r : rules)
+    for (const auto& r : rules) {
       d_rules.push_back(r.second);
+    }
   }
 
   bool matches(const DNSQuestion* dq) const override
   {
-    auto iter = d_rules.begin();
-    for(; iter != d_rules.end(); ++iter)
-      if(!(*iter)->matches(dq))
-        break;
-    return iter == d_rules.end();
+    for (const auto& rule : d_rules) {
+      if (!rule->matches(dq)) {
+        return false;
+      }
+    }
+    return true;
   }
 
   string toString() const override
   {
     string ret;
-    for(const auto& rule : d_rules) {
-      if(!ret.empty())
+    for (const auto& rule : d_rules) {
+      if (!ret.empty()) {
         ret+= " && ";
+      }
       ret += "("+ rule->toString()+")";
     }
     return ret;
   }
 private:
-
-  vector<std::shared_ptr<DNSRule> > d_rules;
-
+  std::vector<std::shared_ptr<DNSRule> > d_rules;
 };
 
 
 class OrRule : public DNSRule
 {
 public:
-  OrRule(const vector<pair<int, shared_ptr<DNSRule> > >& rules)
+  OrRule(const std::vector<pair<int, std::shared_ptr<DNSRule> > >& rules)
   {
-    for(const auto& r : rules)
+    for (const auto& r : rules) {
       d_rules.push_back(r.second);
+    }
   }
 
   bool matches(const DNSQuestion* dq) const override
   {
-    auto iter = d_rules.begin();
-    for(; iter != d_rules.end(); ++iter)
-      if((*iter)->matches(dq))
+    for (const auto& rule: d_rules) {
+      if (rule->matches(dq)) {
         return true;
+      }
+    }
     return false;
   }
 
   string toString() const override
   {
     string ret;
-    for(const auto& rule : d_rules) {
-      if(!ret.empty())
+    for (const auto& rule : d_rules) {
+      if (!ret.empty()) {
         ret+= " || ";
+      }
       ret += "("+ rule->toString()+")";
     }
     return ret;
   }
 private:
-
-  vector<std::shared_ptr<DNSRule> > d_rules;
-
+  std::vector<std::shared_ptr<DNSRule> > d_rules;
 };
 
 
@@ -470,7 +505,7 @@ public:
   }
   bool matches(const DNSQuestion* dq) const override
   {
-    return d_regex.match(dq->qname->toStringNoDot());
+    return d_regex.match(dq->ids.qname.toStringNoDot());
   }
 
   string toString() const override
@@ -493,7 +528,7 @@ public:
   }
   bool matches(const DNSQuestion* dq) const override
   {
-    return RE2::FullMatch(dq->qname->toStringNoDot(), d_re2);
+    return RE2::FullMatch(dq->ids.qname.toStringNoDot(), d_re2);
   }
 
   string toString() const override
@@ -522,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:
@@ -567,7 +602,7 @@ public:
   }
   bool matches(const DNSQuestion* dq) const override
   {
-    return d_smn.check(*dq->qname);
+    return d_smn.check(dq->ids.qname);
   }
   string toString() const override
   {
@@ -590,7 +625,7 @@ public:
 
   bool matches(const DNSQuestion* dq) const override
   {
-    return d_qname==*dq->qname;
+    return d_qname==dq->ids.qname;
   }
   string toString() const override
   {
@@ -605,7 +640,7 @@ public:
     QNameSetRule(const DNSNameSet& names) : qname_idx(names) {}
 
     bool matches(const DNSQuestion* dq) const override {
-        return qname_idx.find(*dq->qname) != qname_idx.end();
+        return qname_idx.find(dq->ids.qname) != qname_idx.end();
     }
 
     string toString() const override {
@@ -625,7 +660,7 @@ public:
   }
   bool matches(const DNSQuestion* dq) const override
   {
-    return d_qtype == dq->qtype;
+    return d_qtype == dq->ids.qtype;
   }
   string toString() const override
   {
@@ -644,7 +679,7 @@ public:
   }
   bool matches(const DNSQuestion* dq) const override
   {
-    return d_qclass == dq->qclass;
+    return d_qclass == dq->ids.qclass;
   }
   string toString() const override
   {
@@ -680,7 +715,7 @@ public:
   }
   bool matches(const DNSQuestion* dq) const override
   {
-    return htons(d_port) == dq->local->sin4.sin_port;
+    return htons(d_port) == dq->ids.origDest.sin4.sin_port;
   }
   string toString() const override
   {
@@ -712,7 +747,7 @@ private:
 class NotRule : public DNSRule
 {
 public:
-  NotRule(shared_ptr<DNSRule>& rule): d_rule(rule)
+  NotRule(const std::shared_ptr<DNSRule>& rule): d_rule(rule)
   {
   }
   bool matches(const DNSQuestion* dq) const override
@@ -724,7 +759,7 @@ public:
     return "!("+ d_rule->toString()+")";
   }
 private:
-  shared_ptr<DNSRule> d_rule;
+  std::shared_ptr<DNSRule> d_rule;
 };
 
 class RecordsCountRule : public DNSRule
@@ -857,7 +892,7 @@ public:
   }
   bool matches(const DNSQuestion* dq) const override
   {
-    unsigned int count = dq->qname->countLabels();
+    unsigned int count = dq->ids.qname.countLabels();
     return count < d_min || count > d_max;
   }
   string toString() const override
@@ -877,7 +912,7 @@ public:
   }
   bool matches(const DNSQuestion* dq) const override
   {
-    size_t const wirelength = dq->qname->wirelength();
+    size_t const wirelength = dq->ids.qname.wirelength();
     return wirelength < d_min || wirelength > d_max;
   }
   string toString() const override
@@ -921,7 +956,7 @@ public:
     }
 
     EDNS0Record edns0;
-    if (!getEDNS0Record(*dq, edns0)) {
+    if (!getEDNS0Record(dq->getData(), edns0)) {
       return false;
     }
 
@@ -945,7 +980,7 @@ public:
   bool matches(const DNSQuestion* dq) const override
   {
     EDNS0Record edns0;
-    if (!getEDNS0Record(*dq, edns0)) {
+    if (!getEDNS0Record(dq->getData(), edns0)) {
       return false;
     }
 
@@ -1021,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
@@ -1035,17 +1070,17 @@ 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
   {
-    if (!dq->qTag) {
+    if (!dq->ids.qTag) {
       return false;
     }
 
-    const auto it = dq->qTag->find(d_tag);
-    if (it == dq->qTag->cend()) {
+    const auto it = dq->ids.qTag->find(d_tag);
+    if (it == dq->ids.qTag->cend()) {
       return false;
     }
 
@@ -1284,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)
   {
   }
 
@@ -1315,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());
     }
index 536fb39cce9e58e0ad56950a4d12d7bc09d68b45..1a376a8eada1d8956556cab041a2fdc9e9c4512d 100644 (file)
@@ -25,6 +25,7 @@
 
 #ifndef DISABLE_SECPOLL
 #include <string>
+#include <ctime>
 
 extern std::string g_secPollSuffix;
 extern time_t g_secPollInterval;
index a85d83cd37f4071bc83f66e45a07c48223ed1942..684c46fceee1913ff9aeaf004b32366b50805e41 100644 (file)
@@ -2,6 +2,7 @@
 #include "dnsdist-session-cache.hh"
 #include "dnsdist-tcp-downstream.hh"
 #include "dnsdist-tcp-upstream.hh"
+#include "dnsdist-downstream-connection.hh"
 
 #include "dnsparser.hh"
 
@@ -68,34 +69,41 @@ bool ConnectionToBackend::reconnect()
   d_proxyProtocolPayloadSent = false;
 
   do {
-    vinfolog("TCP connecting to downstream %s (%d)", d_ds->getNameWithAddr(), d_downstreamFailures);
+    DEBUGLOG("TCP connecting to downstream "<<d_ds->getNameWithAddr()<<" ("<<d_downstreamFailures<<")");
     DEBUGLOG("Opening TCP connection to backend "<<d_ds->getNameWithAddr());
     ++d_ds->tcpNewConnections;
     try {
-      auto socket = std::make_unique<Socket>(d_ds->d_config.remote.sin4.sin_family, SOCK_STREAM, 0);
-      DEBUGLOG("result of socket() is "<<socket->getHandle());
+      auto socket = Socket(d_ds->d_config.remote.sin4.sin_family, SOCK_STREAM, 0);
+      DEBUGLOG("result of socket() is "<<socket.getHandle());
+
+      /* disable NAGLE, which does not play nicely with delayed ACKs.
+         In theory we could be wasting up to 500 milliseconds waiting for
+         the other end to acknowledge our initial packet before we could
+         send the rest. */
+      setTCPNoDelay(socket.getHandle());
+
+#ifdef SO_BINDTODEVICE
+      if (!d_ds->d_config.sourceItfName.empty()) {
+        int res = setsockopt(socket.getHandle(), SOL_SOCKET, SO_BINDTODEVICE, d_ds->d_config.sourceItfName.c_str(), d_ds->d_config.sourceItfName.length());
+        if (res != 0) {
+          vinfolog("Error setting up the interface on backend TCP socket '%s': %s", d_ds->getNameWithAddr(), stringerror());
+        }
+      }
+#endif
 
       if (!IsAnyAddress(d_ds->d_config.sourceAddr)) {
-        SSetsockopt(socket->getHandle(), SOL_SOCKET, SO_REUSEADDR, 1);
+        SSetsockopt(socket.getHandle(), SOL_SOCKET, SO_REUSEADDR, 1);
 #ifdef IP_BIND_ADDRESS_NO_PORT
         if (d_ds->d_config.ipBindAddrNoPort) {
-          SSetsockopt(socket->getHandle(), SOL_IP, IP_BIND_ADDRESS_NO_PORT, 1);
-        }
-#endif
-#ifdef SO_BINDTODEVICE
-        if (!d_ds->d_config.sourceItfName.empty()) {
-          int res = setsockopt(socket->getHandle(), SOL_SOCKET, SO_BINDTODEVICE, d_ds->d_config.sourceItfName.c_str(), d_ds->d_config.sourceItfName.length());
-          if (res != 0) {
-            vinfolog("Error setting up the interface on backend TCP socket '%s': %s", d_ds->getNameWithAddr(), stringerror());
-          }
+          SSetsockopt(socket.getHandle(), SOL_IP, IP_BIND_ADDRESS_NO_PORT, 1);
         }
 #endif
-        socket->bind(d_ds->d_config.sourceAddr, false);
+        socket.bind(d_ds->d_config.sourceAddr, false);
       }
-      socket->setNonBlocking();
+      socket.setNonBlocking();
 
       gettimeofday(&d_connectionStartTime, nullptr);
-      auto handler = std::make_unique<TCPIOHandler>(d_ds->d_config.d_tlsSubjectName, d_ds->d_config.d_tlsSubjectIsAddr, socket->releaseHandle(), timeval{0,0}, d_ds->d_tlsCtx, d_connectionStartTime.tv_sec);
+      auto handler = std::make_unique<TCPIOHandler>(d_ds->d_config.d_tlsSubjectName, d_ds->d_config.d_tlsSubjectIsAddr, socket.releaseHandle(), timeval{0,0}, d_ds->d_tlsCtx);
       if (!tlsSession && d_ds->d_tlsCtx) {
         tlsSession = g_sessionCache.getSession(d_ds->getID(), d_connectionStartTime.tv_sec);
       }
@@ -110,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;
@@ -140,6 +148,62 @@ void TCPConnectionToBackend::release(){
   }
 }
 
+static uint32_t getSerialFromRawSOAContent(const std::vector<uint8_t>& raw)
+{
+  /* minimal size for a SOA record, as defined by rfc1035:
+     MNAME (root): 1
+     RNAME (root): 1
+     SERIAL: 4
+     REFRESH: 4
+     RETRY: 4
+     EXPIRE: 4
+     MINIMUM: 4
+     = 22 bytes
+  */
+  if (raw.size() < 22) {
+    throw std::runtime_error("Invalid content of size " + std::to_string(raw.size()) + " for a SOA record");
+  }
+  /* As rfc1025 states that "all domain names in the RDATA section of these RRs may be compressed",
+     and we don't want to parse these names, start at the end */
+  uint32_t serial = 0;
+  memcpy(&serial, &raw.at(raw.size() - 20), sizeof(serial));
+  return ntohl(serial);
+}
+
+static bool getSerialFromIXFRQuery(TCPQuery& query)
+{
+  try {
+    size_t proxyPayloadSize = query.d_proxyProtocolPayloadAdded ? query.d_idstate.d_proxyProtocolPayloadSize : 0;
+    if (query.d_buffer.size() <= (proxyPayloadSize + sizeof(uint16_t))) {
+      return false;
+    }
+
+    size_t payloadSize = query.d_buffer.size() - sizeof(uint16_t) - proxyPayloadSize;
+
+    MOADNSParser parser(true, reinterpret_cast<const char*>(query.d_buffer.data() + sizeof(uint16_t) + proxyPayloadSize), payloadSize);
+
+    for (const auto& record : parser.d_answers) {
+      if (record.first.d_place != DNSResourceRecord::AUTHORITY || record.first.d_class != QClass::IN || record.first.d_type != QType::SOA) {
+        return false;
+      }
+
+      auto unknownContent = getRR<UnknownRecordContent>(record.first);
+      if (!unknownContent) {
+        return false;
+      }
+      const auto& raw = unknownContent->getRawContent();
+      query.d_ixfrQuerySerial = getSerialFromRawSOAContent(raw);
+      return true;
+    }
+  }
+  catch (const MOADNSException& e) {
+    DEBUGLOG("Exception when parsing IXFR TCP Query to DNS: " << e.what());
+    /* ponder what to do here, shall we close the connection? */
+  }
+
+  return false;
+}
+
 static void editPayloadID(PacketBuffer& payload, uint16_t newId, size_t proxyProtocolPayloadSize, bool sizePrepended)
 {
   /* we cannot do a direct cast as the alignment might be off (the size of the payload might have been prepended, which is bad enough,
@@ -168,19 +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_idstate.d_proxyProtocolPayloadSize = query.d_proxyProtocolPayload.size();
     }
   }
   else if (connectionState == ConnectionState::proxySent) {
     if (query.d_proxyProtocolPayloadAdded) {
-      if (query.d_buffer.size() < query.d_proxyProtocolPayload.size()) {
+      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_proxyProtocolPayload.size());
+      // 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_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_proxyProtocolPayload.size() : 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)
@@ -199,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());
 
@@ -292,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;
@@ -334,7 +404,7 @@ void TCPConnectionToBackend::handleIO(std::shared_ptr<TCPConnectionToBackend>& c
 
     if (connectionDied) {
 
-      DEBUGLOG("connection died, number of failures is "<<conn->d_downstreamFailures<<", retries is "<<conn->d_ds->d_retries);
+      DEBUGLOG("connection died, number of failures is "<<conn->d_downstreamFailures<<", retries is "<<conn->d_ds->d_config.d_retries);
 
       if (conn->d_downstreamFailures < conn->d_ds->d_config.d_retries) {
 
@@ -364,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());
@@ -484,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 {
@@ -512,6 +583,7 @@ void TCPConnectionToBackend::handleTimeout(const struct timeval& now, bool write
 void TCPConnectionToBackend::notifyAllQueriesFailed(const struct timeval& now, FailureReason reason)
 {
   d_connectionDied = true;
+  d_ds->reportTimeoutOrError();
 
   /* we might be terminated while notifying a query sender */
   d_ds->outstanding -= d_pendingResponses.size();
@@ -520,15 +592,13 @@ void TCPConnectionToBackend::notifyAllQueriesFailed(const struct timeval& now, F
   auto pendingResponses = std::move(d_pendingResponses);
   d_pendingResponses.clear();
 
-  auto increaseCounters = [reason](std::shared_ptr<TCPQuerySender>& sender) {
+  auto increaseCounters = [reason](const ClientState* cs) {
     if (reason == FailureReason::timeout) {
-      const ClientState* cs = sender->getClientState();
       if (cs) {
         ++cs->tcpDownstreamTimeouts;
       }
     }
     else if (reason == FailureReason::gaveUp) {
-      const ClientState* cs = sender->getClientState();
       if (cs) {
         ++cs->tcpGaveUp;
       }
@@ -537,26 +607,29 @@ void TCPConnectionToBackend::notifyAllQueriesFailed(const struct timeval& now, F
 
   try {
     if (d_state == State::sendingQueryToBackend) {
+      increaseCounters(d_currentQuery.d_query.d_idstate.cs);
       auto sender = d_currentQuery.d_sender;
       if (sender->active()) {
-        increaseCounters(sender);
-        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));
       }
     }
 
     for (auto& query : pendingQueries) {
+      increaseCounters(query.d_query.d_idstate.cs);
       auto sender = query.d_sender;
       if (sender->active()) {
-        increaseCounters(sender);
-        sender->notifyIOError(std::move(query.d_query.d_idstate), now);
+        TCPResponse response(std::move(query.d_query));
+        sender->notifyIOError(now, std::move(response));
       }
     }
 
     for (auto& response : pendingResponses) {
+      increaseCounters(response.second.d_query.d_idstate.cs);
       auto sender = response.second.d_sender;
       if (sender->active()) {
-        increaseCounters(sender);
-        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));
       }
     }
   }
@@ -570,28 +643,6 @@ void TCPConnectionToBackend::notifyAllQueriesFailed(const struct timeval& now, F
   release();
 }
 
-static uint32_t getSerialFromRawSOAContent(const std::vector<uint8_t>& raw)
-{
-  /* minimal size for a SOA record, as defined by rfc1035:
-     MNAME (root): 1
-     RNAME (root): 1
-     SERIAL: 4
-     REFRESH: 4
-     RETRY: 4
-     EXPIRE: 4
-     MINIMUM: 4
-     = 22 bytes
-  */
-  if (raw.size() < 22) {
-    throw std::runtime_error("Invalid content of size " + std::to_string(raw.size()) + " for a SOA record");
-  }
-  /* As rfc1025 states that "all domain names in the RDATA section of these RRs may be compressed",
-     and we don't want to parse these names, start at the end */
-  uint32_t serial = 0;
-  memcpy(&serial, &raw.at(raw.size() - 20), sizeof(serial));
-  return ntohl(serial);
-}
-
 IOState TCPConnectionToBackend::handleResponse(std::shared_ptr<TCPConnectionToBackend>& conn, const struct timeval& now)
 {
   d_downstreamFailures = 0;
@@ -623,6 +674,7 @@ IOState TCPConnectionToBackend::handleResponse(std::shared_ptr<TCPConnectionToBa
     TCPResponse response;
     response.d_buffer = std::move(d_responseBuffer);
     response.d_connection = conn;
+    response.d_ds = conn->d_ds;
     /* we don't move the whole IDS because we will need for the responses to come */
     response.d_idstate.qtype = it->second.d_query.d_idstate.qtype;
     response.d_idstate.qname = it->second.d_query.d_idstate.qname;
@@ -636,15 +688,15 @@ IOState TCPConnectionToBackend::handleResponse(std::shared_ptr<TCPConnectionToBa
       --conn->d_ds->outstanding;
       /* marking as idle for now, so we can accept new queries if our queues are empty */
       if (d_pendingQueries.empty() && d_pendingResponses.empty()) {
-        t_downstreamTCPConnectionsManager.moveToIdle(conn);
         d_state = State::idle;
+        t_downstreamTCPConnectionsManager.moveToIdle(conn);
       }
     }
 
     sender->handleXFRResponse(now, std::move(response));
     if (done) {
-      t_downstreamTCPConnectionsManager.moveToIdle(conn);
       d_state = State::idle;
+      t_downstreamTCPConnectionsManager.moveToIdle(conn);
       return IOState::Done;
     }
 
@@ -657,18 +709,37 @@ IOState TCPConnectionToBackend::handleResponse(std::shared_ptr<TCPConnectionToBa
 
   --conn->d_ds->outstanding;
   auto ids = std::move(it->second.d_query.d_idstate);
+  const double udiff = ids.queryRealTime.udiff();
+  conn->d_ds->updateTCPLatency(udiff);
+  if (d_responseBuffer.size() >= sizeof(dnsheader)) {
+    dnsheader dh;
+    memcpy(&dh, d_responseBuffer.data(), sizeof(dh));
+    conn->d_ds->reportResponse(dh.rcode);
+  }
+  else {
+    conn->d_ds->reportTimeoutOrError();
+  }
+
   d_pendingResponses.erase(it);
   /* marking as idle for now, so we can accept new queries if our queues are empty */
   if (d_pendingQueries.empty() && d_pendingResponses.empty()) {
-    t_downstreamTCPConnectionsManager.moveToIdle(conn);
     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));
+    TCPResponse response(std::move(d_responseBuffer), std::move(ids), conn, conn->d_ds);
+    sender->handleResponse(now, std::move(response));
   }
 
   if (!d_pendingQueries.empty()) {
@@ -677,15 +748,12 @@ 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 {
     DEBUGLOG("nothing to do, waiting for a new query");
-    t_downstreamTCPConnectionsManager.moveToIdle(conn);
     d_state = State::idle;
+    t_downstreamTCPConnectionsManager.moveToIdle(conn);
     return IOState::Done;
   }
 }
@@ -730,8 +798,10 @@ bool TCPConnectionToBackend::matchesTLVs(const std::unique_ptr<std::vector<Proxy
 bool TCPConnectionToBackend::isXFRFinished(const TCPResponse& response, TCPQuery& query)
 {
   bool done = false;
+
   try {
     MOADNSParser parser(true, reinterpret_cast<const char*>(response.d_buffer.data()), response.d_buffer.size());
+
     if (parser.d_header.rcode != 0U) {
       done = true;
     }
@@ -745,24 +815,36 @@ bool TCPConnectionToBackend::isXFRFinished(const TCPResponse& response, TCPQuery
         if (!unknownContent) {
           continue;
         }
-        auto raw = unknownContent->getRawContent();
+        const auto& raw = unknownContent->getRawContent();
         auto serial = getSerialFromRawSOAContent(raw);
-        ++query.d_xfrSerialCount;
-        if (query.d_xfrMasterSerial == 0) {
+        if (query.d_xfrPrimarySerial == 0) {
           // store the first SOA in our client's connection metadata
-          ++query.d_xfrMasterSerialCount;
-          query.d_xfrMasterSerial = serial;
+          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
+                 with a single SOA record of the server's current version.
+            */
+            done = true;
+            break;
+          }
         }
-        else if (query.d_xfrMasterSerial == serial) {
-          ++query.d_xfrMasterSerialCount;
-          // figure out if it's end when receiving master's SOA again
+
+        ++query.d_xfrSerialCount;
+        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;
           }
         }
       }
@@ -774,3 +856,18 @@ bool TCPConnectionToBackend::isXFRFinished(const TCPResponse& response, TCPQuery
   }
   return done;
 }
+
+void setTCPDownstreamMaxIdleConnectionsPerBackend(uint64_t max)
+{
+  DownstreamTCPConnectionsManager::setMaxIdleConnectionsPerDownstream(max);
+}
+
+void setTCPDownstreamCleanupInterval(uint64_t interval)
+{
+  DownstreamTCPConnectionsManager::setCleanupInterval(interval);
+}
+
+void setTCPDownstreamMaxIdleTime(uint64_t max)
+{
+  DownstreamTCPConnectionsManager::setMaxIdleTime(max);
+}
index 7f3cd6a0d8a25baaae1cd37319a5c9d1381d7398..a165dc18cb1282dcff0d5fc0c3e0e6d0c223040d 100644 (file)
@@ -1,12 +1,5 @@
 #pragma once
 
-#include <boost/multi_index_container.hpp>
-#include <boost/multi_index/ordered_index.hpp>
-#include <boost/multi_index/sequenced_index.hpp>
-#include <boost/multi_index/key_extractors.hpp>
-
-#include <queue>
-
 #include "sstuff.hh"
 #include "tcpiohandler-mplexer.hh"
 #include "dnsdist.hh"
@@ -32,13 +25,19 @@ public:
   }
 
   /* whether the underlying socket has been closed under our feet, basically */
-  bool isUsable() const
+  bool isUsable()
   {
     if (!d_handler) {
+      d_connectionDied = true;
       return false;
     }
 
-    return d_handler->isUsable();
+    if (d_handler->isUsable()) {
+      return true;
+    }
+
+    d_connectionDied = true;
+    return false;
   }
 
   const std::shared_ptr<DownstreamState>& getDS() const
@@ -157,7 +156,8 @@ protected:
 
     struct timeval res = now;
     res.tv_sec += d_ds->d_config.checkTimeout / 1000; /* ms to s */
-    res.tv_usec += (d_ds->d_config.checkTimeout % 1000) / 1000; /* remaining ms to µs */
+    res.tv_usec += (d_ds->d_config.checkTimeout % 1000) * 1000; /* remaining ms to µs */
+    normalizeTV(res);
 
     return res;
   }
@@ -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)
   {
   }
 
@@ -246,7 +246,7 @@ public:
 
   bool reachedMaxConcurrentQueries() const override
   {
-    const size_t concurrent = d_pendingQueries.size() + d_pendingResponses.size();
+    const size_t concurrent = d_pendingQueries.size() + d_pendingResponses.size() + (d_state == State::sendingQueryToBackend ? 1 : 0);
     if (concurrent > 0 && concurrent >= d_ds->d_config.d_maxInFlightQueriesPerConn) {
       return true;
     }
@@ -304,292 +304,6 @@ private:
   State d_state{State::idle};
 };
 
-template <class T> class DownstreamConnectionsManager
-{
-  struct SequencedTag {};
-  struct OrderedTag {};
-
-  typedef multi_index_container<
-    std::shared_ptr<T>,
-    indexed_by <
-      ordered_unique<tag<OrderedTag>,
-                     identity<std::shared_ptr<T>>
-                     >,
-      /* new elements are added to the front of the sequence */
-      sequenced<tag<SequencedTag> >
-      >
-  > list_t;
-  struct ConnectionLists
-  {
-    list_t d_actives;
-    list_t d_idles;
-  };
-
-public:
-  static void setMaxIdleConnectionsPerDownstream(size_t max)
-  {
-    s_maxIdleConnectionsPerDownstream = max;
-  }
-
-  static void setCleanupInterval(uint16_t interval)
-  {
-    s_cleanupInterval = interval;
-  }
-
-  static void setMaxIdleTime(uint16_t max)
-  {
-    s_maxIdleTime = max;
-  }
-
-  std::shared_ptr<T> getConnectionToDownstream(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& ds, const struct timeval& now, std::string&& proxyProtocolPayload)
-  {
-    struct timeval freshCutOff = now;
-    freshCutOff.tv_sec -= 1;
-
-    auto backendId = ds->getID();
-
-    cleanupClosedConnections(now);
-
-    const bool haveProxyProtocol = ds->d_config.useProxyProtocol || !proxyProtocolPayload.empty();
-    if (!haveProxyProtocol) {
-      const auto& it = d_downstreamConnections.find(backendId);
-      if (it != d_downstreamConnections.end()) {
-        /* first scan idle connections, more recent first */
-        auto entry = findUsableConnectionInList(now, freshCutOff, it->second.d_idles, true);
-        if (entry) {
-          ++ds->tcpReusedConnections;
-          it->second.d_actives.insert(entry);
-          return entry;
-        }
-
-        /* then scan actives ones, more recent first as well */
-        entry = findUsableConnectionInList(now, freshCutOff, it->second.d_actives, false);
-        if (entry) {
-          ++ds->tcpReusedConnections;
-          return entry;
-        }
-      }
-    }
-
-    auto newConnection = std::make_shared<T>(ds, mplexer, now, std::move(proxyProtocolPayload));
-    if (!haveProxyProtocol) {
-      auto& list = d_downstreamConnections[backendId].d_actives;
-      list.template get<SequencedTag>().push_front(newConnection);
-    }
-
-    return newConnection;
-  }
-
-  void cleanupClosedConnections(const struct timeval& now)
-  {
-    if (s_cleanupInterval == 0 || (d_nextCleanup != 0 && d_nextCleanup > now.tv_sec)) {
-      return;
-    }
-
-    d_nextCleanup = now.tv_sec + s_cleanupInterval;
-
-    struct timeval freshCutOff = now;
-    freshCutOff.tv_sec -= 1;
-    struct timeval idleCutOff = now;
-    idleCutOff.tv_sec -= s_maxIdleTime;
-
-    for (auto dsIt = d_downstreamConnections.begin(); dsIt != d_downstreamConnections.end(); ) {
-      cleanUpList(dsIt->second.d_idles, now, freshCutOff, idleCutOff);
-      cleanUpList(dsIt->second.d_actives, now, freshCutOff, idleCutOff);
-
-      if (dsIt->second.d_idles.empty() && dsIt->second.d_actives.empty()) {
-        dsIt = d_downstreamConnections.erase(dsIt);
-      }
-      else {
-        ++dsIt;
-      }
-    }
-  }
-
-  size_t clear()
-  {
-    size_t count = 0;
-    for (const auto& downstream : d_downstreamConnections) {
-      count += downstream.second.d_actives.size();
-      for (auto& conn : downstream.second.d_actives) {
-        conn->stopIO();
-      }
-      count += downstream.second.d_idles.size();
-      for (auto& conn : downstream.second.d_idles) {
-        conn->stopIO();
-      }
-    }
-
-    d_downstreamConnections.clear();
-    return count;
-  }
-
-  size_t count() const
-  {
-    return getActiveCount() + getIdleCount();
-  }
-
-  size_t getActiveCount() const
-  {
-    size_t count = 0;
-    for (const auto& downstream : d_downstreamConnections) {
-      count += downstream.second.d_actives.size();
-    }
-    return count;
-  }
-
-  size_t getIdleCount() const
-  {
-    size_t count = 0;
-    for (const auto& downstream : d_downstreamConnections) {
-      count += downstream.second.d_idles.size();
-    }
-    return count;
-  }
-
-  bool removeDownstreamConnection(std::shared_ptr<T>& conn)
-  {
-    auto backendIt = d_downstreamConnections.find(conn->getDS()->getID());
-    if (backendIt == d_downstreamConnections.end()) {
-      return false;
-    }
-
-    /* idle list first */
-    {
-      auto it = backendIt->second.d_idles.find(conn);
-      if (it != backendIt->second.d_idles.end()) {
-        backendIt->second.d_idles.erase(it);
-        return true;
-      }
-    }
-    /* then active */
-    {
-      auto it = backendIt->second.d_actives.find(conn);
-      if (it != backendIt->second.d_actives.end()) {
-        backendIt->second.d_actives.erase(it);
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  bool moveToIdle(std::shared_ptr<T>& conn)
-  {
-    auto backendIt = d_downstreamConnections.find(conn->getDS()->getID());
-    if (backendIt == d_downstreamConnections.end()) {
-      return false;
-    }
-
-    auto it = backendIt->second.d_actives.find(conn);
-    if (it == backendIt->second.d_actives.end()) {
-      return false;
-    }
-
-    backendIt->second.d_actives.erase(it);
-
-    if (backendIt->second.d_idles.size() >= s_maxIdleConnectionsPerDownstream) {
-      backendIt->second.d_idles.template get<SequencedTag>().pop_back();
-    }
-
-    backendIt->second.d_idles.template get<SequencedTag>().push_front(conn);
-    return true;
-  }
-
-protected:
-
-  void cleanUpList(list_t& list, const struct timeval& now, const struct timeval& freshCutOff, const struct timeval& idleCutOff)
-  {
-    auto& sidx = list.template get<SequencedTag>();
-    for (auto connIt = sidx.begin(); connIt != sidx.end(); ) {
-      if (!(*connIt)) {
-        connIt = sidx.erase(connIt);
-        continue;
-      }
-
-      auto& entry = *connIt;
-
-      /* don't bother checking freshly used connections */
-      if (freshCutOff < entry->getLastDataReceivedTime()) {
-        ++connIt;
-        continue;
-      }
-
-      if (entry->isIdle() && entry->getLastDataReceivedTime() < idleCutOff) {
-        /* idle for too long */
-        connIt = sidx.erase(connIt);
-        continue;
-      }
-
-      if (entry->isUsable()) {
-        ++connIt;
-        continue;
-      }
-
-      connIt = sidx.erase(connIt);
-    }
-  }
-
-  std::shared_ptr<T> findUsableConnectionInList(const struct timeval& now, const struct timeval& freshCutOff, list_t& list, bool removeIfFound)
-  {
-    auto& sidx = list.template get<SequencedTag>();
-    for (auto listIt = sidx.begin(); listIt != sidx.end(); ) {
-      if (!(*listIt)) {
-        listIt = sidx.erase(listIt);
-        continue;
-      }
-
-      auto& entry = *listIt;
-      if (isConnectionUsable(entry, now, freshCutOff)) {
-        entry->setReused();
-        // make a copy since the iterator will be invalidated after erasing
-        auto result = entry;
-        if (removeIfFound) {
-          sidx.erase(listIt);
-        }
-        return result;
-      }
-
-      if (entry->willBeReusable(false)) {
-        ++listIt;
-        continue;
-      }
-
-      /* that connection will not be usable later, no need to keep it in that list */
-      listIt = sidx.erase(listIt);
-    }
-
-    return nullptr;
-  }
-
-  bool isConnectionUsable(const std::shared_ptr<T>& conn, const struct timeval& now, const struct timeval& freshCutOff)
-  {
-    if (!conn->canBeReused()) {
-      return false;
-    }
-
-    /* for connections that have not been used very recently,
-       check whether they have been closed in the meantime */
-    if (freshCutOff < conn->getLastDataReceivedTime()) {
-      /* used recently enough, skip the check */
-      return true;
-    }
-
-    return conn->isUsable();
-  }
-
-  static size_t s_maxIdleConnectionsPerDownstream;
-  static uint16_t s_cleanupInterval;
-  static uint16_t s_maxIdleTime;
-
-  std::map<boost::uuids::uuid, ConnectionLists> d_downstreamConnections;
-
-  time_t d_nextCleanup{0};
-};
-
-template <class T> size_t DownstreamConnectionsManager<T>::s_maxIdleConnectionsPerDownstream{10};
-template <class T> uint16_t DownstreamConnectionsManager<T>::s_cleanupInterval{60};
-template <class T> uint16_t DownstreamConnectionsManager<T>::s_maxIdleTime{300};
-
-using DownstreamTCPConnectionsManager = DownstreamConnectionsManager<TCPConnectionToBackend>;
-extern thread_local DownstreamTCPConnectionsManager t_downstreamTCPConnectionsManager;
+void setTCPDownstreamMaxIdleConnectionsPerBackend(uint64_t max);
+void setTCPDownstreamCleanupInterval(uint64_t interval);
+void setTCPDownstreamMaxIdleTime(uint64_t max);
index 6fa76ab6a1a2415f49a9c6260ffaa5c280146932..c6410df0c9c69c7e20c6fd34817f2c8b645d3dab 100644 (file)
@@ -2,24 +2,35 @@
 
 #include "dolog.hh"
 #include "dnsdist-tcp.hh"
+#include "dnsdist-tcp-downstream.hh"
+
+struct TCPCrossProtocolResponse;
 
 class TCPClientThreadData
 {
 public:
-  TCPClientThreadData(): localRespRuleActions(g_respruleactions.getLocal()), mplexer(std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent()))
+  TCPClientThreadData():
+    localRespRuleActions(g_respruleactions.getLocal()), localCacheInsertedRespRuleActions(g_cacheInsertedRespRuleActions.getLocal()), mplexer(std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent()))
   {
   }
 
   LocalHolders holders;
-  LocalStateHolder<vector<DNSDistResponseRuleAction> > localRespRuleActions;
+  LocalStateHolder<vector<DNSDistResponseRuleAction>> localRespRuleActions;
+  LocalStateHolder<vector<DNSDistResponseRuleAction>> 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)
+  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;
@@ -39,7 +50,7 @@ public:
   IncomingTCPConnectionState(const IncomingTCPConnectionState& rhs) = delete;
   IncomingTCPConnectionState& operator=(const IncomingTCPConnectionState& rhs) = delete;
 
-  ~IncomingTCPConnectionState();
+  virtual ~IncomingTCPConnectionState();
 
   void resetForNewQuery();
 
@@ -105,28 +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(IDState&& 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);
 
@@ -134,11 +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;
+  }
 
-  const ClientState* getClientState() const override
+  virtual bool forwardViaUDPFirst() const
   {
-    return d_ci.cs;
+    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
   {
@@ -147,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;
@@ -170,8 +209,9 @@ static void handleTimeout(std::shared_ptr<IncomingTCPConnectionState>& state, bo
   size_t d_proxyProtocolNeed{0};
   size_t d_queriesCount{0};
   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};
index 9154f2f650f2d237a9e6a4a8f3a2b8149b191242..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
 {
@@ -72,33 +75,20 @@ struct ConnectionInfo
   int fd{-1};
 };
 
-struct InternalQuery
+class InternalQuery
 {
+public:
   InternalQuery()
   {
   }
 
-  InternalQuery(PacketBuffer&& buffer, IDState&& state) :
+  InternalQuery(PacketBuffer&& buffer, InternalQueryState&& state) :
     d_idstate(std::move(state)), d_buffer(std::move(buffer))
   {
   }
 
-  InternalQuery(InternalQuery&& rhs) :
-    d_idstate(std::move(rhs.d_idstate)), d_proxyProtocolPayload(std::move(rhs.d_proxyProtocolPayload)), d_buffer(std::move(rhs.d_buffer)), d_xfrMasterSerial(rhs.d_xfrMasterSerial), d_xfrSerialCount(rhs.d_xfrSerialCount), d_downstreamFailures(rhs.d_downstreamFailures), d_xfrMasterSerialCount(rhs.d_xfrMasterSerialCount), d_proxyProtocolPayloadAdded(rhs.d_proxyProtocolPayloadAdded)
-  {
-  }
-  InternalQuery& operator=(InternalQuery&& rhs)
-  {
-    d_idstate = std::move(rhs.d_idstate);
-    d_buffer = std::move(rhs.d_buffer);
-    d_proxyProtocolPayload = std::move(rhs.d_proxyProtocolPayload);
-    d_xfrMasterSerial = rhs.d_xfrMasterSerial;
-    d_xfrSerialCount = rhs.d_xfrSerialCount;
-    d_downstreamFailures = rhs.d_downstreamFailures;
-    d_xfrMasterSerialCount = rhs.d_xfrMasterSerialCount;
-    d_proxyProtocolPayloadAdded = rhs.d_proxyProtocolPayloadAdded;
-    return *this;
-  }
+  InternalQuery(InternalQuery&& rhs) = default;
+  InternalQuery& operator=(InternalQuery&& rhs) = default;
 
   InternalQuery(const InternalQuery& rhs) = delete;
   InternalQuery& operator=(const InternalQuery& rhs) = delete;
@@ -108,13 +98,14 @@ struct InternalQuery
     return d_idstate.qtype == QType::AXFR || d_idstate.qtype == QType::IXFR;
   }
 
-  IDState d_idstate;
+  InternalQueryState d_idstate;
   std::string d_proxyProtocolPayload;
   PacketBuffer d_buffer;
-  uint32_t d_xfrMasterSerial{0};
+  uint32_t d_ixfrQuerySerial{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};
 };
@@ -131,15 +122,39 @@ struct TCPResponse : public TCPQuery
     memset(&d_cleartextDH, 0, sizeof(d_cleartextDH));
   }
 
-  TCPResponse(PacketBuffer&& buffer, IDState&& state, std::shared_ptr<ConnectionToBackend> conn) :
-    TCPQuery(std::move(buffer), std::move(state)), d_connection(conn)
+  TCPResponse(PacketBuffer&& buffer, InternalQueryState&& state, std::shared_ptr<ConnectionToBackend> conn, std::shared_ptr<DownstreamState> ds) :
+    TCPQuery(std::move(buffer), std::move(state)), d_connection(std::move(conn)), d_ds(std::move(ds))
   {
-    memset(&d_cleartextDH, 0, sizeof(d_cleartextDH));
+    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));
+    }
+  }
+
+  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));
+    }
+  }
+
+  bool isAsync() const
+  {
+    return d_async;
   }
 
   std::shared_ptr<ConnectionToBackend> d_connection{nullptr};
+  std::shared_ptr<DownstreamState> d_ds{nullptr};
   dnsheader d_cleartextDH;
-  bool d_selfGenerated{false};
+  bool d_async{false};
 };
 
 class TCPQuerySender
@@ -150,10 +165,9 @@ public:
   }
 
   virtual bool active() const = 0;
-  virtual const ClientState* getClientState() 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(IDState&& 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 */
@@ -171,6 +185,10 @@ struct CrossProtocolQuery
   CrossProtocolQuery()
   {
   }
+  CrossProtocolQuery(InternalQuery&& query_, std::shared_ptr<DownstreamState>& downstream_) :
+    query(std::move(query_)), downstream(downstream_)
+  {
+  }
 
   CrossProtocolQuery(CrossProtocolQuery&& rhs) = delete;
   virtual ~CrossProtocolQuery()
@@ -178,28 +196,29 @@ struct CrossProtocolQuery
   }
 
   virtual std::shared_ptr<TCPQuerySender> getTCPQuerySender() = 0;
+  virtual DNSQuestion getDQ()
+  {
+    auto& ids = query.d_idstate;
+    DNSQuestion dq(ids, query.d_buffer);
+    return dq;
+  }
+
+  virtual DNSResponse getDR()
+  {
+    auto& ids = query.d_idstate;
+    DNSResponse dr(ids, query.d_buffer, downstream);
+    return dr;
+  }
 
   InternalQuery query;
   std::shared_ptr<DownstreamState> downstream{nullptr};
-  size_t proxyProtocolPayloadSize{0};
-  bool isXFR{false};
+  bool d_isResponse{false};
 };
 
 class TCPClientCollection
 {
 public:
-  TCPClientCollection(size_t maxThreads);
-
-  int getThread()
-  {
-    if (d_numthreads == 0) {
-      throw std::runtime_error("No TCP worker thread yet");
-    }
-
-    uint64_t pos = d_pos++;
-    ++d_queued;
-    return d_tcpclientthreads.at(pos % d_numthreads).d_newConnectionPipe.getHandle();
-  }
+  TCPClientCollection(size_t maxThreads, std::vector<ClientState*> tcpAcceptStates);
 
   bool passConnectionToThread(std::unique_ptr<ConnectionInfo>&& conn)
   {
@@ -208,16 +227,16 @@ public:
     }
 
     uint64_t pos = d_pos++;
-    auto pipe = d_tcpclientthreads.at(pos % d_numthreads).d_newConnectionPipe.getHandle();
-    auto tmp = conn.release();
-
-    if (write(pipe, &tmp, sizeof(tmp)) != sizeof(tmp)) {
-      ++g_stats.tcpQueryPipeFull;
-      delete tmp;
-      tmp = nullptr;
+    /* 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 (!d_tcpclientthreads.at(pos % d_numthreads).d_querySender.send(std::move(conn))) {
+      --d_queued;
+      ++dnsdist::metrics::g_stats.tcpQueryPipeFull;
       return false;
     }
-    ++d_queued;
+
     return true;
   }
 
@@ -228,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;
     }
 
@@ -262,7 +276,7 @@ public:
   }
 
 private:
-  void addTCPClientThread();
+  void addTCPClientThread(std::vector<ClientState*>& tcpAcceptStates);
 
   struct TCPWorkerThread
   {
@@ -270,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))
     {
     }
 
@@ -280,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;
@@ -293,3 +306,5 @@ private:
 };
 
 extern std::unique_ptr<TCPClientCollection> g_tcpclientthreads;
+
+std::unique_ptr<CrossProtocolQuery> getTCPCrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion);
index e860b72b3e04e260e51d1f446bebe67d6a7843ef..8443ab5319f3e0249fd851b6fd495980199b808b 100644 (file)
@@ -5,6 +5,7 @@ race:updateTCPLatency
 race:handleStats
 race:ClientState::updateTCPMetrics
 race:DownstreamState::updateTCPMetrics
+race:DownstreamState::updateTCPLatency
 # There is a race when we update the status of a backend,
 # but eventual consistency is fine there
 race:DownstreamState::setDown
@@ -13,5 +14,11 @@ race:DownstreamState::setAuto
 # Same thing for whether a backend has been stopped,
 # eventual consistency is fine
 race:DownstreamState::stop
-race:updateHealthCheckResult
+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
index e021e5866c7d00af4eaed8d616ebbf7e254ce949..7325025823d77d6ca20b7e114973bbc1dfd83fb9 100644 (file)
@@ -1,11 +1,14 @@
 #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 setWebserverAPIRequiresAuthentication(bool);
+void setWebserverDashboardRequiresAuthentication(bool);
 void setWebserverStatsRequireAuthentication(bool);
 void setWebserverMaxConcurrentConnections(size_t);
 
@@ -15,3 +18,5 @@ void registerBuiltInWebHandlers();
 void clearWebHandlers();
 
 std::string getWebserverConfig();
+
+bool addMetricDefinition(const dnsdist::prometheus::PrometheusMetricDefinition& def);
diff --git a/pdns/dnsdistdist/dnsdist-xsk.cc b/pdns/dnsdistdist/dnsdist-xsk.cc
new file mode 100644 (file)
index 0000000..058e381
--- /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 = g_respruleactions.getLocal();
+    auto localCacheInsertedRespRuleActions = g_cacheInsertedRespRuleActions.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 */
index bb11a26dd7e3c6e1f32d90bd9071f39cb673a451..eb75e7632a511bd478340c2f8e0f6195c9b521a8 100644 (file)
@@ -3,7 +3,7 @@ Description=DNS Loadbalancer
 Documentation=man:dnsdist(1)
 Documentation=https://dnsdist.org
 Wants=network-online.target
-After=network-online.target
+After=network-online.target time-sync.target
 
 [Service]
 ExecStartPre=@bindir@/dnsdist --check-config
@@ -27,6 +27,8 @@ LimitNOFILE=16384
 # Sandboxing
 # Note: adding CAP_SYS_ADMIN (or CAP_BPF for Linux >= 5.8) is required to use eBPF support,
 # and CAP_NET_RAW to be able to set the source interface to contact a backend
+# If an AppArmor policy is in use, it might have to be updated to allow dnsdist to keep the
+# capability: adding a 'capability bpf,' (for CAP_BPF) line to the policy is usually enough.
 CapabilityBoundingSet=CAP_NET_BIND_SERVICE
 AmbientCapabilities=CAP_NET_BIND_SERVICE
 LockPersonality=true
@@ -48,6 +50,12 @@ RestrictRealtime=true
 RestrictSUIDSGID=true
 SystemCallArchitectures=native
 SystemCallFilter=~ @clock @debug @module @mount @raw-io @reboot @swap @cpu-emulation @obsolete
+ProtectProc=invisible
+PrivateIPC=true
+RemoveIPC=true
+DevicePolicy=closed
+# Not enabled by default because it does not play well with LuaJIT
+# MemoryDenyWriteExecute=true
 
 [Install]
 WantedBy=multi-user.target
index 258f84a4ea2f5878b034b92c5f2f1fdd2291bf03..276ffd8f1d3ba70b48a1d8b39b559f8e9219bacf 100644 (file)
 -----BEGIN PGP PUBLIC KEY BLOCK-----
+Comment: GPGTools - http://gpgtools.org
 
-mQENBFY4pAcBCACIU5HRkBG3VcBfJaqetxIoKdLRxW3XmeCwruLFt6DN3q8bTtsN
-uQMJHa8OY0aKWJoXjOQSbBoKSGVAFKTmpCUfH4vhErt8DWqyglRfio2L3cTe48GZ
-jiObdXLZxnsINAx2WbcpoCRKTjdWX0MH2Jg/yf5PS6nb+glclRsDQmVGQjt92v23
-nNdsCp8I9rjP1+bQy5iHB1IiQuFJ6DBQJhgWQzksT2azZ83aADvc4/+Fg7VFYSzZ
-Hkp98NfyzUkiUzYi0I5Oy4KvyoXeS/CX9WtQGM1vjZAXXiD+ODJ0OvB2EsCUT6t4
-i9pWWh/+LnNtRWIVn8PJeQbCAO2wJlMxX28BABEBAAG0KFJlbWkgR2Fjb2duZSA8
-cmVtaS5nYWNvZ25lQHBvd2VyZG5zLmNvbT6JAT0EEwEKACcFAlawcTICGwMFCRLM
-AwAFCwkIBwQFFQoJCAsFFgIDAQACHgECF4AACgkQogjtT4r1hEZ0zgf+Iy73sdDP
-kxOYi/xVJIvBB8o+uuPLDgh51Bx50vrJTBNm+9YgLyycQpQyYyw+NyY+xS9Ibtiz
-p8PuT0Ga4S8gdVhGtbQtLS7yJD7swlSTyOBR+/gHoJgaxmDeueeB8tAv2ERfJjhp
-4C+fIa7piU4Iwe7wvgrw6dO9eFkBv0oHCqpOI5CM/yHqdr493Qon3YfpCZGiv558
-yE9Xrojs6fMSwcolWwjnZW72KFXraryXKiIThwoJb9OOZWXAKfZD/aZv5BMxzN+D
-4ZB+kq0f7qHbITh+Kd1MdMBoMFG6Lg7x1LY5SvhgfHGF3UZ8tLb3RgWFDuwrBTcz
-6EQvArw/lWPA5bQtR2Fjb2duZSwgUmVtaSA8cmVtaS5nYWNvZ25lQG9wZW4teGNo
-YW5nZS5jb20+iQExBBMBAgAbBQJWOKQHAhsDBAsJCAcGFQgCCQoLBQkSzAMAAAoJ
-EKII7U+K9YRGzDAH/1ktPsroXqFq8QmeKmEKPgI5WnpxUz0MKxAzYqI+oI0k/3gG
-kFtf/raxT88DawUnpwxOm8D3D3S+dZl4Pt9Hzcxy1qwTw7DKgZT0yL6QW0P21ao1
-dyZ1PCPHR33SpiJyUawiTNtYA06a0HanwX0hyR4DOUxtvn1MaXSyAIQUZJ17ePk7
-hNu9gyX+oB8Zw7NaKkOpAMQtADSchLnxVyRnhETdjrBbUsoDnFIh3l8FsjO+0Rn+
-94JEvfmwqH7YrjOIGykom5OAW1FQWSQrNvHw5wLJiLLWZXVBmYCLstL1Hx9edw0y
-X5TfgiBhL0VMJPp6wyMGJFdKa/XmdJc/zhYcMye5AQ0EVjikBwEIAI69cRGYdcoJ
-bM4vePhyzRXIET4ETZLYNr8Thn+uHQK3iby+Yg60tpaxTJJ4eQTbo+5yWdq+NDjk
-vdaB9apFZPr/QsqP98cAqQjj0ApIcx6bkFl6DBjMP9/9LRus08Z2HQvffX2gh9JS
-wPKA8Bwh5WUh4LB/fqkjco2iV70vh1aBmM8rmp1OjkzOxDOXO0Qj4TY0/ScxyyEp
-8GsYA6cIuxM+srnXK4Og6kv7lMJMjbVsnrMdQxfctlSVDcGuDSiCZEh/p4hKMSvT
-QXwQJgCnOKOfij7Ogb6b6YmGPYEymmi8qTxzXL3kEwxb/9ulx8s6wmZsLhpXGAY2
-6B/wVsym4bkAEQEAAYkBMQQYAQIAGwUCVjikBwIbDAQLCQgHBhUIAgkKCwUJEswD
-AAAKCRCiCO1PivWERlyUB/9z7UBtAJK1zqZDjFxS0ynQhFYjTRzfeFSGnHlZ+tM2
-5l88YWHis+yt+OfxKgE4tz9/8gx3Ibe497mD4b2jI8t7JjerxMF+1HuND2xQ1ppv
-Jh6S3ZTnz1xdEkVA/Ck0g+vltlSXxHAyOgw31JgsfcmGdVObvjvAI/tZdoHUG4AI
-qD50IKs3+N6oJ0XLHzZrFbF4OxGx7KUaSTkdV8l5f/Sqwz+6epI8Fk0NTaEYwsNK
-ml/d6HS3iiKXdq4koZQexrXlE6bNCH7oi/eAgOfD1dmL2WqTdSpsA6IR1PK53Te8
-7Xu42wsr9Vb/bT+jbNE78nAQKpTmWHYGm60/j3HG+IwnmQINBE5fJpEBEADl7Epp
-8pxg5ENBY4KM7U/lrxRg33BPDJcZTxqnCLbNCdEOSO1TEj3jWl1HEh236NlWLvHs
-XgrsKiB1jX037q62QKrp10trQMsM6QiEUjwmrGJxgxv2D/+U2PJPh6/ElFhx1PqG
-EC1Ih3pTpP1YINzfX6cQ9/e3nc64BcBTQqYA2/YIv4pHMYXZrPm398JZbPpT0ot9
-ggdLulUYSRJQ9dfNJbGpstMMfOkA2IFvfmKc5BT5Y/ZAayF7xPBEGbBMLaZuT8q+
-x5S39ZyzxzCMSIJD7nYAh7qI0xiosfu8YyjXPN3x1OYXkdBKzYEk8ji9xgyNZ/9H
-lsq3JhJzuGKuXKuC3GKf8BcNw0JH+/VWmq+3kd30a8dyGgCW+YJok+zyo51WWVLe
-J//5tZ2/GvRhbIivA6Gp2cxQlwl9Ouj7SkBNRWvjBJUrN0NF1FxKo1Yq9OECq2Kg
-Ln3l2vURW+LUtdXDbZcn9GcYbGHIE0xdCGQSH/H+DkglT63rQIgBN2MTQ4lhun/5
-ABLq7s82BAtakhQ5S+3gD+LykCcvCxgHApV28yJJT3ZZ+Qt6uNtHf2y6T4eJpiE+
-bWJpG3ujCwzQxu3x5L76jOgiRaj6HcwzT79LpjZMzhnK1sKhDAuJP2VNIYhAXn8U
-F+z54dmBRK58t8zQVop+BpJAE7QM/DFDp3uLhwARAQABtCxQZXRlciB2YW4gRGlq
-ayA8cGV0ZXIudmFuLmRpamtAcG93ZXJkbnMuY29tPokCOAQTAQIAIgUCVMofzwIb
-AwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ3PUT+n7tGfOjhg/+NtAiH9AV
-GTmbpdNHNyWrmPxgO5XKtfLZ+4gz6D1QpFwFO/YPL9iN8RhzsZJCestI6tP9vuBP
-vku7Nd1IFfuZlUmg26gftUTQUmRMd/lMm264WOYPXu12g8+PvkUwXfyoAcd32nOp
-SMkpiFymRN8GtTzIm4qOgWA2+mdFWMl0xTSuKv60MiNIszEKD80UxDS2bSj1cv2V
-BxDFwmlrzPEa/ozxAI9t9CxY8Lv6CsEfr5yHSWOkV/mqSo+4OegdNjiRRoeMo0/b
-UBOtkZSykj2ONSVBn1oIOYQtForUhtRyZFLJIiO11szngRDqRYOslmMMLuZ2k+/b
-/K4E9jvJvz7yt+Y3BPOsjRtV9tP7oSKJpTN43PTnTbKMM2RpVTN3bPtfhZ1+iQub
-k0y2H2XaKOnKX6wEdjaWOoBR6SRzVzSz8dgOqvkfBhA/bJchwWHnyckk7bZJXMsz
-afnkJZzW/eVeuopUgkyWeMHp6lngpwpSqCPXn10zm3bBYNSOFdCCX2Qs3hB8fLi8
-OQGv9puikZmvdIwL1jWP1GEkkP94xFTtv4wqgBykySjTMBs9zEDOOGq2x2ndUJZu
-UpoY7AHBtzmMrfwuuHwyFHfTVpalE36S6f7D8UA631vO/BGcDj+5xnAhhTBiiFbw
-orNjAfZs4/bfKam4v5rYb+riz+OJYVa0m9K0LVBldGVyIHZhbiBEaWprIDxwZXRl
-ci52YW4uZGlqa0BuZXRoZXJsYWJzLm5sPokCOAQTAQIAIgUCTl8mkQIbAwYLCQgH
-AwIGFQgCCQoLBBYCAwECHgECF4AACgkQ3PUT+n7tGfOhBBAAmMRVRstwF3huVcvo
-RbdUyogwVvEG3gKIdGOxKjTb6tQxYs6pkERF/Ke/Si5+GtbcCpM7Q4yio8taNYU1
-44c50qJ4El1xccMk87ypFSltxiHkJevnhfT13lZlTywaKmd3zDE7rwnyD7m+7HfG
-xwRplPBZlPDJ6VYDvs7EmbpaU8/En6VPIi6FULcqgFRZHB82vET+KP/S2RUWghe9
-zNsBCoHr6cCkdm+2hHhbKzhvOYJa6j09SPSvf57KOxYQHQDhhrVvBok5yG5uL8ar
-33B+nvXMkLwVxIxaagY9GemDqgumTELxit5X/XoNSCIyfFQ/IkASlv4AfQwy52an
-q2dRpYGz+FaenJY9eqO3rDOztffVfaiP3cA0qOdKmCU6BQQEqTgZPI64GjdL6TJc
-P1Zb3vuTIpW1RB/dTkQdRHTxQD8O4Qk8YOcxr2W6NczXuH/+LKLFrRGEldf5lEpH
-AQBmqPofzA1rIXTjkl3AwnpMh4+7KZXaW3IbkTQzmXlI5zh8Qs0cnIe+OGLUVvbe
-WoHE2G92bz5G3C0XP5C0XCKE/9wQEy57mVyPkGS/DHc0REnGg+L9B7zaYtktowJ2
-U/76PO9KuxWtFaTLD6LTxPT4On39XligRSSFJ82nXDboW2sVWoLhelXG2N1rfYin
-DdrKCk6oSQLE2zLf4LVu7Ov9Ar25Ag0ETl8mkQEQAM4WIsHIK/1+/39QZbh376iV
-Xfc4NVdE3ID/Lozz9JDanjkpScpikwugDwguVx+8JdO2tTyo6JTzpiZ+Coaxmjud
-JpUTT7fD5ONcAd1stpHKUQFwJczU6LSXpTQCpmhV5s13pwumxjymKRlotxLdr9+z
-xFl0e4VTFb5oj4Ik2wu6sehcIt73AxM38C8smFRrRegPQL2Xnq9BE+WUF2yyY3TO
-VAK5TP2MbwQTkrTOiTYJZdNHNlvjIpZaxHKOLqytNXSmXn1k20nitmyssIzv0aEC
-1UdktWIL/gD1Z+SjrJQB7/y56Dx7o6gr6J2MZZeo7a211TLdblejD6bMjGaH4CTn
-jzmkMtDC/2b+FUc3x3/GlQF4hWB4iaT4aCjiKOVNQgaQyAeRTsv1BUoqf8LDytW1
-/MdalLYElKS77t69HEQ9HSyt7QHU3sjAG6qgso8yWn8ebYCefm1lyZSP3BbvZ/Up
-oKuB+aGlXjteaXQhIRLRA1TgijiGA3Yw1dTcz2Cb42w4UNZw4r55yN60QDRBH4l1
-yrRPltdyAaX3qEg44U/Z7LU2YTDX+4JL1O4ZE+snDVsTPMpuZLvRFkxCLG1FTXZa
-cZRXfzlFzw6YWhpnHUYORO3fGhb+PKMKYEloTyLywjkVLHFbvaPts96dCxWyDrcM
-OqhgiLOLJo7qC+/Sq8k9ABEBAAGJAh8EGAECAAkFAk5fJpECGwwACgkQ3PUT+n7t
-GfNv1A//dYWV+vL1jiL+X4vRSCrDM8bBmt/cZfN5O0i3HYPMdSD9lVr9O+WYKJog
-xEXX1ofgEO74rwZxGw0crrMN8VM9SgMZ3jioGI15NF3INnA1r53GNGhJ4JVnz0KV
-2NKtshk7CtSxrjoR8qplwbMMICVgTIERVP1enuOb3FEtbhI4rcy+2UTw3hwURBhI
-fUotVFO6SKu3ZLscItbiNxpTqTpL6AIp9UOrZjcqfCuFs8P+57uusAHcp6GYhhIh
-NIdXf64RQs7gtdLVW71z0diSxu3KFWlrXOx0rrm7RTAQn1VOLl4W5oBPvcF2ZVQv
-d84I74TMtpP0MRDFgLuK0HHFVyDff0vx76rubQgom6z8ajiIa6MfEmd7z9xhQT5P
-U0FApYY6H/kW7ao+f2h2IIjz/+QjHuYn0CqqcjkkLC76RAgQjHYO9NIpL9Gi9O+I
-2AFz8YjOK3hOpxMrF/LjPJtxBXGFEwP4ud+hzDMjwaa7PklcmDPUBuSDIgbNvsVN
-A6gn7AkbQn6NH+DImdrpzgpSr1FHMbjIWqpXWbAZtmOurxn9f5ZXPKAgMvlV4TS4
-NZqnWT5HZCKs2b5Ped2L+zAdLP5NmyzJrSIyVTJ7JMLLfCLaWu/qsHRGt1w86gew
-g7uMPdA1IEvjjXaIWNhYKUq6ik+DNrq0Y3fUuRg35QHaPTcab+eZAg0EVPRvsgEQ
-AMeXMm92zU2ooQOE4AbQhYY3gn+MG87l088WrAMlpTfbH7jBPDQ47EJyAVh3NY+X
-XucXCMLzJ5e9sAIJk3PrYqDmjWVYDox3Hx5rMKIY65N1Rud1kMGWsgQCzU5RmarF
-NLJ0OdpE0K0tMTajS3gxqJ1zOOKdSfZGS+u2+UKyLUelB07mZROv9uanu/ia8I/m
-8RG5jb6pVzUpuoWW0J5XQoA6mvWREbJDgP0sWWgWSvt+0XRtrcHR9sie7a4ynjow
-L6M+iTm4ShPrqX5TuxmwJSQcfTZjqz+5cnppyTzj+mG2/jHBERGWkL3sx37s3uoh
-hWt2EZVuyIcUQMigGssCp9216K+ihyC4tEj8RSfbon35t7OGYJlRS7V/raIm4GdY
-LCOkQs0yUIila6AcC5xpRnHXHIXvUNHrk1nAyz5PEER/6BiW+vMObonx+GR8oUfo
-+2uMg4LSYKx+o5jgWBFiSdNl3gQK5+RswpdFfzyq9gZf4WvVOCdBH0YKEQ8iJYX1
-7drkn4OWMG7u0QnQs6GdZTcClQJMTWVBIaCtRMNCxuKX6XI9WOgwj6vHM/ijeugP
-LOsUSv+uWcK/fH7SSqmtJdMmpwNKFUmr4ZTaWZ5QLLv6kXbIoKqEKFwXkJAPgm36
-mVEE+ruy3FoNl0um9S0W8tGXgs7pMmM0AZ/nHb6R++6lABEBAAG0KFBpZXRlciBM
-ZXhpcyA8cGlldGVyLmxleGlzQHBvd2VyZG5zLmNvbT6JAj4EEwECACgFAlT0b7IC
-GwMFCQlmAYAGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEF5QcVvy/+GnS1YQ
-AMbqFxpsYGMsVRpwrFZ1/NXyfekLk7XCWD++YgwZ10d8jXQAv0BHl5ubkpCEuZdb
-7SqEkLgKbjRfw4KDqri7iDGjwoYckmqh1xl20qyTYkfeQnKizuBWmNsVL0JT0xUz
-KhcHBh1bWx4FPF+iojOlZQLKwViPpGOacstlBcPfRPQhaP7QLKrOVvVQd0ebfbbs
-jSBP+olik6OWRiyXiIfsAmGq0OFDtk+f7jI2UIMC+/ADpulzMmJdr8l28wgoudtV
-M+w4LB6hbFOYSvVy+kcMqWI+yQ64Dy6OnFVI5cZH3jxQXviqEs+QNzM4jUQ37Kp3
-TPBEDvHNQkdYllk7E24ecF/bi/3kDISBfVobSdMGMERxPJhnNWCG3sEP+WPT5beD
-KywcWX4kOHpmhL3wRxJbZzEusA7IXyeeb2AWfsJBdMtM5Ur2u4yW9Dq9uNFDvXvo
-4mQXqM0aPmpKN5IrSzTBT/9bKvu6iOI+JBiEY/A5wCySrqIsjqeJ5Wac6dPCcNsx
-LV1qdEVMV2eg86A/lbymPiQg5xJzF6RSFwmVpbye7HFqcKTLwbZJHzf46CpgFyK0
-Wuavp90Nxrr0oTaHxfnAHlTDdXMybm3Z7wlEXEg2hSh5rrTKYfjgtoIJRkCjaRh4
-0jRkIM0YLx5uR5PBk6hAzjik/7sYvgG5XuPra3L6Y8XGuQINBFT0b7IBEACjecdg
-3e1IF3zBMadFbFah7ZSPFK4Y8Q+OMbeiu0TzXP65rRXDQi595jdIcQY6/7gB1Igu
-qC0HWUo/Ns7GFnNnWbrAaoVWpLjHXgMJ9hqdyIEgluoJECH53d0Y73oi+PBoYUU5
-z2tHi7AiiJc9qMG4m9q2P7xUrnqCqmGO4pU9nFJTFUAodf/ioNk9EdmciLmFUm7X
-kHNtUcKVQGWER7videdWLW1fhHAzhI1hYkN85ZfIULfrVNZBn1U/L4nry7P7HO0I
-QxoK7POs6apxU4JyATEyvsnjYU+UOCDPXRIKHAZ4joEnFhyHPyURgdMLxQb0s1hn
-bTEC+szvqb9kC0rCan1GRb6/VeW9eRi1CoBpHtQEwY6k+YgWpvcfR0w9+6BH5aqy
-pGWnNDCWcOTINUrouALb68oxgnEAowhWIa0ujUYy+PMYF0AFArjLVxu1IBKaMD/W
-sk0ws389xAnbVW81bhHN2Ye2NznDe3YfK5FkUyWXO6GA1tFQw+joxt6+TPcTxRJL
-S/MG/gXcluQE3Kv+jteqi/dbt5A+potX6qGN+F1GJwD/mQKyULklzlcZCIYZN9On
-KVbSxfn2xQ89bjvkNvRjuO33x0IozIr/R/uz4T0H9Ve4UoNj2vT4pH/Ba/ergQSf
-rrAJMDyIB+SRIgY7LCQFB3rOIvg/HiqAY3VL1wARAQABiQIlBBgBAgAPBQJU9G+y
-AhsMBQkJZgGAAAoJEF5QcVvy/+Gng+UP/0CjLMF30xjRim/+/qzx+2OZ1S6R6B2m
-p971lQxB8gCA7dn80UhSZZMHfMeo2N34itI4HEhQb+2jTOgQvNjv36zMppZjHQUg
-4+xabvZU33FrB2hhD/ZdNTm7lCD87vKxz07flApkscw20VenY8E81z15GuWLK/Uq
-E9wK5sbVoFB36mwNFqgh8W3oBBJTJoxWBFnqZu7arsaXhEWpVW2+36I2SWaWFmIP
-wrUbuwIXSuv+h+ksEOdVXr87AgxX4qsPs44N6z57yhCIz6g3ow7R06IolsDSE8wy
-OWL313X6UE1R5QyqAX1yQ0BtmcPRh05SC/vRe7WeP2q+TyHMrM1/YLN/W9X4Y7lo
-PL38fpmWMEaNT/RgZPFjqDbqxYpyN6Kymdsfr3YPYNrzcYlc0WRplvpZh3D67PhI
-9XuRsb2c7GAU1jVze9Za1ZrPpcCCJgp562I9E1D+a4x7w9fsiGDkPOm5Iy3HTg9F
-H8VUWM3uwret74ZlQyQkE1XYgRjqHrjqJOajJg8Qw4meItY0QB/5kxmAW1h96OoK
-BUZq5GaQ8AhtPnH+4peGQHG9fvSL58pukqeLGHkSwgdMPIFYZTHiIDt2tVkbi7vE
-3uvKPm1bZpvM2T6m9ZUkVWV39P1W9lkqWvXSVfit1GRUpFd2onM7Rs0jxbZ9VfiR
-i2OblZ9WkvtsmQGiBDz2UqURBADq+b0jXuV5JOOq+WrJJEOreZoptPiO+gtEQf1I
-TUTXEMDJWnnyGQ2LafrwbS7eD/Ih8yLvk32FL1CiITA8FkS59v8vRRRd8Ag046cE
-ENAsFbESXAnpv4EVXKzK/K1IlJj4ZFAId6ARv4n96CmSxR6kc+SSywoNkeH310z3
-yDq/YwCg72sX/D6YNASqBTd2lVDxNcW2fgkD/jgyGV5261rU0EKqIcN+/W1CwCXI
-wm0MGRN4/fMQfzoC6sux519M6mB+4HLtW7lWLP5LVBlMiC8AJlHJf711NNPxV5Xo
-l+rOlc78tpfxbr0N19/QDUPVhIgEL3rui0x2YWWME0uCPTZWKe9+RJEQOPA/RPoD
-b9v8XMzcDx3RVAVyBACDUeqNJ6Z8e+mcXjC6DRBvg4jt0bd1k0/FN/a6Gxrpdpgl
-U8XSBErJhB5rvxfVhVwYrO8M4uyTx/2a29ssRCFAOGtIjr3R6J4hoRusgDTr3NRj
-qjKbw/2EVpN+oePu9oGIQYy/5woZRN4ftabntQkqXtjoIjIl2JcA0Nr81sl1obQZ
-YmVydCBodWJlcnQgPGFodUBkczlhLm5sPohfBBMRAgAXBQI89lKlBQsHCgMEAxUD
-AgMWAgECF4AAEgkQHF7pkNLnFXUHZUdQRwABASq1AKDkdusIoMiNKktSMWfCbg/o
-MJcmYwCg38laBCCqB2Oudv6+OebHWSMHrNi0JWJlcnQgaHViZXJ0IChmb3gpIDxo
-dWJlcnRAZm94LWl0LmNvbT6ISQQwEQIACQUCVA/k0wIdIAAKCRAcXumQ0ucVdWFP
-AKC9315eBt4gCqWUfUj6EfaexeTj/ACgnv7tMyoH4Nv7jK1BG4JQ0S7Fewe0JmJl
-cnQgaHViZXJ0IDxiZXJ0Lmh1YmVydEBuZXRzY291dC5jb20+iEkEMBECAAkFAlQP
-5PICHSAACgkQHF7pkNLnFXWhrgCg3bm+cERc+F75j2DaMhdStYhcCoMAoLzC6QFr
-VqICjXAWt7LUhRetEb+LtDFiZXJ0IGh1YmVydCAocG93ZXJkbnMpIDxiZXJ0Lmh1
-YmVydEBwb3dlcmRucy5jb20+iGIEExECACIFAld/SwoCGyMGCwkIBwMCBhUIAgkK
-CwQWAgMBAh4BAheAAAoJEBxe6ZDS5xV1BKEAnikLxRY1dyV+u+r9ImnaY7AmZ+x6
-AJ9GWMGzivQldWwZYPYYh7f3TTE0+bQzYmVydCBodWJlcnQgKGNvcnBvcmF0ZSkg
-PGJlcnQuaHViZXJ0QG5ldGhlcmxhYnMubmw+iF8EExECABcFAkKMrMIFCwcKAwQD
-FQMCAxYCAQIXgAASCRAcXumQ0ucVdQdlR1BHAAEBRq4AoOGYEW+xuB9eTyj3ziV+
-gvTw9MBCAJ9x4wINXzTfVj/Lz/VJ5W4h9PBRGbkBDQQ89lKoEAQAvChVI1iQYngK
-QtFxxelx4Uv+10B/HaIn4Obk2LqJrbc6yS+zatqOBl0pM7jOTRRZp549P7U72jAp
-CW2/bKzdcQNJlJRV7FIe5E1qZNf84AsKBHqphe/7FxHYypekmcvAiZG1B5cmQDEW
-+ebIBqrPBolNFYUjgDaPMZz0Nr5xoyMAAwYD/jfkkn6jJwMSZPUHMuVGBTQlCQ3+
-b70XClBV5uN0UIKyWx7dRtZD7vuf+NqblygnRlsAsEuh99ggWKOL7zUjcXJKtHWr
-MhjhVtPg/4we19rOY7Z9/n8Jc427dTffAX84CHLuuSEZomYQ1uds9DMMayRSiO5B
-OOXqeP9ItLElyHb4iE4EGBECAAYFAjz2UqgAEgkQHF7pkNLnFXUHZUdQRwABARDz
-AKDK/3G2YXuVXtDDiPe599ncuzJEPwCg471sTokR9Dn33H9ZFpjspd5Z+dGZAQ0E
-WOzWBQEIALuqBv3556Glk00Hu866hDtDEOtLeyVXOJA8ySsKYIwacAHzaTa2whLL
-zfx3XdwBWKtly1o3hlduwfwL1l3aMh4zamHFgl58a+P6fGTlPEEehi+1silIT3QP
-bqxzOowiwe93UVkJiTqhapGbFDmnguiLZYTWhgAuGYRrEpvtNmnJU+6TrDTO8DH8
-34uoYTESqs+fuOVw6Ab84th+Qucq1LB3yKsHhyq7m0en81a22xVXIl5+CKZts7pH
-8bRTTSMn6eo97k1KJ2E15hoRnnrshlduxhzbRjrx1wfqOZ0mVzuNHSJYlGvUKnbt
-NTatOZXRfUAlqMqcsYkXz8t3QLz/cuUAEQEAAbQtV2lua2VscywgRXJpayA8ZXJp
-ay53aW5rZWxzQG9wZW4teGNoYW5nZS5jb20+iQExBBMBAgAbBQJY7NYFAhsDBAsJ
-CAcGFQoJCAsCBQkSzAMAAAoJEG/8M0ObDQTfcIcH/32n9IqQwvOqh+rNjl3vHn3o
-n4MdUebEIIg3QkhGtBb912Rdbvqp2lJxLDtgI1EolYbmab1HRRBXh0x4ErGt2yJS
-ruyQrTPp6RKX/dP7tAghTPHtiZ5JK/KjhvuBgjbZ4xiy3ge/ZVJoEOuxzPfZlK+M
-Oz75RqT7eH4mBvfB4oBr67OTfAzbYQOGRXNSsRzhHr9xCGXk1zlNHheyXrwpPm9w
-D2RahRPRXscagv+HKI7W8taDLY500C3iX7ux3VfzJcy0ub4m0ru96VFJRrdwi8O7
-WT7oJEZvxV/QtG7sXfo7dt+ryRAKxu3er24Hmk1S9iVhowEGnq/JRMOIg1ioRj25
-AQ0EWOzWBQEIAJ+8XbWUGbMEpYf0gEfnxznD6WxBf3j4E2GWiqfGYHd5rQPMErrk
-0DXmxCwSWjJf0+96KNvJ4wrQ/G5gAUj7R7OChXWFt/KZeaEBCJQd0de41pjBQ7+k
-Vb8cRTBt3gCLWC0xEkbYn7jk9T/Rqm7fOkkmt8x2i5+jk83M+lteR1aFbwIIA9dM
-uG5lm5jz+a1Hu6fK65A2V8lsBacp3+D3NNXIwl19UEh7u1H6Pg1R67BuePT2iKo/
-TYyLrfD/G4pLr8HoU19wXEkJq4S/yzoYr9oABZ3spTSafNoVYaxqmerpBHSC5EY/
-D1t2QfR0C6pUVOVjxaGjYNoaajd0kA4BXqcAEQEAAYkBMQQYAQIAGwUCWOzWBQIb
-DAQLCQgHBhUKCQgLAgUJEswDAAAKCRBv/DNDmw0E3+DaCACIyXcUOmgyGqFXmRXC
-8MVzc5NcKEE6amh13Cwb75xjmXI9p2nvcklCiIAF4MrJJqR22Hkok0SqlcrUb5vj
-Jw2/CZ4PNdbWM1PaB7AyKmiqvM4lpFfH2hR1U1miQZdM8V1CXmzOH6DGwuZNU3jU
-NyYvEbidIxBcJT282Zp/jC9hZFGLL7VL1he0hUvF3WyDmQo9RSe0xNrLCTNN+HE2
-VaTEk7L0dAcVS/NbOv0BJkdB0LqlHGOAE5ahv/iUxO/6FCpxjtb6qfCQwUQXjRrM
-STSwdSTTlKA015yy44aEXfRnMH9zOPKYbZeJMFOCsfc8fU3LLuacV5Kv6l4aJyRY
-JaN/
-=t8zo
+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
+tC1QZXRlciB2YW4gRGlqayA8cGV0ZXIudmFuLmRpamtAbmV0aGVybGFicy5ubD6J
+AjgEEwECACIFAk5fJpECGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJENz1
+E/p+7RnzoQQQAJjEVUbLcBd4blXL6EW3VMqIMFbxBt4CiHRjsSo02+rUMWLOqZBE
+Rfynv0oufhrW3AqTO0OMoqPLWjWFNeOHOdKieBJdcXHDJPO8qRUpbcYh5CXr54X0
+9d5WZU8sGipnd8wxO68J8g+5vux3xscEaZTwWZTwyelWA77OxJm6WlPPxJ+lTyIu
+hVC3KoBUWRwfNrxE/ij/0tkVFoIXvczbAQqB6+nApHZvtoR4Wys4bzmCWuo9PUj0
+r3+eyjsWEB0A4Ya1bwaJOchubi/Gq99wfp71zJC8FcSMWmoGPRnpg6oLpkxC8Yre
+V/16DUgiMnxUPyJAEpb+AH0MMudmp6tnUaWBs/hWnpyWPXqjt6wzs7X31X2oj93A
+NKjnSpglOgUEBKk4GTyOuBo3S+kyXD9WW977kyKVtUQf3U5EHUR08UA/DuEJPGDn
+Ma9lujXM17h//iyixa0RhJXX+ZRKRwEAZqj6H8wNayF045JdwMJ6TIePuymV2lty
+G5E0M5l5SOc4fELNHJyHvjhi1Fb23lqBxNhvdm8+RtwtFz+QtFwihP/cEBMue5lc
+j5Bkvwx3NERJxoPi/Qe82mLZLaMCdlP++jzvSrsVrRWkyw+i08T0+Dp9/V5YoEUk
+hSfNp1w26FtrFVqC4XpVxtjda32Ipw3aygpOqEkCxNsy3+C1buzr/QK9iEYEEBEC
+AAYFAlFruf0ACgkQHF7pkNLnFXVs5QCgta+8ZUOeL4v7TqkwxYndHlflBqsAniga
+MY0FMHCAkKc+MIubun0ww9QHiEYEExECAAYFAlITG0oACgkQ7JEU4tMwuvwvawCg
+n+/TsPo3BDq+mX/EfvKhFQEdCUIAn0KZVZwDN7mZTa49Amy/T5Ai6hIriQIcBBAB
+AgAGBQJSExvtAAoJEHcKmPCS30+Zo9wQAI+4GsOZCtV1jd8M88KIDl5b0Kh0ogK/
+pg6orYu0kyDF9W16p5qEn0sTZP2QP4+DyZWDfPTe1fxHlSac3KXMTqGtLKDq0xP0
+WIoqjhSnMRmvhmODNxnODueSL7Jmg8cvXKvj7FEYaI+mqgChyikX9JSJdTWiMuQM
+OC6adGr2EL77e6e3jAaI31OtCTbam+EAJFwxpYSlBMop+SemBbeokHHRivEyg2hu
+O6o7m4SprxkvZZWmiT7DmdIGhaPt5CHoDbUdUbj8ni2EdSZnYJcCUpPHJqF1FUkw
+sc8NDH6tiAo7cHsjkrDAx5Waetnt9mRkMkG0tUgnULcChcx6NJiG3lhIWNDnj9ML
+zhA7kDaktvtEwCyuQA8iyWWfOgIABofYJk3zbPG63N1XhWyZ/ic3IbmnWWrEaK2x
+YDABvTN6s8nOgex0D+kArhsPE+RMWPzF9F+2YgrbP7R68ek2+/4SdQNifUloMDJJ
+A38nmxkM0SsLNgbIWLaltw3GwT/0LQ7sTtcLLMhl7bkgyYLmmII8MxXPQhvr1oXX
+17t6fwJLiQjokO0CVw20CT6QeFo4P+pgoYkSPn7tFtfkB3sgZhea3Lr545NDpK5V
+j/0WxMOYhqEUmgCRjyzmczklyoXPMFD7rX5LxEENXUnxkGGkKFB6OIMq7zrheBsc
+B0/wcZcO05pFiEYEEBECAAYFAlII5FkACgkQahHbMDrZuj6xHACfWTyc9fNJJybm
+0E8tJmVmQFjuyTwAoJXupHi40ejoB7Wzv9ikitJXXpcAiQIiBBEBAgAMBQJUYG5b
+BYMDwmcAAAoJEAYchHN7TRAcCKQP/1/nQjNDuVPHxe/ukv3v+EMwenhCq0Sq/kCa
+e9MDMU3pdu0ZYdXmdKbWdyJi/sqmdAaRttYcJDxzO7seC3auTdSCZGs5ENFszgEq
+OPIyYFJHuSCnHz9W1+ZIKKCE8O32KllAltSJBN4AgtKLgDVlQzHVbszUJrzJhvxf
+ArTuAeY13zTWq4yRAWX3VhM/z6mHC4jt4csk7mwgGoEthJ6/evUE3zh2uQX8EeQK
+QXFGn8URk52tZLuUq7s1tAeiABwgkCo9vtEcHHIYXyk6tCFY9R9fA2doqRXpFXTi
+ituk5QkT9EJSDSeBrB6NvgXaJLHWoqp//+0pKpOWm0LYP2SHm2MY4FyTv8YB+aQC
+j1FgzeJU17SE4h31isliTB1qieasa4v4BvHqJpmpD5k9P8L9dsMIMVc6KVhkvhc9
+bmEbKVbf77U83O0YAwQxuImIjFvmZMTDZEk0vepm3VL1ZG3FCrERrmcek/NVD755
+YNtVEN9rvG0LFGm2Gbs9+AWtfjmhhVrOHNYrsSfiuykSnzKf20dHBegB7AZMdxv5
+CCJ1uIw4Wv9Km4jaR5NUwOjEccn8fHwoRC4SxWj7FWbl5j48RTM0eZansWMjkE4O
+UKOx5LRexfiyUFQ+4/w5+Z7afTr4v8yhs84bld4eDt9D8S/Ynj3CPItWsO56Kwei
+uzBVkVaLiQIcBBABAgAGBQJVB+GXAAoJEF5QcVvy/+GnMCIQAITJ/73QIgrsUFh6
+fWGfKMOgY8f2JUfwe5g/vSO2BQPScSgTjoKdpy4DCILI3WzSZ2xzxOlS0SMj8hoD
+IwQxSydYuZhIfAmlUmaT0Q5p6Zaef7/+pFRVkas9CA4NE4V3ZCEhQjVvEI8bXabd
+ld452PE2Fahi6m58JEFwFnU84sIIsQJCiFFFFj7OxNGGMK63vZFxgE9dhW1kpMGB
+fxdKLFyglEpll2qGbCp13shFLeZSCg5WJ/pC6R2t0K5tW2XAHz7TRj94dnFTVD8D
+MlydrBrxYh7DMVaeFLDgepxtT5n8yW/RLThHAvg7Qyvie/l5bt8Ukk12ISPv7sY9
+bYdM0wHWj0913RKbK5Ic22LM3RK3My/yXeKMI0u8PTUuCppIiCRhqNjFr23Xsaix
+OYRDSsvo6ca70oCUzyVMJs1nmknFoimw9yRhT4bUN5yS30E9jqhgNb06cXUcCM/r
+PYvVqe2/OoUMYBHjRHqn64uHzvtnnrJKAGUk0EqTdqyRCfWzo3+mClCzeyes2P0z
+zGYzwZ/fo+fIVcT552wWCbJa7KW7XcW7CTzWgAucupK7tm9jOezvd2Zt68lROAVR
+L1F+P2HUQvzLcXVaSqOIHkiySMAKfVEBfwA6y2oBjUkBv0oKuSV8xk+cq3B3sxDQ
+ra4Vw2MjyyiCrw+piIntgqnSrzTviQI5BBMBCgAjFiEEt+XF44bdUAOtVxPgrImY
+jct//fEFAlnd4lsFgweGH4AACgkQrImYjct//fHjpg/+LN+cdoeASfejFq+mU6r8
++WV31ZaZyWpr6S8tH3G3YvPdASkMl1/B5FIEnSpFHIc/JaZp1AU7a2YvhTRgK4vG
+YXZTltKKWShxE5oQ+5Ep70rU9dVlbBMzZau3Z3GKwITSemSyd3qmZSnD6w5UFGEG
+LXT24F6h/yL9EArNbUmfLRptuq07+UI7Hr1qnwBe4ymGvFngJMg590dNnjouDsLS
+MKvH2sMVACdz5N/fy4t63APhKGweluLO8FxAtEd42Dj0wSXDGwoW96eIG2mJsdgG
+Fr6Uhgk3KQnqXEUtJfCOQhaNOiz/b5uhgQ9/kxrhlgk5fgi+ztYUCY9UpXGhVRuv
+d0rmmUpaAGlPSH+Yd1QZexcIkCmKbsNM9yNndx3DuZmAeVIvapA1AsflksGHwi6l
+/U/3QJ7TVO1IZbsiDyoqZyo63RpuP8jsy50r6L2xj+j/ZLKCHH58/w8o+qw6f6A/
+xxdpRYU+P2lpsHRPPDTuTkI5+IOVzibjqMILT5NeYEytmcHpXqrHYN+0TXLY/vSW
+NRiDojU0A2HlEcHFWu6PO+jbYGxDx9jiAtwW0O8UExQTIYoP/pGg8WZKEclG70W6
+nxhnBhwDPrvma4uVmMsH4T9XVaXX+STv26s71qtsouxRHUfyBaN0GYQDs/SELVsg
+gZslolTci4yTHmb0BhVET0GJATMEEgEIAB0WIQRWlObIM8rVEjhhucmZAzeH8Ry7
+VAUCW8c1sAAKCRCZAzeH8Ry7VKTzB/4k5/tlyCypnSy1Xnb657rDbUnSk4TGQD5A
+itDFviXzt3hLW+YjHQUwUBSsi/Pp/w9EoVHd25Y1l7P6ZUlV3NT1iKRH6UAFP0p3
+k9sbFN28+IoS59vl+2DpwHy6DDwj6FV8Cd3UDKeoW+6PuMVPnJlADJYFM9rUhri3
+1YxnG9fHMkGwZGNd7Ye+whhIdI+KgPkmK1or3c1eTFWoHMVauAdg01iXz/F6CbUr
+VoT527GRqMAR6KTkQQ0Ch8elwTtp/5girRH4a64Xbq2pOI88sAASjgfty0JvN8u6
+BQrx1xK/l0D06C5rr/ArYOgx6jsmDtCgfB2R0wH4doqHoe9WFq3fiF0EExECAB0W
+IQS7gq9g2+DPgdGGuTBQKbfQFEC55AUCW8M8TQAKCRBQKbfQFEC55K30AJ4+nUlC
+r+BbW2dM811xEz/xlo7OtgCgwhqe4gO3yudXTqqwW4fsPyj26tyJAjMEEwEKAB0W
+IQRhPku5L/nxcvrPC4LuEaJYliHpkQUCW8M6HwAKCRDuEaJYliHpkUIyD/9oYYzO
+KCbxFDWJWAZo0x2YNdDdf5zZed0uxn7jljURqzCiWIFgXGQLjgsLGxXJbYnKhkmX
+fggk3KY2sno4VS7ms7jqB5kMN/kxU/PiZbrzSCdWtjhMFxufgq2oft42tR3ukW2m
+mAH1lj8vhY0Tq/jXSHuZ1TJoyQdaZlSAKFehzrZNadhIYl/a5sW9XSVLgV5Ic/RL
+r2aeL21OJ0cdnU2hw3sK98fTwvU/RmEGSSJPBu6h5hUMxjR/qTBLEMvVbEaT5QuD
+vGAfampY9pmawqHA7ykUWwTEiBPjgjjNiQ5Znfag91jQNKs3wRH03xf3jH1AOSxI
+LJMmhnzXwuyy9/YX7fC76KyFAfeDU3fVfX165wQDuu66zSn3zJxGxa/FwNr1aI/5
+DhDSXu2lqg3oJx6DiiCPOVgCur6XGJsE4EmHtbuIq0fb0KJHcGXFcsg81cM2/mBN
+R3Gewk6+C3OJlPe73Eq7WB1JghKo92bKfbc50+E9qr0sfZvoiQ28/bpfbCGEqCkB
+sucpWgH0u+xfdPSs473H8YxY73HLHaBiBFlPb5OD22c7ff9rUPIxz1/G/HRvmJDk
+w4aS5PqPwFU6OwReYU6K4lqdFQscr3+VPPxF+eeACKnCciLbAOuvVMC2nGd1nXIx
+6w3ISNKaBofmf3SvJgtvWfgFLifZx3ADB6uv6YkCMwQTAQgAHRYhBJlv1LkSXQzr
+4yYeHBXgoyUMySoFBQJbwzcNAAoJEBXgoyUMySoFJDAP/AgmhccU+8gn5Rgv+f7t
+zjvNaI4mpQM07/gUTPPJCYg+yy+xe7V+OtJedjuSFd+IMKym+iXKLXDD+SA2bqt7
+RcQN/tsbKMTy7k2KraFbbtv3fLGekwgtKcYbyHHYwmOfqYTup+dr/XEYMugAjKP5
+ga6FG3E0aP4B69FBVfOeG/D+z/6VtAqbbsvvNeDBicqL/MGyHnMl31CXUZmcT2J9
+gXXNsLEVWfUdh/uabEo+QoI/ZkqbBQ+6mLKxWgnZ/K5H3OUr7mbiKyKG2+6qfY8Z
+XOCNlekuK0DTdKSaQvmnbqJiE0BFJqucZN6IaEWPaFwocgzH+GteQh6h7iW8hQeq
+2z4Kc7/qW4cOoSdVGc73EZvDWvqoYgBO8MKC9xCFy4fFHnqwbkxkKTS5hTkaluOb
+HSCu19Atn1CcNBzoSO/LnagUQVDMBGmxN6A1mU+lwJnpPOpdlLKv669IrTaGLh6W
+w5KOQwXtM3/PMC2ngM+0640yIP9zVPrUBWxuXC5UHhBppIITwSg0wWBnTu2FF+F9
+SdS7daQ7zG6azEle6qi4XBklvzfnW4YDlc7/grwD0kgRSj2RtnPCMuMZLwlvBNu7
+NAwHHr4ktIw6FcHqvpFT2r+eHcMOiA5WL23nrX2yzD4xj6R+65XBryBh/P+y198S
+I8KKX/St5S7Y71xCgF7GfHDdiQIzBBIBCAAdFiEEt2zUZxwJaLqofeYcXlBxW/L/
+4acFAlvDMxQACgkQXlBxW/L/4aeeBA/5AfHGXU9mrdfpofDigMMvojXoyMBJNIyQ
+MZ7Tw/j64XmC5JOsNbbWJ0wJDWgVj8tI27rc0hYHxKBRo4v5nmLjcbG3Zg1/7g/w
+ukgGhFC1DevsgRKBbMJiF1oiAzp3Mn8smNAqDFE37W1qvhJrHP35py7ycNIpRc5w
+jEiCJwSqLAxXMzWu8fpg3rFatQXi/8VH90TjoAaEXkJ1eCXFoKOacZMwWi/v93Ax
+u4ePlci8YDyZ7EKXoLUuLZeSafFLGcWQGATbTvau6ATQ2EEA6jjgIosXHRUaJKXM
+FC21OQIzs6CejrmCBEovr43JIju0AH7vFqBoQck0B0Lt2g+xW+nSnSJrEOERY3p+
+O0g5q+rl74+G4J4xExqVH/jSdOKP+eknckKLBHLozarOrBJ8y/rN1hUoqLMBQZgr
+g8NdPaIALe+Fub1SW/mVY5xQ5tX6CgaSDGl1UWWpG0NbWU2r/l48FlFZnYeUPmZv
+exXasS53pJ1zXfrnAA1sEGd8KJVu54B56EoLBoVk0pmmtkMpxG2DynlEfRdllZo2
+oLfn9DhcPr2/OJTnTMkNwO12cFEGfS0GAKE7aHrNSydgwysKf+N1mueSbHVCcq5D
+yiuA7gepEmB+bWKqhZPEqBamOSnU6vRszbuy4FQnwI4jQlPYQkTrIPi0CIJnTX2o
+SCllVj6HQhiJAjMEEgEIAB0WIQRzP5dju4GBm7I373Kz6hjuXm3eIwUCW8MyagAK
+CRCz6hjuXm3eIwdMD/9GJJDPKVPacmPIe0eqBTZjvr5mil5iCeb2v1yMacdHTK8p
+JDV6kT7gy+YKsrgF+l59+yy1ELgrcWjWR1HewChr12MnfMVWVLCS5wlJw8YEY5F2
+BjGdaqFIeZyJmmYwQ3PjJ5jZ3AKTS8qSFUTE86hdwFS1NXYaQJrBMieyN+f39PSF
+JtiQ4In+wUhJbCFJkI9l2ZekBTYsd+7saY+E/dxjYL7ayyteJuI5+H99430L50LK
++vWdzSMF8PQGK+gUHROJWrWVsG535iOLVDK9uQWJaBooPpHj334/K9Z3M9YJOFI1
+TfU4ts7VEUBEDG1Q731OpXzJKZrHJ3AVuVDsw+sgTIijXo8oRf7vaU5Z/MN23BBF
+9slvsY4t7M06G0Afvir0gXcbJvPKuCdyq98JOIp5GoJ3tFaOtyUzknucTaND+wy6
+c5wymaUZNc2vcJqYVYrhtI7ZZ1Nye31G2gZvK0yORrormIaGZ3Ah1knJUs5wcNcc
+WgIQF/IQe8r0ZCt0eOJhvbC78VcaC4bhxOx3qYTsT6N6cNpXCdKeOdviNVNVAGTV
+oUyXhFxCMXENNX0zqgMxXYylV/sNT8+jZJ3goMw99kFVYElVe1apbbHZQ1Upl8+b
+ofX0AT7iG1DAulnAO5lnGLltkg/xnvwceWqj8cm/S+l/ki8Xx9jxzXNTlFEWMIkC
+HAQQAQgABgUCW8WxhQAKCRBq9U117gNVoEPUD/9xNvqOZO+knOTIO1gwrldPJfG3
+sarISY/QHw0hu95zm7RvPbvB3USNCFs/34c9NR6qohuEjuXd3yo0BVAUzq9ACSwb
+ocwuRGHOw7dfH8jxQOjLfMsls8Qh6UeVYTXCU1DrDO82yiUbhdCyJZN3UKDsUFAL
+bmEfCeOvTITXSglvKTl49jvbIqlOsBAtVllfStnnv9/DaZrAfh3/aC0DZ4up2Kvg
+DKc5h7fRMtm7fYJJhgdIYkzaKoBxOUKLgcJcuE9wclj76wxQXaVemVoJreZ0qxdF
+9P6vttkXdc7j1yzJwIQgSEUOcy5BhMsh15qff/eMYxj6gASmMQKQurxaKNyfd9Av
+pSmLSL0mi8AzfRdKAax6gwparkKKTQq9g9xZxY4GR2XKYhbPflMc8BLQKB0yHpr3
++798/Wk01z4ahXlp7K6oK1xt8Lw459mKDbI5R1Gc77Ica6/bs5gBDg7963K9PkuP
+VmvgLkbL/W5LXYBXCGF3B+vJwVwgI7b1wtqeQl7CM47AUoTuJE772fD4deK4gSEO
+3c2EJPtTLOXBFswCO1ft9XQ2V9aHGkW5cMX5Mw+so7HYz8wtflza5VcZrhKDx/R9
+XQEHyPb8pYQdZOBAYg0nZpT3/Qp60wujwLBP2vVZ2Ck8daqGWVINsYzAlBazBgdS
+I/8mTAQkR107EE2iurQsUGV0ZXIgdmFuIERpamsgPHBldGVyLnZhbi5kaWprQHBv
+d2VyZG5zLmNvbT6JAjgEEwECACIFAlTKH88CGwMGCwkIBwMCBhUIAgkKCwQWAgMB
+Ah4BAheAAAoJENz1E/p+7Rnzo4YP/jbQIh/QFRk5m6XTRzclq5j8YDuVyrXy2fuI
+M+g9UKRcBTv2Dy/YjfEYc7GSQnrLSOrT/b7gT75LuzXdSBX7mZVJoNuoH7VE0FJk
+THf5TJtuuFjmD17tdoPPj75FMF38qAHHd9pzqUjJKYhcpkTfBrU8yJuKjoFgNvpn
+RVjJdMU0rir+tDIjSLMxCg/NFMQ0tm0o9XL9lQcQxcJpa8zxGv6M8QCPbfQsWPC7
++grBH6+ch0ljpFf5qkqPuDnoHTY4kUaHjKNP21ATrZGUspI9jjUlQZ9aCDmELRaK
+1IbUcmRSySIjtdbM54EQ6kWDrJZjDC7mdpPv2/yuBPY7yb8+8rfmNwTzrI0bVfbT
++6EiiaUzeNz0502yjDNkaVUzd2z7X4WdfokLm5NMth9l2ijpyl+sBHY2ljqAUekk
+c1c0s/HYDqr5HwYQP2yXIcFh58nJJO22SVzLM2n55CWc1v3lXrqKVIJMlnjB6epZ
+4KcKUqgj159dM5t2wWDUjhXQgl9kLN4QfHy4vDkBr/abopGZr3SMC9Y1j9RhJJD/
+eMRU7b+MKoAcpMko0zAbPcxAzjhqtsdp3VCWblKaGOwBwbc5jK38Lrh8MhR301aW
+pRN+kun+w/FAOt9bzvwRnA4/ucZwIYUwYohW8KKzYwH2bOP23ympuL+a2G/q4s/j
+iWFWtJvSiQIcBBABAgAGBQJVB+GXAAoJEF5QcVvy/+GnalsP/1hpf68ZnxklO1dv
+CZsxDplcL5qdYvKA/GTp3cNMV91tI7BgcfRFJqrKaEnziVXKmhQ/u1nFEEvmka8E
+TmLuJlVMCP3L6ZX3kQp4z9tH+TgRhCLqcCBpJEoWc38GSKZ9lEUGJXSjH/dPEGMh
+kVQbs/7ROBj/wLyMinPYa3Uy4LonrAj4ORQPuYVOPNBLP8dMhBy2eBxOriUW9mI/
+ylwEO3+SDy78BOUmKH0s9+LHJmjtgi6duPvuMuiaQqRKGw0VAQM84haxCi2MhicV
+vdgrW+AhASkJlkT0SUATD/rJ9chXKo/ODxmI/VQ4QNAnDv8BWF9vW6JH9RLbI97z
+HF76xVpgOCIzpUIbWVu6f+Xv1tASja2Npw6KH9RtrlQ87O52x5btcyCHAh/7VjhT
+WvscQRtFF3ekaqaMEWMZx2fDlfOlnsrLPAaOJ7nu5/THM/CmO0giv4eOIh9GYG4V
+LH3Ym+lWJ65yIZPrjoXZ3i+zEow2xjz29Kg7x2pcQVY6NNptmOTllaPPe0JumoMC
+AByPxVWfhdcc8zZGih3WYgQ1inYiwgJLYmz1FFF/GljkaTkTvMVeQe8kt+K3C7dU
+rAHaJY/WbwtPFAdkWMs3ZM8XT3K3yp+sJTK5j3Ycm2hGonZfg3YM7SBOu2ILKV59
+0m+RJ/w0zizPk7ARbfxX/wOueLdLiQI5BBMBCgAjFiEEt+XF44bdUAOtVxPgrImY
+jct//fEFAlnd4lsFgweGH4AACgkQrImYjct//fGfrg/9GRfVANnlRc32mf9jsoIg
+X7AWkz8WPighTGWgHFDS68YIPlskOC4QoVvkCaaEhF66TweZ2vqXhNVvIrkduy6b
+ISXjTDX9tv5lcm3I6dmarJOWfPRWdj7fsVFxUjpsN39/RJ6RsGQqqPnny9hjmd5r
+hUY9ilfhM0hxGsoOCIqiyjfE9ckWtLbx/Ozd1oDaomWgMzriPO0R/TtHq+BbmW2v
+MG6wkDuSxQWCjt75441p2kM3J5veBjJird0sKysgWIHJOqAc4rKXRYFLHbm1qQqK
+58hHcKpwtW8nNQ4NNMgfSra1wTZ1Ownk2N/aG8bfZj1bBck25jGb5hn8Tfq7eBnX
+tcNWP7ByEj7UVt1YwYsOmSF4vR3n6keNfCIJ5ZdCGFtQnFCalU8j9XyuBpqO4pub
+BwOa/TmQI2yVePYlII3UDrDXckJNaARQKgXvBLjWMN+mRgD4BW4Zl7vppytGbWY1
+HbhU/1etnqk6bsrG6FPfuS/hxFlHc0XXqms2Gufp4gi7MQ+2Ipj3VS2KrflC/CrV
+Qj1E1zzbOLTuPpScd7gRVQ59cGhTxZ4ZVwrDhVhujlzRgzkVhB+CmXvOqLUNuBB1
+Vj0sypt3h/Y8LIIDlTNGgaXKEM9S/3Bz0/U1VldHEUqqZvTcn0fS5T/kCpl4nxOi
+UNTLP+/KFF+Nu6KR1U7MjauJATMEEgEIAB0WIQRWlObIM8rVEjhhucmZAzeH8Ry7
+VAUCW8c1rgAKCRCZAzeH8Ry7VHpwCACv4po8TfHnCti4qiRjhab6G1LZt0LuSX1X
+VF/4kqQN9iYSdMvkDWOLVTg9oy79QRCGQ7OF5wzU286LTfY9lOSyVDp8oB5t/hBC
+6LgM+F6EjFsTH5xznuYShDct60aJP2yvCUDujq8el+jyHW+iEL3is6+TwA95lYtA
+ZHq5rBkJe6mt9j0cds82j8JSq5VQE0Z7/4iihNeYeghlSeYLCT8EC+Lixbzl9fgf
+kMdQar+wlfqpSgy90Vafuft6m1WwFh03rOOB3FRnTnRCGCcYsRfXboZegkK7Xk02
+475g/RvZZe98Ik2sKUx4dbIQdPPJhJbG8ydOhrmDeR8BwGj5IUQwiF0EExECAB0W
+IQS7gq9g2+DPgdGGuTBQKbfQFEC55AUCW8M8SwAKCRBQKbfQFEC55A2yAKC8SLXp
+lcLRt58sCsJLITuNeW0edwCcD64dEB1q8Ch53IWH+Kn4IIVyvm2JAjMEEwEKAB0W
+IQRhPku5L/nxcvrPC4LuEaJYliHpkQUCW8M6HgAKCRDuEaJYliHpkam/D/49I0tp
+41dBMihn2iw1kLrmMSLXaRe5aX+hMg07V9hpL3fmT83jSi4XpA99KaphONRfqrEX
+jl4o4rj5H44rLWZ9c4JBRtugfYgDhoRRsJa372fEYXWLb/dt7PBR0u8ufRllnKhf
+dNwPFlumtUREiqqnft4hbpNz+C1cpjqYSCx86s0xmAQj5r7VOV/9H8XUvB3/Q7wu
+1sR3w8Em0VbKV4wmic5VM5h6VuWUb84pGbc79g9uiu92aODvR1hTWceh3BDuzmQ9
+JqG+isfTsIb/pTELJzP5ABpgYbpC6pdRrbOj7CSxBS+VVDYU2zls4lcaexPlpg4c
+zNdnFvss+KnL8j8U9OS5IlmGBR8VJRYHrni9/ZpPgwu5SyX9M56E+cQ25wokN8fM
+H6dGndZ/o4IVprfAxXCwglnx5d5uaPTHmsbSD6sR2WcZg3s0I/ylFuuXfcacF3wH
+AJaCjaY/CPf3opTbY3dY98/sWtSqpK2Ao1HHWmQkZJhIHmVuOHh4lNGI0h1KYkj/
+r8WbMdwJC6vT0Jbpy6fHbFH+EQimmjcqdiG2g2OCs2MU5f/xQvEoZIaCAfjDjTYY
+lLd3cTH53/aMffN0kBDGvKqMTimEa0Mf16zfH6em6vAVGEidsQJ94jcz9EfM/Tge
+fymh7rqq6ISJE6sq0GB/nLtbgsbr3UP88Ggvc4kCMwQTAQgAHRYhBJlv1LkSXQzr
+4yYeHBXgoyUMySoFBQJbwzcMAAoJEBXgoyUMySoF26AQAK9g7a8k91pYS4yYnWdK
+kSLLl/b3vBnukRr45dqU2qxoxFeHGcGIhOaE5xnSMWx5ra1zqGPbYoOost/mq7MJ
+KaOwjXJUoj+KXdeMs5eKJvR4KHSfQbBJ7BoUjjBNdEBj41offE5GhObBWAn6ASHT
+KgTBAX6Lt+N/D0BlBpll34THnAyvXblrmDPUCcL0pvw5Yt00uYV/HYS86OF2zj5W
++JdgzJFYoNKuwDYwMb39KM0C3/mnb5S+cFSvLCs+qVKCVzdt7tbGO8zQu4dgA/XH
+iNfourxxNLgSZLXgwtOOLq/RYkbCwRIGoCYv3HOGGfWT04cg7O4F0wUYf+82MmrT
+lqd3Z7JeM3jBEU/JCJbqN2pqNacBNxeNhM+dKSj8DKFjHm0ItWZOacEaSNczeIae
+hpjq5P+D7wXf+JMDkrnYwoU5bgpwx1xrJQvVVgiByQvfTFGCaaNFIGG6n2UX9c2B
+EzyxfHM7dMsWYWiJYRygvdEDM65lQ2dhWW7oO2B4y5FvuqNWHm2qBYgy1XKGDeBx
+QpjCzDPMvNHd1YDg91bIraB9f1+1EiHbMJofJP0Uds0zr1Zd4KBZAO+CC/UNvTri
+Vk8JKjevrPjMZPvzCXLpUYPCw9MxD6ZfkbUDPxOL+sX/856BjMlB+J0JdVgBkP9k
+JOqARqmvX4v87D10HGpvn2xviQEzBBIBCAAdFiEEqEV7iIacLNgYR70IuAd10BL0
+SI8FAlvDM7kACgkQuAd10BL0SI8KkQf/bz/RW0uLGuckqpthhXM4EGv2uDp9cbZE
+eoBUf+xK/5vRyBZVeRIM6jhGv+zjhVr6/y+OmJoDRfJnzt8Az+HNvoIW6Tu34BPj
+nmg7VWDb3OosD2nyi4Pkn8mmJWQrnxQiKYWVo6l3zgAT0s4dmrUDGSTUS932Q2yO
+p6CMrH2cmNOpulG97qtOJWf2NDiaunIsc8hD4V5QNS+C03Lg3oDrSP3z1CehjpvF
+nwyQnSmNJU1k1hILzSpBMGrilGZVdzprIIowOga4v61WNoWFpJkdYm5edfIy96kc
+HsXrKMlR+pjDdVexv3EzyK5FEMOQd6ygnV4+e22bvNyvrQ/AzneFS4kCMwQSAQgA
+HRYhBHM/l2O7gYGbsjfvcrPqGO5ebd4jBQJbwzJpAAoJELPqGO5ebd4j3l0P/1BS
+vkPLLbzhh/6TRvTTHZ4GSYbLXov37BCbUAHRhASewcv2ryENKIf3EDVk5BB5ZEug
+czU/shy2TqTA5Nb3JNRrEqgBGSn27OLmYFXHMzXRxREUsRAgG25xHQVEEj+/tk12
+angSjPek1e7KytNjdKh56cuhTDa9QUuADHL0wEZpcgHBOijFnB6YqFqXMiskjTjI
+o/gKoI+LQ4J2buAzq42bQiBsidUPMeB9e2vwq6ssBSo6axyDUQ4AAFK3X4uo2HFr
+J6Qt5uUjwSt/L01nFnNjQEDx5m9NlpXi2c5iS3Lm6+Kx2U5Pcvs/C8AvVklVJHUb
+BPyVfnIuggx02PX1iTG6Vo/l5I65swVVj1dojuSoiVMLSiFmR8zgLkPP/elubWt1
+z6exSSAeb4xPREKm1zqROcINVlYcPrB80kBOSww7ixS6O9gxRxhDuHbgapzjznJF
+Vnren+zNPqJ5yTMBwqUgZAEG9TXOFNHYQO1FBHPEbBI/ICO2kIcCtbXG+PQbAcB+
+lirRLchlZau0YQxC8LlT8Clc30W8ZjJ78jBY5tbN4ArQnZZqwEA5mOZ9lcHIbwbV
+ny5mRAoqnu463qpJThtDBvEm8Ej0mXayd2PZ8Fi0B4kg9M9aM7w4vAX4z83fOMcw
+EyPKYRkAkwoQ/yvxDfYqowhcOyMzAk4JPkdXeBndiQIzBBMBCAAdFiEEktPR7/7Q
+ieSQeRfBok0RNW6j3zkFAlvDLyMACgkQok0RNW6j3zmgaA/+O8tj/8a536he8yzJ
+OJmxIm2MAJeAJonCJYLUXawui6Dx43TgcbuqEE2DQ3ylhtAMdLwmkpqCZ+HV+sya
+8FkEMscNvJ2rnlCieMs5eEfvEkuAh2gqI/ZmNJyZUbxPo69/8BYxeR7Iqg1DuWJ/
+E7qHMd/oHocSyw+9YS1Qf82lHDges0fLwoSibAVhmw4GsEKIjZSvRi8Q2Ng62GN8
+nVAhgRHe4tJCaOE7B3naZJ2TcsDWaVoGEwD2DYwhi3BnjU7oFcg0leiA7+6Uh7x9
+uL/0wb2QmH50NvVo/57W+sqRFbqDoTl0deSZIpaA5KjNpAy/rApvIYi5mP6LIqCT
+FJiz94rcWgtm9Rx6m4MjGbMGMkVm05tp52V4VQ7Meck4L2w0X77P/TAZ1AIlYKe3
+SojBpIMn8dxiN4E6GGsaA+eQn119ZX9jghz8gDA7PXb1iwkfawrnr8ajSHwcGVR5
+AYYaMuGTLFWYMHcN6ZnzNo8LCDKZuYk4BkvT+3wyvVGVemyc9wRo4c39XrtaXmms
+3+riWsE6LhK6rZLWnrAxX0NaT9XBKXqJLKJM+AbDOhLT1YbZSzgwxJl7zF6PT/hw
+er4/BxCFjX7sHgt49VeutlUmimM0OO58CMfEs4Q3UiLdUOpdATB/OUBtLGRQxj6m
+dVd+I5/ztbe4PzNXNIVS78Ty6xGJAhwEEAECAAYFAlvDZ+QACgkQV2ZtHcFUWHmt
+Vg/+LZSRMv57LtZm7dVBiaxh3URIDR37PkDlRdfPQb5cJr8j1JcBIjM0VFMsY6CZ
+X/zgZIc8mEXulwx9yXVc4E/OvM6qZ7drWq6DXSLpLEO7KJNG/PX72S8Nl+A12dxX
+NoiaSedVLZiA6kv8IoTXUC4zvmqaL9fIguilWCJCeK0AlTZlp0eBGw5TuuI4ffdm
+lfVO1MLJ5mgWZefRMMh017dKkZCjzEeVL1fMEPuhxFWiztOeVd+50La0pTeWmmWk
+o0op33e8NyjPOOYXfTML70Zr8vIcRe3g2nCq+3UU0YAO4raRyH1f/uqV6lpg8Uqm
+7zmcotKiRJuf3ReH1NL1u4EFubdQE6oWVXcieB4Wu6K+wPgM7uBv6sr57xN55BZG
+iqdtGY4w4VMK0ElylVyY7DWUdvgPB4JmHAxLQz8K6tcMOW+SFpbM28R/ZCJiLkGL
+tDAXr1E4jZG0tuAklapWjFbp2Q583mMHwcvistrbbvjbMZWeUP9uz5oFBlvDyF0F
+k+4F+3rR23moW+SZpBNxAcSfu8uzHs0o0C90uKfdaZL5fKMjXZNoIdeRXNdCY8s4
+c2BbUrqRIn/qmtlINXrHpRXypBrC2eDkjRGuapOOJ6d36C8XvOoR74T/w9V8m7Sp
+nUzZuFJTJitfEgnAyA7S+ecP7UYyVUj/LT6/V0BcRFU/AjSJAhwEEAEIAAYFAlvF
+sYUACgkQavVNde4DVaBmmw/9FB4m/SEqakuytfTydAZu8WfVmZoQjIiP1xt8wQRg
+Ajf2zajeuO1+0ThR6EtzyQKJLNwfWMfCoLnMRNzdQ6+gplIhfvqQGsGjfOXYqITN
+mqq5Emt6/d+WVWF/uqexS6HTwq2Fh1FxJsKQoh3+W2/Vn+GHu1hxJau+TTh7OOUh
+gAyYk4e6z8XuFg8S0AnKsGbKtBCkMOfBjttapXZUUvGDyq+ZwDlY4EQPXjXaTMgW
+/al2FGIPAyTt8JgeouzpA1iDrtTc1gWs+MLpB0y0XhpXtj5ahK6CPROY3+52ptWi
+YN3iLjGtqnDBcqa7A8wKiNiYZ5Ke1bwm40/nlEIS7gRLeEDyTV+a0NshxxTKivKR
+WB7E63T123aO4tr8XfVcrTqtj5QVMNrI9e+JpUhI6/v5ZYpYQCafJagppbcSwQJH
+71v36M8UBUqQEuB4e5bvmx0SPz8cuzBX17HMIgC9nLDhuRKOp3q8vT2g/k/Yh1L2
+NBlyGveIbmKa5GudcbjWmK7+GeXn4lrl4lBznMqQjIm5sPj9qqd4vvLXONUL6FhQ
+USAL7+Ld78SxRWv2JLK/j7UkwN7f+srBRUgbEus3lzj6OExIHIBmUCCJUS0kCOSJ
+Ecp+qlanOFN890KzaAg4AIn55Wrif5y962T7NodvHn/63lnBoztJE+Fha2yy36n2
+FPi5Ag0ETl8mkQEQAM4WIsHIK/1+/39QZbh376iVXfc4NVdE3ID/Lozz9JDanjkp
+ScpikwugDwguVx+8JdO2tTyo6JTzpiZ+CoaxmjudJpUTT7fD5ONcAd1stpHKUQFw
+JczU6LSXpTQCpmhV5s13pwumxjymKRlotxLdr9+zxFl0e4VTFb5oj4Ik2wu6sehc
+It73AxM38C8smFRrRegPQL2Xnq9BE+WUF2yyY3TOVAK5TP2MbwQTkrTOiTYJZdNH
+NlvjIpZaxHKOLqytNXSmXn1k20nitmyssIzv0aEC1UdktWIL/gD1Z+SjrJQB7/y5
+6Dx7o6gr6J2MZZeo7a211TLdblejD6bMjGaH4CTnjzmkMtDC/2b+FUc3x3/GlQF4
+hWB4iaT4aCjiKOVNQgaQyAeRTsv1BUoqf8LDytW1/MdalLYElKS77t69HEQ9HSyt
+7QHU3sjAG6qgso8yWn8ebYCefm1lyZSP3BbvZ/UpoKuB+aGlXjteaXQhIRLRA1Tg
+ijiGA3Yw1dTcz2Cb42w4UNZw4r55yN60QDRBH4l1yrRPltdyAaX3qEg44U/Z7LU2
+YTDX+4JL1O4ZE+snDVsTPMpuZLvRFkxCLG1FTXZacZRXfzlFzw6YWhpnHUYORO3f
+Ghb+PKMKYEloTyLywjkVLHFbvaPts96dCxWyDrcMOqhgiLOLJo7qC+/Sq8k9ABEB
+AAGJAh8EGAECAAkFAk5fJpECGwwACgkQ3PUT+n7tGfNv1A//dYWV+vL1jiL+X4vR
+SCrDM8bBmt/cZfN5O0i3HYPMdSD9lVr9O+WYKJogxEXX1ofgEO74rwZxGw0crrMN
+8VM9SgMZ3jioGI15NF3INnA1r53GNGhJ4JVnz0KV2NKtshk7CtSxrjoR8qplwbMM
+ICVgTIERVP1enuOb3FEtbhI4rcy+2UTw3hwURBhIfUotVFO6SKu3ZLscItbiNxpT
+qTpL6AIp9UOrZjcqfCuFs8P+57uusAHcp6GYhhIhNIdXf64RQs7gtdLVW71z0diS
+xu3KFWlrXOx0rrm7RTAQn1VOLl4W5oBPvcF2ZVQvd84I74TMtpP0MRDFgLuK0HHF
+VyDff0vx76rubQgom6z8ajiIa6MfEmd7z9xhQT5PU0FApYY6H/kW7ao+f2h2IIjz
+/+QjHuYn0CqqcjkkLC76RAgQjHYO9NIpL9Gi9O+I2AFz8YjOK3hOpxMrF/LjPJtx
+BXGFEwP4ud+hzDMjwaa7PklcmDPUBuSDIgbNvsVNA6gn7AkbQn6NH+DImdrpzgpS
+r1FHMbjIWqpXWbAZtmOurxn9f5ZXPKAgMvlV4TS4NZqnWT5HZCKs2b5Ped2L+zAd
+LP5NmyzJrSIyVTJ7JMLLfCLaWu/qsHRGt1w86gewg7uMPdA1IEvjjXaIWNhYKUq6
+ik+DNrq0Y3fUuRg35QHaPTcab+eZAQ0EVjikBwEIAIhTkdGQEbdVwF8lqp63Eigp
+0tHFbdeZ4LCu4sW3oM3erxtO2w25Awkdrw5jRopYmheM5BJsGgpIZUAUpOakJR8f
+i+ESu3wNarKCVF+KjYvdxN7jwZmOI5t1ctnGewg0DHZZtymgJEpON1ZfQwfYmD/J
+/k9Lqdv6CVyVGwNCZUZCO33a/bec12wKnwj2uM/X5tDLmIcHUiJC4UnoMFAmGBZD
+OSxPZrNnzdoAO9zj/4WDtUVhLNkeSn3w1/LNSSJTNiLQjk7Lgq/Khd5L8Jf1a1AY
+zW+NkBdeIP44MnQ68HYSwJRPq3iL2lZaH/4uc21FYhWfw8l5BsIA7bAmUzFfbwEA
+EQEAAbQoUmVtaSBHYWNvZ25lIDxyZW1pLmdhY29nbmVAcG93ZXJkbnMuY29tPokB
+PQQTAQoAJwUCVrBxMgIbAwUJEswDAAULCQgHBAUVCgkICwUWAgMBAAIeAQIXgAAK
+CRCiCO1PivWERnTOB/4jLvex0M+TE5iL/FUki8EHyj6648sOCHnUHHnS+slME2b7
+1iAvLJxClDJjLD43Jj7FL0hu2LOnw+5PQZrhLyB1WEa1tC0tLvIkPuzCVJPI4FH7
++AegmBrGYN6554Hy0C/YRF8mOGngL58hrumJTgjB7vC+CvDp0714WQG/SgcKqk4j
+kIz/Iep2vj3dCifdh+kJkaK/nnzIT1euiOzp8xLByiVbCOdlbvYoVetqvJcqIhOH
+Cglv045lZcAp9kP9pm/kEzHM34PhkH6SrR/uodshOH4p3Ux0wGgwUbouDvHUtjlK
++GB8cYXdRny0tvdGBYUO7CsFNzPoRC8CvD+VY8DltC1HYWNvZ25lLCBSZW1pIDxy
+ZW1pLmdhY29nbmVAb3Blbi14Y2hhbmdlLmNvbT6JATEEEwECABsFAlY4pAcCGwME
+CwkIBwYVCAIJCgsFCRLMAwAACgkQogjtT4r1hEbMMAf/WS0+yuheoWrxCZ4qYQo+
+AjlaenFTPQwrEDNioj6gjST/eAaQW1/+trFPzwNrBSenDE6bwPcPdL51mXg+30fN
+zHLWrBPDsMqBlPTIvpBbQ/bVqjV3JnU8I8dHfdKmInJRrCJM21gDTprQdqfBfSHJ
+HgM5TG2+fUxpdLIAhBRknXt4+TuE272DJf6gHxnDs1oqQ6kAxC0ANJyEufFXJGeE
+RN2OsFtSygOcUiHeXwWyM77RGf73gkS9+bCoftiuM4gbKSibk4BbUVBZJCs28fDn
+AsmIstZldUGZgIuy0vUfH153DTJflN+CIGEvRUwk+nrDIwYkV0pr9eZ0lz/OFhwz
+J7kBDQRWOKQHAQgAjr1xEZh1yglszi94+HLNFcgRPgRNktg2vxOGf64dAreJvL5i
+DrS2lrFMknh5BNuj7nJZ2r40OOS91oH1qkVk+v9Cyo/3xwCpCOPQCkhzHpuQWXoM
+GMw/3/0tG6zTxnYdC999faCH0lLA8oDwHCHlZSHgsH9+qSNyjaJXvS+HVoGYzyua
+nU6OTM7EM5c7RCPhNjT9JzHLISnwaxgDpwi7Ez6yudcrg6DqS/uUwkyNtWyesx1D
+F9y2VJUNwa4NKIJkSH+niEoxK9NBfBAmAKc4o5+KPs6BvpvpiYY9gTKaaLypPHNc
+veQTDFv/26XHyzrCZmwuGlcYBjboH/BWzKbhuQARAQABiQExBBgBAgAbBQJWOKQH
+AhsMBAsJCAcGFQgCCQoLBQkSzAMAAAoJEKII7U+K9YRGXJQH/3PtQG0AkrXOpkOM
+XFLTKdCEViNNHN94VIaceVn60zbmXzxhYeKz7K345/EqATi3P3/yDHcht7j3uYPh
+vaMjy3smN6vEwX7Ue40PbFDWmm8mHpLdlOfPXF0SRUD8KTSD6+W2VJfEcDI6DDfU
+mCx9yYZ1U5u+O8Aj+1l2gdQbgAioPnQgqzf43qgnRcsfNmsVsXg7EbHspRpJOR1X
+yXl/9KrDP7p6kjwWTQ1NoRjCw0qaX93odLeKIpd2riShlB7GteUTps0IfuiL94CA
+58PV2YvZapN1KmwDohHU8rndN7zte7jbCyv1Vv9tP6Ns0TvycBAqlOZYdgabrT+P
+ccb4jCeZAQ0EWOzWBQEIALuqBv3556Glk00Hu866hDtDEOtLeyVXOJA8ySsKYIwa
+cAHzaTa2whLLzfx3XdwBWKtly1o3hlduwfwL1l3aMh4zamHFgl58a+P6fGTlPEEe
+hi+1silIT3QPbqxzOowiwe93UVkJiTqhapGbFDmnguiLZYTWhgAuGYRrEpvtNmnJ
+U+6TrDTO8DH834uoYTESqs+fuOVw6Ab84th+Qucq1LB3yKsHhyq7m0en81a22xVX
+Il5+CKZts7pH8bRTTSMn6eo97k1KJ2E15hoRnnrshlduxhzbRjrx1wfqOZ0mVzuN
+HSJYlGvUKnbtNTatOZXRfUAlqMqcsYkXz8t3QLz/cuUAEQEAAbQtV2lua2Vscywg
+RXJpayA8ZXJpay53aW5rZWxzQG9wZW4teGNoYW5nZS5jb20+iQExBBMBAgAbBQJY
+7NYFAhsDBAsJCAcGFQoJCAsCBQkSzAMAAAoJEG/8M0ObDQTfcIcH/32n9IqQwvOq
+h+rNjl3vHn3on4MdUebEIIg3QkhGtBb912Rdbvqp2lJxLDtgI1EolYbmab1HRRBX
+h0x4ErGt2yJSruyQrTPp6RKX/dP7tAghTPHtiZ5JK/KjhvuBgjbZ4xiy3ge/ZVJo
+EOuxzPfZlK+MOz75RqT7eH4mBvfB4oBr67OTfAzbYQOGRXNSsRzhHr9xCGXk1zlN
+HheyXrwpPm9wD2RahRPRXscagv+HKI7W8taDLY500C3iX7ux3VfzJcy0ub4m0ru9
+6VFJRrdwi8O7WT7oJEZvxV/QtG7sXfo7dt+ryRAKxu3er24Hmk1S9iVhowEGnq/J
+RMOIg1ioRj25AQ0EWOzWBQEIAJ+8XbWUGbMEpYf0gEfnxznD6WxBf3j4E2GWiqfG
+YHd5rQPMErrk0DXmxCwSWjJf0+96KNvJ4wrQ/G5gAUj7R7OChXWFt/KZeaEBCJQd
+0de41pjBQ7+kVb8cRTBt3gCLWC0xEkbYn7jk9T/Rqm7fOkkmt8x2i5+jk83M+lte
+R1aFbwIIA9dMuG5lm5jz+a1Hu6fK65A2V8lsBacp3+D3NNXIwl19UEh7u1H6Pg1R
+67BuePT2iKo/TYyLrfD/G4pLr8HoU19wXEkJq4S/yzoYr9oABZ3spTSafNoVYaxq
+merpBHSC5EY/D1t2QfR0C6pUVOVjxaGjYNoaajd0kA4BXqcAEQEAAYkBMQQYAQIA
+GwUCWOzWBQIbDAQLCQgHBhUKCQgLAgUJEswDAAAKCRBv/DNDmw0E3+DaCACIyXcU
+OmgyGqFXmRXC8MVzc5NcKEE6amh13Cwb75xjmXI9p2nvcklCiIAF4MrJJqR22Hko
+k0SqlcrUb5vjJw2/CZ4PNdbWM1PaB7AyKmiqvM4lpFfH2hR1U1miQZdM8V1CXmzO
+H6DGwuZNU3jUNyYvEbidIxBcJT282Zp/jC9hZFGLL7VL1he0hUvF3WyDmQo9RSe0
+xNrLCTNN+HE2VaTEk7L0dAcVS/NbOv0BJkdB0LqlHGOAE5ahv/iUxO/6FCpxjtb6
+qfCQwUQXjRrMSTSwdSTTlKA015yy44aEXfRnMH9zOPKYbZeJMFOCsfc8fU3LLuac
+V5Kv6l4aJyRYJaN/mQENBFwsoP8BCACU+waQJk8NT0hkuTQwVEJjHiLHsHIPlj1w
+487uzBVnZ3jaacd1iPz6v5OTDVcT6qaQ2f6NQosNpuLKzJr4lZTxRC2dIho+R7Oj
+WKQ4vZ/XYbjRH/52+nT39VHEF6yTYj/rVDZvAsuu8+sTJ4hkiGkqQv43OfDtbMCR
+3LdkwPNfgZ5KCmdFrmcOg3kovaUbffBhe8mFwZDVws7XnZJntvrhYi0zRH3MYmLn
+d1WBBiVWcvqZDQsP8FwssFtmcjPgANpHBC/Q78eaji3XhcL4JGcpzok7nV6nbjkY
+q/kgkxlYviyRdIW/Xm8tZWyFDjOktKFBQv4+S02j1D5Hqb8YUc8lABEBAAG0L01v
+ZXJiZWVrLCBPdHRvIDxvdHRvLm1vZXJiZWVrQG9wZW4teGNoYW5nZS5jb20+iQE6
+BBMBAgAkBQJcLKD/AhsDBAsJCAcGFQoJCAsCBRYCAwEAAp4BBQkSzAMAAAoJEOrK
+uQsZY+wr0xcIAIEUvf0YeJ0LRN6uNo0IXuEqq/G+wvjq2drc/AQCxHB4yPyF65ad
+0OQnphzCRTRSPmcVmRqNkqxc+BvORtwcX0we/KcS/4NshJ1MFel3X79jXowOPSuJ
+zp+IwGWs3hkvTuI9U6dT75i+8jfG9XFDjO8q1l9Nr2WEmxXwtJ9vCIbLShMV1tnJ
+tsW75obyhLVfXGIQBqYSDEWXwLEccILI+mizvwWPk+wI2ReefXUDi1QIn4Ckbv9T
+wKVlI0wrHoNCPhz8Tp25RzUktpGT+GyGSAgDMRBBP35BRGF/jUF5KrmmYhC9XH5z
+a/XzBL2lCj4xg0yr0nV1wGpPdAC9SDw8bFW0Kk90dG8gTW9lcmJlZWsgPG90dG8u
+bW9lcmJlZWtAcG93ZXJkbnMuY29tPokBOgQTAQIAJAUCY2TfXQIbAwKeAQUWAgMB
+AAYVCgkICwIECwkIBwUWAgMBAAAKCRDqyrkLGWPsK3dpB/9DXbiyiFn78wyJaI9O
+m0bhcjaRG2RN7+ECGEHbTpbbMlOlOteWbl3JWEzcuvPiUTlI8Q6vf/+UAGFyh9cM
+cayX7sPE+iH1OUKu+tgSDi1Qb1USAORg7wv038KGQhZcRpP6XeTBcBFjQ+sFvp2Y
+upUhSSj6DcxYCdtDVZD9MpENOU/jtu0n1cxeYkYwgY+40+I/hju6dIMK9PCypQmN
+faKJnd908EoROU62OWQ0kmaekfsMs8LqSijHwyycsd3wzAm/wRNZcG9KMjlLKtAA
+pWbUNRdgeD80ZY/OQxCy4ATyHsmIbyujMHSQQscfenQvAITq3cysaLI3F1HdBOdX
+chVHuQENBFwsoP8BCAC+gR20VVklctCTUykArlBpfxd6LHhHJW1L1oImBmkujUNZ
+j0kiSWDYPkdDn9fpztiZLUqM0+YKvaQHwBqYEDyTmVv7zk6PG1iYeiucVWZ4uSXJ
+f/ywDHwwVBcp6NRG+rfvoy+tBxoFiBFgykm/Fnr1l++ppeGPvsZBt4HheTRzYMR4
+XTaQ7JNrhwpykiYM3yS6ithOxylYxKRU4NpHuRTlv/OjdK6VLILi0UDiZ2SbW9At
+cQXX+p1C5LOh4nERsbnmsdlzZeb4iCu8Inz1MsPGorX806K5ZWZBeWGQMBYv5Y7I
+pu5hwz1T3UlCmzxtfbxVZQW0fADVH5fOvTTJqYOXABEBAAGJAToEGAECACQFAlws
+oP8CGwwECwkIBwYVCgkICwIFFgIDAQACngEFCRLMAwAACgkQ6sq5Cxlj7Cs3mAgA
+kj52FeeO+wOf3M4YwZNUwz4wYsIYET+V/j1vzZHTAHJNc6w+TxrF96bmAIEzKZ9O
+0C3w3YQy3NPfioXafID6dkkoYKUth+XdBvklh6G6X0kqDINoowtrZrY4u2QjGFi8
+lEEe0oqoRnIEftAaq2Aaosj5Bsp6/IuzxHbzDYI/vYSWG/iygejsfn/0MXPmOZkd
+7oOf1zuPhSp0wItuFBUSzN3UrTM6+D72CbVn4/JfayvMldpyb9hLxJLUuUYyomCm
+96HeMJn+KPTvsU/c1jCYyBCvFbr9QQBR6jenGhwVvVI0nUCnnCy70dC//miyE4vo
+y6CI3b5MGO+W7v8asfDCgw==
+=zkWd
 -----END PGP PUBLIC KEY BLOCK-----
diff --git a/pdns/dnsdistdist/docs/advanced/asynchronous-processing.rst b/pdns/dnsdistdist/docs/advanced/asynchronous-processing.rst
new file mode 100644 (file)
index 0000000..404e017
--- /dev/null
@@ -0,0 +1,52 @@
+Asynchronous processing
+=======================
+
+Since 1.8.0, dnsdist has the ability to process queries and responses in an asynchronous way, suspending them to continue processing other queries and responses, while we are waiting for an external event to occur.
+
+This is done by calling the :meth:`DNSQuestion:suspend` method on a query or a response to pause it, then later the :func:`getAsynchronousObject` to retrieve it before resuming via :meth:`AsynchronousObject:resume`.
+
+A timeout must be supplied when pausing a query or a response, to prevent paused objects from piling up, consuming memory. When the timeout expires, the suspended object is automatically retrieved and resumes its processing where it was left.
+
+.. figure:: ../imgs/AsyncQuery.png
+   :align: center
+   :alt: Asynchronous processing of queries and responses
+
+The following code shows a very simple example that forwards queries and responses to an external component over a unix network socket, and resumes them when it gets an answer from the external component.
+
+.. code-block:: lua
+
+    local asyncID = 0
+    local asyncResponderEndpoint = newNetworkEndpoint('/path/to/unix/network/socket/remote/endpoint')
+    local listener = newNetworkListener()
+    listener:addUnixListeningEndpoint('/path/to/unix/network/socket/local/endpoint', 0, gotAsyncResponse)
+    listener:start()
+
+    function gotAsyncResponse(endpointID, message, from)
+      local queryID = tonumber(message)
+      local asyncObject = getAsynchronousObject(asyncID, queryID)
+      local dq = asyncObject:getDQ()
+      dq:setTag(filteringTagName, filteringTagValue)
+      asyncObject:resume()
+    end
+
+    function passQueryToAsyncFilter(dq)
+      local timeout = 500 -- 500 ms
+      local buffer = dq:getContent()
+      local id = dq.dh:getID()
+      dq:suspend(asyncID, id, timeout)
+      asyncResponderEndpoint:send(buffer)
+      return DNSAction.Allow
+    end
+
+  function passResponseToAsyncFilter(dr)
+      local timeout = 500 -- 500 ms
+      local buffer = dr:getContent()
+      local id = dr.dh:getID()
+      dr:suspend(asyncID, id, timeout)
+      asyncResponderEndpoint:send(buffer)
+      return DNSResponseAction.Allow
+    end
+
+    addAction(AllRule(), LuaAction(passQueryToAsyncFilter))
+    addCacheHitResponseAction(AllRule(), LuaResponseAction(passResponseToAsyncFilter))
+    addResponseAction(AllRule(), LuaResponseAction(passResponseToAsyncFilter))
index b74fff93f40b5d6ffc1c5268073e8ccf432b53b3..822e7fc587f6728b17c16cbc3825a02bec4d2c55 100644 (file)
@@ -1,6 +1,9 @@
 AXFR, IXFR and NOTIFY
 =====================
 
+In front of primaries
+---------------------
+
 When :program:`dnsdist` is deployed in front of a primary authoritative server, it might receive
 AXFR or IXFR queries destined to this primary. There are two issues that can arise in this kind of setup:
 
@@ -24,6 +27,28 @@ and moving the source address check to :program:`dnsdist`'s side::
   Before 1.4.0, the QTypes were in the ``dnsdist`` namespace. Use ``dnsdist.AXFR`` and ``dnsdist.IXFR`` in these versions.
   Before 1.4.0, the RCodes were in the ``dnsdist`` namespace. Use ``dnsdist.REFUSED`` in these versions.
 
+A different way would be to configure dnsdist to pass the source IP of the client to the backend. The different options
+to do that are described in :doc:`Passing the source address to the backend <passing-source-address>`.
+
+.. warning::
+
+  Be wary of dnsdist caching the responses to AXFR and IXFR queries and sending these to the wrong clients.
+  This is mitigated by default when the source IP of the client is passed using EDNS Client Subnet, but
+  not when the proxy protocol is used, so disabling caching for these kinds of queries is advised:
+
+  .. code-block:: lua
+
+    -- this rule will not stop the processing, but disable caching for AXFR and IXFR responses
+    addAction(OrRule({QTypeRule(DNSQType.AXFR), QTypeRule(DNSQType.IXFR)}), SetSkipCacheAction())
+    -- this rule will route SOA, AXFR and IXFR queries to a specific pool of servers
+    addAction(OrRule({QTypeRule(DNSQType.SOA), QTypeRule(DNSQType.AXFR), QTypeRule(DNSQType.IXFR)}), PoolAction("primary"))
+
+.. versionchanged:: 1.8.0
+  Since 1.8.0, dnsdist will no longer cache responses to AXFR and IXFR queries.
+
+In front of secondaries
+-----------------------
+
 When :program:`dnsdist` is deployed in front of secondaries, however, an issue might arise with NOTIFY
 queries, because the secondary will receive a notification coming from the :program:`dnsdist` address,
 and not the primary's one. One way to fix this issue is to allow NOTIFY from the :program:`dnsdist`
@@ -34,3 +59,14 @@ check to :program:`dnsdist`'s side::
 
 .. versionchanged:: 1.4.0
   Before 1.4.0, the RCodes were in the ``dnsdist`` namespace. Use ``dnsdist.REFUSED`` in these versions.
+
+.. warning::
+
+  Be wary of dnsdist caching the responses to NOTIFY queries and sending these to the wrong clients.
+  This is mitigated by default when the source IP of the client is passed using EDNS Client Subnet, but
+  not when the proxy protocol is used, so disabling caching for these kinds of queries is advised:
+
+  .. code-block:: lua
+
+    -- this rule will disable the caching of responses for NOTIFY queries
+    addAction(OpcodeRule(DNSOpcode.Notify), SetSkipCacheAction())
index 488480406ebef17f97f690862c22d100d03aeb4e..2905b95e96c9259f696ee44d87ea0a052a8178d6 100644 (file)
@@ -3,6 +3,18 @@ eBPF Socket Filtering
 
 :program:`dnsdist` can use `eBPF <http://www.brendangregg.com/ebpf.html>`_ socket filtering on recent Linux kernels (4.1+) built with eBPF support (``CONFIG_BPF``, ``CONFIG_BPF_SYSCALL``, ideally ``CONFIG_BPF_JIT``). It requires dnsdist to have the ``CAP_SYS_ADMIN`` capabilities at startup, or the more restrictive ``CAP_BPF`` one since Linux 5.8.
 
+.. note::
+   To retain the required capability, ``CAP_SYS_ADMIN`` or ``CAP_BPF`` depending on the Linux kernel version, it is necessary to call :func:`addCapabilitiesToRetain` during startup, as :program:`dnsdist` drops capabilities after startup.
+
+.. note::
+   eBPF can be used by unprivileged users lacking the ``CAP_SYS_ADMIN`` (or ``CAP_BPF``) capability on some kernels, depending on the value of the ``kernel.unprivileged_bpf_disabled`` sysctl. Since 5.15 that kernel build setting ``BPF_UNPRIV_DEFAULT_OFF`` is enabled by default, which prevents unprivileged users from using eBPF.
+
+.. note::
+   ``AppArmor`` users might need to update their policy to allow dnsdist to keep the ``CAP_SYS_ADMIN`` (or ``CAP_BPF``) capability. Adding a ``capability bpf,`` (for ``CAP_BPF``) line to the policy file is usually enough.
+
+.. note::
+   In addition to keeping the correct capability, large maps might require an increase of ``RLIMIT_MEMLOCK``, as mentioned below.
+
 This feature allows dnsdist to ask the kernel to discard incoming packets in kernel-space instead of them being copied to userspace just to be dropped, thus being a lot of faster. The current implementation supports dropping UDP and TCP queries based on the source IP and UDP datagrams on exact DNS names. We have not been able to implement suffix matching yet, due to a limit on the maximum number of EBPF instructions.
 
 The following figure show the CPU usage of dropping around 20k qps of traffic, first in userspace (34 to 36) then in kernel space with eBPF (37 to 39). The spikes are caused because the drops are triggered by dynamic rules, so the first spike is the abuse traffic before a rule is automatically inserted, and the second spike is because the rule expires automatically after 60s before being inserted again.
@@ -13,7 +25,7 @@ The following figure show the CPU usage of dropping around 20k qps of traffic, f
 
 The BPF filter can be used to block incoming queries manually::
 
-  > bpf = newBPFFilter(1024, 1024, 1024)
+  > bpf = newBPFFilter({ipv4MaxItems=1024, ipv6MaxItems=1024, qnamesMaxItems=1024})
   > bpf:attachToAllBinds()
   > bpf:block(newCA("2001:DB8::42"))
   > bpf:blockQName(newDNSName("evildomain.com"), 255)
@@ -30,14 +42,16 @@ Contrary to source address filtering, qname filtering only works over UDP. TCP q
 
   addAction(AndRule({TCPRule(true), makeRule("evildomain.com")}), DropAction())
 
-The :meth:`BPFFilter:attachToAllBinds` method attaches the filter to every existing bind at runtime, but it's also possible to define a default BPF filter at configuration time, so it's automatically attached to every bind::
+The :meth:`BPFFilter:attachToAllBinds` method attaches the filter to every existing bind at runtime. It cannot use at configuration time. The :func:`setDefaultBPFFilter()` should be used at configuration time.
 
-  bpf = newBPFFilter(1024, 1024, 1024)
+The :meth:`BPFFilter:attachToAllBinds` automatically attached to every bind::
+
+  bpf = newBPFFilter({ipv4MaxItems=1024, ipv6MaxItems=1024, qnamesMaxItems=1024})
   setDefaultBPFFilter(bpf)
 
 Finally, it's also possible to attach it to specific binds at runtime::
 
-  > bpf = newBPFFilter(1024, 1024, 1024)
+  > bpf = newBPFFilter({ipv4MaxItems=1024, ipv6MaxItems=1024, qnamesMaxItems=1024})
   > showBinds()
   #   Address              Protocol  Queries
   0   [::]:53              UDP       0
@@ -45,9 +59,28 @@ 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
 
-  bpf = newBPFFilter(1024, 1024, 1024)
+  -- 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)
   function maintenance()
@@ -55,16 +88,45 @@ 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.8.2, the metrics for the BPF filter registered via :func:`setDefaultBPFFilter` are exported as well.
 
-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`.
+Requirements
+------------
 
-That feature might require an increase of the memory limit associated to a socket, via the sysctl setting ``net.core.optmem_max``.
+In addition to the capabilities explained above, that feature might require an increase of the memory limit associated to a socket, via the sysctl setting ``net.core.optmem_max``.
 When attaching an eBPF program to a socket, the size of the program is checked against this limit, and the default value might not be enough.
-Large map sizes might also require an increase of ``RLIMIT_MEMLOCK``, which can be done by adding ``LimitMEMLOCK=infinity`` in the systemd unit file.
+
+Large map sizes might also require an increase of ``RLIMIT_MEMLOCK``, which can be done by adding ``LimitMEMLOCK=limit`` in the systemd unit file, where limit is specified using byte as unit. It can also be done manually for testing purposes, in a non-permanent way, by using ``ulimit -l``.
+
+To change the default hard limit on ``RLIMIT_MEMLOCK`` add the following line to ``/etc/security/limits.conf`` for the user, specifying a limit in units of 1k, for example:
+  > $USER   hard    memlock   1024
+
+External program, maps and XDP filtering
+----------------------------------------
+
+Since 1.7.0 dnsdist has the ability to expose its eBPF map to external programs. That feature makes it possible to populate the client IP addresses and qnames maps from dnsdist, usually using the dynamic block mechanism, and to act on the content of these maps from an external program, including a XDP one.
+For example, to instruct dnsdist to create under the ``/sys/fs/bpf`` mount point of type ``bpf`` three maps of maximum 1024 entries each, respectively pinned to ``/sys/fs/bpf/dnsdist/addr-v4``, ``/sys/fs/bpf/dnsdist/addr-v6``, ``/sys/fs/bpf/dnsdist/qnames`` for IPv4 addresses, IPv6 ones, and qnames:
+
+.. code-block:: lua
+
+  bpf = newBPFFilter({maxItems=1024, pinnedPath='/sys/fs/bpf/dnsdist/addr-v4'}, {maxItems=1024, pinnedPath='/sys/fs/bpf/dnsdist/addr-v6'}, {maxItems=1024, pinnedPath='/sys/fs/bpf/dnsdist/qnames'}, true)
+
+.. note::
+   By default only root can write into a bpf mount point, but it is possible to create a ``dnsdist/`` sub-directory with ``mkdir`` and to make it owned by the ``dnsdist`` user with ``chown``.
+
+The last parameter to :func:`newBPFFilter` is set to ``true`` to indicate to dnsdist not to load its internal eBPF socket filter program, which is not needed since packets will be intercepted by an external program and would at best duplicate the work done by the other program. It also tell dnsdist to use a slightly different format for the eBPF maps:
+
+ * IPv4 and IPv6 maps still use the address as key, but the value contains an action field in addition to the 'matched' counter, to allow for more actions than just dropping the packet
+ * the qname map now uses the qname and qtype as key, instead of using only the qname, and the value contains the action and counter fields described above instead of having a counter and the qtype
+
+The first, legacy format is still used because of the limitations of eBPF socket filter programs on older kernels, and the number of instructions in particular, that prevented us from using the qname and qtype as key. We will likely switch to the newer format by default once Linux distributions stop shipping these older kernels. XDP programs require newer kernel versions anyway and have thus fewer limitations.
+
+XDP programs are more powerful than eBPF socket filtering ones as they are not limited to accepting or denying a packet, but can immediately craft and send an answer. They are also executed a bit earlier in the kernel networking path so can provide better performance.
+
+A sample program using the maps populated by dnsdist in an external XDP program can be found in the `contrib/ directory of our git repository <https://github.com/PowerDNS/pdns/tree/master/contrib>`__. That program supports answering with a TC=1 response instead of simply dropping the packet.
+
index a028a9292f73d20722c05a35da5d009815841462..0160f2d7cf1abe369ab6911626afbefbd175571e 100644 (file)
@@ -19,5 +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 97b80510699d850d88b17f937e71b240174878a5..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`.
@@ -94,7 +97,7 @@ For that feature to work, dnsdist will look up twice into the packet cache when
 That feature is enabled by setting ``disableZeroScope=false`` on :func:`newServer` (default) and ``parseECS=true`` on :func:`newPacketCache` (not the default).
 
 
-Things are different for XPF and the proxy protocol, because dnsdist then does the cache lookup **before** adding the payload. It means that caching can still be enabled as long as the response is not source-dependant, but should be disabled otherwise.
+Things are different for XPF and the proxy protocol, because dnsdist then does the cache lookup **before** adding the payload. It means that caching can still be enabled as long as the response is not source-dependent, but should be disabled otherwise.
 
 +------------------+----------+---------------------+----------------+------------------------+
 | Protocol         | Standard | Require DNS parsing | Contains ports | Caching                |
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 357587dd59c5b36e3369dfb23b31825ac42bf630..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
 ------------------------
@@ -69,7 +87,7 @@ For GnuTLS:
 Sessions management for outgoing connections
 --------------------------------------------
 
-Since 1.7, dnsdist supports securing the connection toward backends using DNS over TLS. For these connections, it keeps a cache of TLS tickets to be able to resume a TLS session quickly. By default that cache contains up to 20 TLS tickets per-backend, is cleaned up every every 60s, and TLS tickets expire if they have not been used after 600 seconds.
+Since 1.7, dnsdist supports securing the connection toward backends using DNS over TLS. For these connections, it keeps a cache of TLS tickets to be able to resume a TLS session quickly. By default that cache contains up to 20 TLS tickets per-backend, is cleaned up every 60s, and TLS tickets expire if they have not been used after 600 seconds.
 These values can be set at configuration time via:
 
  * :func:`setOutgoingTLSSessionsCacheMaxTicketsPerBackend`
index ed940657fe66f14418a58aa6f9d48d12d5dc12e5..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
 ------------
 
@@ -111,6 +121,16 @@ For incoming and outgoing DNS over TLS support, the choice of the TLS provider (
 
 Since 1.8.0, incoming DNS over TLS might also benefit from experimental support for TLS acceleration engines, like Intel QAT. See :func:`loadTLSEngine`, and the `tlsAsyncMode` parameter of :func:`addTLSLocal` for more information.
 
+Incoming and outgoing DNS over TLS, as well as outgoing DNS over HTTPS, might benefit from experimental support kernel-accelerated TLS on Linux, when supported by the kernel and OpenSSL. See the `ktls` options on :func:`addTLSLocal` and :func:`newServer` for more information. Kernel support for kTLS might be verified by looking at the counters in ``/proc/net/tls_stat``. Note that:
+
+ * 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
 -------------
 
@@ -183,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 15ad3e1bf1a59200a5864bc5331d04dc179b2403..dc561f910d281126dd93a5b95a6a7a0d8b165472 100644 (file)
 Changelog
 =========
 
+.. 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
+    :tickets: 12177
+
+    Fix building with boost < 1.56
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12460
+    :tickets: 12453
+
+    lock.hh: include <stdexcept>
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12569
+
+    dnsdist-protocols.hh: include <cstdint> (Sander Hoentjen)
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12621
+    :tickets: 12074
+
+    Add getPoolNames() function, returning a list of pool names (Christof Chen)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12535
+
+    Fix the formatting of 'showServers'
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12529
+    :tickets: 11905
+
+    Properly record the incoming flags on a timeout
+
+  .. change::
+    :tags: Bug Fixes, Metrics
+    :pullreq: 12484
+    :tickets: 11498
+
+    Properly update rcode-related metrics on RCodeAction hits
+
+  .. change::
+    :tags: Bug Fixes, DNS over TLS, DNS over HTTPS
+    :pullreq: 12421
+    :tickets: 12341
+
+    Skip invalid OCSP files after issuing a warning
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12365
+    :tickets: 12357
+
+    Prevent an underflow of the TCP d_queued counter
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTPS
+    :pullreq: 12327
+
+    Fix the health-check timeout computation for DoH backend
+
+  .. change::
+    :tags: Bug Fixes, Webserver
+    :pullreq: 12260
+    :tickets: 9349
+
+    Properly encode json strings containing binary data
+
+  .. change::
+    :tags: Bug Fixes, DNS over TLS
+    :pullreq: 12237
+    :tickets: 12236
+
+    Ignore unclean TLS session shutdown
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12100
+    :tickets: 12099
+
+    Properly handle single-SOA XFR responses
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11830
+    :tickets: 4155
+
+    Also reconnect on ENETUNREACH. (Asgeir Storesund Nilsen)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11729
+    :tickets: 11728
+
+    Fix a bug in SetEDNSOptionAction
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11718
+
+    Fix the number of concurrent queries on a backend TCP conn
+
+.. 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
+
+    Fix 'Unknown key' issue for actions and rules parameters
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12672
+
+    Fix a dnsheader unaligned case
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12654
+
+    secpoll: explicitly include necessary ctime header for time_t
+
+.. 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
+
+    Use the correct source address when harvesting failed
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12639
+
+    Fix a race when a cross-protocol query triggers an IO error
+
+  .. change::
+    :tags: Improvements, Metrics, Webserver
+    :pullreq: 12638
+
+    Report per-incoming transport latencies in the web interface
+
+  .. change::
+    :tags: Improvements, Metrics
+    :pullreq: 12648
+
+    Report the TCP latency for TCP-only Do53, DoT and DoH backends
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12626
+
+    Count hits in the StatNode
+
+.. 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
+
+    Add Lua bindings for PB requestorID, deviceName and deviceID
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12593
+
+    Clean up the fortify and LTO m4 by not directly editing flags
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12592
+
+    Only increment the 'servfail-responses' metric on backend responses (phonedph1)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12586
+
+    Fix the harvesting of destination addresses
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12589
+
+    YaHTTP: Better detection of whether C++11 features are available
+
+  .. change::
+    :tags: Bug Fixes, Protobuf
+    :pullreq: 12588
+
+    Fix compilation with DoH disabled (Adam Majer)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12587
+
+    Skip signal-unsafe logging when we are about to exit, with TSAN
+
+.. 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
+
+    Include <cstdint> in dnsdist-protocols.hh (Sander Hoentjen)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12543
+
+    Enable Link-Time Optimization for our packages
+
+  .. change::
+    :tags: Improvements, Metrics
+    :pullreq: 12553
+
+    Add support for custom prometheus names in custom metrics
+
+  .. change::
+    :tags: Improvements, Protobuf
+    :pullreq: 12520
+
+    Add support for metadata in protobuf messages
+
+  .. change::
+    :tags: Improvements, DNS over HTTPS, DNS over TLS, Performance
+    :pullreq: 12545
+
+    Enable experimental kTLS support with OpenSSL on Linux
+
+  .. change::
+    :tags: Improvements, Performance
+    :pullreq: 12537
+
+    Improve the scalability of MaxQPSIPRule()
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12538
+
+    Stop using the deprecated `boost::optional::get_value_or`
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12535
+
+    Fix the formatting of 'showServers'
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12529
+    :tickets: 11905
+
+    Properly record the incoming flags on a timeout
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12530
+    :tickets: 10932
+
+    List version number early
+
+  .. change::
+    :tags: Improvements, DNS over TLS, DNS over HTTPS
+    :pullreq: 12423
+
+    OpenSSL 3.0: Offer TLS providers as an alternative to TLS engines
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12518
+
+    Remove duplicate code in xdp (Y7n05h)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 10115
+
+    Warn on unsupported parameters (Aki Tuomi)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12469
+    :tickets: 12417
+
+    Add unit tests for the Lua FFI interface
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12492
+
+    Refactor 'cannot be used at runtime' handling
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12417
+
+    Add the ability to change the qname and owner names in DNS packets
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12481
+    :tickets: 7611
+
+    Fail if we can't check the configuration file
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTPS
+    :pullreq: 12483
+    :tickets: 12019
+
+    Apply the max number of concurrent conns per client to DoH
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12484
+    :tickets: 11498
+
+    Properly update rcode-related metrics on RCodeAction hits
+
+  .. change::
+    :tags: New Features, Webserver
+    :pullreq: 12473
+    :tickets: 6154, 10468
+
+    Add an API endpoint to remove entries from caches
+
+  .. change::
+    :tags: Improvements, Webserver
+    :pullreq: 12474
+    :tickets: 10360
+
+    Add an option for unauthenticated access to the dashboard
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12388
+
+    Implement async processing of queries and responses
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12441
+
+    Add a configure option to enable LTO
+
+  .. change::
+    :tags: Bug Fixes, Metrics
+    :pullreq: 12424
+    :tickets: 10517, 11216
+
+    Better handling of multiple carbon servers
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12427
+
+    Add a new configure option to initialize automatic variables
+
+  .. change::
+    :tags: Improvements, DNS over HTTPS, DNS over TLS
+    :pullreq: 12421
+    :tickets: 12341
+
+    Skip invalid OCSP files after issuing a warning
+
+  .. change::
+    :tags: Improvements, DNS over HTTPS, DNS over TLS
+    :pullreq: 12435
+
+    Gracefully handle a failure to create a TLS server context
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12381
+
+    Enable FORTIFY_SOURCE=3 when supported by the compiler
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12405
+
+    Proper accounting of response and cache hits
+
+  .. change::
+    :tags: Improvements, DNS over HTTPS
+    :pullreq: 12386
+
+    Merge the 'main' and 'client' DoH threads in single acceptor mode
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12384
+
+    Add the ability to cap the TTL of records after insertion into the cache
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12411
+
+    Support OpenSSL 3.0 for ipcipher CA6 encryption/decryption
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12383
+
+    Stronger guarantees against data race in the UDP path
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12402
+
+    Add bindings for the current and query times in DQ/DR
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12400
+
+    Add SetReducedTTLResponseAction
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12385
+
+    Add a Lua FFI interface for metrics
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12387
+
+    Handle out-of-memory exceptions in the UDP receiver thread
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12365
+    :tickets: 12357
+
+    Prevent an underflow of the TCP d_queued counter
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12100
+    :tickets: 12099
+
+    Properly handle single-SOA XFR responses
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTPS
+    :pullreq: 12327
+
+    Fix the health-check timeout computation for DoH backend
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12280
+
+    Add a new chain of rules triggered after cache insertion
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11554
+
+    Raise RLIMIT_MEMLOCK automatically when eBPF is requested (Yogesh Singh)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12248
+    :tickets: 11153
+
+    Systemd: Add "After" dependency on time-sync.target (Kevin P. Fleming)
+
+  .. change::
+    :tags: Improvements, DNS over TLS
+    :pullreq: 12237
+    :tickets: 12236
+
+    Ignore unclean TLS session shutdown
+
+  .. change::
+    :tags: Improvements, Performance
+    :pullreq: 12276
+
+    Reduce useless wake-ups from the event loop
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11020
+
+    Added XDP middleware for dropped/redirected queries logging (Mini Pierre)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11863
+
+    DNSName constructor use memchr instead of strchr and cleanup with string_view (Axel Viala)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12177
+    :tickets: 12142
+
+    Fix building with boost < 1.56
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12065
+
+    Implement a 'lazy' health-checking mode
+
+  .. change::
+    :tags: Improvements, DNS over HTTPS, DNS over TLS
+    :pullreq: 11675
+
+    Skip DoT/DoH frontend when a tls configuration error occurs
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12074
+    :tickets: 12073
+
+    Add getPoolNames() function, returning a list of pool names (Christof Chen)
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12082
+
+    Cleaner way of getting the IP/masks associated to a network interface
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12077
+    :tickets: 12075
+
+    Retain output when expunging from multiple caches (Christof Chen)
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12022
+
+    Add Lua helpers to look into the content of DNS payloads
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11994
+
+    Add more Lua bindings for network-related operations
+
+  .. change::
+    :tags: Improvements, Performance, DNS over HTTPS
+    :pullreq: 11901
+
+    Faster cache-lookups for DNS over HTTPS queries
+
+  .. change::
+    :tags: Improvements, Performance
+    :pullreq: 12003
+
+    Add a 'single acceptor thread' build option, reducing the number of threads
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12008
+
+    Add Lua binding for inspecting the in-memory ring buffers
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11729
+    :tickets: 11728
+
+    Fix a bug in SetEDNSOptionAction
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12007
+
+    Add Lua bindings to look up domain and IP addresses from the cache
+
+  .. change::
+    :tags: Improvements, DNS over HTTPS
+    :pullreq: 12000
+
+    Speed up DoH handling by preventing allocations and copies
+
+  .. change::
+    :tags: Improvements, Metrics
+    :pullreq: 11987
+
+    Slightly reduce the number of allocations in API calls
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11993
+
+    Add build-time options to disable the dynamic blocks and UDP response delay
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11992
+
+    Add missing thread names
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11988
+
+    Add a build option (define) to prevent loading OpenSSL's errors
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11862
+    :tickets: 11853
+
+    Properly load ciphers and digests with OpenSSL 3.0
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11889
+
+    Add local ComboAddress parameter for SBind() at TeeAction() (@FredericDT)
+
+  .. change::
+    :tags: Improvements, Performance
+    :pullreq: 11883
+
+    Make recording queries/responses in the ringbuffers optional
+
+  .. change::
+    :tags: Improvements, Performance
+    :pullreq: 11852
+
+    Slightly reduce contention around a pool's servers
+
+  .. change::
+    :tags: Improvements, Performance, DNS over HTTPS
+    :pullreq: 11851
+
+    Only call getsockname() once per incoming DoH connection
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11844
+
+    Do not keep the mplexer created for the initial health-check around
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11830
+    :tickets: 4155
+
+    Also reconnect on ENETUNREACH. (Asgeir Storesund Nilsen)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11761
+
+    Keep retained capabilities even when switching user/group
+
+  .. change::
+    :tags: Improvements, Performance
+    :pullreq: 11734
+
+    Set TCP_NODELAY on the TCP connection to backends
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11723
+
+    Use getrandom() if available
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11713
+
+    Implement a limit of concurrent connections to a backend
+
+  .. change::
+    :tags: Improvements, Metrics
+    :pullreq: 11716
+
+    Add more detailed metrics
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11718
+
+    Fix the number of concurrent queries on a backend TCP conn
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11712
+    :tickets: 11585
+
+    Fill ringbuffers with responses served from the cache
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11696
+
+    Bind to the requested src interface without a src address
+
+  .. change::
+    :tags: Improvements, Performance
+    :pullreq: 11689
+
+    Avoid allocating memory in LB policies for small number of servers
+
+  .. change::
+    :tags: Improvements, Metrics
+    :pullreq: 11707
+
+    Compute backend latency earlier, to avoid internal latency
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11698
+
+    Implement `SuffixMatchTree::getBestMatch()` to get the name that matched
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11711
+
+    Log listening addresses and version at the 'info' level
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11651
+
+    Refactor sendfromto (Y7n05h)
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11526
+
+    Use BPF_MAP_TYPE_LPM_TRIE for range matching (Y7n05h)
+
+  .. change::
+    :tags: Improvements, Performance
+    :pullreq: 11624
+
+    SuffixMatchTree: Improve lookup performance
+
+  .. change::
+    :tags: Improvements, Metrics
+    :pullreq: 11659
+
+    Add 'statistics' to the general API endpoint
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11668
+
+    Optionally send 'verbose' messages to a file, and log them at 'DEBUG' level otherwise
+
+  .. change::
+    :tags: New Features, Metrics
+    :pullreq: 11674
+
+    Add support for user defined metrics
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11669
+
+    Log when exiting due to a SIGTERM signal
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11673
+
+    Add the protocol (Do53, DoT, DoH, ...) of backends in the API
+
+  .. change::
+    :tags: Improvements, Metrics
+    :pullreq: 11656
+
+    Add a counter for the number of cache cleanups
+
+  .. change::
+    :tags: Improvements, Performance
+    :pullreq: 11655
+
+    Change dns_tolower() and dns_toupper() to use a table
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11637
+
+    Add getVerbose() function
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11606
+
+    Add Lua bindings to access the DNS payload as a string
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11620
+    :tickets: 11619
+
+    Remove implicit type conversion (Y7n05h)
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTPS
+    :pullreq: 11621
+    :tickets: 11604
+
+    Fix a crash on a invalid protocol in DoH forwarded-for header
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11604
+
+    Fix invalid proxy protocol payload on a DoH TC to TCP retry
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11567
+
+    Add setVerbose() to switch the verbose mode at runtime
+
+  .. change::
+    :tags: Improvements, Performance
+    :pullreq: 11577
+    :tickets: 11576
+
+    Scan the UDP buckets only when we have outstanding queries
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11543
+    :tickets: 11488
+
+   Log when a console message exceeds the maximum size
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11578
+
+    Include the address of the backend in 'relayed to' messages
+
+  .. change::
+    :tags: Improvements, Webserver, Metrics
+    :pullreq: 11514
+
+    Add an option for unauthenticated access to the API
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11573
+
+    Better log message when no downstream server are available
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11547
+    :tickets: 11434
+
+    Add a 'getAddressAndPort()' method to DOHFrontend and TLSFrontend objects
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11545
+    :tickets: 11501
+
+    Use the correct outgoing protocol in our ring buffers
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11546
+    :tickets: 11383
+
+    Raise the number of entries in a packet cache to at least 1
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11535
+    :tickets: 11526
+
+    Merge multiple parameters in newBPFFilter (Y7n05h)
+
+  .. change::
+    :tags: Improvements, Performance
+    :pullreq: 11531
+
+    Prevent allocations in two corner cases
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11523
+
+    Reject BPFFilter::attachToAllBinds() at configuration time (Y7n05h)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11515
+
+    Add more build-time options to select features
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11517
+
+    Multiplexer: Take the maximum number of events as a hint
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11497
+    :tickets: 9994
+
+    Add setTCPFastOpenKey() (Y7n05h)
+
+  .. change::
+    :tags: Improvements, Performance
+    :pullreq: 11437
+    :tickets: 11422
+
+    Only allocate the health-check mplexer when needed
+
+  .. change::
+    :tags: Improvements, DNS over HTTPS, DNS over TLS
+    :pullreq: 11415
+
+    More useful default ports for DoT/DoH backends
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11388
+
+    Add --log-timestamps flag
+
+  .. change::
+    :tags: New Features, DNS over HTTPS, DNS over TLS
+    :pullreq: 11293
+
+    Dynamic discovery and upgrade of backends
+
+  .. change::
+    :tags: New Features, Security
+    :pullreq: 11163
+
+    Allow randomly selecting a backend UDP socket and query ID
+
+  .. change::
+    :tags: Removals
+    :pullreq: 11324
+    :tickets: 11201
+
+    Remove the leak warning with GnuTLS >= 3.7.3
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11174
+
+    Add a parameter to PoolAction to keep processing rules
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11173
+
+    Add Lua FFI helpers for protocol and MAC address access, proxy protocol payload generation
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11196
+
+    Fix build with OpenSSL 3.0.0
+
+  .. change::
+    :tags: Improvements, Performance
+    :pullreq: 11171
+
+    Defer the actual allocation of the ring buffer entries
+
+  .. change::
+    :tags: Improvements, DNS over HTTPS, DNS over TLS
+    :pullreq: 11166
+
+    Libssl: Load only the ciphers and digests needed for TLS, not all of them
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11184
+
+    Add support to store mac address in query rings
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11178
+
+    Build with `-fvisibility=hidden` by default
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11126
+
+    Add newThread() function
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 10950
+
+    Add a lot more of build-time options to select features
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11098
+
+    Lua support to remove resource records from a response
+
+  .. change::
+    :tags: New Features, DNS over HTTPS, DNS over TLS
+    :pullreq: 11027
+
+    Add support for password protected PKCS12 files for TLS configuration
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11051
+
+    Add support to spoof a full self-generated response from lua
+
+  .. change::
+    :tags: New Features
+    :pullreq: 10949
+
+    Add a Lua FFI helper to generate proxy protocol payloads
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11017
+
+    Add Lua bindings to get the list of network interfaces, addresses
+
+  .. change::
+    :tags: New Features, DNS over TLS
+    :pullreq: 10734
+
+    Add experimental support for TLS asynchronous engines
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11059
+
+    Add lua support to limit TTL values of responses
+
+.. 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.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11948
+
+    add el9/9stream targets
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11974
+
+    docker images: upgrade to Debian bullseye
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11742
+
+    dh_builddeb: force gzip compression (this makes the Ubuntu Jammy packages compatible with our Debian-hosted repositories)
+
+.. 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
+    :tickets: 11576
+
+    Scan the UDP buckets only when we have outstanding queries
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11580
+    :tickets: 11422
+
+    Only allocate the health-check mplexer when needed
+
+  .. change::
+    :tags: Bug Fixes, Metrics
+    :pullreq: 11664
+    :tickets: 11602
+
+    Add missing descriptions for prometheus metrics
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTPS
+    :pullreq: 11665
+    :tickets: 11604
+
+    Fix invalid proxy protocol payload on a DoH TC to TCP retry
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11666
+    :tickets: 11606
+
+    Add Lua bindings to access the DNS payload as a string
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTPS
+    :pullreq: 11667
+    :tickets: 11621
+
+    Fix a crash on a invalid protocol in DoH forwarded-for header
+
+.. 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
+
+    Fix compilation with OpenSSL 3.0.0
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11094
+    :tickets: 11081
+
+    Docker images: remove capability requirements
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11292
+    :tickets: 11290
+
+    Docker image: install ca-certificates
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11335
+    :tickets: 11330
+
+    Fix a use-after-free in case of a network error in the middle of a XFR query
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11550
+    :tickets: 11504
+
+    Properly use eBPF when the DynBlock is not set
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11176
+    :tickets: 11113
+
+    Work around a compiler bug seen on OpenBSD/amd64 using clang-13
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11197
+
+    Stop using the now deprecated and useless std::binary_function
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTPS, DNS over TLS
+    :pullreq: 11251
+    :tickets: 11249
+
+    Set Server Name Indication on outgoing TLS connections (DoT, DoH)
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTPS
+    :pullreq: 11253
+    :tickets: 11250
+
+    Fix the health-check timeout for outgoing DoH connections
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11255
+    :tickets: 11254
+
+    Fix 'inConfigCheck()'
+
+  .. change::
+    :tags: Bug Fixes, Metrics
+    :pullreq: 11323
+    :tickets: 11239
+
+    Fix the latency-count metric
+
+  .. change::
+    :tags: Improvements, DNS over HTTPS, DNS over TLS
+    :pullreq: 11324
+    :tickets: 11201
+
+    Remove the leak warning with GnuTLS >= 3.7.3
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11545
+    :tickets: 11501
+
+    Use the correct outgoing protocol in our ring buffers
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11546
+    :tickets: 11383
+
+    Raise the number of entries in a packet cache to at least 1
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11547
+    :tickets: 11434
+
+    Add a 'getAddressAndPort()' method to DOHFrontend and TLSFrontend objects
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11565
+
+    Fix wrong eBPF values (qtype, counter) being inserted for qnames
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11572
+    :tickets: 11375
+
+    The check interval applies to health-check, not timeouts
+
 .. 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
@@ -16,6 +1991,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
@@ -93,6 +2070,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
@@ -216,6 +2195,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
@@ -319,6 +2300,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
@@ -482,6 +2465,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
@@ -533,10 +2518,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
@@ -614,6 +2603,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
@@ -637,6 +2628,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
@@ -673,6 +2666,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
@@ -738,6 +2733,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
@@ -811,6 +2808,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
@@ -1054,7 +3053,7 @@ Changelog
     :tags: Bug Fixes
     :pullreq: 9925
 
-    Appease clang++ 12 ASAN on MacOS
+    Appease clang++ 12 ASAN on macOS
 
   .. change::
     :tags: Improvements
@@ -1205,6 +3204,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
@@ -1241,6 +3242,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
@@ -1294,6 +3297,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
@@ -1304,6 +3309,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
@@ -1359,6 +3366,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
@@ -1431,6 +3440,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
@@ -1479,6 +3490,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
@@ -1780,6 +3793,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
@@ -1820,6 +3835,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
@@ -1836,6 +3853,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
@@ -1976,6 +3995,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
@@ -2055,6 +4077,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
@@ -2096,6 +4121,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
@@ -2381,6 +4408,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
@@ -2418,6 +4447,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
@@ -2447,6 +4478,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
@@ -2717,6 +4750,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
@@ -2903,6 +4938,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
@@ -2913,6 +4950,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
@@ -3226,6 +5265,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
@@ -3471,6 +5512,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
@@ -3549,6 +5592,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 259417be177551c377e9f3d002636eff19df2b21..781d0ef529702ac3b1adb29b89d87eaa2cff8317 100644 (file)
@@ -8,18 +8,22 @@ End of life statements
      - Release date
      - Security-Only updates
      - End of Life
+   * - 1.8
+     - March 30 2023
+     -
+     -
    * - 1.7
      - January 17 2022
-     -
+     - March 30 2023
      -
    * - 1.6
      - May 11 2021
-     - 
+     - March 30 2023
      - 
    * - 1.5
      - July 30 2020
      - January 17 2022
-     - 
+     - EOL (March 30 2023)
    * - 1.4
      - November 20 2019
      - May 2021
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 69574c8021d2971507a0ed91b736af2cbff0e15f..52e534915ea4a29eca5df97bb55da85c982f34dd 100644 (file)
@@ -25,21 +25,10 @@ It works as well for authoritative as for recursive servers.
 
 Healthcheck
 -----------
-dnsdist uses a health check, sent once every second, to determine the availability of a backend server.
 
-By default, an A query for "a.root-servers.net." is sent.
-A different query type, class and target can be specified by passing, respectively, the ``checkType``, ``checkClass`` and ``checkName`` parameters to :func:`newServer`.
+dnsdist uses health-check queries, sent once every second, to determine the availability of a backend server. Since 1.8.0, it also supports a ``lazy`` health-checking mode which only sends active health-check queries after a configurable threshold of regular queries have failed, see below.
 
-The default behavior is to consider any valid response with an RCODE different from ServFail as valid.
-If the ``mustResolve`` parameter of :func:`newServer` is set to ``true``, a response will only be considered valid if its RCODE differs from NXDomain, ServFail and Refused.
-
-The number of health check failures before a server is considered down is configurable via the ``maxCheckFailures`` parameter, defaulting to 1.
-The CD flag can be set on the query by setting ``setCD`` to true.
-e.g.::
-
-  newServer({address="192.0.2.1", checkType="AAAA", checkType=DNSClass.CHAOS, checkName="a.root-servers.net.", mustResolve=true})
-
-You can turn on logging of health check errors using the :func:`setVerboseHealthChecks` function.
+By default, an ``A`` query for the "a.root-servers.net." name is sent. A different query type, class and target can be specified by passing, respectively, the ``checkType``, ``checkClass`` and ``checkName`` parameters to :func:`newServer`. The interval between two health-check queries can be set via the ``checkInterval`` interval parameter, and the amount of time for a response to be received via the ``checkTimeout`` one.
 
 Since the 1.3.0 release, the ``checkFunction`` option is also supported, taking a ``Lua`` function as parameter. This function receives a DNSName, two integers and a ``DNSHeader`` object (:ref:`DNSHeader`)
 representing the QName, QType and QClass of the health check query as well as the DNS header, as they are defined before the function was called. The function must return a DNSName and two integers
@@ -57,6 +46,48 @@ The following example sets the CD flag to true and change the QName to "powerdns
 
     newServer({address="2620:0:0ccd::2", checkFunction=myHealthCheck})
 
+The default behavior is to consider any valid response with an ``RCODE`` different from ``ServFail`` as valid.
+If the ``mustResolve`` parameter of :func:`newServer` is set to ``true``, a response will only be considered valid if its ``RCODE`` differs from ``NXDomain``, ``ServFail`` and ``Refused``.
+
+The number of health check failures before a server is considered down is configurable via the ``maxCheckFailures`` parameter, defaulting to 1. In the same way, the number of consecutive successful health checks needed for a server to be considered available can be set via the ``rise`` parameter, defaulting to 1.
+
+The ``CD`` flag can be set on the query by setting ``setCD`` to true.
+e.g.::
+
+  newServer({address="192.0.2.1", checkType="AAAA", checkClass=DNSClass.CHAOS, checkName="a.root-servers.net.", mustResolve=true})
+
+You can turn on logging of health check errors using the :func:`setVerboseHealthChecks` function.
+
+Lazy health-checking
+~~~~~~~~~~~~~~~~~~~~
+
+In some setups, especially on low-end devices, it might not make sense to actively send queries to the backend at a regular interval. Using the feedback from the results of regular queries can instead be used to infer if a backend might not be working properly.
+
+Since 1.8.0, dnsdist implements a ``lazy`` mode that can be set via the ``healthCheckMode`` option on :func:`newServer`. In this mode, dnsdist will only send active health-check queries after seeing a configurable amount of regular queries failing. It will then place the backend in a ``PotentialFailure`` state, from the initial ``Healthy`` one, and send health-check queries every ``checkInterval`` seconds. If ``maxCheckFailures`` of these fail, the backend is then moved to a ``Failed`` state and marked as ``down``, and active health-check queries are sent every ``lazyHealthCheckFailedInterval`` seconds. After ``rise`` successful, consecutive queries, the backend will be moved back to the ``Healthy`` state and marked as ``up`` again, and health-check queries will stop.
+
+.. figure:: ../imgs/DNSDistLazyHealthChecks.png
+   :align: center
+   :alt: DNSDist Lazy health checks
+
+The threshold of failed regular queries is configured via ``lazyHealthCheckThreshold``, indicating of percentage of regular queries that should have resulted in a failure over the last recent queries. Only the results of the last ``lazyHealthCheckSampleSize`` queries will be considered, as the results are kept in a in-memory circular buffer. The results of at least ``lazyHealthCheckMinSampleCount`` queries should be present for the threshold to be considered meaningful, to avoid an issue with a too small sample.
+
+By default both queries that resulted in a timeout and those that received a ``ServFail`` answer are considered failures, but it is possible to set ``lazyHealthCheckMode`` to ``TimeoutOnly`` so that only timeouts are considered failures.
+
+So for example, if we set ``healthCheckMode`` to ``lazy``, ``lazyHealthCheckSampleSize`` to 100, ``lazyHealthCheckMinSampleCount`` to 10, ``lazyHealthCheckThreshold`` to 30, ``maxCheckFailures`` to 2 and ``rise`` to 2:
+
+- nothing will happen until at least 10 queries have been received
+- only the results of the last 100 queries will be considered
+- if at least 30 of these last 100 have failed, the threshold will be reached and active health-check queries will be sent every ``checkInterval`` seconds
+- if the health-check query is successful, the backend will stay ``up`` and no more query will be sent
+- but if instead two consecutive queries fail, the backend will be marked as ``down`` and health-check queries will be sent every ``lazyHealthCheckFailedInterval`` seconds
+- it will take two consecutive, successful health-checks for the backend to go back to ``Healthy`` and be marked `up` again
+
+.. code-block:: lua
+
+    newServer({address="192.0.2.1", healthCheckMode='lazy', checkInterval=1, lazyHealthCheckFailedInterval=30, rise=2, maxCheckFailures=3, lazyHealthCheckThreshold=30, lazyHealthCheckSampleSize=100,  lazyHealthCheckMinSampleCount=10, lazyHealthCheckMode='TimeoutOnly'})
+
+The 'lazy' mode also supports using an exponential back-off time between health-check queries, once a backend has been moved to the 'down' state. This can be enabled by setting the ``lazyHealthCheckUseExponentialBackOff`` parameter to 'true'. Once the backend has been marked as 'down', the first query will be sent after ``lazyHealthCheckFailedInterval`` seconds, the second one after 2 times ``lazyHealthCheckFailedInterval`` seconds, the third after 4 times ``lazyHealthCheckFailedInterval`` seconds, and so on and so forth, until ``lazyHealthCheckMaxBackOff`` has been reached. Then probes will be sent every ``lazyHealthCheckMaxBackOff`` seconds (default is 3600 so one hour) until the backend comes 'up' again.
+
 Source address selection
 ------------------------
 
@@ -97,7 +128,7 @@ channel.
 
 The TCP-only mode for a backend can be enabled by using the ``tcpOnly`` parameter of the :func:`newServer` command.
 
-The DNS over TLS mode via the the ``tls`` parameter of the :func:`newServer` command. Additional parameters control the
+The DNS over TLS mode 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``).
 
@@ -108,8 +139,8 @@ If it is absolutely necessary to support UDP exchanges over an untrusted network
 1.8.0 to make spoofing attempts harder:
 
 - :func:`setRandomizedIdsOverUDP` will randomize the IDs in outgoing queries, at a small performance cost. :func:`setMaxUDPOutstanding`
-should be set at its highest possible value (default since 1.4.0) to make that setting fully efficient.
+  should be set at its highest possible value (default since 1.4.0) to make that setting fully efficient.
 
 - :func:`setRandomizedOutgoingSockets` can be used to randomize the outgoing socket used when forwarding a query to a backend.
-This requires configuring the backend to use more than one outgoing socket via the ``sockets`` parameter of :func:`newServer`
-to be of any use.
+  This requires configuring the backend to use more than one outgoing socket via the ``sockets`` parameter of :func:`newServer`
+  to be of any use.
index a8958283c0b0f885a2453ebda582258f74702aec..c987b0c69f5b538f23c9f9a7c6b8dfe90e973c1c 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`).
 
@@ -40,18 +54,6 @@ Starting with dnsdist 1.3.0, a new :ref:`dynBlockRulesGroup` function can be use
 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 c710aa78fc90bb47bd5b86cdd0485d6f08433ccb..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.
@@ -140,6 +140,15 @@ URL Endpoints
       # HELP dnsdist_queries Number of received queries
       # TYPE dnsdist_queries counter
       dnsdist_queries 0
+      # HELP dnsdist_frontend_nxdomain Number of NXDomain answers sent to clients
+      # TYPE dnsdist_frontend_nxdomain counter
+      dnsdist_frontend_nxdomain 0
+      # HELP dnsdist_frontend_servfail Number of SERVFAIL answers sent to clients
+      # TYPE dnsdist_frontend_servfail counter
+      dnsdist_frontend_servfail 0
+      # HELP dnsdist_frontend_noerror Number of NoError answers sent to clients
+      # TYPE dnsdist_frontend_noerror counter
+      dnsdist_frontend_noerror 0
       # HELP dnsdist_acl_drops Number of packets dropped because of the ACL
       # TYPE dnsdist_acl_drops counter
       dnsdist_acl_drops 0
@@ -155,6 +164,9 @@ URL Endpoints
       # HELP dnsdist_rule_servfail Number of SERVFAIL answers received because of a rule
       # TYPE dnsdist_rule_servfail counter
       dnsdist_rule_servfail 0
+      # HELP dnsdist_rule_truncated Number of truncated answers returned because of a rule
+      # TYPE dnsdist_rule_truncated counter
+      dnsdist_rule_truncated 0
       # HELP dnsdist_self_answered Number of self-answered responses
       # TYPE dnsdist_self_answered counter
       dnsdist_self_answered 0
@@ -200,18 +212,90 @@ URL Endpoints
       # HELP dnsdist_latency_avg1000000 Average response latency in microseconds of the last 1000000 packets
       # TYPE dnsdist_latency_avg1000000 gauge
       dnsdist_latency_avg1000000 0
+      # HELP dnsdist_latency_tcp_avg100 Average response latency, in microseconds, of the last 100 packets received over TCP
+      # TYPE dnsdist_latency_tcp_avg100 gauge
+      dnsdist_latency_tcp_avg100 0
+      # HELP dnsdist_latency_tcp_avg1000 Average response latency, in microseconds, of the last 1000 packets received over TCP
+      # TYPE dnsdist_latency_tcp_avg1000 gauge
+      dnsdist_latency_tcp_avg1000 0
+      # HELP dnsdist_latency_tcp_avg10000 Average response latency, in microseconds, of the last 10000 packets received over TCP
+      # TYPE dnsdist_latency_tcp_avg10000 gauge
+      dnsdist_latency_tcp_avg10000 0
+      # HELP dnsdist_latency_tcp_avg1000000 Average response latency, in microseconds, of the last 1000000 packets received over TCP
+      # TYPE dnsdist_latency_tcp_avg1000000 gauge
+      dnsdist_latency_tcp_avg1000000 0
+      # HELP dnsdist_latency_dot_avg100 Average response latency, in microseconds, of the last 100 packets received over DoT
+      # TYPE dnsdist_latency_dot_avg100 gauge
+      dnsdist_latency_dot_avg100 0
+      # HELP dnsdist_latency_dot_avg1000 Average response latency, in microseconds, of the last 1000 packets received over DoT
+      # TYPE dnsdist_latency_dot_avg1000 gauge
+      dnsdist_latency_dot_avg1000 0
+      # HELP dnsdist_latency_dot_avg10000 Average response latency, in microseconds, of the last 10000 packets received over DoT
+      # TYPE dnsdist_latency_dot_avg10000 gauge
+      dnsdist_latency_dot_avg10000 0
+      # HELP dnsdist_latency_dot_avg1000000 Average response latency, in microseconds, of the last 1000000 packets received over DoT
+      # TYPE dnsdist_latency_dot_avg1000000 gauge
+      dnsdist_latency_dot_avg1000000 0
+      # HELP dnsdist_latency_doh_avg100 Average response latency, in microseconds, of the last 100 packets received over DoH
+      # TYPE dnsdist_latency_doh_avg100 gauge
+      dnsdist_latency_doh_avg100 0
+      # HELP dnsdist_latency_doh_avg1000 Average response latency, in microseconds, of the last 1000 packets received over DoH
+      # TYPE dnsdist_latency_doh_avg1000 gauge
+      dnsdist_latency_doh_avg1000 0
+      # HELP dnsdist_latency_doh_avg10000 Average response latency, in microseconds, of the last 10000 packets received over DoH
+      # TYPE dnsdist_latency_doh_avg10000 gauge
+      dnsdist_latency_doh_avg10000 0
+      # HELP dnsdist_latency_doh_avg1000000 Average response latency, in microseconds, of the last 1000000 packets received over DoH
+      # TYPE dnsdist_latency_doh_avg1000000 gauge
+      dnsdist_latency_doh_avg1000000 0
       # HELP dnsdist_uptime Uptime of the dnsdist process in seconds
       # TYPE dnsdist_uptime gauge
-      dnsdist_uptime 39
+      dnsdist_uptime 19
       # HELP dnsdist_real_memory_usage Current memory usage in bytes
       # TYPE dnsdist_real_memory_usage gauge
-      dnsdist_real_memory_usage 10276864
+      dnsdist_real_memory_usage 52269056
+      # HELP dnsdist_udp_in_errors From /proc/net/snmp InErrors
+      # TYPE dnsdist_udp_in_errors counter
+      dnsdist_udp_in_errors 0
+      # HELP dnsdist_udp_noport_errors From /proc/net/snmp NoPorts
+      # TYPE dnsdist_udp_noport_errors counter
+      dnsdist_udp_noport_errors 86
+      # HELP dnsdist_udp_recvbuf_errors From /proc/net/snmp RcvbufErrors
+      # TYPE dnsdist_udp_recvbuf_errors counter
+      dnsdist_udp_recvbuf_errors 0
+      # HELP dnsdist_udp_sndbuf_errors From /proc/net/snmp SndbufErrors
+      # TYPE dnsdist_udp_sndbuf_errors counter
+      dnsdist_udp_sndbuf_errors 0
+      # HELP dnsdist_udp_in_csum_errors From /proc/net/snmp InCsumErrors
+      # TYPE dnsdist_udp_in_csum_errors counter
+      dnsdist_udp_in_csum_errors 0
+      # HELP dnsdist_udp6_in_errors From /proc/net/snmp6 Udp6InErrors
+      # TYPE dnsdist_udp6_in_errors counter
+      dnsdist_udp6_in_errors 0
+      # HELP dnsdist_udp6_recvbuf_errors From /proc/net/snmp6 Udp6RcvbufErrors
+      # TYPE dnsdist_udp6_recvbuf_errors counter
+      dnsdist_udp6_recvbuf_errors 0
+      # HELP dnsdist_udp6_sndbuf_errors From /proc/net/snmp6 Udp6SndbufErrors
+      # TYPE dnsdist_udp6_sndbuf_errors counter
+      dnsdist_udp6_sndbuf_errors 0
+      # HELP dnsdist_udp6_noport_errors From /proc/net/snmp6 Udp6NoPorts
+      # TYPE dnsdist_udp6_noport_errors counter
+      dnsdist_udp6_noport_errors 195
+      # HELP dnsdist_udp6_in_csum_errors From /proc/net/snmp6 Udp6InCsumErrors
+      # TYPE dnsdist_udp6_in_csum_errors counter
+      dnsdist_udp6_in_csum_errors 0
+      # HELP dnsdist_tcp_listen_overflows From /proc/net/netstat ListenOverflows
+      # TYPE dnsdist_tcp_listen_overflows counter
+      dnsdist_tcp_listen_overflows 0
       # HELP dnsdist_noncompliant_queries Number of queries dropped as non-compliant
       # TYPE dnsdist_noncompliant_queries counter
       dnsdist_noncompliant_queries 0
       # HELP dnsdist_noncompliant_responses Number of answers from a backend dropped as non-compliant
       # TYPE dnsdist_noncompliant_responses counter
       dnsdist_noncompliant_responses 0
+      # HELP dnsdist_proxy_protocol_invalid Number of queries dropped because of an invalid Proxy Protocol header
+      # TYPE dnsdist_proxy_protocol_invalid counter
+      dnsdist_proxy_protocol_invalid 0
       # HELP dnsdist_rdqueries Number of received queries with the recursion desired bit set
       # TYPE dnsdist_rdqueries counter
       dnsdist_rdqueries 0
@@ -224,39 +308,302 @@ URL Endpoints
       # HELP dnsdist_cache_misses Number of times an answer not found in the cache
       # TYPE dnsdist_cache_misses counter
       dnsdist_cache_misses 0
-      # HELP dnsdist_cpu_user_msec Milliseconds spent by dnsdist in the user state
-      # TYPE dnsdist_cpu_user_msec counter
-      dnsdist_cpu_user_msec 28
+      # HELP dnsdist_cpu_iowait Time waiting for I/O to complete by the whole system, in units of USER_HZ
+      # TYPE dnsdist_cpu_iowait counter
+      dnsdist_cpu_iowait 0
+      # HELP dnsdist_cpu_steal Stolen time, which is the time spent by the whole system in other operating systems when running in a virtualized environment, in units of USER_HZ
+      # TYPE dnsdist_cpu_steal counter
+      dnsdist_cpu_steal 0
       # HELP dnsdist_cpu_sys_msec Milliseconds spent by dnsdist in the system state
       # TYPE dnsdist_cpu_sys_msec counter
-      dnsdist_cpu_sys_msec 32
+      dnsdist_cpu_sys_msec 38
+      # HELP dnsdist_cpu_user_msec Milliseconds spent by dnsdist in the user state
+      # TYPE dnsdist_cpu_user_msec counter
+      dnsdist_cpu_user_msec 38
       # HELP dnsdist_fd_usage Number of currently used file descriptors
       # TYPE dnsdist_fd_usage gauge
-      dnsdist_fd_usage 17
+      dnsdist_fd_usage 32
       # HELP dnsdist_dyn_blocked Number of queries dropped because of a dynamic block
       # TYPE dnsdist_dyn_blocked counter
       dnsdist_dyn_blocked 0
       # HELP dnsdist_dyn_block_nmg_size Number of dynamic blocks entries
       # TYPE dnsdist_dyn_block_nmg_size gauge
       dnsdist_dyn_block_nmg_size 0
-      dnsdist_server_queries{server="1_1_1_1",address="1.1.1.1:53"} 0
-      dnsdist_server_drops{server="1_1_1_1",address="1.1.1.1:53"} 0
-      dnsdist_server_latency{server="1_1_1_1",address="1.1.1.1:53"} 0
-      dnsdist_server_senderrors{server="1_1_1_1",address="1.1.1.1:53"} 0
-      dnsdist_server_outstanding{server="1_1_1_1",address="1.1.1.1:53"} 0
-      dnsdist_server_order{server="1_1_1_1",address="1.1.1.1:53"} 1
-      dnsdist_server_weight{server="1_1_1_1",address="1.1.1.1:53"} 1
-      dnsdist_server_queries{server="1_0_0_1",address="1.0.0.1:53"} 0
-      dnsdist_server_drops{server="1_0_0_1",address="1.0.0.1:53"} 0
-      dnsdist_server_latency{server="1_0_0_1",address="1.0.0.1:53"} 0
-      dnsdist_server_senderrors{server="1_0_0_1",address="1.0.0.1:53"} 0
-      dnsdist_server_outstanding{server="1_0_0_1",address="1.0.0.1:53"} 0
-      dnsdist_server_order{server="1_0_0_1",address="1.0.0.1:53"} 1
-      dnsdist_server_weight{server="1_0_0_1",address="1.0.0.1:53"} 2
-      dnsdist_frontend_queries{frontend="127.0.0.1:1153",proto="udp"} 0
-      dnsdist_frontend_queries{frontend="127.0.0.1:1153",proto="tcp"} 0
-      dnsdist_pool_servers{pool="_default_"} 2
-      dnsdist_pool_cache_size{pool="_default_"} 200000
+      # HELP dnsdist_security_status Security status of this software. 0=unknown, 1=OK, 2=upgrade recommended, 3=upgrade mandatory
+      # TYPE dnsdist_security_status gauge
+      dnsdist_security_status 0
+      # HELP dnsdist_doh_query_pipe_full Number of DoH queries dropped because the internal pipe used to distribute queries was full
+      # TYPE dnsdist_doh_query_pipe_full counter
+      dnsdist_doh_query_pipe_full 0
+      # HELP dnsdist_doh_response_pipe_full Number of DoH responses dropped because the internal pipe used to distribute responses was full
+      # TYPE dnsdist_doh_response_pipe_full counter
+      dnsdist_doh_response_pipe_full 0
+      # HELP dnsdist_outgoing_doh_query_pipe_full Number of outgoing DoH queries dropped because the internal pipe used to distribute queries was full
+      # TYPE dnsdist_outgoing_doh_query_pipe_full counter
+      dnsdist_outgoing_doh_query_pipe_full 0
+      # HELP dnsdist_tcp_query_pipe_full Number of TCP queries dropped because the internal pipe used to distribute queries was full
+      # TYPE dnsdist_tcp_query_pipe_full counter
+      dnsdist_tcp_query_pipe_full 0
+      # HELP dnsdist_tcp_cross_protocol_query_pipe_full Number of TCP cross-protocol queries dropped because the internal pipe used to distribute queries was full
+      # TYPE dnsdist_tcp_cross_protocol_query_pipe_full counter
+      dnsdist_tcp_cross_protocol_query_pipe_full 0
+      # HELP dnsdist_tcp_cross_protocol_response_pipe_full Number of TCP cross-protocol responses dropped because the internal pipe used to distribute queries was full
+      # TYPE dnsdist_tcp_cross_protocol_response_pipe_full counter
+      dnsdist_tcp_cross_protocol_response_pipe_full 0
+      # HELP dnsdist_latency Histogram of responses by latency (in milliseconds)
+      # TYPE dnsdist_latency histogram
+      dnsdist_latency_bucket{le="1"} 0
+      dnsdist_latency_bucket{le="10"} 0
+      dnsdist_latency_bucket{le="50"} 0
+      dnsdist_latency_bucket{le="100"} 0
+      dnsdist_latency_bucket{le="1000"} 0
+      dnsdist_latency_bucket{le="+Inf"} 0
+      dnsdist_latency_sum 0
+      dnsdist_latency_count 0
+      # HELP dnsdist_server_status Whether this backend is up (1) or down (0)
+      # TYPE dnsdist_server_status gauge
+      # HELP dnsdist_server_queries Amount of queries relayed to server
+      # TYPE dnsdist_server_queries counter
+      # HELP dnsdist_server_responses Amount of responses received from this server
+      # TYPE dnsdist_server_responses counter
+      # HELP dnsdist_server_noncompliantresponses Amount of non-compliant responses received from this server
+      # TYPE dnsdist_server_noncompliantresponses counter
+      # HELP dnsdist_server_drops Amount of queries not answered by server
+      # TYPE dnsdist_server_drops counter
+      # HELP dnsdist_server_latency Server's latency when answering questions in milliseconds
+      # TYPE dnsdist_server_latency gauge
+      # HELP dnsdist_server_senderrors Total number of OS send errors while relaying queries
+      # TYPE dnsdist_server_senderrors counter
+      # HELP dnsdist_server_outstanding Current number of queries that are waiting for a backend response
+      # TYPE dnsdist_server_outstanding gauge
+      # HELP dnsdist_server_order The order in which this server is picked
+      # TYPE dnsdist_server_order gauge
+      # HELP dnsdist_server_weight The weight within the order in which this server is picked
+      # TYPE dnsdist_server_weight gauge
+      # HELP dnsdist_server_tcpdiedsendingquery The number of TCP I/O errors while sending the query
+      # TYPE dnsdist_server_tcpdiedsendingquery counter
+      # HELP dnsdist_server_tcpdiedreadingresponse The number of TCP I/O errors while reading the response
+      # TYPE dnsdist_server_tcpdiedreadingresponse counter
+      # HELP dnsdist_server_tcpgaveup The number of TCP connections failing after too many attempts
+      # TYPE dnsdist_server_tcpgaveup counter
+      # HELP dnsdist_server_tcpconnecttimeouts The number of TCP connect timeouts
+      # TYPE dnsdist_server_tcpconnecttimeouts counter
+      # HELP dnsdist_server_tcpreadtimeouts The number of TCP read timeouts
+      # TYPE dnsdist_server_tcpreadtimeouts counter
+      # HELP dnsdist_server_tcpwritetimeouts The number of TCP write timeouts
+      # TYPE dnsdist_server_tcpwritetimeouts counter
+      # HELP dnsdist_server_tcpcurrentconnections The number of current TCP connections
+      # TYPE dnsdist_server_tcpcurrentconnections gauge
+      # HELP dnsdist_server_tcpmaxconcurrentconnections The maximum number of concurrent TCP connections
+      # TYPE dnsdist_server_tcpmaxconcurrentconnections counter
+      # HELP dnsdist_server_tcptoomanyconcurrentconnections Number of times we had to enforce the maximum number of concurrent TCP connections
+      # TYPE dnsdist_server_tcptoomanyconcurrentconnections counter
+      # HELP dnsdist_server_tcpnewconnections The number of established TCP connections in total
+      # TYPE dnsdist_server_tcpnewconnections counter
+      # HELP dnsdist_server_tcpreusedconnections The number of times a TCP connection has been reused
+      # TYPE dnsdist_server_tcpreusedconnections counter
+      # HELP dnsdist_server_tcpavgqueriesperconn The average number of queries per TCP connection
+      # TYPE dnsdist_server_tcpavgqueriesperconn gauge
+      # HELP dnsdist_server_tcpavgconnduration The average duration of a TCP connection (ms)
+      # TYPE dnsdist_server_tcpavgconnduration gauge
+      # HELP dnsdist_server_tlsresumptions The number of times a TLS session has been resumed
+      # TYPE dnsdist_server_tlsresumptions counter
+      # HELP dnsdist_server_tcplatency Server's latency when answering TCP questions in milliseconds
+      # TYPE dnsdist_server_tcplatency gauge
+      dnsdist_server_status{server="9_9_9_9:443",address="9.9.9.9:443"} 1
+      dnsdist_server_queries{server="9_9_9_9:443",address="9.9.9.9:443"} 0
+      dnsdist_server_responses{server="9_9_9_9:443",address="9.9.9.9:443"} 0
+      dnsdist_server_noncompliantresponses{server="9_9_9_9:443",address="9.9.9.9:443"} 0
+      dnsdist_server_drops{server="9_9_9_9:443",address="9.9.9.9:443"} 0
+      dnsdist_server_latency{server="9_9_9_9:443",address="9.9.9.9:443"} 0
+      dnsdist_server_tcplatency{server="9_9_9_9:443",address="9.9.9.9:443"} 0
+      dnsdist_server_senderrors{server="9_9_9_9:443",address="9.9.9.9:443"} 0
+      dnsdist_server_outstanding{server="9_9_9_9:443",address="9.9.9.9:443"} 0
+      dnsdist_server_order{server="9_9_9_9:443",address="9.9.9.9:443"} 1
+      dnsdist_server_weight{server="9_9_9_9:443",address="9.9.9.9:443"} 1
+      dnsdist_server_tcpdiedsendingquery{server="9_9_9_9:443",address="9.9.9.9:443"} 0
+      dnsdist_server_tcpdiedreadingresponse{server="9_9_9_9:443",address="9.9.9.9:443"} 0
+      dnsdist_server_tcpgaveup{server="9_9_9_9:443",address="9.9.9.9:443"} 0
+      dnsdist_server_tcpreadtimeouts{server="9_9_9_9:443",address="9.9.9.9:443"} 0
+      dnsdist_server_tcpwritetimeouts{server="9_9_9_9:443",address="9.9.9.9:443"} 0
+      dnsdist_server_tcpconnecttimeouts{server="9_9_9_9:443",address="9.9.9.9:443"} 0
+      dnsdist_server_tcpcurrentconnections{server="9_9_9_9:443",address="9.9.9.9:443"} 0
+      dnsdist_server_tcpmaxconcurrentconnections{server="9_9_9_9:443",address="9.9.9.9:443"} 1
+      dnsdist_server_tcptoomanyconcurrentconnections{server="9_9_9_9:443",address="9.9.9.9:443"} 0
+      dnsdist_server_tcpnewconnections{server="9_9_9_9:443",address="9.9.9.9:443"} 19
+      dnsdist_server_tcpreusedconnections{server="9_9_9_9:443",address="9.9.9.9:443"} 0
+      dnsdist_server_tcpavgqueriesperconn{server="9_9_9_9:443",address="9.9.9.9:443"} 0.173831
+      dnsdist_server_tcpavgconnduration{server="9_9_9_9:443",address="9.9.9.9:443"} 3.92628
+      dnsdist_server_tlsresumptions{server="9_9_9_9:443",address="9.9.9.9:443"} 18
+      # HELP dnsdist_frontend_queries Amount of queries received by this frontend
+      # TYPE dnsdist_frontend_queries counter
+      # HELP dnsdist_frontend_noncompliantqueries Amount of non-compliant queries received by this frontend
+      # TYPE dnsdist_frontend_noncompliantqueries counter
+      # HELP dnsdist_frontend_responses Amount of responses sent by this frontend
+      # TYPE dnsdist_frontend_responses counter
+      # HELP dnsdist_frontend_tcpdiedreadingquery Amount of TCP connections terminated while reading the query from the client
+      # TYPE dnsdist_frontend_tcpdiedreadingquery counter
+      # HELP dnsdist_frontend_tcpdiedsendingresponse Amount of TCP connections terminated while sending a response to the client
+      # 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_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
+      # TYPE dnsdist_frontend_tcpcurrentconnections gauge
+      # HELP dnsdist_frontend_tcpmaxconcurrentconnections Maximum number of concurrent incoming TCP connections from clients
+      # TYPE dnsdist_frontend_tcpmaxconcurrentconnections counter
+      # HELP dnsdist_frontend_tcpavgqueriesperconnection The average number of queries per TCP connection
+      # TYPE dnsdist_frontend_tcpavgqueriesperconnection gauge
+      # HELP dnsdist_frontend_tcpavgconnectionduration The average duration of a TCP connection (ms)
+      # TYPE dnsdist_frontend_tcpavgconnectionduration gauge
+      # HELP dnsdist_frontend_tlsqueries Number of queries received by dnsdist over TLS, by TLS version
+      # TYPE dnsdist_frontend_tlsqueries counter
+      # HELP dnsdist_frontend_tlsnewsessions Amount of new TLS sessions negotiated
+      # TYPE dnsdist_frontend_tlsnewsessions counter
+      # HELP dnsdist_frontend_tlsresumptions Amount of TLS sessions resumed
+      # TYPE dnsdist_frontend_tlsresumptions counter
+      # HELP dnsdist_frontend_tlsunknownticketkeys Amount of attempts to resume TLS session from an unknown key (possibly expired)
+      # TYPE dnsdist_frontend_tlsunknownticketkeys counter
+      # HELP dnsdist_frontend_tlsinactiveticketkeys Amount of TLS sessions resumed from an inactive key
+      # TYPE dnsdist_frontend_tlsinactiveticketkeys counter
+      # HELP dnsdist_frontend_tlshandshakefailures Amount of TLS handshake failures
+      # TYPE dnsdist_frontend_tlshandshakefailures counter
+      dnsdist_frontend_queries{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
+      dnsdist_frontend_noncompliantqueries{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
+      dnsdist_frontend_responses{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
+      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_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
+      dnsdist_frontend_tcpavgqueriesperconnection{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
+      dnsdist_frontend_tcpavgconnectionduration{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
+      dnsdist_frontend_tlsnewsessions{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
+      dnsdist_frontend_tlsresumptions{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
+      dnsdist_frontend_tlsunknownticketkeys{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
+      dnsdist_frontend_tlsinactiveticketkeys{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
+      dnsdist_frontend_tlsqueries{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0",tls="tls10"} 0
+      dnsdist_frontend_tlsqueries{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0",tls="tls11"} 0
+      dnsdist_frontend_tlsqueries{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0",tls="tls12"} 0
+      dnsdist_frontend_tlsqueries{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0",tls="tls13"} 0
+      dnsdist_frontend_tlsqueries{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0",tls="unknown"} 0
+      dnsdist_frontend_tlshandshakefailures{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0",error="dhKeyTooSmall"} 0
+      dnsdist_frontend_tlshandshakefailures{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0",error="inappropriateFallBack"} 0
+      dnsdist_frontend_tlshandshakefailures{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0",error="noSharedCipher"} 0
+      dnsdist_frontend_tlshandshakefailures{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0",error="unknownCipherType"} 0
+      dnsdist_frontend_tlshandshakefailures{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0",error="unknownKeyExchangeType"} 0
+      dnsdist_frontend_tlshandshakefailures{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0",error="unknownProtocol"} 0
+      dnsdist_frontend_tlshandshakefailures{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0",error="unsupportedEC"} 0
+      dnsdist_frontend_tlshandshakefailures{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0",error="unsupportedProtocol"} 0
+      dnsdist_frontend_queries{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
+      dnsdist_frontend_noncompliantqueries{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
+      dnsdist_frontend_responses{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
+      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_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
+      dnsdist_frontend_tcpavgqueriesperconnection{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
+      dnsdist_frontend_tcpavgconnectionduration{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
+      dnsdist_frontend_tlsnewsessions{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
+      dnsdist_frontend_tlsresumptions{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
+      dnsdist_frontend_tlsunknownticketkeys{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
+      dnsdist_frontend_tlsinactiveticketkeys{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
+      dnsdist_frontend_tlsqueries{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0",tls="tls10"} 0
+      dnsdist_frontend_tlsqueries{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0",tls="tls11"} 0
+      dnsdist_frontend_tlsqueries{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0",tls="tls12"} 0
+      dnsdist_frontend_tlsqueries{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0",tls="tls13"} 0
+      dnsdist_frontend_tlsqueries{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0",tls="unknown"} 0
+      dnsdist_frontend_tlshandshakefailures{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0",error="dhKeyTooSmall"} 0
+      dnsdist_frontend_tlshandshakefailures{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0",error="inappropriateFallBack"} 0
+      dnsdist_frontend_tlshandshakefailures{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0",error="noSharedCipher"} 0
+      dnsdist_frontend_tlshandshakefailures{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0",error="unknownCipherType"} 0
+      dnsdist_frontend_tlshandshakefailures{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0",error="unknownKeyExchangeType"} 0
+      dnsdist_frontend_tlshandshakefailures{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0",error="unknownProtocol"} 0
+      dnsdist_frontend_tlshandshakefailures{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0",error="unsupportedEC"} 0
+      dnsdist_frontend_tlshandshakefailures{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0",error="unsupportedProtocol"} 0
+      dnsdist_frontend_queries{frontend="127.0.0.1:53",proto="UDP",thread="0"} 0
+      dnsdist_frontend_noncompliantqueries{frontend="127.0.0.1:53",proto="UDP",thread="0"} 0
+      dnsdist_frontend_responses{frontend="127.0.0.1:53",proto="UDP",thread="0"} 0
+      dnsdist_frontend_queries{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
+      dnsdist_frontend_noncompliantqueries{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
+      dnsdist_frontend_responses{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
+      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_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
+      dnsdist_frontend_tcpavgqueriesperconnection{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
+      dnsdist_frontend_tcpavgconnectionduration{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
+      # HELP dnsdist_frontend_http_connects Number of DoH TCP connections established to this frontend
+      # TYPE dnsdist_frontend_http_connects counter
+      # HELP dnsdist_frontend_doh_http_method_queries Number of DoH queries received by dnsdist, by HTTP method
+      # TYPE dnsdist_frontend_doh_http_method_queries counter
+      # HELP dnsdist_frontend_doh_http_version_queries Number of DoH queries received by dnsdist, by HTTP version
+      # TYPE dnsdist_frontend_doh_http_version_queries counter
+      # HELP dnsdist_frontend_doh_bad_requests Number of requests that could not be converted to a DNS query
+      # TYPE dnsdist_frontend_doh_bad_requests counter
+      # HELP dnsdist_frontend_doh_responses Number of responses sent, by type
+      # TYPE dnsdist_frontend_doh_responses counter
+      # HELP dnsdist_frontend_doh_version_status_responses Number of requests that could not be converted to a DNS query
+      # TYPE dnsdist_frontend_doh_version_status_responses counter
+      dnsdist_frontend_http_connects{frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_http_method_queries{method="get",frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_http_method_queries{method="post",frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_http_version_queries{version="1",frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_http_version_queries{version="2",frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_bad_requests{frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_responses{type="error",frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_responses{type="redirect",frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_responses{type="valid",frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_version_status_responses{httpversion="1",status="200",frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_version_status_responses{httpversion="1",status="400",frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_version_status_responses{httpversion="1",status="403",frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_version_status_responses{httpversion="1",status="500",frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_version_status_responses{httpversion="1",status="502",frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_version_status_responses{httpversion="1",status="other",frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_version_status_responses{httpversion="2",status="200",frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_version_status_responses{httpversion="2",status="400",frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_version_status_responses{httpversion="2",status="403",frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_version_status_responses{httpversion="2",status="500",frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_version_status_responses{httpversion="2",status="502",frontend="[::1]:443",thread="0"} 0
+      dnsdist_frontend_doh_version_status_responses{httpversion="2",status="other",frontend="[::1]:443",thread="0"} 0
+      # HELP dnsdist_pool_servers Number of servers in that pool
+      # TYPE dnsdist_pool_servers gauge
+      # HELP dnsdist_pool_active_servers Number of available servers in that pool
+      # TYPE dnsdist_pool_active_servers gauge
+      # HELP dnsdist_pool_cache_size Maximum number of entries that this cache can hold
+      # TYPE dnsdist_pool_cache_size gauge
+      # HELP dnsdist_pool_cache_entries Number of entries currently present in that cache
+      # TYPE dnsdist_pool_cache_entries gauge
+      # HELP dnsdist_pool_cache_hits Number of hits from that cache
+      # TYPE dnsdist_pool_cache_hits counter
+      # HELP dnsdist_pool_cache_misses Number of misses from that cache
+      # TYPE dnsdist_pool_cache_misses counter
+      # HELP dnsdist_pool_cache_deferred_inserts Number of insertions into that cache skipped because it was already locked
+      # TYPE dnsdist_pool_cache_deferred_inserts counter
+      # HELP dnsdist_pool_cache_deferred_lookups Number of lookups into that cache skipped because it was already locked
+      # TYPE dnsdist_pool_cache_deferred_lookups counter
+      # HELP dnsdist_pool_cache_lookup_collisions Number of lookups into that cache that triggered a collision (same hash but different entry)
+      # TYPE dnsdist_pool_cache_lookup_collisions counter
+      # HELP dnsdist_pool_cache_insert_collisions Number of insertions into that cache that triggered a collision (same hash but different entry)
+      # TYPE dnsdist_pool_cache_insert_collisions counter
+      # HELP dnsdist_pool_cache_ttl_too_shorts Number of insertions into that cache skipped because the TTL of the answer was not long enough
+      # TYPE dnsdist_pool_cache_ttl_too_shorts counter
+      # HELP dnsdist_pool_cache_cleanup_count_total Number of times the cache has been scanned to remove expired entries, if any
+      # TYPE dnsdist_pool_cache_cleanup_count_total counter
+      dnsdist_pool_servers{pool="_default_"} 1
+      dnsdist_pool_active_servers{pool="_default_"} 1
+      dnsdist_pool_cache_size{pool="_default_"} 100
       dnsdist_pool_cache_entries{pool="_default_"} 0
       dnsdist_pool_cache_hits{pool="_default_"} 0
       dnsdist_pool_cache_misses{pool="_default_"} 0
@@ -265,6 +612,16 @@ URL Endpoints
       dnsdist_pool_cache_lookup_collisions{pool="_default_"} 0
       dnsdist_pool_cache_insert_collisions{pool="_default_"} 0
       dnsdist_pool_cache_ttl_too_shorts{pool="_default_"} 0
+      dnsdist_pool_cache_cleanup_count_total{pool="_default_"} 0
+      # HELP dnsdist_rule_hits Number of hits of that rule
+      # TYPE dnsdist_rule_hits counter
+      # 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
+      # TYPE dnsdist_dynblocks_nmg_top_offenders_hits_per_second gauge
+      # 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
+      # TYPE dnsdist_dynblocks_smt_top_offenders_hits_per_second gauge
+      # HELP dnsdist_info Info from dnsdist, value is always 1
+      # TYPE dnsdist_info gauge
+      dnsdist_info{version="1.7.3"} 1
 
   **Example prometheus configuration**:
 
@@ -280,6 +637,45 @@ URL Endpoints
         username: dontcare
         password: yoursecret
 
+.. http:delete:: /api/v1/cache?pool=<pool-name>&name=<dns-name>[&type=<dns-type>][&suffix=]
+
+  .. versionadded:: 1.8.0
+
+  Allows removing entries from a cache. The pool to which the cache is associated should be specified in the ``pool`` parameter, and the name to remove in the ``name`` parameter.
+  By default only entries matching the exact name will be removed, but it is possible to remove all entries below that name by passing the ``suffix`` parameter set to any value.
+  By default entries for all types for the name are removed, but it is possible to only remove entries for a specific type by passing the ``type`` parameter set to the requested type. Supported values are DNS type names as a strings (``AAAA``), or numerical values (as either ``#64`` or ``TYPE64``).
+
+  **Example request**:
+
+   .. sourcecode:: http
+
+      DELETE /api/v1/cache?pool=&name=free.fr HTTP/1.1
+      Accept: */*
+      Accept-Encoding: gzip, deflate
+      Connection: keep-alive
+      Content-Length: 0
+      Host: localhost:8080
+      X-API-Key: supersecretAPIkey
+
+
+  **Example response**:
+   .. sourcecode:: http
+
+      HTTP/1.1 200 OK
+      Connection: close
+      Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'
+      Content-Type: application/json
+      Transfer-Encoding: chunked
+      X-Content-Type-Options: nosniff
+      X-Frame-Options: deny
+      X-Permitted-Cross-Domain-Policies: none
+      X-Xss-Protection: 1; mode=block
+
+      {
+          "count": "1",
+          "status": "purged"
+      }
+
 .. http:get:: /api/v1/servers/localhost
 
   Get a quick overview of several parameters.
@@ -397,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
 ~~~~~~~~~~~~
 
@@ -424,27 +830,76 @@ JSON Objects
   :property string type: "ConfigSetting"
   :property string value: The value for this setting
 
+.. json:object:: DoHFrontend
+
+  A description of a DoH bind dnsdist is listening on.
+
+  :property integer bad-requests: Number of requests that could not be converted to a DNS query
+  :property integer error-responses: Number of HTTP responses sent with a non-200 code
+  :property integer get-queries: Number of DoH queries received via the GET HTTP method
+  :property integer http-connects: Number of DoH TCP connections established to this frontend
+  :property integer http1-queries: Number of DoH queries received over HTTP/1
+  :property integer http1-x00-responses: Number of DoH responses sent, over HTTP/1, per response code (200, 400, 403, 500, 502)
+  :property integer http1-other-responses: Number of DoH responses sent, over HTTP/1, with another response code
+  :property integer http2-queries: Number of DoH queries received over HTTP/2
+  :property integer http2-x00-responses: Number of DoH responses sent, over HTTP/2, per response code (200, 400, 403, 500, 502)
+  :property integer http1-other-responses: Number of DoH responses sent, over HTTP/2, with another response code
+  :property integer post-queries: Number of DoH queries received via the POST HTTP method
+  :property integer redirect-responses: Number of HTTP redirect responses sent
+  :property integer valid-responses: Number of valid DoH (2xx) responses sent
+
 .. json:object:: Frontend
 
   A description of a bind dnsdist is listening on.
 
   :property string address: IP and port that is listened on
   :property integer id: Internal identifier
+  :property integer nonCompliantQueries: Amount of non-compliant queries received by this frontend
   :property integer queries: The number of received queries on this bind
-  :property boolean udp: true if this is a UDP bind
+  :property integer responses: Amount of responses sent by this frontend
   :property boolean tcp: true if this is a TCP bind
+  :property integer tcpAvgConnectionDuration: The average duration of a TCP connection (ms)
+  :property integer tcpAvgQueriesPerConnection: The average number of queries per TCP connection
+  :property integer tcpClientTimeouts: Amount of TCP connections terminated by a timeout while reading from the client
+  :property integer tcpCurrentConnections: Amount of current incoming TCP connections from clients
+  :property integer tcpDiedReadingQuery: Amount of TCP connections terminated while reading the query from the client
+  :property integer tcpDiedSendingResponse: Amount of TCP connections terminated while sending a response to the client
+  :property integer tcpDownstreamTimeouts: Amount of TCP connections terminated by a timeout while reading from the backend
+  :property integer tcpGaveUp: Amount of TCP connections terminated after too many attempts to get a connection to the backend
+  :property integer tcpMaxConcurrentConnections: Maximum number of concurrent incoming TCP connections from clients
+  :property integer tls10Queries: Number of queries received by dnsdist over TLS 1.0
+  :property integer tls11Queries: Number of queries received by dnsdist over TLS 1.1
+  :property integer tls12Queries: Number of queries received by dnsdist over TLS 1.2
+  :property integer tls13Queries: Number of queries received by dnsdist over TLS 1.3
+  :property integer tlsHandshakeFailuresDHKeyTooSmall: Amount of TLS connections where the client has negotiated a not strong enough diffie-hellman key during the TLS handshake
+  :property integer tlsHandshakeFailuresInappropriateFallBack: Amount of TLS connections where the client tried to negotiate an invalid, too old, TLS version
+  :property integer tlsHandshakeFailuresNoSharedCipher: Amount of TLS connections were no cipher shared by both the client and the server could been found during the TLS handshake
+  :property integer tlsHandshakeFailuresUnknownCipher: Amount of TLS connections where the client has tried to negotiate an unknown TLS cipher
+  :property integer tlsHandshakeFailuresUnknownKeyExchangeType: Amount of TLS connections where the client has tried to negotiate an unknown TLS key-exchange mechanism
+  :property integer tlsHandshakeFailuresUnknownProtocol: Amount of TLS connections where the client has tried to negotiate an unknown TLS version
+  :property integer tlsHandshakeFailuresUnsupportedEC: Amount of TLS connections where the client has tried to negotiate an unsupported elliptic curve
+  :property integer tlsHandshakeFailuresUnsupportedProtocol: Amount of TLS connections where the client has tried to negotiate a unsupported TLS version
+  :property integer tlsInactiveTicketKey: Amount of TLS sessions resumed from an inactive key
+  :property integer tlsNewSessions: Amount of new TLS sessions negotiated
+  :property integer tlsResumptions: Amount of TLS sessions resumed
+  :property integer tlsUnknownQueries: Number of queries received by dnsdist over an unknown TLS version
+  :property integer tlsUnknownTicketKey: Amount of attempts to resume TLS session from an unknown key (possibly expired)
+
+  :property string type: UDP, TCP, DoT or DoH
+  :property boolean udp: true if this is a UDP bind
 
 .. json:object:: Pool
 
   A description of a pool of backend servers.
 
   :property integer id: Internal identifier
+  :property integer cacheCleanupCount: Number of times that cache was scanned for expired entries, or just to remove entries because it is full
   :property integer cacheDeferredInserts: The number of times an entry could not be inserted in the associated cache, if any, because of a lock
   :property integer cacheDeferredLookups: The number of times an entry could not be looked up from the associated cache, if any, because of a lock
   :property integer cacheEntries: The current number of entries in the associated cache, if any
   :property integer cacheHits: The number of cache hits for the associated cache, if any
-  :property integer cacheLookupCollisions: The number of times an entry retrieved from the cache based on the query hash did not match the actual query
   :property integer cacheInsertCollisions: The number of times an entry could not be inserted into the cache because a different entry with the same hash already existed
+  :property integer cacheLookupCollisions: The number of times an entry retrieved from the cache based on the query hash did not match the actual query
   :property integer cacheMisses: The number of cache misses for the associated cache, if any
   :property integer cacheSize: The maximum number of entries in the associated cache, if any
   :property integer cacheTTLTooShorts: The number of times an entry could not be inserted into the cache because its TTL was set below the minimum threshold
@@ -457,8 +912,10 @@ JSON Objects
 
   :property string action: The action taken when the rule matches (e.g. "to pool abuse")
   :property dict action-stats: A list of statistics whose content varies depending on the kind of rule
+  :property integer creationOrder: The order in which a rule has been created, mostly used for automated tools
   :property integer id: The position of this rule
   :property integer matches: How many times this rule was hit
+  :property string name: The name assigned to this rule by the administrator, if any
   :property string rule: The matchers for the packet (e.g. "qname==bad-domain1.example., bad-domain2.example.")
   :property string uuid: The UUID of this rule
 
@@ -477,19 +934,43 @@ JSON Objects
 
   :property string address: The remote IP and port
   :property integer id: Internal identifier
-  :property integer latency: The current latency of this backend server
+  :property integer latency: The current latency of this backend server for UDP queries, in milliseconds
   :property string name: The name of this server
+  :property integer: nonCompliantResponses: Amount of non-compliant responses
   :property integer order: Order number
   :property integer outstanding: Number of currently outstanding queries
   :property [string] pools: The pools this server belongs to
+  :property string protocol: The protocol used by this server (Do53, DoT, DoH)
   :property integer qps: The current number of queries per second to this server
   :property integer qpsLimit: The configured maximum number of queries per second
   :property integer queries: Total number of queries sent to this backend
+  :property integer responses: Amount of responses received from this server
   :property integer reuseds: Number of queries for which a response was not received in time
   :property integer sendErrors: Number of network errors while sending a query to this server
   :property string state: The state of the server (e.g. "DOWN" or "up")
+  :property integer tcpAvgConnectionDuration: The average duration of a TCP connection (ms)
+  :property integer tcpAvgQueriesPerConnection: The average number of queries per TCP connection
+  :property integer tcpConnectTimeouts: The number of TCP connect timeouts
+  :property integer tcpCurrentConnections: The number of current TCP connections
+  :property integer tcpDiedReadingResponse: The number of TCP I/O errors while reading the response
+  :property integer tcpDiedSendingQuery: The number of TCP I/O errors while sending the query
+  :property integer tcpGaveUp: The number of TCP connections failing after too many attempts
+  :property integer tcpLatency: Server's latency when answering TCP questions in milliseconds
+  :property integer tcpMaxConcurrentConnections: The maximum number of concurrent TCP connections
+  :property integer tcpNewConnections: The number of established TCP connections in total
+  :property integer tcpReadTimeouts: The number of TCP read timeouts
+  :property integer tcpReusedConnections: The number of times a TCP connection has been reused
+  :property integer tcpTooManyConcurrentConnections: Number of times we had to enforce the maximum number of concurrent TCP connections
+  :property integer tcpWriteTimeouts: The number of TCP write timeouts
+  :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 per second by 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
 
@@ -498,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/AsyncQuery.png b/pdns/dnsdistdist/docs/imgs/AsyncQuery.png
new file mode 100644 (file)
index 0000000..b9c5ab1
Binary files /dev/null and b/pdns/dnsdistdist/docs/imgs/AsyncQuery.png differ
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/DNSDistLazyHealthChecks.png b/pdns/dnsdistdist/docs/imgs/DNSDistLazyHealthChecks.png
new file mode 100644 (file)
index 0000000..d05157d
Binary files /dev/null and b/pdns/dnsdistdist/docs/imgs/DNSDistLazyHealthChecks.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 35f2925c6cb23e1132b5416348a4fb6c11326670..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)
@@ -72,14 +74,18 @@ Release tarballs are available `from the downloads site <https://downloads.power
 
 The release tarballs have detached PGP signatures, signed by one of these PGP keys:
 
-* `D630 0CAB CBF4 69BB E392 E503 A208 ED4F 8AF5 8446 <https://pgp.mit.edu/pks/lookup?op=get&search=0xA208ED4F8AF58446>`__
-* `FBAE 0323 821C 7706 A5CA 151B DCF5 13FA 7EED 19F3 <https://pgp.mit.edu/pks/lookup?op=get&search=0xDCF513FA7EED19F3>`__
-* `1628 90D0 689D D12D D33E 4696 1C5E E990 D2E7 1575 <https://pgp.mit.edu/pks/lookup?op=get&search=0x1C5EE990D2E71575>`__
-* `B76C D467 1C09 68BA A87D E61C 5E50 715B F2FF E1A7 <https://pgp.mit.edu/pks/lookup?op=get&search=0x5E50715BF2FFE1A7>`__
-* `16E1 2866 B773 8C73 976A 5743 6FFC 3343 9B0D 04DF <https://pgp.mit.edu/pks/lookup?op=get&search=0x6FFC33439B0D04DF>`__
+* `FBAE 0323 821C 7706 A5CA 151B DCF5 13FA 7EED 19F3 <https://pgp.mit.edu/pks/lookup?op=get&search=0xDCF513FA7EED19F3>`_
+* `D630 0CAB CBF4 69BB E392 E503 A208 ED4F 8AF5 8446 <https://pgp.mit.edu/pks/lookup?op=get&search=0xA208ED4F8AF58446>`_
+* `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://dnsdist.org/_static/dnsdist-keyblock.asc <https://dnsdist.org/_static/dnsdist-keyblock.asc>`__.
 
+Older (1.0.x) releases can also be signed with one of the following keys:
+
+* `1628 90D0 689D D12D D33E 4696 1C5E E990 D2E7 1575 <https://pgp.mit.edu/pks/lookup?op=get&search=0x1C5EE990D2E71575>`_
+* `B76C D467 1C09 68BA A87D E61C 5E50 715B F2FF E1A7 <https://pgp.mit.edu/pks/lookup?op=get&search=0x5E50715BF2FFE1A7>`_
+
 * Untar the tarball and ``cd`` into the source directory
 * Run ``./configure``
 * Run ``make`` or ``gmake`` (on BSD)
@@ -116,14 +122,22 @@ Our ``configure`` script provides a fair number of options with regard to which
 * ``DISABLE_BUILTIN_HTML`` removes the built-in web pages
 * ``DISABLE_CARBON`` for carbon support
 * ``DISABLE_COMPLETION`` for completion support in the console
+* ``DISABLE_DELAY_PIPE`` removes the ability to delay UDP responses
 * ``DISABLE_DEPRECATED_DYNBLOCK`` for legacy dynamic blocks not using the new ``DynBlockRulesGroup`` interface
+* ``DISABLE_DYNBLOCKS`` disables the new dynamic block interface
 * ``DISABLE_ECS_ACTIONS`` to disable actions altering EDNS Client Subnet
+* ``DISABLE_FALSE_SHARING_PADDING`` to disable the padding of atomic counters, which is inserted to prevent false sharing but increases the memory use significantly
+* ``DISABLE_HASHED_CREDENTIALS`` to disable password-hashing support
 * ``DISABLE_LUA_WEB_HANDLERS`` for custom Lua web handlers support
+* ``DISABLE_OCSP_STAPLING`` for OCSP stapling
+* ``DISABLE_OPENSSL_ERROR_STRINGS`` to disable the loading of OpenSSL's error strings, reducing the memory use at the cost of human-readable error messages
+* ``DISABLE_NPN`` for Next Protocol Negotiation, superseded by ALPN
 * ``DISABLE_PROMETHEUS`` for prometheus
 * ``DISABLE_PROTOBUF`` for protocol-buffer support, including dnstap
 * ``DISABLE_RECVMMSG`` for ``recvmmsg`` support
 * ``DISABLE_RULES_ALTERING_QUERIES`` to remove rules altering the content of queries
 * ``DISABLE_SECPOLL`` for security polling
+* ``DISABLE_WEB_CACHE_MANAGEMENT`` to disable cache management via the API
 * ``DISABLE_WEB_CONFIG`` to disable accessing the configuration via the web interface
 
 Additionally several Lua bindings can be removed when they are not needed, as they increase the memory required during compilation and the size of the final binary:
@@ -140,3 +154,6 @@ Additionally several Lua bindings can be removed when they are not needed, as th
 * ``DISABLE_QPS_LIMITER_BINDINGS``
 * ``DISABLE_SUFFIX_MATCH_BINDINGS``
 * ``DISABLE_TOP_N_BINDINGS``
+
+Finally a build flag can be used to make use a single thread to handle all incoming UDP queries from clients, no matter how many :func:`addLocal` directives are present in the configuration. It also moves the task of accepting incoming TCP connections to the TCP workers themselves, removing the TCP acceptor threads. This option is destined to resource-constrained environments where dnsdist needs to listen on several addresses, over several interfaces, and one thread is enough to handle the traffic and therefore the overhead of using multiples threads for that task does not make sense.
+This option can be enabled by setting ``USE_SINGLE_ACCEPTOR_THREAD``.
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 122e731ba3ec5984f6beb40823065f339ab6d0ae..020f3be42902f94c0c839bffaff0c2ec34e384d1 100644 (file)
@@ -83,7 +83,10 @@ Listen Sockets
   .. versionchanged:: 1.6.0
     Added ``maxInFlight`` and ``maxConcurrentTCPConnections`` parameters.
 
-  Add to the list of listen addresses.
+  .. 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.
                       The default port is 53.
@@ -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,15 +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.
+
+  .. 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.
+  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.
 
@@ -140,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.
@@ -156,10 +166,66 @@ Listen Sockets
   * ``trustForwardedForHeader``: bool - Whether to parse any existing X-Forwarded-For header in the HTTP query and use the right-most value as the client source address and port, for ACL checks, rules, logging and so on. Default is false.
   * ``tcpListenQueueSize=SOMAXCONN``: int - Set the size of the listen queue. Default is ``SOMAXCONN``.
   * ``internalPipeBufferSize=0``: int - Set the size in bytes of the internal buffer of the pipes used internally to pass queries and responses between threads. Requires support for ``F_SETPIPE_SZ`` which is present in Linux since 2.6.35. The actual size might be rounded up to a multiple of a page size. 0 means that the OS default size is used. The default value is 0, except on Linux where it is 1048576 since 1.6.0.
-  * ``exactPathMatching=true``: bool - Whether to do exact path matching of the query path against the paths configured in ``urls`` (true, the default since 1.5.0) or to accepts sub-paths (false, and was the default before 1.5.0).
+  * ``exactPathMatching=true``: bool - Whether to do exact path matching of the query path against the paths configured in ``urls`` (true, the default since 1.5.0) or to accepts sub-paths (false, and was the default before 1.5.0). This option was introduced in 1.6.0.
   * ``maxConcurrentTCPConnections=0``: int - Maximum number of concurrent incoming TCP connections. The default is 0 which means unlimited.
   * ``releaseBuffers=true``: bool - Whether OpenSSL should release its I/O buffers when a connection goes idle, saving roughly 35 kB of memory per connection.
   * ``enableRenegotiation=false``: bool - Whether secure TLS renegotiation should be enabled. Disabled by default since it increases the attack surface and is seldom used for DNS.
+  * ``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])
 
@@ -172,14 +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:
@@ -193,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.
@@ -206,7 +276,13 @@ Listen Sockets
   * ``maxConcurrentTCPConnections=0``: int - Maximum number of concurrent incoming TCP connections. The default is 0 which means unlimited.
   * ``releaseBuffers=true``: bool - Whether OpenSSL should release its I/O buffers when a connection goes idle, saving roughly 35 kB of memory per connection.
   * ``enableRenegotiation=false``: bool - Whether secure TLS renegotiation should be enabled (OpenSSL only, the GnuTLS provider does not support it). Disabled by default since it increases the attack surface and is seldom used for DNS.
-  * ``tlsAsyncMode=false``: bool - Whether to enable experimental asynchronous TLS I/O operations if OpenSSL is used as the TLS provider and an asynchronous capable SSL engine is loaded. See also :func:`loadTLSEngine` to load the engine.
+  * ``tlsAsyncMode=false``: bool - Whether to enable experimental asynchronous TLS I/O operations if OpenSSL is used as the TLS implementation and an asynchronous capable SSL engine (or provider) is loaded. See also :func:`loadTLSEngine` or :func:`loadTLSProvider` to load the engine (or provider).
+  * ``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])
 
@@ -325,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.
 
@@ -358,6 +434,9 @@ Webserver configuration
     The optional ``password`` and ``apiKey`` parameters now accept hashed passwords.
     The optional ``hashPlaintextCredentials`` parameter has been added.
 
+  .. versionchanged:: 1.8.0
+    ``apiRequiresAuthentication``, ``dashboardRequiresAuthentication`` optional parameters added.
+
   Setup webserver configuration. See :func:`webserver`.
 
   :param table options: A table with key: value pairs with webserver options.
@@ -368,6 +447,8 @@ Webserver configuration
   * ``apiKey=newKey``: string - Changes the API Key (set to an empty string do disable it). Since 1.7.0 the key should be hashed and salted via the :func:`hashPassword` command.
   * ``customHeaders={[str]=str,...}``: map of string - Allows setting custom headers and removing the defaults.
   * ``acl=newACL``: string - List of IP addresses, as a string, that are allowed to open a connection to the web server. Defaults to "127.0.0.1, ::1".
+  * ``apiRequiresAuthentication``: bool - Whether access to the API (/api endpoints) require a valid API key. Defaults to true.
+  * ``dashboardRequiresAuthentication``: bool - Whether access to the internal dashboard requires a valid password. Defaults to true.
   * ``statsRequireAuthentication``: bool - Whether access to the statistics (/metrics and /jsonstat endpoints) require a valid password or API key. Defaults to true.
   * ``maxConcurrentConnections``: int - The maximum number of concurrent web connections, or 0 which means an unlimited number. Defaults to 100.
   * ``hashPlaintextCredentials``: bool - Whether passwords and API keys provided in plaintext should be hashed during startup, to prevent the plaintext versions from staying in memory. Doing so increases significantly the cost of verifying credentials. Defaults to false.
@@ -456,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
 
@@ -499,10 +580,27 @@ Ringbuffers
 
 .. function:: setRingBuffersLockRetries(num)
 
+  .. deprecated:: 1.8.0
+    Deprecated in 1.8.0 in favor of :func:`setRingBuffersOptions` which provides more options.
+
   Set the number of shards to attempt to lock without blocking before giving up and simply blocking while waiting for the next shard to be available
 
   :param int num: The maximum number of attempts. Defaults to 5 if there is more than one shard, 0 otherwise.
 
+.. function:: setRingBuffersOptions(options)
+
+  .. versionadded:: 1.8.0
+
+  Set the rings buffers configuration
+
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``lockRetries``: int - Set the number of shards to attempt to lock without blocking before giving up and simply blocking while waiting for the next shard to be available. Default to 5 if there is more than one shard, 0 otherwise
+  * ``recordQueries``: boolean - Whether to record queries in the ring buffers. Default is true. Note that :func:`grepq`, several top* commands (:func:`topClients`, :func:`topQueries`, ...) and the :doc:`Dynamic Blocks <../guides/dynblocks>` require this to be enabled.
+  * ``recordResponses``: boolean - Whether to record responses in the ring buffers. Default is true. Note that :func:`grepq`, several top* commands (:func:`topResponses`, :func:`topSlow`, ...) and the :doc:`Dynamic Blocks <../guides/dynblocks>` require this to be enabled.
+
 .. function:: setRingBuffersSize(num [, numberOfShards])
 
   .. versionchanged:: 1.6.0
@@ -533,7 +631,13 @@ Servers
     Added ``addXForwardedHeaders``, ``caStore``, ``checkTCP``, ``ciphers``, ``ciphers13``, ``dohPath``, ``enableRenegotiation``, ``releaseBuffers``, ``subjectName``, ``tcpOnly``, ``tls`` and ``validateCertificates`` to server_table.
 
   .. versionchanged:: 1.8.0
-    Added ``autoUpgrade``, ``autoUpgradeDoHKey``, ``autoUpgradeInterval``, ``autoUpgradeKeep``, ``autoUpgradePool`` and ``subjectAddr`` to server_table.
+    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::
 
@@ -543,66 +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.
-    })
-
-  :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
 
@@ -697,17 +819,27 @@ A server object returned by :func:`getServer` can be manipulated with these func
 
     :param bool status: Set the initial status of the server to ``up`` (true) or ``down`` (false) instead of using the last known status
 
+  .. method:: Server:setDown()
+
+    Set the server in a ``DOWN`` state.
+    The server will not receive queries and the health checks are disabled.
+
+  .. method:: Server:setLazyAuto([status])
+
+    .. versionadded:: 1.8.0
+
+    Set the server in the 'lazy' health-check mode.
+    This will enable health check queries, but only after a configurable threshold of failing regular queries has been reached and
+    only for a short time. See :ref:`Healthcheck` for a more detailed explanation.
+
+    :param bool status: Set the initial status of the server to ``up`` (true) or ``down`` (false) instead of using the last known status
+
   .. method:: Server:setQPS(limit)
 
     Limit the queries per second for this server.
 
     :param int limit: The maximum number of queries per second
 
-  .. method:: Server:setDown()
-
-    Set the server in an ``DOWN`` state.
-    The server will not receive queries and the health checks are disabled
-
   .. method:: Server:setUp()
 
     Set the server in an ``UP`` state.
@@ -750,6 +882,12 @@ Servers that are not assigned to a specific pool get assigned to the default poo
 
   :param string name: The name of the pool
 
+.. function:: getPoolNames() -> [ table of names]
+
+  .. versionadded:: 1.8.0
+
+  Returns a table of all pool names
+
 .. function:: showPools()
 
    Display the name, associated cache, server policy and associated servers for every pool.
@@ -819,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
@@ -835,8 +976,9 @@ See :doc:`../guides/cache` for a how to.
   * ``parseECS=false``: bool - Whether any EDNS Client Subnet option present in the query should be extracted and stored to be able to detect hash collisions involving queries with the same qname, qtype and qclass but a different incoming ECS value. Enabling this option adds a parsing cost and only makes sense if at least one backend might send different responses based on the ECS value, so it's disabled by default. Enabling this option is required for the 'zero scope' option to work
   * ``staleTTL=60``: int - When the backend servers are not reachable, and global configuration ``setStaleCacheEntriesTTL`` is set appropriately, TTL that will be used when a stale cache entry is returned.
   * ``temporaryFailureTTL=60``: int - On a SERVFAIL or REFUSED from the backend, cache for this amount of seconds..
-  * ``cookieHashing=false``: bool - Whether EDNS Cookie values will be hashed, resulting in separate entries for different cookies in the packet cache. This is required if the backend is sending answers with EDNS Cookies, otherwise a client might receive an answer with the wrong cookie.
-  * ``skipOptions={}``: Extra list of EDNS option codes to skip when hashing the packet (if ``cookieHashing`` above is false, EDNS cookie option number will already be added to this list).
+  * ``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
 
@@ -865,6 +1007,22 @@ See :doc:`../guides/cache` for a how to.
     :param int qtype: The type to expunge, can be a pre-defined :ref:`DNSQType`
     :param bool suffixMatch: When set to true, remove all entries under ``name``
 
+  .. method:: PacketCache:getAddressListByDomain(domain)
+
+    .. versionadded:: 1.8.0
+
+    This method looks up the answers present in the cache for the supplied domain, and returns the list of addresses present in the answer section of these answers (in A records for IPv4 addresses, and AAAA records for IPv6 ones). The addresses are returned as a list of :class:`ComboAddress` objects.
+
+    :param DNSName domain: The domain to look for
+
+  .. method:: PacketCache:getDomainListByAddress(addr)
+
+    .. versionadded:: 1.8.0
+
+    Return a list of domains, as :class:`DNSName` objects, for which an answer is present in the cache and has a corresponding A record (for IPv4 addresses) or AAAA record (for IPv6 addresses) in the answer section.
+
+    :param ComboAddress addr: The address to look for
+
   .. method:: PacketCache:getStats()
 
     .. versionadded:: 1.4.0
@@ -962,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)
 
@@ -983,9 +1165,28 @@ Status, Statistics and More
 
   .. versionadded:: 1.8.0
 
-  Return the list of network interfaces configured on the system, as strings
+  Return the list of network interfaces configured on the system, as strings.
   This function requires support for ``getifaddrs``, which is known to be present on FreeBSD, Linux, and OpenBSD at least.
 
+.. function:: getListOfRangesOfNetworkInterface(itf)
+
+  .. versionadded:: 1.8.0
+
+  Return the list of network ranges configured on a given network interface, as strings.
+  This function requires support for ``getifaddrs``, which is known to be present on FreeBSD, Linux, and OpenBSD at least.
+
+  :param str itf: The name of the network interface
+
+.. function:: getMACAddress(ip) -> str
+
+  .. versionadded:: 1.8.0
+
+  Return the link-level address (MAC) corresponding to the supplied neighbour IP address, if known by the kernel.
+  The link-level address is returned as a raw binary string. An empty string is returned if no matching entry has been found.
+  This function is only implemented on Linux.
+
+  :param str ip: The IP address, IPv4 or IPv6, to look up the corresponding link-level address for.
+
 .. function:: getOutgoingTLSSessionCacheSize()
 
   .. versionadded:: 1.7.0
@@ -1012,7 +1213,15 @@ Status, Statistics and More
 
   Return the cache-hit response rules that matched the most.
 
-  :param int top: How many response rules to return.
+  :param int top: How many response rules to return. Default is 10.
+
+.. function:: getTopCacheInsertedResponseRules([top])
+
+  .. versionadded:: 1.8.0
+
+  Return the cache-inserted response rules that matched the most.
+
+  :param int top: How many response rules to return. Default is 10.
 
 .. function:: getTopResponseRules([top])
 
@@ -1020,7 +1229,7 @@ Status, Statistics and More
 
   Return the response rules that matched the most.
 
-  :param int top: How many response rules to return.
+  :param int top: How many response rules to return. Default is 10.
 
 .. function:: getTopRules([top])
 
@@ -1028,7 +1237,7 @@ Status, Statistics and More
 
   Return the rules that matched the most.
 
-  :param int top: How many rules to return.
+  :param int top: How many rules to return. Default is 10.
 
 .. function:: getTopSelfAnsweredRules([top])
 
@@ -1036,10 +1245,13 @@ Status, Statistics and More
 
   Return the self-answered rules that matched the most.
 
-  :param int top: How many rules to return.
+  :param int top: How many rules to return. Default is 10.
+
+.. function:: grepq(selector[, num [, options]])
+              grepq(selectors[, num [, options]])
 
-.. function:: grepq(selector[, num])
-              grepq(selectors[, num])
+  .. versionchanged:: 1.9.0
+    ``options`` optional parameter table added.
 
   Prints the last ``num`` queries and responses matching ``selector`` or ``selectors``.
   Queries and responses are accounted in separate ring buffers, and answers from the packet cache are not stored in the response ring buffer.
@@ -1053,7 +1265,47 @@ 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
+
+  Set whether log messages issued at the verbose level should be logged. This is turned off by default.
+
+  :param bool verbose: Set to true if you want to enable verbose logging
+
+.. function:: getVerbose()
+
+  .. versionadded:: 1.8.0
+
+  Get whether log messages issued at the verbose level should be logged. This is turned off by default.
 
 .. function:: setVerboseHealthChecks(verbose)
 
@@ -1061,6 +1313,17 @@ Status, Statistics and More
 
   :param bool verbose: Set to true if you want to enable health check errors logging
 
+.. function:: setVerboseLogDestination(dest)
+
+  .. versionadded:: 1.8.0
+
+  Set a destination file to write the 'verbose' log messages to, instead of sending them to syslog and/or the standard output which is the default.
+  Note that these messages will no longer be sent to syslog or the standard output once this option has been set.
+  There is no rotation or file size limit.
+  Only use this feature for debugging under active operator control.
+
+  :param str dest: The destination file
+
 .. function:: showBinds()
 
   Print a list of all the current addresses and ports dnsdist is listening on, also called ``frontends``
@@ -1071,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
@@ -1146,6 +1421,19 @@ Status, Statistics and More
 
   * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
 
+.. function:: topCacheInsertedResponseRules([top [, options]])
+
+  .. versionadded:: 1.8.0
+
+  This function shows the cache-inserted response rules that matched the most.
+
+  :param int top: How many rules to show.
+  :param table options: A table with key: value pairs with display options.
+
+  Options:
+
+  * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
+
 .. function:: topClients([num])
 
   Print the top ``num`` clients sending the most queries over length of ringbuffer
@@ -1222,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
@@ -1238,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.
@@ -1258,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
@@ -1316,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
@@ -1345,6 +1712,19 @@ 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]])
 
     Adds a rate-limiting rule for responses of code ``rcode``, equivalent to:
@@ -1367,13 +1747,13 @@ faster than the existing rules.
     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]])
 
@@ -1411,9 +1791,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.
@@ -1429,7 +1812,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
@@ -1476,6 +1859,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.
@@ -1485,44 +1876,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 complete name of that node, ie 'www.powerdns.com'.
+    The number of children of that node.
 
-  .. attribute:: StatNode.labelsCount
+.. class:: StatNodeStats
+
+  Represent the metrics for a given node, for the visitor functions used with :meth:`DynBlockRulesGroup:setSuffixMatchRule` and :meth:`DynBlockRulesGroup:setSuffixMatchRuleFFI`.
+
+  .. attribute:: StatNodeStats.bytes
 
-    The number of labels in that node, for example 3 for 'www.powerdns.com'.
+    The number of bytes for all responses returned for that node.
+
+  .. attribute:: StatNodeStats.drops
+
+    The number of drops for that node.
 
-  .. attribute:: StatNode.noerrors
+  .. 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
 ~~~~~~~~~~~~~~~
 
@@ -1551,6 +1952,20 @@ If you are looking for exact name matching, your might want to consider using a
     :param string name: The suffix to add to the set.
     :param table name: The suffixes to add to the set. Elements of the table should be of the same type, either DNSName or string.
 
+  .. method:: SuffixMatchNode:check(name) -> bool
+
+    Return true if the given name is a sub-domain of one of those in the set, and false otherwise.
+
+    :param DNSName name: The name to test against the set.
+
+  .. method:: SuffixMatchNode:getBestMatch(name) -> DNSName
+
+    .. versionadded:: 1.8.0
+
+    Returns the best match for the supplied name, or nil if there was no match.
+
+    :param DNSName name: The name to look up.
+
   .. method:: SuffixMatchNode:remove(name)
 
     .. versionadded:: 1.5.0
@@ -1561,16 +1976,10 @@ If you are looking for exact name matching, your might want to consider using a
     :param string name: The suffix to remove from the set.
     :param table name: The suffixes to remove from the set. Elements of the table should be of the same type, either DNSName or string.
 
-  .. method:: SuffixMatchNode:check(name) -> bool
-
-    Return true if the given name is a sub-domain of one of those in the set, and false otherwise.
-
-    :param DNSName name: The name to test against the set.
-
 Outgoing TLS tickets cache management
 -------------------------------------
 
-Since 1.7, dnsdist supports securing the connection toward backends using DNS over TLS. For these connections, it keeps a cache of TLS tickets to be able to resume a TLS session quickly. By default that cache contains up to 20 TLS tickets per-backend, is cleaned up every every 60s, and TLS tickets expire if they have not been used after 600 seconds.
+Since 1.7, dnsdist supports securing the connection toward backends using DNS over TLS. For these connections, it keeps a cache of TLS tickets to be able to resume a TLS session quickly. By default that cache contains up to 20 TLS tickets per-backend, is cleaned up every 60s, and TLS tickets expire if they have not been used after 600 seconds.
 These values can be set at configuration time via:
 
 .. function:: setOutgoingTLSSessionsCacheMaxTicketsPerBackend(num)
@@ -1600,20 +2009,78 @@ 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
+
+  Return the current time, in whole seconds and nanoseconds since epoch.
+
+  :returns: A timespec object, see :ref:`timespec`
+
+.. function:: getResolvers(path)
+
+  .. versionadded:: 1.8.0
+
+  This function can be used to get a Lua table of name servers from a file in the resolv.conf format.
+
+  :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)
 
-  .. versionadded:: 1.7.0
+  .. versionadded:: 1.8.0
 
   This function, if it exists, is called when a separate thread (made with :func:`newThread`) calls :func:`submitToMainThread`.
 
 .. function:: newThread(code)
 
-  .. versionadded:: 1.7.0
+  .. versionadded:: 1.8.0
 
   Spawns a separate thread running the supplied code.
   Code is supplied as a string, not as a function object.
@@ -1621,7 +2088,7 @@ Other functions
 
 .. function:: submitToMainThread(cmd, dict)
 
-  .. versionadded:: 1.7.0
+  .. versionadded:: 1.8.0
 
   Must be called from a separate thread (made with :func:`newThread`), submits data to the main thread by calling :func:`threadmessage` in it.
   If no ``threadmessage`` receiver is present in the main thread, ``submitToMainThread`` logs an error but returns normally.
@@ -1651,6 +2118,14 @@ Other functions
 
   :param int size: The maximum size in bytes (default is 512)
 
+.. function:: setTCPFastOpenKey(key)
+
+  .. versionadded:: 1.8.0
+
+  Set the supplied ``TCP Fast Open`` key on all frontends. This can for example be used to allow all dnsdist instances in an anycast cluster to use the same ``TCP Fast Open`` key, reducing round-trips.
+
+  :param string key: The format of the key can be found in ``/proc/sys/net/ipv4/tcp_fastopen_key``
+
 .. function:: makeIPCipherKey(password) -> string
 
   .. versionadded:: 1.4.0
@@ -1671,6 +2146,12 @@ Other functions
   :param int numberOfDaysOfValidity: Number of days this OCSP response should be valid.
   :param int numberOfMinutesOfValidity: Number of minutes this OCSP response should be valid, in addition to the number of days.
 
+.. function:: getRingEntries()
+
+  .. versionadded:: 1.8.0
+
+  Return a list of all the entries, queries and responses alike, that are present in the in-memory ring buffers, as :class:`LuaRingEntry` objects.
+
 .. function:: loadTLSEngine(engineName [, defaultString])
 
   .. versionadded:: 1.8.0
@@ -1682,26 +2163,37 @@ Other functions
   :param string engineName: The name of the engine to load.
   :param string defaultString: The default string to pass to the engine. The exact value depends on the engine but represents the algorithms to register with the engine, as a list of  comma-separated keywords. For example "RSA,EC,DSA,DH,PKEY,PKEY_CRYPTO,PKEY_ASN1".
 
+.. function:: loadTLSProvider(providerName)
+
+  .. versionadded:: 1.8.0
+
+  Load the OpenSSL provider named ``providerName``. Providers can be used to accelerate cryptographic operations, like for example Intel QAT.
+  At the moment up to a maximum of 32 loaded providers are supported, and that support is experimental.
+  Note that :func:`loadTLSProvider` is only available when building against OpenSSL version >= 3.0 and with the `--enable-tls-provider` configure flag on. In other cases, :func:`loadTLSEngine` should be used instead.
+  Some providers might actually degrade performance unless the TLS asynchronous mode of OpenSSL is enabled. To enable it see the ``tlsAsyncMode`` parameter on :func:`addTLSLocal`.
+
+  :param string providerName: The name of the provider to load.
+
 .. function:: newTLSCertificate(pathToCert[, options])
 
   .. 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 PCKS12 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 PCKS12 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 PCKS12 file
+    newTLSCertificate("path/to/domain.p12", {password="passphrase"}) -- use a password protected ``PKCS12`` file
 
 DOHFrontend
 ~~~~~~~~~~~
@@ -1712,6 +2204,12 @@ DOHFrontend
 
   This object represents an address and port dnsdist is listening on for DNS over HTTPS queries.
 
+  .. method:: DOHFrontend:getAddressAndPort() -> string
+
+     .. versionadded:: 1.7.1
+
+     Return the address and port this frontend is listening on.
+
   .. method:: DOHFrontend:loadNewCertificatesAndKeys(certFile(s), keyFile(s))
 
      .. versionadded:: 1.6.1
@@ -1756,6 +2254,111 @@ 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
+~~~~~~~~~~~~
+
+.. class:: LuaRingEntry
+
+  .. versionadded:: 1.8.0
+
+  This object represents an entry from the in-memory ring buffers, query or response.
+
+  .. attribute:: LuaRingEntry.backend
+
+    If this entry is a response, the backend from which it has been received as a :ref:`ComboAddress`.
+
+.. attribute:: LuaRingEntry.dnsheader
+
+    The :ref:`DNSHeader` of this entry.
+
+  .. attribute:: LuaRingEntry.isResponse
+
+    Whether this entry is a response (true) or a request (false).
+
+  .. attribute:: LuaRingEntry.macAddress
+
+    The MAC address of the client as a string, if available.
+
+  .. attribute:: LuaRingEntry.protocol
+
+    The protocol (Do53 UDP, Do53 TCP, DoT, DoH, ...) over which this entry was received, as a string.
+
+  .. attribute:: LuaRingEntry.qname
+
+    The qname of this entry as a :ref:`DNSName`.
+
+  .. attribute:: LuaRingEntry.qtype
+
+    The qtype of this entry as an integer.
+
+  .. attribute:: LuaRingEntry.requestor
+
+    The requestor (client IP) of this entry as a :ref:`ComboAddress`.
+
+  .. attribute:: LuaRingEntry.size
+
+    The size of the DNS payload of that entry, in bytes.
+
+.. attribute:: LuaRingEntry.usec
+
+    The response time (elapsed time between the request was received and the response sent) in milliseconds.
+
+.. attribute:: LuaRingEntry.when
+
+    The timestamp of this entry, as a :ref:`timespec`.
+
+.. _timespec:
+
+timespec
+~~~~~~~~
+
+.. class:: timespec
+
+  .. versionadded:: 1.8.0
+
+  This object represents a timestamp in the timespec format.
+
+  .. attribute:: timespec.tv_sec
+
+    Number of seconds elapsed since Unix epoch.
+
+  .. attribute:: timespec.tv_nsec
+
+    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
 ~~~~~~~~~~
 
@@ -1781,6 +2384,12 @@ TLSFrontend
 
   This object represents the configuration of a listening frontend for DNS over TLS queries. To each frontend is associated a TLSContext.
 
+  .. method:: TLSFrontend:getAddressAndPort() -> string
+
+     .. versionadded:: 1.7.1
+
+     Return the address and port this frontend is listening on.
+
   .. method:: TLSFrontend:loadNewCertificatesAndKeys(certFile(s), keyFile(s))
 
      Create and switch to a new TLS context using the same options than were passed to the corresponding `addTLSLocal()` directive, but loading new certificates and keys from the selected files, replacing the existing ones.
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
diff --git a/pdns/dnsdistdist/docs/reference/custommetrics.rst b/pdns/dnsdistdist/docs/reference/custommetrics.rst
new file mode 100644 (file)
index 0000000..46cb5b8
--- /dev/null
@@ -0,0 +1,69 @@
+Custom Metrics
+=====================================
+
+You can define your own metrics that can be updated using Lua.
+
+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:
+
+ * manipulate counters using :func:`incMetric` and  :func:`decMetric`
+ * update a gauge using :func:`setMetric`
+
+.. function:: declareMetric(name, type, description [, prometheusName]) -> bool
+
+  .. 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
+  :param str type: The desired type in ``gauge`` or ``counter``
+  :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 [, step]) -> int
+
+  .. versionadded:: 1.8.0
+
+  .. 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
+
+  .. 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
+
+  .. versionadded:: 1.8.0
+
+  Get metric value
+
+  :param str name: The name of the metric
+
+.. function:: setMetric(name, value) -> double
+
+  .. versionadded:: 1.8.0
+
+  Set the new value, will issue an error if the metric is not declared or not a ``gauge``
+  Return the new value
+
+  :param str name: The name of the metric
+  :param double value: The new value
index ee5ed9ede056be57878ff9102cb3e45d91ceb2c5..02dc7ec685aa5cc22c12a8e6a84f3c3e50e6e167 100644 (file)
@@ -139,7 +139,7 @@ Context
 
   .. method:: DNSCryptContext:addNewCertificate(cert, key[, active])
 
-    Add a new certificate to the the given context. Active certificates are advertised to
+    Add a new certificate to the given context. Active certificates are advertised to
     clients, inactive ones are not.
 
     :param DNSCryptCert cert: The certificate to add to the context
index 6bb13099cd2de36b71d4bc5e26862239365db45b..01b894299a08655e94cf12f46e76418089412dba 100644 (file)
@@ -50,6 +50,17 @@ Functions and methods of a ``DNSName``
 
     :param DNSName name: The name to check against
 
+  .. method:: DNSName:makeRelative(name) -> DNSName
+
+    .. versionadded:: 1.8.0
+
+    Provided that the current name is part of the supplied name, returns a new DNSName
+    composed only of the labels that are below the supplied name (ie making www.powerdns.com
+    relative to powerdns.com would return only wwww)
+    Otherwise an empty (unset) DNSName is returned.
+
+    :param DNSName name: The name to make us relative against
+
   .. method:: DNSName:toDNSString() -> string
 
     Returns a wire format form of the DNSName, suitable for usage in :func:`SpoofRawAction`.
@@ -59,6 +70,12 @@ Functions and methods of a ``DNSName``
 
     Returns a human-readable form of the DNSName.
 
+  .. method:: DNSName:toStringNoDot() -> string
+
+    .. versionadded:: 1.8.0
+
+    Returns a human-readable form of the DNSName, without the trailing dot.
+
   .. method:: DNSName:wirelength() -> int
 
     Returns the length in bytes of the DNSName as it would be on the wire.
diff --git a/pdns/dnsdistdist/docs/reference/dnsparser.rst b/pdns/dnsdistdist/docs/reference/dnsparser.rst
new file mode 100644 (file)
index 0000000..5661b7e
--- /dev/null
@@ -0,0 +1,128 @@
+DNS Parser
+==========
+
+Since 1.8.0, dnsdist contains a limited DNS parser class that can be used to inspect
+the content of DNS queries and responses in Lua.
+
+The first step is to get the content of the DNS payload into a Lua string,
+for example using :meth:`DNSQuestion:getContent`, or :meth:`DNSResponse:getContent`,
+and then to create a :class:`DNSPacketOverlay` object:
+
+.. code-block:: lua
+
+  function dumpPacket(dq)
+    local packet = dq:getContent()
+    local overlay = newDNSPacketOverlay(packet)
+    print(overlay.qname)
+    print(overlay.qtype)
+    print(overlay.qclass)
+    local count = overlay:getRecordsCountInSection(DNSSection.Answer)
+    print(count)
+    for idx=0, count-1 do
+      local record = overlay:getRecord(idx)
+      print(record.name)
+      print(record.type)
+      print(record.class)
+      print(record.ttl)
+      print(record.place)
+      print(record.contentLength)
+      print(record.contentOffset)
+    end
+    return DNSAction.None
+  end
+
+  addAction(AllRule(), LuaAction(dumpPacket))
+
+
+.. function:: newDNSPacketOverlay(packet) -> DNSPacketOverlay
+
+  .. versionadded:: 1.8.0
+
+  Returns a DNSPacketOverlay
+
+  :param str packet: The DNS payload
+
+.. _DNSPacketOverlay:
+
+DNSPacketOverlay
+----------------
+
+.. class:: DNSPacketOverlay
+
+  .. versionadded:: 1.8.0
+
+  The DNSPacketOverlay object has several attributes, all of them read-only:
+
+  .. attribute:: DNSPacketOverlay.qname
+
+    The qname of this packet, as a :ref:`DNSName`.
+
+  .. attribute:: DNSPacketOverlay.qtype
+
+    The type of the query in this packet.
+
+  .. attribute:: DNSPacketOverlay.qclass
+
+    The class of the query in this packet.
+
+  .. attribute:: DNSPacketOverlay.dh
+
+  It also supports the following methods:
+
+  .. method:: DNSPacketOverlay:getRecordsCountInSection(section) -> int
+
+    Returns the number of records in the ANSWER (1), AUTHORITY (2) and
+    ADDITIONAL (3) :ref:`DNSSection` of this packet. The number of records in the
+    QUESTION (0) is always set to 0, look at the dnsheader if you need
+    the actual qdcount.
+
+    :param int section: The section, see above
+
+  .. method:: DNSPacketOverlay:getRecord(idx) -> DNSRecord
+
+    Get the record at the requested position. The records in the
+    QUESTION sections are not taken into account, so the first record
+    in the answer section would be at position 0.
+
+    :param int idx: The position of the requested record
+
+
+.. _DNSRecord:
+
+DNSRecord object
+==================
+
+.. class:: DNSRecord
+
+  .. versionadded:: 1.8.0
+
+  This object represents an unparsed DNS record, as returned by the :ref:`DNSPacketOverlay` class. It has several attributes, all of them read-only:
+
+  .. attribute:: DNSRecord.name
+
+    The name of this record, as a :ref:`DNSName`.
+
+  .. attribute:: DNSRecord.type
+
+    The type of this record.
+
+  .. attribute:: DNSRecord.class
+
+    The class of this record.
+
+  .. attribute:: DNSRecord.ttl
+
+    The TTL of this record.
+
+  .. attribute:: DNSRecord.place
+
+    The place (section) of this record.
+
+  .. attribute:: DNSRecord.contentLength
+
+    The length, in bytes, of the rdata content of this record.
+
+  .. attribute:: DNSRecord.contentOffset
+
+    The offset since the beginning of the DNS payload, in bytes, at which the
+    rdata content of this record starts.
index 7271f13974178b636a5b5a114d8d88fee7e59d71..69bd0a2bb92c6fa8a16e01143f9cd6f03dffe34c 100644 (file)
@@ -11,6 +11,18 @@ This state can be modified from the various hooks.
 
   The DNSQuestion object has several attributes, many of them read-only:
 
+  .. attribute:: DNSQuestion.deviceID
+
+    .. versionadded:: 1.8.0
+
+    The identifier of the remote device, which will be exported via ProtoBuf if set.
+
+  .. attribute:: DNSQuestion.deviceName
+
+    .. versionadded:: 1.8.0
+
+    The name of the remote device, which will be exported via ProtoBuf if set.
+
   .. attribute:: DNSQuestion.dh
 
     The :ref:`DNSHeader` of this query.
@@ -35,6 +47,12 @@ This state can be modified from the various hooks.
 
     Integer describing the OPCODE of the packet. Can be matched against :ref:`DNSOpcode`.
 
+  .. attribute:: DNSQuestion.pool
+
+    .. versionadded:: 1.8.0
+
+    The pool of servers to which this query will be routed.
+
   .. attribute:: DNSQuestion.qclass
 
     QClass (as an unsigned integer) of this question.
@@ -53,6 +71,12 @@ This state can be modified from the various hooks.
 
     :ref:`ComboAddress` of the remote client.
 
+  .. attribute:: DNSQuestion.requestorID
+
+    .. versionadded:: 1.8.0
+
+    The identifier of the requestor, which will be exported via ProtoBuf if set.
+
   .. attribute:: DNSQuestion.rcode
 
     RCode (as an unsigned integer) of this question.
@@ -66,6 +90,10 @@ This state can be modified from the various hooks.
 
     Whether to skip cache lookup / storing the answer for this question, settable.
 
+  .. attribute:: DNSQuestion.tempFailureTTL
+
+    On a SERVFAIL or REFUSED from the backend, cache for this amount of seconds, settable.
+
   .. attribute:: DNSQuestion.tcp
 
     Whether the query was received over TCP.
@@ -85,6 +113,12 @@ This state can be modified from the various hooks.
     :param int type: The type of the new value, ranging from 0 to 255 (both included)
     :param str value: The binary-safe value
 
+  .. method:: DNSQuestion:getContent() -> str
+
+    .. versionadded:: 1.8.0
+
+    Get the content of the DNS packet as a string
+
   .. method:: DNSQuestion:getDO() -> bool
 
     Get the value of the DNSSEC OK bit.
@@ -100,8 +134,11 @@ This state can be modified from the various hooks.
   .. method:: DNSQuestion:getHTTPHeaders() -> table
 
     .. versionadded:: 1.4.0
+    .. versionchanged:: 1.8.0
+       see ``keepIncomingHeaders`` on :func:`addDOHLocal`
 
     Return the HTTP headers for a DoH query, as a table whose keys are the header names and values the header values.
+    Since 1.8.0 it is necessary to set the ``keepIncomingHeaders`` option to true on :func:`addDOHLocal` to be able to use this method.
 
     :returns: A table of HTTP headers
 
@@ -160,6 +197,14 @@ This state can be modified from the various hooks.
 
     :returns: A table whose keys are types and values are binary-safe strings
 
+  .. method:: DNSQuestion:getQueryTime -> timespec
+
+    .. versionadded:: 1.8.0
+
+    Return the time at which the current query has been received, in whole seconds and nanoseconds since epoch, as a :ref:`timespec` object.
+
+    :returns: A :ref:`timespec` object
+
   .. method:: DNSQuestion:getServerNameIndication() -> string
 
     .. versionadded:: 1.4.0
@@ -190,12 +235,59 @@ This state can be modified from the various hooks.
 
     :returns: The trailing data as a null-safe string
 
+  .. method:: DNSQuestion:changeName(newName) -> bool
+
+    .. versionadded:: 1.8.0
+
+    Change the qname of the current query in the DNS payload.
+    The reverse operation will have to be done on the response to set it back to the initial name, or the client will be confused and likely drop the response.
+    See :func:`DNSResponse:changeName`.
+    Returns false on failure, true on success.
+
+    :param DNSName newName: The new qname to use
+
   .. method:: DNSQuestion:sendTrap(reason)
 
     Send an SNMP trap.
 
     :param string reason: An optional string describing the reason why this trap was sent
 
+  .. method:: DNSQuestion:setContent(data)
+
+    .. versionadded:: 1.8.0
+
+    Replace the whole DNS payload of the query with the supplied data. The new DNS payload must include the DNS header, whose ID will be adjusted to match the one of the existing query.
+    For example, this replaces the whole DNS payload of queries for custom.async.tests.powerdns.com and type A, turning it them into ``FORMERR`` responses, including EDNS with the ``DNSSECOK`` bit set and a UDP payload size of 1232:
+
+    .. code-block:: Lua
+
+      function replaceQueryPayload(dq)
+        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'
+        dq:setContent(raw)
+        return DNSAction.Allow
+      end
+      addAction(AndRule({QTypeRule(DNSQType.A), makeRule('custom.async.tests.powerdns.com')}), LuaAction(replaceQueryPayload))
+
+    :param string data: The raw DNS payload
+
+  .. method:: DNSQuestion:setEDNSOption(code, data)
+
+    .. versionadded:: 1.8.0
+
+    Add arbitrary EDNS option and data to the query. Any existing EDNS content with the same option code will be overwritten.
+
+    :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
@@ -234,13 +326,21 @@ This state can be modified from the various hooks.
 
     :param table values: A table of types and values to send, for example: ``{ [0x00] = "foo", [0x42] = "bar" }``. Note that the type must be an integer. Try to avoid these values: 0x01 - 0x05, 0x20 - 0x25, 0x30 as those are predefined in https://www.haproxy.org/download/2.3/doc/proxy-protocol.txt (search for `PP2_TYPE_ALPN`)
 
+  .. method:: DNSQuestion:setRestartable()
+
+    .. versionadded:: 1.8.0
+
+    Make it possible to restart that query after receiving the response, for example to try a different pool of servers after receiving a SERVFAIL or a REFUSED response.
+    Under the hood, this tells dnsdist to keep a copy of the initial query around so that we can send it a second time if needed. Copying the initial DNS payload has a small memory and CPU cost and thus is not done by default.
+    See also :func:`DNSResponse:restart`.
+
   .. method:: DNSQuestion:setTag(key, value)
 
     .. versionchanged:: 1.7.0
       Prior to 1.7.0 calling :func:`DNSQuestion:setTag` would not overwrite an existing tag value if already set.
 
     Set a tag into the DNSQuestion object. Overwrites the value if any already exists.
-  
+
     :param string key: The tag's key
     :param string value: The tag's value
 
@@ -250,7 +350,7 @@ This state can be modified from the various hooks.
       Prior to 1.7.0 calling :func:`DNSQuestion:setTagArray` would not overwrite existing tag values if already set.
 
     Set an array of tags into the DNSQuestion object. Overwrites the values if any already exist.
-  
+
     :param table tags: A table of tags, using strings as keys and values
 
   .. method:: DNSQuestion:setTrailingData(tail) -> bool
@@ -262,16 +362,32 @@ 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
+
+    .. versionadded:: 1.8.0
+
+    Suspend the processing for the current query, making it asynchronous. The query is then placed into memory, in a map called the Asynchronous Holder, until it is either resumed or the supplied timeout kicks in. The object is stored under a key composed of the tuple (`asyncID`, `queryID`) which is needed to retrieve it later, which can be done via :func:`getAsynchronousObject`.
+    Note that the DNSQuestion object should NOT be accessed after successfully calling this method.
+    Returns true on success and false on failure, indicating that the query has not been suspended and the normal processing will continue.
+
+    :param int asyncID: A numeric identifier used to identify the suspended query for later retrieval. Valid values range from 0 to 65535, both included.
+    :param int queryID: A numeric identifier used to identify the suspended query for later retrieval. This ID does not have to match the query ID present in the initial DNS header. A given (asyncID, queryID) tuple should be unique at a given time. Valid values range from 0 to 65535, both included.
+    :param int timeoutMS: The maximum duration this query will be kept in the asynchronous holder before being automatically resumed,  in milliseconds.
 
 .. _DNSResponse:
 
@@ -300,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)
 
@@ -320,6 +442,53 @@ DNSResponse object
 
     :param string func: The function to call to edit TTLs.
 
+  .. method:: DNSResponse:changeName(initialName) -> bool
+
+    .. versionadded:: 1.8.0
+
+    Change, in the DNS payload of the current response, the qname and the owner name of records to the supplied new name, if they are matching exactly the initial qname.
+    This only makes if the reverse operation was performed on the query, or the client will be confused and likely drop the response.
+    Note that only records whose owner name matches the qname in the initial response will be rewritten, and that only the owner name itself will be altered,
+    not the content of the record rdata. For some records this might cause an issue with compression pointers contained in the payload, as they might
+    no longer point to the correct position in the DNS payload. To prevent that, the records are checked against a list of supported record types,
+    and the rewriting will not be performed if an unsupported type is present. As of 1.8.0 the list of supported types is:
+    A, AAAA, DHCID, TXT, OPT, HINFO, DNSKEY, CDNSKEY, DS, CDS, DLV, SSHFP, KEY, CERT, TLSA, SMIMEA, OPENPGPKEY, NSEC, NSEC3, CSYNC, NSEC3PARAM, LOC, NID, L32, L64, EUI48, EUI64, URI, CAA, NS, PTR, CNAME, DNAME, RRSIG, MX, SOA, SRV
+    Therefore this functionality only makes sense when the initial query is requesting a very simple type, like A or AAAA.
+
+    See also :func:`DNSQuestion:changeName`.
+    Returns false on failure, true on success.
+
+    :param DNSName initialName: The initial qname
+
+  .. method:: DNSResponse:restart()
+
+    .. versionadded:: 1.8.0
+
+    Discard the received response and restart the processing of the query. For this function to be usable, the query should have been made restartable first, via :func:`DNSQuestion:setRestartable`.
+    For example, to restart the processing after selecting a different pool of servers:
+
+    .. code-block:: Lua
+
+      function makeQueryRestartable(dq)
+        -- make it possible to restart that query later
+        -- by keeping a copy of the initial DNS payload around
+        dq:setRestartable()
+        return DNSAction.None
+      end
+      function restartOnServFail(dr)
+        -- 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
+          -- restart the processing of the query
+          dr:restart()
+        end
+        return DNSResponseAction.None
+      end
+      addAction(AllRule(), LuaAction(makeQueryRestartable))
+      addResponseAction(AllRule(), LuaResponseAction(restartOnServFail))
+
 .. _DNSHeader:
 
 DNSHeader (``dh``) object
@@ -341,6 +510,12 @@ DNSHeader (``dh``) object
 
     Get checking disabled flag.
 
+  .. method:: DNSHeader:getID() -> int
+
+    .. versionadded:: 1.8.0
+
+    Get the ID.
+
   .. method:: DNSHeader:getRA() -> bool
 
     Get recursion available flag.
@@ -349,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.
@@ -408,3 +589,57 @@ EDNSOptionView object
   .. method:: EDNSOptionView:getValues()
 
     Return a table of NULL-safe strings values for this EDNS option.
+
+.. _AsynchronousObject:
+
+AsynchronousObject object
+=========================
+
+.. class:: AsynchronousObject
+
+  .. versionadded:: 1.8.0
+
+  This object holds a representation of a DNS query or response that has been suspended.
+
+  .. method:: AsynchronousObject:drop() -> bool
+
+    Drop that object immediately, without resuming it.
+    Returns true on success, false on failure.
+
+  .. method:: AsynchronousObject:getDQ() -> DNSQuestion
+
+    Return a DNSQuestion object for the suspended object.
+
+  .. method:: AsynchronousObject:getDR() -> DNSResponse
+
+    Return a DNSResponse object for the suspended object.
+
+  .. method:: AsynchronousObject:resume() -> bool
+
+    Resume the processing of the suspended object.
+    For a question, it means first checking whether it was turned into a response,
+    and sending the response out it it was. Otherwise do a cache-lookup: on a
+    cache-hit, the response will be sent immediately. On a cache-miss,
+    it means dnsdist will select a backend and send the query to the backend.
+    For a response, it means inserting into the cache if needed and sending the
+    response to the backend.
+    Note that the AsynchronousObject object should NOT be accessed after successfully calling this method.
+    Returns true on success, false on failure.
+
+  .. method:: AsynchronousObject:setRCode(rcode, clearRecords) -> bool
+
+    Set the response code in the DNS header of the current object to the supplied value,
+    optionally removing all records from the existing payload, if any.
+    Returns true on success, false on failure.
+
+    :param int code: The response code to set
+    :param bool clearRecords: Whether to clear all records from the existing payload, if any
+
+.. function:: getAsynchronousObject(asyncID, queryID) -> AsynchronousObject
+
+  .. versionadded:: 1.8.0
+
+  Retrieves an asynchronous object stored into the Asynchronous holder.
+
+    :param int asyncID: A numeric identifier used to identify the query when it was suspended
+    :param int queryID: A numeric identifier used to identify the query when it was suspended
index 5fc3da9f0e4ae348ef59b82fc8c772c86e07dfc0..6e5902f46f13d9a09ebca8821c9c64dd512b82ff 100644 (file)
@@ -7,33 +7,40 @@ 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
   :param int seconds: The number of seconds this block to expire
   :param str msg: A message to display while inserting the block
 
-.. function:: newBPFFilter(maxV4, maxV6, maxQNames) -> BPFFilter
-              newBPFFilter(v4Parameters, v6Parameters, qnamesParameters) -> BPFFilter
+.. function:: newBPFFilter(options) -> BPFFilter
+              newBPFFilter(v4Parameters, v6Parameters, qnamesParameters) -> BPFFilter (1.7.x)
+              newBPFFilter(maxV4, maxV6, maxQNames) -> BPFFilter (before 1.7.0)
 
   .. versionchanged:: 1.7.0
     This function now supports a table for each parameters, and the ability to use pinned eBPF maps.
+  .. versionchanged:: 1.8.0
+    This function now gets its parameters via a table.
 
   Return a new eBPF socket filter with a maximum of maxV4 IPv4, maxV6 IPv6 and maxQNames qname entries in the block tables.
   Maps can be pinned to a filesystem path, which makes their content persistent across restarts and allows external programs to read their content and to add new entries. dnsdist will try to load maps that are pinned to a filesystem path on startups, inheriting any existing entries, and fall back to creating them if they do not exist yet. Note that the user dnsdist is running under must have the right privileges to read and write to the given file, and to go through all the directories in the path leading to that file. The pinned path must be on a filesystem of type ``BPF``, usually below ``/sys/fs/bpf/``.
 
-  :param int maxV4: Maximum number of IPv4 entries in this filter
-  :param int maxV6: Maximum number of IPv6 entries in this filter
-  :param int maxQNames: Maximum number of QName entries in this filter
-
-  :param table v4Params: A table of options for the IPv4 filter map, see below
-  :param table v6Params: A table of options for the IPv6 filter map, see below
-  :param table qnameParams: A table of options for the qnames filter map, see below
+  :param table options: A table with key: value pairs with options.
 
   Options:
 
-  * ``maxItems``: int - The maximum number of entries in a given map. Default is 0 which will not allow any entry at all.
-  * ``pinnedPath``: str - The filesystem path this map should be pinned to.
+  * ``ipv4MaxItems``: int - The maximum number of entries in the IPv4 map. Default is 0 which will not allow any entry at all.
+  * ``ipv4PinnedPath``: str - The filesystem path this map should be pinned to.
+  * ``ipv6MaxItems``: int - The maximum number of entries in the IPv6 map. Default is 0 which will not allow any entry at all.
+  * ``ipv6PinnedPath``: str - The filesystem path this map should be pinned to.
+  * ``cidr4MaxItems``: int - The maximum number of entries in the IPv4 range block map. Default is 0 which will not allow any entry at all.
+  * ``cidr4PinnedPath``: str - The filesystem path this map should be pinned to.
+  * ``cidr6MaxItems``: int - The maximum number of entries in the IPv6 range block map. Default is 0 which will not allow any entry at all.
+  * ``cidr6PinnedPath``: str - The filesystem path this map should be pinned to.
+  * ``qnamesMaxItems``: int - The maximum number of entries in the qname map. Default is 0 which will not allow any entry at all.
+  * ``qnamesPinnedPath``: str - The filesystem path this map should be pinned to.
+  * ``external``: bool - If set to true, DNSDist does not load the internal eBPF program.
 
 .. function:: newDynBPFFilter(bpf) -> DynBPFFilter
 
@@ -66,7 +73,9 @@ These are all the functions, objects and methods related to the :doc:`../advance
   .. method:: BPFFilter:attachToAllBinds()
 
     Attach this filter to every bind already defined.
-    This is the run-time equivalent of :func:`setDefaultBPFFilter`
+    This is the run-time equivalent of :func:`setDefaultBPFFilter`.
+    This method can be used at run-time only.
+
 
   .. method:: BPFFilter:block(address)
 
@@ -74,6 +83,18 @@ These are all the functions, objects and methods related to the :doc:`../advance
 
     :param ComboAddress address: The address to block
 
+  .. method:: BPFFilter:addRangeRule(Netmask , action [, force=false])
+
+    .. versionadded:: 1.8.0
+
+    Block all IP addresses in this range. 
+
+    DNSDist eBPF code first checks if an exact IP match is found, then if a range matches, and finally if a DNSName does.
+
+    :param string Netmask: The ip range to block, allow or truncate
+    :param int action: set ``action``  to ``0`` to allow a range, set ``action`` to ``1`` to block a range, set ``action`` to ``2`` to truncate a range.
+    :param bool force: When ``force`` is set to true, DNSDist always accepts adding a new item to BPF maps, even if the item to be added may already be included in the larger network range.
+
   .. method:: BPFFilter:blockQName(name [, qtype=255])
 
     Block queries for this exact qname. An optional qtype can be used, defaults to 255.
@@ -91,6 +112,18 @@ These are all the functions, objects and methods related to the :doc:`../advance
 
     :param ComboAddress address: The address to unblock
 
+  .. method:: BPFFilter:rmRangeRule(Netmask)
+
+    .. versionadded:: 1.8.0
+
+    :param Netmask string: The rule you want to remove
+
+  .. method:: BPFFilter:lsRangeRule()
+
+    .. versionadded:: 1.8.0
+
+    List all range rule.
+
   .. method:: BPFFilter:unblockQName(name [, qtype=255])
 
     Remove this qname from the block list.
@@ -110,10 +143,10 @@ These are all the functions, objects and methods related to the :doc:`../advance
 
     Exclude this range, or list of ranges, meaning that no dynamic block will ever be inserted for clients in that range. Default to empty, meaning rules are applied to all ranges. When used in combination with :meth:`DynBPFFilter:includeRange`, the more specific entry wins.
 
-    :param int netmasks: A netmask, or list of netmasks, as strings, like for example "192.0.2.1/24"
+    :param str or list of str netmasks: A netmask, or list of netmasks, as strings, like for example "192.0.2.1/24"
 
   .. method:: DynBPFFilter:includeRange(netmasks)
 
     Include this range, or list of ranges, meaning that rules will be applied to this range. When used in combination with :meth:`DynBPFFilter:excludeRange`, the more specific entry wins.
 
-    :param int netmasks: A netmask, or list of netmasks, as strings, like for example "192.0.2.1/24"
+    :param str or list of str netmasks: A netmask, or list of netmasks, as strings, like for example "192.0.2.1/24"
index 9bccb195c18a8163db25f2de590101f37e024ba6..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
@@ -16,6 +17,7 @@ These chapters contain extensive information on all functions and object availab
   dq
   ebpf
   dnscrypt
+  dnsparser
   protobuf
   dnstap
   carbon
@@ -24,4 +26,8 @@ These chapters contain extensive information on all functions and object availab
   kvs
   logging
   web
-  svc
\ No newline at end of file
+  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 f6751d977c3cfb394db15841e8e7617dfbdda586..c4b0c0a7941db4fa9f68b0fbf7630a17fbf616d7 100755 (executable)
@@ -22,3 +22,11 @@ There are some functions to create log output.
   Writes an info line.\r
 \r
   :param str line: The line to write.\r
+\r
+.. function:: vinfolog(line)\r
+\r
+  .. versionadded:: 1.8.0\r
+\r
+  Writes an info line if dnsdist is running in verbose (debug) mode.\r
+\r
+  :param str line: The line to write.\r
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 58e23bc6e238797ff55fb2f8d22d53d6efc432f0..9ac695cc6602d77541077beb5481bae5095061dc 100644 (file)
@@ -11,7 +11,7 @@ SVCRecordParameters
 
     -- reply to SVCB queries for _dns.resolver.arpa. indicating DoT on port 853 of dot.powerdns.com. (192.0.2.1/2001:db8::1), DoH on https://doh.powerdns.com/dns-query (192.0.2.2/2001:db8::2)
     local svc = { 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" }, key42 = "/dns-query{?dns}" })
+                  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), QNameRule('_dns.resolver.arpa.')}, SpoofSVCAction(svc))
     -- reply with NODATA (NXDOMAIN would deny all types at that name and below, including SVC) for other types
index 13ef72667078285795668bfc1ce9b22a3cdc12f4..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:
 
@@ -181,13 +181,14 @@ Tuning related functions
 
   :param int num: maximum number of UDP queries to accept
 
-.. function:: setUDPSocketBufferSize(recv, send)
+.. function:: setUDPSocketBufferSizes(recv, send)
 
   .. versionadded:: 1.7.0
 
   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 08d33f2ecffba11ef4c18d632f97056891ba6090..1f54e8b366ce7af875b29f8b74779320d00a4d30 100644 (file)
@@ -5,3 +5,5 @@ changelog>=0.5.6,<0.6
 sphinxcontrib-httpdomain
 sphinxcontrib-fulltoc
 docutils!=0.15,<0.18
+jinja2<3.1.0
+alabaster==0.7.13
index 83076963f49a5b67e7fb10756c5518cf84a9b292..aa6129bc72bb09bc395a18a243bea0c408d3ece1 100644 (file)
@@ -1,11 +1,15 @@
 Packet Policies
 ===============
 
-dnsdist works in essence like any other loadbalancer:
+.. figure:: imgs/DNSDistFlow.v2.png
+   :align: center
+   :alt: DNSdist packet flows
 
-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, dnsdist attempts to match any of the configured rules in order and when one matches, the associated action is performed.
+:program:`dnsdist` works in essence like any other loadbalancer:
 
-These rule and action combinations are considered policies.
+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. The complete list of selectors (rules) can be found in :doc:`reference/selectors`, and the list of actions in :doc:`reference/actions`.
 
 Packet Actions
 --------------
@@ -19,6 +23,7 @@ Each packet can be:
 - Be delayed
 
 This decision can be taken at different times during the forwarding process.
+All packets not handled by an explicit action are forwarded to a downstream server in the default pool.
 
 Examples
 ~~~~~~~~
@@ -32,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::
 
@@ -72,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
 --------------
@@ -146,1530 +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 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
-
-  Matches DNS over HTTPS queries with a HTTP header ``name`` whose content matches the regular expression ``regex``.
-
-  :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]]]]]])
-
-  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%
-
-.. 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 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 bool name: The name of the tag that has to be set
-  :param bool value: If set, the value the tag has to be set to. Default is unset
-
-.. function:: TCPRule(tcp)
-
-  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 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:`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 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 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])
-
-  .. 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
-
-.. 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
-
-  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.
-
-.. 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]])
-
-  .. versionchanged:: 1.4.0
-    ``ipEncryptKey`` 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.
-  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.
-
-  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.
-
-.. function:: RemoteLogResponseAction(remoteLogger[, alterFunction[, includeCNAME [, options]]])
-
-  .. versionchanged:: 1.4.0
-    ``ipEncryptKey`` 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.
-  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.
-
-  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.
-
-.. 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:: 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:: 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])
-
-  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.
-  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 c7a4d14d38f1e2dbdf6f7afa84bf565f2f4943fc..00477693ecb5be71c4f040af665e9616ee412998 100644 (file)
@@ -1,12 +1,12 @@
 Statistics
 ==========
 
-dnsdist keeps statistics on the queries is receives and send out. They can be accessed in different ways:
+:program:`dnsdist` keeps statistics on the queries it receives and send out. They can be accessed in different ways:
 
 - via the console (see :ref:`Console`), using :func:`dumpStats` for the general ones,
   :func:`showServers()` for the ones related to the backends, :func:`showBinds()` for the frontends,
   `getPool("pool name"):getCache():printStats()` for the ones related to a specific cache and so on
-- via the internal webserver (see :doc:`../guides/webserver`)
+- via the internal webserver (see :doc:`../guides/webserver`) which includes a Prometheus endpoint
 - via Carbon / Graphite / Metronome export (see :doc:`../guides/carbon`)
 - via SNMP (see :doc:`../advanced/snmp`)
 
@@ -14,13 +14,22 @@ 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
 described on this page.
 
+Note that counters that come from ``/proc/net/`` are operating system specific counters.
+They do not reset on service restart and they are not only related to :program:`dnsdist`.
+For more information on these counters, refer to `Linux networking
+counter documentation <https://www.kernel.org/doc/html/latest/networking/snmp_counter.html>`_
+and the `RFC1213 <https://datatracker.ietf.org/doc/html/rfc1213>`_.
+
 acl-drops
 ---------
 The number of packets (or TCP messages) dropped because of the :doc:`ACL <advanced/acl>`.
@@ -62,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.
@@ -101,56 +114,119 @@ 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 for queries received over UDP.
+
+latency-count
+-------------
+Number of queries contributing to response time histogram and latency sum.
+
+latency-doh-avg100
+------------------
+Average response latency, in microseconds, of the last 100 packets received over DoH.
+
+latency-doh-avg1000
+-------------------
+Average response latency, in microseconds, of the last 1000 packets received over DoH.
+
+latency-doh-avg10000
+--------------------
+Average response latency, in microseconds, of the last 10000 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
+------------------
+Average response latency, in microseconds, of the last 100 packets received over DoT.
+
+latency-dot-avg1000
+-------------------
+Average response latency, in microseconds, of the last 1000 packets received over DoT.
+
+latency-dot-avg10000
+--------------------
+Average response latency, in microseconds, of the last 10000 packets received over DoT.
+
+latency-dot-avg1000000
+----------------------
+Average response latency, in microseconds, of the last 1000000 packets received over DoT.
 
 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 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-count
--------------
-Number of queries contributing to response time histogram and latency sum.
+latency-tcp-avg100
+------------------
+Average response latency, in microseconds, of the last 100 packets received over TCP.
 
-latency-bucket
---------------
-Histogram of response time latencies.
+latency-tcp-avg1000
+-------------------
+Average response latency, in microseconds, of the last 1000 packets received over TCP.
+
+latency-tcp-avg10000
+--------------------
+Average response latency, in microseconds, of the last 10000 packets received over TCP.
+
+latency-tcp-avg1000000
+----------------------
+Average response latency, in microseconds, of the last 1000000 packets received over TCP.
 
 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
 ---------
@@ -164,6 +240,10 @@ noncompliant-responses
 ----------------------
 Number of answers from a backend dropped as non-compliant.
 
+outgoing-doh-query-pipe-full
+----------------------------
+Number of outgoing DoH queries dropped because the internal pipe used to distribute queries was full.
+
 proxy-protocol-invalid
 ----------------------
 .. versionadded:: 1.6.0
@@ -184,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
 ---------
@@ -227,11 +307,23 @@ servfail-responses
 ------------------
 Number of servfail answers received from backends.
 
+tcp-cross-protocol-query-pipe-full
+----------------------------------
+Number of TCP cross-protocol queries dropped because the internal pipe used to distribute queries was full.
+
+tcp-cross-protocol-response-pipe-full
+-------------------------------------
+Number of TCP cross-protocol responses dropped because the internal pipe used to distribute queries was full.
+
 tcp-listen-overflows
 --------------------
 .. versionadded:: 1.6.0
 
-From /proc/net/netstat ListenOverflows.
+From ``/proc/net/netstat`` ``ListenOverflows``.
+
+tcp-query-pipe-full
+-------------------
+Number of TCP queries dropped because the internal pipe used to distribute queries was full.
 
 trunc-failures
 --------------
@@ -241,62 +333,62 @@ udp-in-csum-errors
 ------------------
 .. versionadded:: 1.7.0
 
-From /proc/net/snmp InErrors.
+From ``/proc/net/snmp`` ``InErrors``.
 
 udp-in-errors
 -------------
 .. versionadded:: 1.5.0
 
-From /proc/net/snmp InErrors.
+From ``/proc/net/snmp`` ``InErrors``.
 
 udp-noport-errors
 -----------------
 .. versionadded:: 1.5.0
 
-From /proc/net/snmp NoPorts.
+From ``/proc/net/snmp`` ``NoPorts``.
 
 udp-recvbuf-errors
 ------------------
 .. versionadded:: 1.5.0
 
-From /proc/net/snmp RcvbufErrors.
+From ``/proc/net/snmp`` ``RcvbufErrors``.
 
 udp-sndbuf-errors
 -----------------
 .. versionadded:: 1.5.0
 
-From /proc/net/snmp SndbufErrors.
+From ``/proc/net/snmp`` ``SndbufErrors``.
 
 udp6-in-csum-errors
 -------------------
 .. versionadded:: 1.7.0
 
-From /proc/net/snmp6 InErrors.
+From ``/proc/net/snmp6`` ``InErrors``.
 
 udp6-in-errors
 --------------
 .. versionadded:: 1.7.0
 
-From /proc/net/snmp6 InErrors.
+From ``/proc/net/snmp6`` ``InErrors``.
 
 udp6-noport-errors
 ------------------
 .. versionadded:: 1.7.0
 
-From /proc/net/snmp6 NoPorts.
+From ``/proc/net/snmp6`` ``NoPorts``.
 
 udp6-recvbuf-errors
 -------------------
 .. versionadded:: 1.7.0
 
-From /proc/net/snmp6 RcvbufErrors.
+From ``/proc/net/snmp6`` ``RcvbufErrors``.
 
 udp6-sndbuf-errors
 ------------------
 .. versionadded:: 1.7.0
 
-From /proc/net/snmp6 SndbufErrors.
+From ``/proc/net/snmp6`` ``SndbufErrors``.
 
 uptime
 ------
-Uptime of the dnsdist process, in seconds.
+Uptime of the :program:`dnsdist` process, in seconds.
index b03c083cef3f0a708d415b1a358f33e206ec6586..b48887bae61bcc23d66ddd1e3916be9202d704e9 100644 (file)
@@ -1,6 +1,43 @@
 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/1. 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
+--------------
+
+Responses to AXFR and IXFR queries are no longer cached.
+
+Cache-hits are now counted as responses in our metrics.
+
+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.
+
+Latency metrics have been broken down:
+
+* per incoming protocol (Do53 UDP, Do53 TCP, DoT, DoH) for global latency metrics
+* between UDP (Do53) and TCP (Do53 TCP, DoT, DoH) for backend latency metrics
+
 1.7.0 to 1.7.1
 --------------
 
@@ -26,26 +63,28 @@ The packet cache no longer hashes EDNS Cookies by default, which means that two
 
 All TCP worker threads are now created at startup, instead of being created on-demand. The existing behaviour was useful for very small setups but did not scale quickly to a large amount of TCP connections.
 The new behaviour can cause a noticeable increase of TCP connections between dnsdist and its backends, as the TCP connections are not shared between TCP worker threads.
-This is especially true for setups with a large number of frontends (:func:`addLocal`, :func:`addTLSLocal`, and :func:`addDNSCryptBind` directives), as 1.6.0 sets the number of TCP workers to the number of TCP-enabled binds (with a minimum of 10), unless that number has been set explicitely via :func:`setMaxTCPClientThreads`.
+This is especially true for setups with a large number of frontends (:func:`addLocal`, :func:`addTLSLocal`, and :func:`addDNSCryptBind` directives), as 1.6.0 sets the number of TCP workers to the number of TCP-enabled binds (with a minimum of 10), unless that number has been set explicitly via :func:`setMaxTCPClientThreads`.
 
 Several actions have been renamed so that almost all actions that allow further processing of rules start with 'Set', to prevent mistakes:
-- ``DisableECSAction`` to :func:`SetDisableECSAction`
-- ``DisableValidationAction`` to :func:`SetDisableValidationAction`
-- ``ECSOverrideAction`` to :func:`SetECSOverrideAction`
-- ``ECSPrefixLengthAction`` to :func:`SetECSPrefixLengthAction`
-- ``MacAddrAction`` to :func:`SetMacAddrAction`
-- ``NoRecurseAction`` to :func:`SetNoRecurseAction`
-- ``SkipCacheAction`` to :func:`SetSkipCacheAction`
-- ``TagAction`` to :func:`SetTagAction`
-- ``TagResponseAction`` to :func:`SetTagResponseAction`
-- ``TempFailureCacheTTLAction`` to :func:`SetAdditionalProxyProtocolValueAction`
-- ``SetNegativeAndSOAAction`` to :func:`NegativeAndSOAAction`
+
+* ``DisableECSAction`` to :func:`SetDisableECSAction`
+* ``DisableValidationAction`` to :func:`SetDisableValidationAction`
+* ``ECSOverrideAction`` to :func:`SetECSOverrideAction`
+* ``ECSPrefixLengthAction`` to :func:`SetECSPrefixLengthAction`
+* ``MacAddrAction`` to :func:`SetMacAddrAction`
+* ``NoRecurseAction`` to :func:`SetNoRecurseAction`
+* ``SkipCacheAction`` to :func:`SetSkipCacheAction`
+* ``TagAction`` to :func:`SetTagAction`
+* ``TagResponseAction`` to :func:`SetTagResponseAction`
+* ``TempFailureCacheTTLAction`` to :func:`SetAdditionalProxyProtocolValueAction`
+* ``SetNegativeAndSOAAction`` to :func:`NegativeAndSOAAction`
 
 Some ambiguous commands have also been renamed to prevent mistakes:
-- `topCacheHitResponseRule` to :func:`mvCacheHitResponseRuleToTop`
-- `topResponseRule` to :func:`mvResponseRuleToTop`
-- `topRule` to :func:`mvRuleToTop`
-- `topSelfAnsweredResponseRule` to :func:`mvSelfAnsweredResponseRuleToTop`
+
+* `topCacheHitResponseRule` to :func:`mvCacheHitResponseRuleToTop`
+* `topResponseRule` to :func:`mvResponseRuleToTop`
+* `topRule` to :func:`mvRuleToTop`
+* `topSelfAnsweredResponseRule` to :func:`mvSelfAnsweredResponseRuleToTop`
 
 The use of additional parameters on the :func:`webserver` command has been deprecated in favor of using :func:`setWebserverConfig`.
 
@@ -59,6 +98,8 @@ For example, ``addDOHLocal('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem'
 This change also impacts the HTTP response rules set via :meth:`DOHFrontend:setResponsesMap`, since queries whose paths are not allowed will be discarded before the rules are evaluated.
 If you want to accept DoH queries on ``/dns-query`` and redirect ``/rfc`` to the DoH RFC, you need to list ``/rfc`` in the list of paths:
 
+.. code-block:: lua
+
   addDOHLocal('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key', { '/dns-query', '/rfc'})
   map = { newDOHResponseMapEntry("^/rfc$", 307, "https://www.rfc-editor.org/info/rfc8484") }
   dohFE = getDOHFrontend(0)
@@ -136,4 +177,4 @@ In 1.2.0, several configuration options have been changed:
 As the amount of possible settings for listen sockets is growing, all listen-related options must now be passed as a table as the second argument to both :func:`addLocal` and :func:`setLocal`.
 See the function's reference for more information.
 
-The ``BlockFilter`` function is removed, as :func:`addRule` combined with a :func:`DropAction` can do the same.
+The ``BlockFilter`` function is removed, as :func:`addAction` combined with a :func:`DropAction` can do the same.
index 7021684685daeb6cdd4e37e2cc3e0a64900e0933..5ecdd00f6fd648a109e0c3807db9940a5d1eed89 100644 (file)
@@ -2,9 +2,10 @@
 #include "doh.hh"
 
 #ifdef HAVE_DNS_OVER_HTTPS
+#ifdef HAVE_LIBH2OEVLOOP
 #define H2O_USE_EPOLL 1
 
-#include <errno.h>
+#include <cerrno>
 #include <iostream>
 #include <thread>
 
 #include "misc.hh"
 #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"
@@ -54,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
 {
@@ -65,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()
   {
@@ -78,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) {
@@ -159,48 +165,35 @@ public:
   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];
-    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);
-    }
-
-    if (pipe(fd) < 0) {
-      close(dohquerypair[0]);
-      close(dohquerypair[1]);
-      unixDie("Creating a pipe for DNS over HTTPS");
+#ifndef USE_SINGLE_ACCEPTOR_THREAD
+    {
+      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 */
 
-    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
@@ -210,70 +203,113 @@ 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> paths;
-  h2o_globalconf_t h2o_config;
-  h2o_context_t h2o_ctx;
+  std::set<std::string, std::less<>> paths;
+  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};
-  int dohquerypair[2]{-1,-1};
-  int dohresponsepair[2]{-1,-1};
+  ClientState* clientState{nullptr};
+  std::shared_ptr<DOHFrontend> dohFrontend{nullptr};
+#ifndef USE_SINGLE_ACCEPTOR_THREAD
+  pdns::channel::Sender<DOHUnit> d_querySender;
+  pdns::channel::Receiver<DOHUnit> d_queryReceiver;
+#endif /* USE_SINGLE_ACCEPTOR_THREAD */
+  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
 {
   std::shared_ptr<DOHAcceptContext> d_acceptCtx{nullptr};
+  ComboAddress d_remote;
+  ComboAddress d_local;
   struct timeval d_connectionStartTime{0, 0};
   size_t d_nbQueries{0};
   int d_desc{-1};
@@ -283,10 +319,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;
@@ -295,6 +331,8 @@ static void on_socketclose(void *data)
       conn->d_acceptCtx->d_cs->updateTCPMetrics(conn->d_nbQueries, diff.tv_sec * 1000 + diff.tv_usec / 1000);
     }
 
+    dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(conn->d_remote);
+    // you can no longer touch conn, or data, after this call
     t_conns.erase(conn->d_desc);
   }
 }
@@ -341,17 +379,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;
@@ -360,7 +396,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) {
@@ -369,12 +405,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);
@@ -385,18 +423,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 {
@@ -405,7 +446,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);
@@ -418,65 +459,88 @@ 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(DOHUnitUniquePtr&& du_): du(std::move(du_))
-  {
-  }
+  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;
   }
 
-  const ClientState* getClientState() const override
-  {
-    if (!du || !du->dsc || !du->dsc->cs) {
-      throw std::runtime_error("No query associated to this DoHTCPCrossQuerySender");
-    }
-
-    return du->dsc->cs;
-  }
-
   void handleResponse(const struct timeval& now, TCPResponse&& response) override
   {
-    if (!du) {
+    if (!response.d_idstate.du) {
       return;
     }
 
-    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);
+    dohUnit->response = std::move(response.d_buffer);
+    dohUnit->ids = std::move(response.d_idstate);
+    DNSResponse dr(dohUnit->ids, dohUnit->response, dohUnit->downstream);
 
-    thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localRespRuleActions = g_respruleactions.getLocal();
-    DNSResponse dr = makeDNSResponseFromIDState(du->ids, du->response);
-    dnsheader cleartextDH;
-    memcpy(&cleartextDH, dr.getHeader(), sizeof(cleartextDH));
+    dnsheader cleartextDH{};
+    memcpy(&cleartextDH, dr.getHeader().get(), sizeof(cleartextDH));
 
-    if (!processResponse(du->response, localRespRuleActions, dr, false, false)) {
-      du.reset();
-      return;
+    if (!response.isAsync()) {
+      static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localRespRuleActions = g_respruleactions.getLocal();
+      static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localCacheInsertedRespRuleActions = g_cacheInsertedRespRuleActions.getLocal();
+
+      dr.ids.du = std::move(dohUnit);
+
+      if (!processResponse(dynamic_cast<DOHUnit*>(dr.ids.du.get())->response, *localRespRuleActions, *localCacheInsertedRespRuleActions, dr, false)) {
+        if (dr.ids.du) {
+          dohUnit = getDUFromIDS(dr.ids);
+          dohUnit->status_code = 503;
+          sendDoHUnitToTheMainThread(std::move(dohUnit), "Response dropped by rules");
+        }
+        return;
+      }
+
+      if (dr.isAsynchronous()) {
+        return;
+      }
+
+      dohUnit = getDUFromIDS(dr.ids);
     }
 
-    double udiff = du->ids.sentTime.udiff();
-    vinfolog("Got answer from %s, relayed to %s (https), took %f usec", 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);
 
-    handleResponseSent(du->ids, udiff, *dr.remote, du->downstream->d_config.remote, du->response.size(), cleartextDH, du->downstream->getProtocol());
+      auto backendProtocol = dohUnit->downstream->getProtocol();
+      if (backendProtocol == dnsdist::Protocol::DoUDP && dohUnit->tcp) {
+        backendProtocol = dnsdist::Protocol::DoTCP;
+      }
+      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
@@ -484,263 +548,314 @@ public:
     return handleResponse(now, std::move(response));
   }
 
-  void notifyIOError(IDState&& query, const struct timeval& now) override
+  void notifyIOError(const struct timeval& now, TCPResponse&& response) override
   {
-    if (!du) {
+    auto& query = response.d_idstate;
+    if (!query.du) {
       return;
     }
 
-    if (du->rsock == -1) {
+    auto dohUnit = getDUFromIDS(query);
+    if (dohUnit->responseSender == nullptr) {
       return;
     }
 
-    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");
   }
-
-private:
-  DOHUnitUniquePtr du;
 };
 
 class DoHCrossProtocolQuery : public CrossProtocolQuery
 {
 public:
-  DoHCrossProtocolQuery(DOHUnitUniquePtr&& du_): du(std::move(du_))
+  DoHCrossProtocolQuery(DOHUnitUniquePtr&& dohUnit, bool isResponse)
   {
-    query = InternalQuery(std::move(du->query), std::move(du->ids));
+    if (isResponse) {
+      /* happens when a response becomes async */
+      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(dohUnit->query), std::move(dohUnit->ids));
+    }
+
+    /* 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 = du->proxyProtocolPayloadSize > 0;
-    downstream = du->downstream;
-    proxyProtocolPayloadSize = du->proxyProtocolPayloadSize;
+    query.d_proxyProtocolPayloadAdded = query.d_idstate.d_proxyProtocolPayloadSize > 0;
+    downstream = query.d_idstate.du->downstream;
   }
 
   void handleInternalError()
   {
-    du->status_code = 502;
-    sendDoHUnitToTheMainThread(std::move(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
   {
-    auto sender = std::make_shared<DoHTCPCrossQuerySender>(std::move(du));
-    return sender;
+    auto* unit = dynamic_cast<DOHUnit*>(query.d_idstate.du.get());
+    if (unit != nullptr) {
+      unit->downstream = downstream;
+    }
+    return s_sender;
+  }
+
+  DNSQuestion getDQ() override
+  {
+    auto& ids = query.d_idstate;
+    DNSQuestion dq(ids, query.d_buffer);
+    return dq;
+  }
+
+  DNSResponse getDR() override
+  {
+    auto& ids = query.d_idstate;
+    DNSResponse dr(ids, query.d_buffer, downstream);
+    return dr;
+   }
+
+  DOHUnitUniquePtr releaseDU()
+  {
+    return getDUFromIDS(query.d_idstate);
   }
 
 private:
-  DOHUnitUniquePtr du;
+  static std::shared_ptr<DoHTCPCrossQuerySender> s_sender;
 };
 
+std::shared_ptr<DoHTCPCrossQuerySender> DoHCrossProtocolQuery::s_sender = std::make_shared<DoHTCPCrossQuerySender>();
+
+std::unique_ptr<CrossProtocolQuery> getDoHCrossProtocolQueryFromDQ(DNSQuestion& dq, bool isResponse)
+{
+  if (!dq.ids.du) {
+    throw std::runtime_error("Trying to create a DoH cross protocol query without a valid DoH unit");
+  }
+
+  auto dohUnit = getDUFromIDS(dq.ids);
+  if (&dq.ids != &dohUnit->ids) {
+   dohUnit->ids = std::move(dq.ids);
+  }
+
+  dohUnit->ids.origID = dq.getHeader()->id;
+
+  if (!isResponse) {
+    if (dohUnit->query.data() != dq.getMutableData().data()) {
+      dohUnit->query = std::move(dq.getMutableData());
+    }
+  }
+  else {
+    if (dohUnit->response.data() != dq.getMutableData().data()) {
+      dohUnit->response = std::move(dq.getMutableData());
+    }
+  }
+
+  return std::make_unique<DoHCrossProtocolQuery>(std::move(dohUnit), isResponse);
+}
+
 /*
    We are not in the main DoH thread but in the DoH 'client' thread.
 */
-static void processDOHQuery(DOHUnitUniquePtr&& du)
+static void processDOHQuery(DOHUnitUniquePtr&& unit, bool inMainThread = false)
 {
+  const auto handleImmediateResponse = [inMainThread](DOHUnitUniquePtr&& dohUnit, const char* reason) {
+    if (inMainThread) {
+      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. */
+      dohUnit->ids.du.reset();
+    }
+    else {
+      sendDoHUnitToTheMainThread(std::move(dohUnit), reason);
+    }
+  };
+
+  auto& ids = unit->ids;
   uint16_t queryId = 0;
   ComboAddress remote;
-  bool duRefCountIncremented = false;
+
   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;
-      sendDoHUnitToTheMainThread(std::move(du), "DoH killed in flight");
+      unit->status_code = 500;
+      handleImmediateResponse(std::move(unit), "DoH killed in flight");
       return;
     }
-    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;
-      du->status_code = 400;
-      sendDoHUnitToTheMainThread(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;
-
-    /* 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 queryRealTime;
-    gettime(&queryRealTime, true);
+    ++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)) {
-        du->status_code = 400;
-        sendDoHUnitToTheMainThread(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);
 
-        sendDoHUnitToTheMainThread(std::move(du), "DoH empty query");
+        handleImmediateResponse(std::move(unit), "DoH empty query");
         return;
       }
 
-      queryId = ntohs(dh->id);
+      queryId = ntohs(dnsHeader->id);
     }
 
-    uint16_t qtype, qclass;
-    unsigned int qnameWireLength = 0;
-    DNSName qname(reinterpret_cast<const char*>(du->query.data()), du->query.size(), sizeof(dnsheader), false, &qtype, &qclass, &qnameWireLength);
-    DNSQuestion dq(&qname, qtype, qclass, &du->ids.origDest, &du->ids.origRemote, du->query, dnsdist::Protocol::DoH, &queryRealTime);
-    dq.ednsAdded = du->ids.ednsAdded;
-    /* store the raw pointer */
-    dq.du = du.get();
-    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, cs, holders, du->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;
-      sendDoHUnitToTheMainThread(std::move(du), "DoH dropped query");
+      unit = getDUFromIDS(ids);
+      unit->status_code = 403;
+      handleImmediateResponse(std::move(unit), "DoH dropped query");
+      return;
+    }
+    if (result == ProcessQueryResult::Asynchronous) {
       return;
     }
-
     if (result == ProcessQueryResult::SendAnswer) {
-      if (du->response.empty()) {
-        du->response = std::move(du->query);
+      unit = getDUFromIDS(ids);
+      if (unit->response.empty()) {
+        unit->response = std::move(unit->query);
+      }
+      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);
       }
-      sendDoHUnitToTheMainThread(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;
-      sendDoHUnitToTheMainThread(std::move(du), "DoH no backend available");
+      unit->status_code = 500;
+      handleImmediateResponse(std::move(unit), "DoH no backend available");
       return;
     }
 
-    if (du->downstream == nullptr) {
-      du->status_code = 502;
-      sendDoHUnitToTheMainThread(std::move(du), "DoH no backend available");
+    if (downstream == nullptr) {
+      unit->status_code = 502;
+      handleImmediateResponse(std::move(unit), "DoH no backend available");
       return;
     }
 
-    if (du->downstream->isTCPOnly()) {
+    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 (du->downstream->d_config.useProxyProtocol) {
-        proxyProtocolPayload = getProxyProtocolPayload(dq);
+      if (downstream->d_config.useProxyProtocol) {
+        proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
       }
 
-      du->ids.origID = htons(queryId);
-      du->ids.cs = &cs;
-      setIDStateFromDNSQuestion(du->ids, dq, std::move(qname));
-
-      du->tcp = true;
-      std::shared_ptr<DownstreamState>& downstream = du->downstream;
+      unit->ids.origID = htons(queryId);
+      unit->tcp = true;
 
       /* this moves du->ids, careful! */
-      auto cpq = std::make_unique<DoHCrossProtocolQuery>(std::move(du));
+      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 {
-        cpq->handleInternalError();
-        return;
-      }
-    }
-
-    ComboAddress dest = du->ids.origDest;
-    unsigned int idOffset = 0;
-    int64_t generation;
-    IDState* ids = du->downstream->getIDState(idOffset, generation);
-
-    ids->origFD = 0;
-    /* increase the ref count since we are about to store the pointer */
-    du->get();
-    duRefCountIncremented = true;
-    /* store the raw pointer */
-    ids->du = du.get();
-
-    ids->cs = &cs;
-    ids->origID = htons(queryId);
-    setIDStateFromDNSQuestion(*ids, dq, std::move(qname));
-
-    dq.getHeader()->id = idOffset;
-
-    /* If we couldn't harvest the real dest addr, still
-       write down the listening addr since it will be useful
-       (especially if it's not an 'any' one).
-       We need to keep track of which one it is since we may
-       want to use the real but not the listening addr to reply.
-    */
-    if (dest.sin4.sin_family != 0) {
-      ids->origDest = dest;
-      ids->destHarvested = true;
-    }
-    else {
-      ids->origDest = cs.local;
-      ids->destHarvested = false;
-    }
-
-    if (du->downstream->d_config.useProxyProtocol) {
-      size_t payloadSize = 0;
-      if (addProxyProtocol(dq)) {
-        du->proxyProtocolPayloadSize = payloadSize;
-      }
-    }
 
-    int fd = du->downstream->pickSocketForSending();
-    ids->backendFD = fd;
-    try {
-      /* you can't touch du after this line, unless the call returned a non-negative value,
-         because it might already have been freed */
-      ssize_t ret = udpClientSendRequestToBackend(du->downstream, fd, du->query);
-
-      if (ret < 0) {
-        /* we are about to handle the error, make sure that
-           this pointer is not accessed when the state is cleaned,
-           but first check that it still belongs to us */
-        if (ids->tryMarkUnused(generation)) {
-          ids->du = nullptr;
-          du->release();
-          duRefCountIncremented = false;
-          --du->downstream->outstanding;
+      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();
         }
-        ++du->downstream->sendErrors;
-        ++g_stats.downstreamSendErrors;
-        du->status_code = 502;
-        sendDoHUnitToTheMainThread(std::move(du), "DoH internal error");
-        return;
+        unit->status_code = 502;
+        handleImmediateResponse(std::move(unit), "DoH internal error");
       }
-    }
-    catch (const std::exception& e) {
-      if (duRefCountIncremented) {
-        du->release();
+      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();
+        }
       }
-      throw;
+      return;
     }
 
-    vinfolog("Got query for %s|%s from %s (https), relayed to %s", ids->qname.toString(), QType(ids->qtype).toString(), remote.toStringWithPort(), du->downstream->getName());
+    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;
-    sendDoHUnitToTheMainThread(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 */
@@ -750,16 +865,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) {
@@ -790,10 +906,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;
   }
 }
 
@@ -804,79 +920,79 @@ 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 */
-    uint16_t qtype;
-    DNSName qname(reinterpret_cast<const char*>(query.data()), query.size(), sizeof(dnsheader), false, &qtype);
-
-    auto du = std::make_unique<DOHUnit>();
-    du->dsc = dsc;
-    du->req = req;
-    du->ids.origDest = local;
-    du->ids.origRemote = remote;
-    du->rsock = dsc->dohresponsepair[0];
-    du->query = std::move(query);
-    du->path = std::move(path);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    DNSPacketMangler mangler(reinterpret_cast<char*>(query.data()), query.size());
+    mangler.skipDomainName();
+    mangler.skipBytes(4);
+
     /* 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 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->host = std::string(req->authority.base, req->authority.len);
-    du->query_at = req->query_at;
-    du->headers.reserve(req->headers.size);
-    for (size_t i = 0; i < req->headers.size; ++i) {
-      du->headers[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);
+    dohUnit->query_at = req->query_at;
+
+    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) {
+        // 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);
+      }
     }
 
 #ifdef HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME
     h2o_socket_t* sock = req->conn->callbacks->get_socket(req->conn);
     const char * sni = h2o_socket_get_ssl_server_name(sock);
     if (sni != nullptr) {
-      du->sni = sni;
+      dohUnit->sni = sni;
     }
 #endif /* HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME */
+    dohUnit->self = static_cast<DOHUnit**>(h2o_mem_alloc_shared(&req->pool, sizeof(*self), on_generator_dispose));
+    *(dohUnit->self) = dohUnit.get();
 
-    du->self = reinterpret_cast<DOHUnit**>(h2o_mem_alloc_shared(&req->pool, sizeof(*self), on_generator_dispose));
-    auto ptr = du.release();
-    *(ptr->self) = ptr;
-    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;
+#ifdef USE_SINGLE_ACCEPTOR_THREAD
+    processDOHQuery(std::move(dohUnit), true);
+#else /* USE_SINGLE_ACCEPTOR_THREAD */
+    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 */
   }
-  catch(const std::exception& e) {
+  catch (const std::exception& e) {
     vinfolog("Had error parsing DoH DNS packet from %s: %s", remote.toStringWithPort(), e.what());
     h2o_send_error_400(req, "Bad Request", "The DNS query could not be parsed", 0);
   }
 }
 
 /* can only be called from the main DoH thread */
-static bool getHTTPHeaderValue(const h2o_req_t* req, const std::string& headerName, pdns_string_view& value)
+static bool getHTTPHeaderValue(const h2o_req_t* req, const std::string& headerName, std::string_view& value)
 {
   bool found = false;
   /* early versions of boost::string_ref didn't have the ability to compare to string */
-  pdns_string_view headerNameView(headerName);
+  std::string_view headerNameView(headerName);
 
   for (size_t i = 0; i < req->headers.size; ++i) {
-    if (pdns_string_view(req->headers.entries[i].name->base, req->headers.entries[i].name->len) == headerNameView) {
-      value = pdns_string_view(req->headers.entries[i].value.base, req->headers.entries[i].value.len);
+    // 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;
     }
@@ -886,15 +1002,15 @@ 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";
-  pdns_string_view value;
+  std::string_view value;
 
   if (getHTTPHeaderValue(req, headerName, value)) {
     try {
       auto pos = value.rfind(',');
-      if (pos != pdns_string_view::npos) {
+      if (pos != std::string_view::npos) {
         ++pos;
         for (; pos < value.size() && value[pos] == ' '; ++pos)
         {
@@ -904,8 +1020,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());
@@ -914,6 +1029,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;
 }
 
 /*
@@ -924,110 +1041,114 @@ 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
     }
+    // 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);
-    ComboAddress remote;
-    ComboAddress local;
 
-    if (h2o_socket_getpeername(sock, reinterpret_cast<struct sockaddr*>(&remote)) == 0) {
-      /* getpeername failed, likely because the connection has already been closed,
-         but anyway that means we can't get the remote address, which could allow an ACL bypass */
-      h2o_send_error_500(req, getReasonFromStatusCode(500).c_str(), "Internal Server Error - Unable to get remote address", 0);
+    const int descriptor = h2o_socket_get_fd(sock);
+    if (descriptor == -1) {
       return 0;
     }
 
-    h2o_socket_getsockname(sock, reinterpret_cast<struct sockaddr*>(&local));
-    DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(req->conn->ctx->storage.entries[0].data);
+    auto& conn = t_conns.at(descriptor);
+    ++conn.d_nbQueries;
+    if (conn.d_nbQueries == 1) {
+      if (h2o_socket_get_ssl_session_reused(sock) == 0) {
+        ++dsc->clientState->tlsNewSessions;
+      }
+      else {
+        ++dsc->clientState->tlsResumptions;
+      }
 
-    if (dsc->df->d_trustForwardedForHeader) {
-      processForwardedForHeader(req, remote);
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): h2o API
+      h2o_socket_getsockname(sock, reinterpret_cast<struct sockaddr*>(&conn.d_local));
+    }
+
+    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(remote)) {
-      ++g_stats.aclDrops;
+      ++dnsdist::metrics::g_stats.aclDrops;
       vinfolog("Query from %s (DoH) dropped because of ACL", remote.toStringWithPort());
-      h2o_send_error_403(req, "Forbidden", "dns query not allowed because of ACL", 0);
+      h2o_send_error_403(req, "Forbidden", "DoH query not allowed because of ACL", 0);
       return 0;
     }
 
-    const int descriptor = h2o_socket_get_fd(sock);
-    if (descriptor != -1) {
-      auto& conn = t_conns.at(descriptor);
-      ++conn.d_nbQueries;
-      if (conn.d_nbQueries == 1) {
-        if (h2o_socket_get_ssl_session_reused(sock) == 0) {
-          ++dsc->cs->tlsNewSessions;
-        }
-        else {
-          ++dsc->cs->tlsResumptions;
-        }
+    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 (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 (dsc->df->d_exactPathMatching) {
-      // would be nice to be able to use a pdns_string_view there, but we would need heterogeneous lookups
-      // (having string in the set and compare them to string_view, for example. Note that comparing
-      // two boost::string_view uses the pointer, not the content).
-      const std::string pathOnly(req->path_normalized.base, req->path_normalized.len);
+    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);
         return 0;
       }
     }
 
-    // would be nice to be able to use a pdns_string_view there,
+    // would be nice to be able to use a std::string_view there,
     // but regex (called by matches() internally) requires a null-terminated string
     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), local, 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,"-", "+");
@@ -1050,117 +1171,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), local, 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)
+const std::unordered_map<std::string, std::string>& DOHUnit::getHTTPHeaders() const
 {
-}
-
-bool HTTPHeaderRule::matches(const DNSQuestion* dq) const
-{
-  if (!dq->du) {
-    return false;
+  if (!headers) {
+    static const HeadersMap empty{};
+    return empty;
   }
-
-  for (const auto& header : dq->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->du) {
-    return false;
-  }
-
-  if (dq->du->query_at == SIZE_MAX) {
-    return dq->du->path == d_path;
-  }
-  else {
-    return d_path.compare(0, d_path.size(), dq->du->path, 0, dq->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->du) {
-    return false;
-  }
-
-  return d_regex.match(dq->du->getHTTPPath());
-}
-
-string HTTPPathRegexRule::toString() const
-{
-  return d_visual;
-}
-
-std::unordered_map<std::string, std::string> DOHUnit::getHTTPHeaders() const
-{
-  std::unordered_map<std::string, std::string> results;
-  results.reserve(headers.size());
-
-  for (const auto& header : headers) {
-    results.insert(header);
-  }
-
-  return results;
+  return *headers;
 }
 
 std::string DOHUnit::getHTTPPath() const
@@ -1168,17 +1219,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;
 }
@@ -1186,11 +1235,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_)
@@ -1207,64 +1254,45 @@ void DOHUnit::setHTTPResponse(uint16_t statusCode, PacketBuffer&& body_, const s
   contentType = contentType_;
 }
 
+#ifndef USE_SINGLE_ACCEPTOR_THREAD
 /* query has been parsed by h2o, which called doh_handler() in the main DoH thread.
-   In order not to blockfor long, doh_handler() called doh_dispatch_query() which allocated
+   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));
+      auto tmp = receiver.receive();
+      if (!tmp) {
         continue;
       }
-      else if (static_cast<size_t>(got) < sizeof(ptr)) {
-        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;
       }
 
-      // 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
-      }
-
-      processDOHQuery(std::move(du));
+      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)
@@ -1277,71 +1305,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;
+      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();
 
-      du->tcp = true;
-      du->truncated = false;
-      auto cpq = std::make_unique<DoHCrossProtocolQuery>(std::move(du));
+      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;
   }
 
@@ -1351,15 +1379,36 @@ static void on_accept(h2o_socket_t *listener, const char *err)
     return;
   }
 
-  auto concurrentConnections = ++dsc->cs->tcpCurrentConnections;
-  if (dsc->cs->d_tcpConcurrentConnectionsLimit > 0 && concurrentConnections > dsc->cs->d_tcpConcurrentConnectionsLimit) {
-    --dsc->cs->tcpCurrentConnections;
+  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 (concurrentConnections > dsc->cs->tcpMaxConcurrentConnections.load()) {
-    dsc->cs->tcpMaxConcurrentConnections.store(concurrentConnections);
+  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->clientState->tcpCurrentConnections;
+  if (dsc->clientState->d_tcpConcurrentConnectionsLimit > 0 && concurrentConnections > dsc->clientState->d_tcpConcurrentConnectionsLimit) {
+    --dsc->clientState->tcpCurrentConnections;
+    h2o_socket_close(sock);
+    return;
+  }
+
+  if (concurrentConnections > dsc->clientState->tcpMaxConcurrentConnections.load()) {
+    dsc->clientState->tcpMaxConcurrentConnections.store(concurrentConnections);
   }
 
   auto& conn = t_conns[descriptor];
@@ -1368,44 +1417,53 @@ static void on_accept(h2o_socket_t *listener, const char *err)
   conn.d_nbQueries = 0;
   conn.d_acceptCtx = std::atomic_load_explicit(&dsc->accept_ctx, std::memory_order_acquire);
   conn.d_desc = descriptor;
+  conn.d_remote = remote;
 
   sock->on_close.cb = on_socketclose;
   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(const ComboAddress& addr, 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);
 
   return 0;
 }
 
+#ifndef DISABLE_OCSP_STAPLING
 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);
 }
-
-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)
+#endif /* DISABLE_OCSP_STAPLING */
+
+#if OPENSSL_VERSION_MAJOR >= 3
+// 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
+// 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;
@@ -1423,21 +1481,30 @@ 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 = libssl_init_server_context(tlsConfig, acceptCtx.d_ocspResponses);
+  auto [ctx, warnings] = libssl_init_server_context(tlsConfig, acceptCtx.d_ocspResponses);
+  for (const auto& warning : warnings) {
+    warnlog("%s", warning);
+  }
 
   if (tlsConfig.d_enableTickets && tlsConfig.d_numberOfTicketsKeys > 0) {
     acceptCtx.d_ticketKeys = std::make_unique<OpenSSLTLSTicketKeysRing>(tlsConfig.d_numberOfTicketsKeys);
+#if OPENSSL_VERSION_MAJOR >= 3
+    SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx.get(), &ticket_key_callback);
+#else
     SSL_CTX_set_tlsext_ticket_key_cb(ctx.get(), &ticket_key_callback);
+#endif
     libssl_set_ticket_key_callback_data(ctx.get(), &acceptCtx);
   }
 
+#ifndef DISABLE_OCSP_STAPLING
   if (!acceptCtx.d_ocspResponses.empty()) {
     SSL_CTX_set_tlsext_status_cb(ctx.get(), &ocsp_stapling_callback);
     SSL_CTX_set_tlsext_status_arg(ctx.get(), &acceptCtx.d_ocspResponses);
   }
+#endif /* DISABLE_OCSP_STAPLING */
 
   libssl_set_error_counters_callback(ctx, &counters);
 
@@ -1455,91 +1522,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);
+                      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 '" + 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);
-    }
-    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 *))
@@ -1549,7 +1554,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;
   }
 
@@ -1562,36 +1567,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::thread dnsdistThread(dnsdistclient, dsc->dohquerypair[1]);
+    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, 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
@@ -1599,8 +1607,13 @@ void dohThread(ClientState* cs)
 
     setupAcceptContext(*dsc->accept_ctx, *dsc, false);
 
-    if (create_listener(df->d_local, 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, 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));
+      }
     }
 
     bool stop = false;
@@ -1608,12 +1621,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) {
@@ -1624,43 +1637,117 @@ void dohThread(ClientState* cs)
   }
 }
 
-void handleUDPResponseForDoH(DOHUnitUniquePtr&& du, PacketBuffer&& udpResponse, IDState&& 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);
+  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<DNSDistResponseRuleAction>> localRespRuleActions = g_respruleactions.getLocal();
+    static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localCacheInsertedRespRuleActions = g_cacheInsertedRespRuleActions.getLocal();
 
-  const dnsheader* dh = reinterpret_cast<const struct dnsheader*>(du->response.data());
-  if (!dh->tc) {
-    thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localRespRuleActions = g_respruleactions.getLocal();
-    DNSResponse dr = makeDNSResponseFromIDState(du->ids, du->response);
-    dnsheader cleartextDH;
-    memcpy(&cleartextDH, dr.getHeader(), sizeof(cleartextDH));
+    DNSResponse dnsResponse(dohUnit->ids, udpResponse, dohUnit->downstream);
+    dnsheader cleartextDH{};
+    memcpy(&cleartextDH, dnsResponse.getHeader().get(), sizeof(cleartextDH));
 
-    if (!processResponse(du->response, localRespRuleActions, dr, false, true)) {
+    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;
     }
 
-    double udiff = du->ids.sentTime.udiff();
-    vinfolog("Got answer from %s, relayed to %s (https), took %f usec", du->downstream->d_config.remote.toStringWithPort(), du->ids.origRemote.toStringWithPort(), udiff);
+    if (dnsResponse.isAsynchronous()) {
+      return;
+    }
 
-    handleResponseSent(du->ids, udiff, *dr.remote, du->downstream->d_config.remote, du->response.size(), cleartextDH, du->downstream->getProtocol());
+    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);
 
-    ++g_stats.responses;
-    if (du->ids.cs) {
-      ++du->ids.cs->responses;
+    handleResponseSent(dohUnit->ids, udiff, dnsResponse.ids.origRemote, dohUnit->downstream->d_config.remote, dohUnit->response.size(), cleartextDH, dohUnit->downstream->getProtocol(), true);
+
+    ++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();
+  }
+}
 
-void handleDOHTimeout(DOHUnitUniquePtr&& oldDU)
+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 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..d5216aa
--- /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<DNSDistResponseRuleAction>> localRespRuleActions = g_respruleactions.getLocal();
+      static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localCacheInsertedRespRuleActions = g_cacheInsertedRespRuleActions.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..247b67a
--- /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<DNSDistResponseRuleAction>> localRespRuleActions = g_respruleactions.getLocal();
+      static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localCacheInsertedRespRuleActions = g_cacheInsertedRespRuleActions.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 7e2599780bd47087448866cc076df0a280e7bc15..29571e9067d25e2571f06ca71e0f70651387d1f6 100644 (file)
@@ -50,7 +50,8 @@
     </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: <span id="latency"></span> ms, CPU Usage: <span id="cpu"></span>%, Cache hitrate: <span id="phitrate"></span>%, Server selection policy: <span id="server-policy"></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, 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>
     <table width="100%" cellpadding="20">
index c63998f9a5eb010aed2d53ba242130a924c9dc5a..c04a26c950aec0c5e48c312b2ac3b13e01dec7eb 100644 (file)
@@ -1,7 +1,2 @@
-//! moment.js
-//! version : 2.11.1
-//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
-//! license : MIT
-//! momentjs.com
-!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return Uc.apply(null,arguments)}function b(a){Uc=a}function c(a){return"[object Array]"===Object.prototype.toString.call(a)}function d(a){return a instanceof Date||"[object Date]"===Object.prototype.toString.call(a)}function e(a,b){var c,d=[];for(c=0;c<a.length;++c)d.push(b(a[c],c));return d}function f(a,b){return Object.prototype.hasOwnProperty.call(a,b)}function g(a,b){for(var c in b)f(b,c)&&(a[c]=b[c]);return f(b,"toString")&&(a.toString=b.toString),f(b,"valueOf")&&(a.valueOf=b.valueOf),a}function h(a,b,c,d){return Da(a,b,c,d,!0).utc()}function i(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function j(a){return null==a._pf&&(a._pf=i()),a._pf}function k(a){if(null==a._isValid){var b=j(a);a._isValid=!(isNaN(a._d.getTime())||!(b.overflow<0)||b.empty||b.invalidMonth||b.invalidWeekday||b.nullInput||b.invalidFormat||b.userInvalidated),a._strict&&(a._isValid=a._isValid&&0===b.charsLeftOver&&0===b.unusedTokens.length&&void 0===b.bigHour)}return a._isValid}function l(a){var b=h(NaN);return null!=a?g(j(b),a):j(b).userInvalidated=!0,b}function m(a){return void 0===a}function n(a,b){var c,d,e;if(m(b._isAMomentObject)||(a._isAMomentObject=b._isAMomentObject),m(b._i)||(a._i=b._i),m(b._f)||(a._f=b._f),m(b._l)||(a._l=b._l),m(b._strict)||(a._strict=b._strict),m(b._tzm)||(a._tzm=b._tzm),m(b._isUTC)||(a._isUTC=b._isUTC),m(b._offset)||(a._offset=b._offset),m(b._pf)||(a._pf=j(b)),m(b._locale)||(a._locale=b._locale),Wc.length>0)for(c in Wc)d=Wc[c],e=b[d],m(e)||(a[d]=e);return a}function o(b){n(this,b),this._d=new Date(null!=b._d?b._d.getTime():NaN),Xc===!1&&(Xc=!0,a.updateOffset(this),Xc=!1)}function p(a){return a instanceof o||null!=a&&null!=a._isAMomentObject}function q(a){return 0>a?Math.ceil(a):Math.floor(a)}function r(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=q(b)),c}function s(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&r(a[d])!==r(b[d]))&&g++;return g+f}function t(){}function u(a){return a?a.toLowerCase().replace("_","-"):a}function v(a){for(var b,c,d,e,f=0;f<a.length;){for(e=u(a[f]).split("-"),b=e.length,c=u(a[f+1]),c=c?c.split("-"):null;b>0;){if(d=w(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&s(e,c,!0)>=b-1)break;b--}f++}return null}function w(a){var b=null;if(!Yc[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=Vc._abbr,require("./locale/"+a),x(b)}catch(c){}return Yc[a]}function x(a,b){var c;return a&&(c=m(b)?z(a):y(a,b),c&&(Vc=c)),Vc._abbr}function y(a,b){return null!==b?(b.abbr=a,Yc[a]=Yc[a]||new t,Yc[a].set(b),x(a),Yc[a]):(delete Yc[a],null)}function z(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return Vc;if(!c(a)){if(b=w(a))return b;a=[a]}return v(a)}function A(a,b){var c=a.toLowerCase();Zc[c]=Zc[c+"s"]=Zc[b]=a}function B(a){return"string"==typeof a?Zc[a]||Zc[a.toLowerCase()]:void 0}function C(a){var b,c,d={};for(c in a)f(a,c)&&(b=B(c),b&&(d[b]=a[c]));return d}function D(a){return a instanceof Function||"[object Function]"===Object.prototype.toString.call(a)}function E(b,c){return function(d){return null!=d?(G(this,b,d),a.updateOffset(this,c),this):F(this,b)}}function F(a,b){return a.isValid()?a._d["get"+(a._isUTC?"UTC":"")+b]():NaN}function G(a,b,c){a.isValid()&&a._d["set"+(a._isUTC?"UTC":"")+b](c)}function H(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else if(a=B(a),D(this[a]))return this[a](b);return this}function I(a,b,c){var d=""+Math.abs(a),e=b-d.length,f=a>=0;return(f?c?"+":"":"-")+Math.pow(10,Math.max(0,e)).toString().substr(1)+d}function J(a,b,c,d){var e=d;"string"==typeof d&&(e=function(){return this[d]()}),a&&(bd[a]=e),b&&(bd[b[0]]=function(){return I(e.apply(this,arguments),b[1],b[2])}),c&&(bd[c]=function(){return this.localeData().ordinal(e.apply(this,arguments),a)})}function K(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function L(a){var b,c,d=a.match($c);for(b=0,c=d.length;c>b;b++)bd[d[b]]?d[b]=bd[d[b]]:d[b]=K(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function M(a,b){return a.isValid()?(b=N(b,a.localeData()),ad[b]=ad[b]||L(b),ad[b](a)):a.localeData().invalidDate()}function N(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(_c.lastIndex=0;d>=0&&_c.test(a);)a=a.replace(_c,c),_c.lastIndex=0,d-=1;return a}function O(a,b,c){td[a]=D(b)?b:function(a,d){return a&&c?c:b}}function P(a,b){return f(td,a)?td[a](b._strict,b._locale):new RegExp(Q(a))}function Q(a){return R(a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}))}function R(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function S(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),"number"==typeof b&&(d=function(a,c){c[b]=r(a)}),c=0;c<a.length;c++)ud[a[c]]=d}function T(a,b){S(a,function(a,c,d,e){d._w=d._w||{},b(a,d._w,d,e)})}function U(a,b,c){null!=b&&f(ud,a)&&ud[a](b,c._a,c,a)}function V(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function W(a,b){return c(this._months)?this._months[a.month()]:this._months[Ed.test(b)?"format":"standalone"][a.month()]}function X(a,b){return c(this._monthsShort)?this._monthsShort[a.month()]:this._monthsShort[Ed.test(b)?"format":"standalone"][a.month()]}function Y(a,b,c){var d,e,f;for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),d=0;12>d;d++){if(e=h([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}}function Z(a,b){var c;return a.isValid()?"string"==typeof b&&(b=a.localeData().monthsParse(b),"number"!=typeof b)?a:(c=Math.min(a.date(),V(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a):a}function $(b){return null!=b?(Z(this,b),a.updateOffset(this,!0),this):F(this,"Month")}function _(){return V(this.year(),this.month())}function aa(a){return this._monthsParseExact?(f(this,"_monthsRegex")||ca.call(this),a?this._monthsShortStrictRegex:this._monthsShortRegex):this._monthsShortStrictRegex&&a?this._monthsShortStrictRegex:this._monthsShortRegex}function ba(a){return this._monthsParseExact?(f(this,"_monthsRegex")||ca.call(this),a?this._monthsStrictRegex:this._monthsRegex):this._monthsStrictRegex&&a?this._monthsStrictRegex:this._monthsRegex}function ca(){function a(a,b){return b.length-a.length}var b,c,d=[],e=[],f=[];for(b=0;12>b;b++)c=h([2e3,b]),d.push(this.monthsShort(c,"")),e.push(this.months(c,"")),f.push(this.months(c,"")),f.push(this.monthsShort(c,""));for(d.sort(a),e.sort(a),f.sort(a),b=0;12>b;b++)d[b]=R(d[b]),e[b]=R(e[b]),f[b]=R(f[b]);this._monthsRegex=new RegExp("^("+f.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+e.join("|")+")$","i"),this._monthsShortStrictRegex=new RegExp("^("+d.join("|")+")$","i")}function da(a){var b,c=a._a;return c&&-2===j(a).overflow&&(b=c[wd]<0||c[wd]>11?wd:c[xd]<1||c[xd]>V(c[vd],c[wd])?xd:c[yd]<0||c[yd]>24||24===c[yd]&&(0!==c[zd]||0!==c[Ad]||0!==c[Bd])?yd:c[zd]<0||c[zd]>59?zd:c[Ad]<0||c[Ad]>59?Ad:c[Bd]<0||c[Bd]>999?Bd:-1,j(a)._overflowDayOfYear&&(vd>b||b>xd)&&(b=xd),j(a)._overflowWeeks&&-1===b&&(b=Cd),j(a)._overflowWeekday&&-1===b&&(b=Dd),j(a).overflow=b),a}function ea(b){a.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+b)}function fa(a,b){var c=!0;return g(function(){return c&&(ea(a+"\nArguments: "+Array.prototype.slice.call(arguments).join(", ")+"\n"+(new Error).stack),c=!1),b.apply(this,arguments)},b)}function ga(a,b){Jd[a]||(ea(b),Jd[a]=!0)}function ha(a){var b,c,d,e,f,g,h=a._i,i=Kd.exec(h)||Ld.exec(h);if(i){for(j(a).iso=!0,b=0,c=Nd.length;c>b;b++)if(Nd[b][1].exec(i[1])){e=Nd[b][0],d=Nd[b][2]!==!1;break}if(null==e)return void(a._isValid=!1);if(i[3]){for(b=0,c=Od.length;c>b;b++)if(Od[b][1].exec(i[3])){f=(i[2]||" ")+Od[b][0];break}if(null==f)return void(a._isValid=!1)}if(!d&&null!=f)return void(a._isValid=!1);if(i[4]){if(!Md.exec(i[4]))return void(a._isValid=!1);g="Z"}a._f=e+(f||"")+(g||""),wa(a)}else a._isValid=!1}function ia(b){var c=Pd.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(ha(b),void(b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b))))}function ja(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 100>a&&a>=0&&isFinite(h.getFullYear())&&h.setFullYear(a),h}function ka(a){var b=new Date(Date.UTC.apply(null,arguments));return 100>a&&a>=0&&isFinite(b.getUTCFullYear())&&b.setUTCFullYear(a),b}function la(a){return ma(a)?366:365}function ma(a){return a%4===0&&a%100!==0||a%400===0}function na(){return ma(this.year())}function oa(a,b,c){var d=7+b-c,e=(7+ka(a,0,d).getUTCDay()-b)%7;return-e+d-1}function pa(a,b,c,d,e){var f,g,h=(7+c-d)%7,i=oa(a,d,e),j=1+7*(b-1)+h+i;return 0>=j?(f=a-1,g=la(f)+j):j>la(a)?(f=a+1,g=j-la(a)):(f=a,g=j),{year:f,dayOfYear:g}}function qa(a,b,c){var d,e,f=oa(a.year(),b,c),g=Math.floor((a.dayOfYear()-f-1)/7)+1;return 1>g?(e=a.year()-1,d=g+ra(e,b,c)):g>ra(a.year(),b,c)?(d=g-ra(a.year(),b,c),e=a.year()+1):(e=a.year(),d=g),{week:d,year:e}}function ra(a,b,c){var d=oa(a,b,c),e=oa(a+1,b,c);return(la(a)-d+e)/7}function sa(a,b,c){return null!=a?a:null!=b?b:c}function ta(b){var c=new Date(a.now());return b._useUTC?[c.getUTCFullYear(),c.getUTCMonth(),c.getUTCDate()]:[c.getFullYear(),c.getMonth(),c.getDate()]}function ua(a){var b,c,d,e,f=[];if(!a._d){for(d=ta(a),a._w&&null==a._a[xd]&&null==a._a[wd]&&va(a),a._dayOfYear&&(e=sa(a._a[vd],d[vd]),a._dayOfYear>la(e)&&(j(a)._overflowDayOfYear=!0),c=ka(e,0,a._dayOfYear),a._a[wd]=c.getUTCMonth(),a._a[xd]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;7>b;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[yd]&&0===a._a[zd]&&0===a._a[Ad]&&0===a._a[Bd]&&(a._nextDay=!0,a._a[yd]=0),a._d=(a._useUTC?ka:ja).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[yd]=24)}}function va(a){var b,c,d,e,f,g,h,i;b=a._w,null!=b.GG||null!=b.W||null!=b.E?(f=1,g=4,c=sa(b.GG,a._a[vd],qa(Ea(),1,4).year),d=sa(b.W,1),e=sa(b.E,1),(1>e||e>7)&&(i=!0)):(f=a._locale._week.dow,g=a._locale._week.doy,c=sa(b.gg,a._a[vd],qa(Ea(),f,g).year),d=sa(b.w,1),null!=b.d?(e=b.d,(0>e||e>6)&&(i=!0)):null!=b.e?(e=b.e+f,(b.e<0||b.e>6)&&(i=!0)):e=f),1>d||d>ra(c,f,g)?j(a)._overflowWeeks=!0:null!=i?j(a)._overflowWeekday=!0:(h=pa(c,d,e,f,g),a._a[vd]=h.year,a._dayOfYear=h.dayOfYear)}function wa(b){if(b._f===a.ISO_8601)return void ha(b);b._a=[],j(b).empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,k=0;for(e=N(b._f,b._locale).match($c)||[],c=0;c<e.length;c++)f=e[c],d=(h.match(P(f,b))||[])[0],d&&(g=h.substr(0,h.indexOf(d)),g.length>0&&j(b).unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),k+=d.length),bd[f]?(d?j(b).empty=!1:j(b).unusedTokens.push(f),U(f,d,b)):b._strict&&!d&&j(b).unusedTokens.push(f);j(b).charsLeftOver=i-k,h.length>0&&j(b).unusedInput.push(h),j(b).bigHour===!0&&b._a[yd]<=12&&b._a[yd]>0&&(j(b).bigHour=void 0),b._a[yd]=xa(b._locale,b._a[yd],b._meridiem),ua(b),da(b)}function xa(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function ya(a){var b,c,d,e,f;if(0===a._f.length)return j(a).invalidFormat=!0,void(a._d=new Date(NaN));for(e=0;e<a._f.length;e++)f=0,b=n({},a),null!=a._useUTC&&(b._useUTC=a._useUTC),b._f=a._f[e],wa(b),k(b)&&(f+=j(b).charsLeftOver,f+=10*j(b).unusedTokens.length,j(b).score=f,(null==d||d>f)&&(d=f,c=b));g(a,c||b)}function za(a){if(!a._d){var b=C(a._i);a._a=e([b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],function(a){return a&&parseInt(a,10)}),ua(a)}}function Aa(a){var b=new o(da(Ba(a)));return b._nextDay&&(b.add(1,"d"),b._nextDay=void 0),b}function Ba(a){var b=a._i,e=a._f;return a._locale=a._locale||z(a._l),null===b||void 0===e&&""===b?l({nullInput:!0}):("string"==typeof b&&(a._i=b=a._locale.preparse(b)),p(b)?new o(da(b)):(c(e)?ya(a):e?wa(a):d(b)?a._d=b:Ca(a),k(a)||(a._d=null),a))}function Ca(b){var f=b._i;void 0===f?b._d=new Date(a.now()):d(f)?b._d=new Date(+f):"string"==typeof f?ia(b):c(f)?(b._a=e(f.slice(0),function(a){return parseInt(a,10)}),ua(b)):"object"==typeof f?za(b):"number"==typeof f?b._d=new Date(f):a.createFromInputFallback(b)}function Da(a,b,c,d,e){var f={};return"boolean"==typeof c&&(d=c,c=void 0),f._isAMomentObject=!0,f._useUTC=f._isUTC=e,f._l=c,f._i=a,f._f=b,f._strict=d,Aa(f)}function Ea(a,b,c,d){return Da(a,b,c,d,!1)}function Fa(a,b){var d,e;if(1===b.length&&c(b[0])&&(b=b[0]),!b.length)return Ea();for(d=b[0],e=1;e<b.length;++e)(!b[e].isValid()||b[e][a](d))&&(d=b[e]);return d}function Ga(){var a=[].slice.call(arguments,0);return Fa("isBefore",a)}function Ha(){var a=[].slice.call(arguments,0);return Fa("isAfter",a)}function Ia(a){var b=C(a),c=b.year||0,d=b.quarter||0,e=b.month||0,f=b.week||0,g=b.day||0,h=b.hour||0,i=b.minute||0,j=b.second||0,k=b.millisecond||0;this._milliseconds=+k+1e3*j+6e4*i+36e5*h,this._days=+g+7*f,this._months=+e+3*d+12*c,this._data={},this._locale=z(),this._bubble()}function Ja(a){return a instanceof Ia}function Ka(a,b){J(a,0,0,function(){var a=this.utcOffset(),c="+";return 0>a&&(a=-a,c="-"),c+I(~~(a/60),2)+b+I(~~a%60,2)})}function La(a,b){var c=(b||"").match(a)||[],d=c[c.length-1]||[],e=(d+"").match(Ud)||["-",0,0],f=+(60*e[1])+r(e[2]);return"+"===e[0]?f:-f}function Ma(b,c){var e,f;return c._isUTC?(e=c.clone(),f=(p(b)||d(b)?+b:+Ea(b))-+e,e._d.setTime(+e._d+f),a.updateOffset(e,!1),e):Ea(b).local()}function Na(a){return 15*-Math.round(a._d.getTimezoneOffset()/15)}function Oa(b,c){var d,e=this._offset||0;return this.isValid()?null!=b?("string"==typeof b?b=La(qd,b):Math.abs(b)<16&&(b=60*b),!this._isUTC&&c&&(d=Na(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?cb(this,Za(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?e:Na(this):null!=b?this:NaN}function Pa(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function Qa(a){return this.utcOffset(0,a)}function Ra(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Na(this),"m")),this}function Sa(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(La(pd,this._i)),this}function Ta(a){return this.isValid()?(a=a?Ea(a).utcOffset():0,(this.utcOffset()-a)%60===0):!1}function Ua(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Va(){if(!m(this._isDSTShifted))return this._isDSTShifted;var a={};if(n(a,this),a=Ba(a),a._a){var b=a._isUTC?h(a._a):Ea(a._a);this._isDSTShifted=this.isValid()&&s(a._a,b.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Wa(){return this.isValid()?!this._isUTC:!1}function Xa(){return this.isValid()?this._isUTC:!1}function Ya(){return this.isValid()?this._isUTC&&0===this._offset:!1}function Za(a,b){var c,d,e,g=a,h=null;return Ja(a)?g={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(g={},b?g[b]=a:g.milliseconds=a):(h=Vd.exec(a))?(c="-"===h[1]?-1:1,g={y:0,d:r(h[xd])*c,h:r(h[yd])*c,m:r(h[zd])*c,s:r(h[Ad])*c,ms:r(h[Bd])*c}):(h=Wd.exec(a))?(c="-"===h[1]?-1:1,g={y:$a(h[2],c),M:$a(h[3],c),d:$a(h[4],c),h:$a(h[5],c),m:$a(h[6],c),s:$a(h[7],c),w:$a(h[8],c)}):null==g?g={}:"object"==typeof g&&("from"in g||"to"in g)&&(e=ab(Ea(g.from),Ea(g.to)),g={},g.ms=e.milliseconds,g.M=e.months),d=new Ia(g),Ja(a)&&f(a,"_locale")&&(d._locale=a._locale),d}function $a(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function _a(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function ab(a,b){var c;return a.isValid()&&b.isValid()?(b=Ma(b,a),a.isBefore(b)?c=_a(a,b):(c=_a(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c):{milliseconds:0,months:0}}function bb(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||(ga(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period)."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=Za(c,d),cb(this,e,a),this}}function cb(b,c,d,e){var f=c._milliseconds,g=c._days,h=c._months;b.isValid()&&(e=null==e?!0:e,f&&b._d.setTime(+b._d+f*d),g&&G(b,"Date",F(b,"Date")+g*d),h&&Z(b,F(b,"Month")+h*d),e&&a.updateOffset(b,g||h))}function db(a,b){var c=a||Ea(),d=Ma(c,this).startOf("day"),e=this.diff(d,"days",!0),f=-6>e?"sameElse":-1>e?"lastWeek":0>e?"lastDay":1>e?"sameDay":2>e?"nextDay":7>e?"nextWeek":"sameElse",g=b&&(D(b[f])?b[f]():b[f]);return this.format(g||this.localeData().calendar(f,this,Ea(c)))}function eb(){return new o(this)}function fb(a,b){var c=p(a)?a:Ea(a);return this.isValid()&&c.isValid()?(b=B(m(b)?"millisecond":b),"millisecond"===b?+this>+c:+c<+this.clone().startOf(b)):!1}function gb(a,b){var c=p(a)?a:Ea(a);return this.isValid()&&c.isValid()?(b=B(m(b)?"millisecond":b),"millisecond"===b?+c>+this:+this.clone().endOf(b)<+c):!1}function hb(a,b,c){return this.isAfter(a,c)&&this.isBefore(b,c)}function ib(a,b){var c,d=p(a)?a:Ea(a);return this.isValid()&&d.isValid()?(b=B(b||"millisecond"),"millisecond"===b?+this===+d:(c=+d,+this.clone().startOf(b)<=c&&c<=+this.clone().endOf(b))):!1}function jb(a,b){return this.isSame(a,b)||this.isAfter(a,b)}function kb(a,b){return this.isSame(a,b)||this.isBefore(a,b)}function lb(a,b,c){var d,e,f,g;return this.isValid()?(d=Ma(a,this),d.isValid()?(e=6e4*(d.utcOffset()-this.utcOffset()),b=B(b),"year"===b||"month"===b||"quarter"===b?(g=mb(this,d),"quarter"===b?g/=3:"year"===b&&(g/=12)):(f=this-d,g="second"===b?f/1e3:"minute"===b?f/6e4:"hour"===b?f/36e5:"day"===b?(f-e)/864e5:"week"===b?(f-e)/6048e5:f),c?g:q(g)):NaN):NaN}function mb(a,b){var c,d,e=12*(b.year()-a.year())+(b.month()-a.month()),f=a.clone().add(e,"months");return 0>b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)}function nb(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function ob(){var a=this.clone().utc();return 0<a.year()&&a.year()<=9999?D(Date.prototype.toISOString)?this.toDate().toISOString():M(a,"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]"):M(a,"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]")}function pb(b){var c=M(this,b||a.defaultFormat);return this.localeData().postformat(c)}function qb(a,b){return this.isValid()&&(p(a)&&a.isValid()||Ea(a).isValid())?Za({to:this,from:a}).locale(this.locale()).humanize(!b):this.localeData().invalidDate()}function rb(a){return this.from(Ea(),a)}function sb(a,b){return this.isValid()&&(p(a)&&a.isValid()||Ea(a).isValid())?Za({from:this,to:a}).locale(this.locale()).humanize(!b):this.localeData().invalidDate()}function tb(a){return this.to(Ea(),a)}function ub(a){var b;return void 0===a?this._locale._abbr:(b=z(a),null!=b&&(this._locale=b),this)}function vb(){return this._locale}function wb(a){switch(a=B(a)){case"year":this.month(0);case"quarter":case"month":this.date(1);case"week":case"isoWeek":case"day":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return"week"===a&&this.weekday(0),"isoWeek"===a&&this.isoWeekday(1),"quarter"===a&&this.month(3*Math.floor(this.month()/3)),this}function xb(a){return a=B(a),void 0===a||"millisecond"===a?this:this.startOf(a).add(1,"isoWeek"===a?"week":a).subtract(1,"ms")}function yb(){return+this._d-6e4*(this._offset||0)}function zb(){return Math.floor(+this/1e3)}function Ab(){return this._offset?new Date(+this):this._d}function Bb(){var a=this;return[a.year(),a.month(),a.date(),a.hour(),a.minute(),a.second(),a.millisecond()]}function Cb(){var a=this;return{years:a.year(),months:a.month(),date:a.date(),hours:a.hours(),minutes:a.minutes(),seconds:a.seconds(),milliseconds:a.milliseconds()}}function Db(){return this.isValid()?this.toISOString():"null"}function Eb(){return k(this)}function Fb(){return g({},j(this))}function Gb(){return j(this).overflow}function Hb(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}}function Ib(a,b){J(0,[a,a.length],0,b)}function Jb(a){return Nb.call(this,a,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)}function Kb(a){return Nb.call(this,a,this.isoWeek(),this.isoWeekday(),1,4)}function Lb(){return ra(this.year(),1,4)}function Mb(){var a=this.localeData()._week;return ra(this.year(),a.dow,a.doy)}function Nb(a,b,c,d,e){var f;return null==a?qa(this,d,e).year:(f=ra(a,d,e),b>f&&(b=f),Ob.call(this,a,b,c,d,e))}function Ob(a,b,c,d,e){var f=pa(a,b,c,d,e),g=ka(f.year,0,f.dayOfYear);return this.year(g.getUTCFullYear()),this.month(g.getUTCMonth()),this.date(g.getUTCDate()),this}function Pb(a){return null==a?Math.ceil((this.month()+1)/3):this.month(3*(a-1)+this.month()%3)}function Qb(a){return qa(a,this._week.dow,this._week.doy).week}function Rb(){return this._week.dow}function Sb(){return this._week.doy}function Tb(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function Ub(a){var b=qa(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function Vb(a,b){return"string"!=typeof a?a:isNaN(a)?(a=b.weekdaysParse(a),"number"==typeof a?a:null):parseInt(a,10)}function Wb(a,b){return c(this._weekdays)?this._weekdays[a.day()]:this._weekdays[this._weekdays.isFormat.test(b)?"format":"standalone"][a.day()]}function Xb(a){return this._weekdaysShort[a.day()]}function Yb(a){return this._weekdaysMin[a.day()]}function Zb(a,b,c){var d,e,f;for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),d=0;7>d;d++){if(e=Ea([2e3,1]).day(d),c&&!this._fullWeekdaysParse[d]&&(this._fullWeekdaysParse[d]=new RegExp("^"+this.weekdays(e,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[d]=new RegExp("^"+this.weekdaysShort(e,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[d]=new RegExp("^"+this.weekdaysMin(e,"").replace(".",".?")+"$","i")),this._weekdaysParse[d]||(f="^"+this.weekdays(e,"")+"|^"+this.weekdaysShort(e,"")+"|^"+this.weekdaysMin(e,""),this._weekdaysParse[d]=new RegExp(f.replace(".",""),"i")),c&&"dddd"===b&&this._fullWeekdaysParse[d].test(a))return d;if(c&&"ddd"===b&&this._shortWeekdaysParse[d].test(a))return d;if(c&&"dd"===b&&this._minWeekdaysParse[d].test(a))return d;if(!c&&this._weekdaysParse[d].test(a))return d}}function $b(a){if(!this.isValid())return null!=a?this:NaN;var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=Vb(a,this.localeData()),this.add(a-b,"d")):b}function _b(a){if(!this.isValid())return null!=a?this:NaN;var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function ac(a){return this.isValid()?null==a?this.day()||7:this.day(this.day()%7?a:a-7):null!=a?this:NaN}function bc(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function cc(){return this.hours()%12||12}function dc(a,b){J(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function ec(a,b){return b._meridiemParse}function fc(a){return"p"===(a+"").toLowerCase().charAt(0)}function gc(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function hc(a,b){b[Bd]=r(1e3*("0."+a))}function ic(){return this._isUTC?"UTC":""}function jc(){return this._isUTC?"Coordinated Universal Time":""}function kc(a){return Ea(1e3*a)}function lc(){return Ea.apply(null,arguments).parseZone()}function mc(a,b,c){var d=this._calendar[a];return D(d)?d.call(b,c):d}function nc(a){var b=this._longDateFormat[a],c=this._longDateFormat[a.toUpperCase()];return b||!c?b:(this._longDateFormat[a]=c.replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a])}function oc(){return this._invalidDate}function pc(a){return this._ordinal.replace("%d",a)}function qc(a){return a}function rc(a,b,c,d){var e=this._relativeTime[c];return D(e)?e(a,b,c,d):e.replace(/%d/i,a)}function sc(a,b){var c=this._relativeTime[a>0?"future":"past"];return D(c)?c(b):c.replace(/%s/i,b)}function tc(a){var b,c;for(c in a)b=a[c],D(b)?this[c]=b:this["_"+c]=b;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function uc(a,b,c,d){var e=z(),f=h().set(d,b);return e[c](f,a)}function vc(a,b,c,d,e){if("number"==typeof a&&(b=a,a=void 0),a=a||"",null!=b)return uc(a,b,c,e);var f,g=[];for(f=0;d>f;f++)g[f]=uc(a,f,c,e);return g}function wc(a,b){return vc(a,b,"months",12,"month")}function xc(a,b){return vc(a,b,"monthsShort",12,"month")}function yc(a,b){return vc(a,b,"weekdays",7,"day")}function zc(a,b){return vc(a,b,"weekdaysShort",7,"day")}function Ac(a,b){return vc(a,b,"weekdaysMin",7,"day")}function Bc(){var a=this._data;return this._milliseconds=se(this._milliseconds),this._days=se(this._days),this._months=se(this._months),a.milliseconds=se(a.milliseconds),a.seconds=se(a.seconds),a.minutes=se(a.minutes),a.hours=se(a.hours),a.months=se(a.months),a.years=se(a.years),this}function Cc(a,b,c,d){var e=Za(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function Dc(a,b){return Cc(this,a,b,1)}function Ec(a,b){return Cc(this,a,b,-1)}function Fc(a){return 0>a?Math.floor(a):Math.ceil(a)}function Gc(){var a,b,c,d,e,f=this._milliseconds,g=this._days,h=this._months,i=this._data;return f>=0&&g>=0&&h>=0||0>=f&&0>=g&&0>=h||(f+=864e5*Fc(Ic(h)+g),g=0,h=0),i.milliseconds=f%1e3,a=q(f/1e3),i.seconds=a%60,b=q(a/60),i.minutes=b%60,c=q(b/60),i.hours=c%24,g+=q(c/24),e=q(Hc(g)),h+=e,g-=Fc(Ic(e)),d=q(h/12),h%=12,i.days=g,i.months=h,i.years=d,this}function Hc(a){return 4800*a/146097}function Ic(a){return 146097*a/4800}function Jc(a){var b,c,d=this._milliseconds;if(a=B(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+Hc(b),"month"===a?c:c/12;switch(b=this._days+Math.round(Ic(this._months)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 1440*b+d/6e4;case"second":return 86400*b+d/1e3;case"millisecond":return Math.floor(864e5*b)+d;default:throw new Error("Unknown unit "+a)}}function Kc(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*r(this._months/12)}function Lc(a){return function(){return this.as(a)}}function Mc(a){return a=B(a),this[a+"s"]()}function Nc(a){return function(){return this._data[a]}}function Oc(){return q(this.days()/7)}function Pc(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function Qc(a,b,c){var d=Za(a).abs(),e=Ie(d.as("s")),f=Ie(d.as("m")),g=Ie(d.as("h")),h=Ie(d.as("d")),i=Ie(d.as("M")),j=Ie(d.as("y")),k=e<Je.s&&["s",e]||1>=f&&["m"]||f<Je.m&&["mm",f]||1>=g&&["h"]||g<Je.h&&["hh",g]||1>=h&&["d"]||h<Je.d&&["dd",h]||1>=i&&["M"]||i<Je.M&&["MM",i]||1>=j&&["y"]||["yy",j];return k[2]=b,k[3]=+a>0,k[4]=c,Pc.apply(null,k)}function Rc(a,b){return void 0===Je[a]?!1:void 0===b?Je[a]:(Je[a]=b,!0)}function Sc(a){var b=this.localeData(),c=Qc(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function Tc(){var a,b,c,d=Ke(this._milliseconds)/1e3,e=Ke(this._days),f=Ke(this._months);a=q(d/60),b=q(a/60),d%=60,a%=60,c=q(f/12),f%=12;var g=c,h=f,i=e,j=b,k=a,l=d,m=this.asSeconds();return m?(0>m?"-":"")+"P"+(g?g+"Y":"")+(h?h+"M":"")+(i?i+"D":"")+(j||k||l?"T":"")+(j?j+"H":"")+(k?k+"M":"")+(l?l+"S":""):"P0D"}var Uc,Vc,Wc=a.momentProperties=[],Xc=!1,Yc={},Zc={},$c=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,_c=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,ad={},bd={},cd=/\d/,dd=/\d\d/,ed=/\d{3}/,fd=/\d{4}/,gd=/[+-]?\d{6}/,hd=/\d\d?/,id=/\d\d\d\d?/,jd=/\d\d\d\d\d\d?/,kd=/\d{1,3}/,ld=/\d{1,4}/,md=/[+-]?\d{1,6}/,nd=/\d+/,od=/[+-]?\d+/,pd=/Z|[+-]\d\d:?\d\d/gi,qd=/Z|[+-]\d\d(?::?\d\d)?/gi,rd=/[+-]?\d+(\.\d{1,3})?/,sd=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,td={},ud={},vd=0,wd=1,xd=2,yd=3,zd=4,Ad=5,Bd=6,Cd=7,Dd=8;J("M",["MM",2],"Mo",function(){return this.month()+1}),J("MMM",0,0,function(a){return this.localeData().monthsShort(this,a)}),J("MMMM",0,0,function(a){return this.localeData().months(this,a)}),A("month","M"),O("M",hd),O("MM",hd,dd),O("MMM",function(a,b){return b.monthsShortRegex(a)}),O("MMMM",function(a,b){return b.monthsRegex(a)}),S(["M","MM"],function(a,b){b[wd]=r(a)-1}),S(["MMM","MMMM"],function(a,b,c,d){var e=c._locale.monthsParse(a,d,c._strict);null!=e?b[wd]=e:j(c).invalidMonth=a});var Ed=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/,Fd="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),Gd="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),Hd=sd,Id=sd,Jd={};a.suppressDeprecationWarnings=!1;var Kd=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,Ld=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,Md=/Z|[+-]\d\d(?::?\d\d)?/,Nd=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Od=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Pd=/^\/?Date\((\-?\d+)/i;a.createFromInputFallback=fa("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),J("Y",0,0,function(){var a=this.year();return 9999>=a?""+a:"+"+a}),J(0,["YY",2],0,function(){return this.year()%100}),J(0,["YYYY",4],0,"year"),J(0,["YYYYY",5],0,"year"),J(0,["YYYYYY",6,!0],0,"year"),A("year","y"),O("Y",od),O("YY",hd,dd),O("YYYY",ld,fd),O("YYYYY",md,gd),O("YYYYYY",md,gd),S(["YYYYY","YYYYYY"],vd),S("YYYY",function(b,c){c[vd]=2===b.length?a.parseTwoDigitYear(b):r(b)}),S("YY",function(b,c){c[vd]=a.parseTwoDigitYear(b)}),S("Y",function(a,b){b[vd]=parseInt(a,10)}),a.parseTwoDigitYear=function(a){return r(a)+(r(a)>68?1900:2e3)};var Qd=E("FullYear",!1);a.ISO_8601=function(){};var Rd=fa("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var a=Ea.apply(null,arguments);return this.isValid()&&a.isValid()?this>a?this:a:l()}),Sd=fa("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var a=Ea.apply(null,arguments);return this.isValid()&&a.isValid()?a>this?this:a:l()}),Td=function(){return Date.now?Date.now():+new Date};Ka("Z",":"),Ka("ZZ",""),O("Z",qd),O("ZZ",qd),S(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=La(qd,a)});var Ud=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var Vd=/(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,Wd=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;
-Za.fn=Ia.prototype;var Xd=bb(1,"add"),Yd=bb(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var Zd=fa("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});J(0,["gg",2],0,function(){return this.weekYear()%100}),J(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Ib("gggg","weekYear"),Ib("ggggg","weekYear"),Ib("GGGG","isoWeekYear"),Ib("GGGGG","isoWeekYear"),A("weekYear","gg"),A("isoWeekYear","GG"),O("G",od),O("g",od),O("GG",hd,dd),O("gg",hd,dd),O("GGGG",ld,fd),O("gggg",ld,fd),O("GGGGG",md,gd),O("ggggg",md,gd),T(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=r(a)}),T(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),J("Q",0,"Qo","quarter"),A("quarter","Q"),O("Q",cd),S("Q",function(a,b){b[wd]=3*(r(a)-1)}),J("w",["ww",2],"wo","week"),J("W",["WW",2],"Wo","isoWeek"),A("week","w"),A("isoWeek","W"),O("w",hd),O("ww",hd,dd),O("W",hd),O("WW",hd,dd),T(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=r(a)});var $d={dow:0,doy:6};J("D",["DD",2],"Do","date"),A("date","D"),O("D",hd),O("DD",hd,dd),O("Do",function(a,b){return a?b._ordinalParse:b._ordinalParseLenient}),S(["D","DD"],xd),S("Do",function(a,b){b[xd]=r(a.match(hd)[0],10)});var _d=E("Date",!0);J("d",0,"do","day"),J("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),J("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),J("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),J("e",0,0,"weekday"),J("E",0,0,"isoWeekday"),A("day","d"),A("weekday","e"),A("isoWeekday","E"),O("d",hd),O("e",hd),O("E",hd),O("dd",sd),O("ddd",sd),O("dddd",sd),T(["dd","ddd","dddd"],function(a,b,c,d){var e=c._locale.weekdaysParse(a,d,c._strict);null!=e?b.d=e:j(c).invalidWeekday=a}),T(["d","e","E"],function(a,b,c,d){b[d]=r(a)});var ae="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),be="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),ce="Su_Mo_Tu_We_Th_Fr_Sa".split("_");J("DDD",["DDDD",3],"DDDo","dayOfYear"),A("dayOfYear","DDD"),O("DDD",kd),O("DDDD",ed),S(["DDD","DDDD"],function(a,b,c){c._dayOfYear=r(a)}),J("H",["HH",2],0,"hour"),J("h",["hh",2],0,cc),J("hmm",0,0,function(){return""+cc.apply(this)+I(this.minutes(),2)}),J("hmmss",0,0,function(){return""+cc.apply(this)+I(this.minutes(),2)+I(this.seconds(),2)}),J("Hmm",0,0,function(){return""+this.hours()+I(this.minutes(),2)}),J("Hmmss",0,0,function(){return""+this.hours()+I(this.minutes(),2)+I(this.seconds(),2)}),dc("a",!0),dc("A",!1),A("hour","h"),O("a",ec),O("A",ec),O("H",hd),O("h",hd),O("HH",hd,dd),O("hh",hd,dd),O("hmm",id),O("hmmss",jd),O("Hmm",id),O("Hmmss",jd),S(["H","HH"],yd),S(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),S(["h","hh"],function(a,b,c){b[yd]=r(a),j(c).bigHour=!0}),S("hmm",function(a,b,c){var d=a.length-2;b[yd]=r(a.substr(0,d)),b[zd]=r(a.substr(d)),j(c).bigHour=!0}),S("hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[yd]=r(a.substr(0,d)),b[zd]=r(a.substr(d,2)),b[Ad]=r(a.substr(e)),j(c).bigHour=!0}),S("Hmm",function(a,b,c){var d=a.length-2;b[yd]=r(a.substr(0,d)),b[zd]=r(a.substr(d))}),S("Hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[yd]=r(a.substr(0,d)),b[zd]=r(a.substr(d,2)),b[Ad]=r(a.substr(e))});var de=/[ap]\.?m?\.?/i,ee=E("Hours",!0);J("m",["mm",2],0,"minute"),A("minute","m"),O("m",hd),O("mm",hd,dd),S(["m","mm"],zd);var fe=E("Minutes",!1);J("s",["ss",2],0,"second"),A("second","s"),O("s",hd),O("ss",hd,dd),S(["s","ss"],Ad);var ge=E("Seconds",!1);J("S",0,0,function(){return~~(this.millisecond()/100)}),J(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),J(0,["SSS",3],0,"millisecond"),J(0,["SSSS",4],0,function(){return 10*this.millisecond()}),J(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),J(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),J(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),J(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),J(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),A("millisecond","ms"),O("S",kd,cd),O("SS",kd,dd),O("SSS",kd,ed);var he;for(he="SSSS";he.length<=9;he+="S")O(he,nd);for(he="S";he.length<=9;he+="S")S(he,hc);var ie=E("Milliseconds",!1);J("z",0,0,"zoneAbbr"),J("zz",0,0,"zoneName");var je=o.prototype;je.add=Xd,je.calendar=db,je.clone=eb,je.diff=lb,je.endOf=xb,je.format=pb,je.from=qb,je.fromNow=rb,je.to=sb,je.toNow=tb,je.get=H,je.invalidAt=Gb,je.isAfter=fb,je.isBefore=gb,je.isBetween=hb,je.isSame=ib,je.isSameOrAfter=jb,je.isSameOrBefore=kb,je.isValid=Eb,je.lang=Zd,je.locale=ub,je.localeData=vb,je.max=Sd,je.min=Rd,je.parsingFlags=Fb,je.set=H,je.startOf=wb,je.subtract=Yd,je.toArray=Bb,je.toObject=Cb,je.toDate=Ab,je.toISOString=ob,je.toJSON=Db,je.toString=nb,je.unix=zb,je.valueOf=yb,je.creationData=Hb,je.year=Qd,je.isLeapYear=na,je.weekYear=Jb,je.isoWeekYear=Kb,je.quarter=je.quarters=Pb,je.month=$,je.daysInMonth=_,je.week=je.weeks=Tb,je.isoWeek=je.isoWeeks=Ub,je.weeksInYear=Mb,je.isoWeeksInYear=Lb,je.date=_d,je.day=je.days=$b,je.weekday=_b,je.isoWeekday=ac,je.dayOfYear=bc,je.hour=je.hours=ee,je.minute=je.minutes=fe,je.second=je.seconds=ge,je.millisecond=je.milliseconds=ie,je.utcOffset=Oa,je.utc=Qa,je.local=Ra,je.parseZone=Sa,je.hasAlignedHourOffset=Ta,je.isDST=Ua,je.isDSTShifted=Va,je.isLocal=Wa,je.isUtcOffset=Xa,je.isUtc=Ya,je.isUTC=Ya,je.zoneAbbr=ic,je.zoneName=jc,je.dates=fa("dates accessor is deprecated. Use date instead.",_d),je.months=fa("months accessor is deprecated. Use month instead",$),je.years=fa("years accessor is deprecated. Use year instead",Qd),je.zone=fa("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Pa);var ke=je,le={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},me={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},ne="Invalid date",oe="%d",pe=/\d{1,2}/,qe={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},re=t.prototype;re._calendar=le,re.calendar=mc,re._longDateFormat=me,re.longDateFormat=nc,re._invalidDate=ne,re.invalidDate=oc,re._ordinal=oe,re.ordinal=pc,re._ordinalParse=pe,re.preparse=qc,re.postformat=qc,re._relativeTime=qe,re.relativeTime=rc,re.pastFuture=sc,re.set=tc,re.months=W,re._months=Fd,re.monthsShort=X,re._monthsShort=Gd,re.monthsParse=Y,re._monthsRegex=Id,re.monthsRegex=ba,re._monthsShortRegex=Hd,re.monthsShortRegex=aa,re.week=Qb,re._week=$d,re.firstDayOfYear=Sb,re.firstDayOfWeek=Rb,re.weekdays=Wb,re._weekdays=ae,re.weekdaysMin=Yb,re._weekdaysMin=ce,re.weekdaysShort=Xb,re._weekdaysShort=be,re.weekdaysParse=Zb,re.isPM=fc,re._meridiemParse=de,re.meridiem=gc,x("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===r(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=fa("moment.lang is deprecated. Use moment.locale instead.",x),a.langData=fa("moment.langData is deprecated. Use moment.localeData instead.",z);var se=Math.abs,te=Lc("ms"),ue=Lc("s"),ve=Lc("m"),we=Lc("h"),xe=Lc("d"),ye=Lc("w"),ze=Lc("M"),Ae=Lc("y"),Be=Nc("milliseconds"),Ce=Nc("seconds"),De=Nc("minutes"),Ee=Nc("hours"),Fe=Nc("days"),Ge=Nc("months"),He=Nc("years"),Ie=Math.round,Je={s:45,m:45,h:22,d:26,M:11},Ke=Math.abs,Le=Ia.prototype;Le.abs=Bc,Le.add=Dc,Le.subtract=Ec,Le.as=Jc,Le.asMilliseconds=te,Le.asSeconds=ue,Le.asMinutes=ve,Le.asHours=we,Le.asDays=xe,Le.asWeeks=ye,Le.asMonths=ze,Le.asYears=Ae,Le.valueOf=Kc,Le._bubble=Gc,Le.get=Mc,Le.milliseconds=Be,Le.seconds=Ce,Le.minutes=De,Le.hours=Ee,Le.days=Fe,Le.weeks=Oc,Le.months=Ge,Le.years=He,Le.humanize=Sc,Le.toISOString=Tc,Le.toString=Tc,Le.toJSON=Tc,Le.locale=ub,Le.localeData=vb,Le.toIsoString=fa("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Tc),Le.lang=Zd,J("X",0,0,"unix"),J("x",0,0,"valueOf"),O("x",od),O("X",rd),S("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),S("x",function(a,b,c){c._d=new Date(r(a))}),a.version="2.11.1",b(Ea),a.fn=ke,a.min=Ga,a.max=Ha,a.now=Td,a.utc=h,a.unix=kc,a.months=wc,a.isDate=d,a.locale=x,a.invalid=l,a.duration=Za,a.isMoment=p,a.weekdays=yc,a.parseZone=lc,a.localeData=z,a.isDuration=Ja,a.monthsShort=xc,a.weekdaysMin=Ac,a.defineLocale=y,a.weekdaysShort=zc,a.normalizeUnits=B,a.relativeTimeThreshold=Rc,a.prototype=ke;var Me=a;return Me});
\ No newline at end of file
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.moment=t()}(this,function(){"use strict";var H;function f(){return H.apply(null,arguments)}function a(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function F(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function c(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function L(e){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(e).length;for(var t in e)if(c(e,t))return;return 1}function o(e){return void 0===e}function u(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function V(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function G(e,t){for(var n=[],s=e.length,i=0;i<s;++i)n.push(t(e[i],i));return n}function E(e,t){for(var n in t)c(t,n)&&(e[n]=t[n]);return c(t,"toString")&&(e.toString=t.toString),c(t,"valueOf")&&(e.valueOf=t.valueOf),e}function l(e,t,n,s){return Pt(e,t,n,s,!0).utc()}function m(e){return null==e._pf&&(e._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidEra:null,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1,parsedDateParts:[],era:null,meridiem:null,rfc2822:!1,weekdayMismatch:!1}),e._pf}function A(e){if(null==e._isValid){var t=m(e),n=j.call(t.parsedDateParts,function(e){return null!=e}),n=!isNaN(e._d.getTime())&&t.overflow<0&&!t.empty&&!t.invalidEra&&!t.invalidMonth&&!t.invalidWeekday&&!t.weekdayMismatch&&!t.nullInput&&!t.invalidFormat&&!t.userInvalidated&&(!t.meridiem||t.meridiem&&n);if(e._strict&&(n=n&&0===t.charsLeftOver&&0===t.unusedTokens.length&&void 0===t.bigHour),null!=Object.isFrozen&&Object.isFrozen(e))return n;e._isValid=n}return e._isValid}function I(e){var t=l(NaN);return null!=e?E(m(t),e):m(t).userInvalidated=!0,t}var j=Array.prototype.some||function(e){for(var t=Object(this),n=t.length>>>0,s=0;s<n;s++)if(s in t&&e.call(this,t[s],s,t))return!0;return!1},Z=f.momentProperties=[],z=!1;function $(e,t){var n,s,i,r=Z.length;if(o(t._isAMomentObject)||(e._isAMomentObject=t._isAMomentObject),o(t._i)||(e._i=t._i),o(t._f)||(e._f=t._f),o(t._l)||(e._l=t._l),o(t._strict)||(e._strict=t._strict),o(t._tzm)||(e._tzm=t._tzm),o(t._isUTC)||(e._isUTC=t._isUTC),o(t._offset)||(e._offset=t._offset),o(t._pf)||(e._pf=m(t)),o(t._locale)||(e._locale=t._locale),0<r)for(n=0;n<r;n++)o(i=t[s=Z[n]])||(e[s]=i);return e}function q(e){$(this,e),this._d=new Date(null!=e._d?e._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),!1===z&&(z=!0,f.updateOffset(this),z=!1)}function h(e){return e instanceof q||null!=e&&null!=e._isAMomentObject}function B(e){!1===f.suppressDeprecationWarnings&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+e)}function e(r,a){var o=!0;return E(function(){if(null!=f.deprecationHandler&&f.deprecationHandler(null,r),o){for(var e,t,n=[],s=arguments.length,i=0;i<s;i++){if(e="","object"==typeof arguments[i]){for(t in e+="\n["+i+"] ",arguments[0])c(arguments[0],t)&&(e+=t+": "+arguments[0][t]+", ");e=e.slice(0,-2)}else e=arguments[i];n.push(e)}B(r+"\nArguments: "+Array.prototype.slice.call(n).join("")+"\n"+(new Error).stack),o=!1}return a.apply(this,arguments)},a)}var J={};function Q(e,t){null!=f.deprecationHandler&&f.deprecationHandler(e,t),J[e]||(B(t),J[e]=!0)}function d(e){return"undefined"!=typeof Function&&e instanceof Function||"[object Function]"===Object.prototype.toString.call(e)}function X(e,t){var n,s=E({},e);for(n in t)c(t,n)&&(F(e[n])&&F(t[n])?(s[n]={},E(s[n],e[n]),E(s[n],t[n])):null!=t[n]?s[n]=t[n]:delete s[n]);for(n in e)c(e,n)&&!c(t,n)&&F(e[n])&&(s[n]=E({},s[n]));return s}function K(e){null!=e&&this.set(e)}f.suppressDeprecationWarnings=!1,f.deprecationHandler=null;var ee=Object.keys||function(e){var t,n=[];for(t in e)c(e,t)&&n.push(t);return n};function r(e,t,n){var s=""+Math.abs(e);return(0<=e?n?"+":"":"-")+Math.pow(10,Math.max(0,t-s.length)).toString().substr(1)+s}var te=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,ne=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,se={},ie={};function s(e,t,n,s){var i="string"==typeof s?function(){return this[s]()}:s;e&&(ie[e]=i),t&&(ie[t[0]]=function(){return r(i.apply(this,arguments),t[1],t[2])}),n&&(ie[n]=function(){return this.localeData().ordinal(i.apply(this,arguments),e)})}function re(e,t){return e.isValid()?(t=ae(t,e.localeData()),se[t]=se[t]||function(s){for(var e,i=s.match(te),t=0,r=i.length;t<r;t++)ie[i[t]]?i[t]=ie[i[t]]:i[t]=(e=i[t]).match(/\[[\s\S]/)?e.replace(/^\[|\]$/g,""):e.replace(/\\/g,"");return function(e){for(var t="",n=0;n<r;n++)t+=d(i[n])?i[n].call(e,s):i[n];return t}}(t),se[t](e)):e.localeData().invalidDate()}function ae(e,t){var n=5;function s(e){return t.longDateFormat(e)||e}for(ne.lastIndex=0;0<=n&&ne.test(e);)e=e.replace(ne,s),ne.lastIndex=0,--n;return e}var oe={};function t(e,t){var n=e.toLowerCase();oe[n]=oe[n+"s"]=oe[t]=e}function _(e){return"string"==typeof e?oe[e]||oe[e.toLowerCase()]:void 0}function ue(e){var t,n,s={};for(n in e)c(e,n)&&(t=_(n))&&(s[t]=e[n]);return s}var le={};function n(e,t){le[e]=t}function he(e){return e%4==0&&e%100!=0||e%400==0}function y(e){return e<0?Math.ceil(e)||0:Math.floor(e)}function g(e){var e=+e,t=0;return t=0!=e&&isFinite(e)?y(e):t}function de(t,n){return function(e){return null!=e?(fe(this,t,e),f.updateOffset(this,n),this):ce(this,t)}}function ce(e,t){return e.isValid()?e._d["get"+(e._isUTC?"UTC":"")+t]():NaN}function fe(e,t,n){e.isValid()&&!isNaN(n)&&("FullYear"===t&&he(e.year())&&1===e.month()&&29===e.date()?(n=g(n),e._d["set"+(e._isUTC?"UTC":"")+t](n,e.month(),We(n,e.month()))):e._d["set"+(e._isUTC?"UTC":"")+t](n))}var i=/\d/,w=/\d\d/,me=/\d{3}/,_e=/\d{4}/,ye=/[+-]?\d{6}/,p=/\d\d?/,ge=/\d\d\d\d?/,we=/\d\d\d\d\d\d?/,pe=/\d{1,3}/,ke=/\d{1,4}/,ve=/[+-]?\d{1,6}/,Me=/\d+/,De=/[+-]?\d+/,Se=/Z|[+-]\d\d:?\d\d/gi,Ye=/Z|[+-]\d\d(?::?\d\d)?/gi,k=/[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i;function v(e,n,s){be[e]=d(n)?n:function(e,t){return e&&s?s:n}}function Oe(e,t){return c(be,e)?be[e](t._strict,t._locale):new RegExp(M(e.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(e,t,n,s,i){return t||n||s||i})))}function M(e){return e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}var be={},xe={};function D(e,n){var t,s,i=n;for("string"==typeof e&&(e=[e]),u(n)&&(i=function(e,t){t[n]=g(e)}),s=e.length,t=0;t<s;t++)xe[e[t]]=i}function Te(e,i){D(e,function(e,t,n,s){n._w=n._w||{},i(e,n._w,n,s)})}var S,Y=0,O=1,b=2,x=3,T=4,N=5,Ne=6,Pe=7,Re=8;function We(e,t){if(isNaN(e)||isNaN(t))return NaN;var n=(t%(n=12)+n)%n;return e+=(t-n)/12,1==n?he(e)?29:28:31-n%7%2}S=Array.prototype.indexOf||function(e){for(var t=0;t<this.length;++t)if(this[t]===e)return t;return-1},s("M",["MM",2],"Mo",function(){return this.month()+1}),s("MMM",0,0,function(e){return this.localeData().monthsShort(this,e)}),s("MMMM",0,0,function(e){return this.localeData().months(this,e)}),t("month","M"),n("month",8),v("M",p),v("MM",p,w),v("MMM",function(e,t){return t.monthsShortRegex(e)}),v("MMMM",function(e,t){return t.monthsRegex(e)}),D(["M","MM"],function(e,t){t[O]=g(e)-1}),D(["MMM","MMMM"],function(e,t,n,s){s=n._locale.monthsParse(e,s,n._strict);null!=s?t[O]=s:m(n).invalidMonth=e});var Ce="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),Ue="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),He=/D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/,Fe=k,Le=k;function Ve(e,t){var n;if(e.isValid()){if("string"==typeof t)if(/^\d+$/.test(t))t=g(t);else if(!u(t=e.localeData().monthsParse(t)))return;n=Math.min(e.date(),We(e.year(),t)),e._d["set"+(e._isUTC?"UTC":"")+"Month"](t,n)}}function Ge(e){return null!=e?(Ve(this,e),f.updateOffset(this,!0),this):ce(this,"Month")}function Ee(){function e(e,t){return t.length-e.length}for(var t,n=[],s=[],i=[],r=0;r<12;r++)t=l([2e3,r]),n.push(this.monthsShort(t,"")),s.push(this.months(t,"")),i.push(this.months(t,"")),i.push(this.monthsShort(t,""));for(n.sort(e),s.sort(e),i.sort(e),r=0;r<12;r++)n[r]=M(n[r]),s[r]=M(s[r]);for(r=0;r<24;r++)i[r]=M(i[r]);this._monthsRegex=new RegExp("^("+i.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+s.join("|")+")","i"),this._monthsShortStrictRegex=new RegExp("^("+n.join("|")+")","i")}function Ae(e){return he(e)?366:365}s("Y",0,0,function(){var e=this.year();return e<=9999?r(e,4):"+"+e}),s(0,["YY",2],0,function(){return this.year()%100}),s(0,["YYYY",4],0,"year"),s(0,["YYYYY",5],0,"year"),s(0,["YYYYYY",6,!0],0,"year"),t("year","y"),n("year",1),v("Y",De),v("YY",p,w),v("YYYY",ke,_e),v("YYYYY",ve,ye),v("YYYYYY",ve,ye),D(["YYYYY","YYYYYY"],Y),D("YYYY",function(e,t){t[Y]=2===e.length?f.parseTwoDigitYear(e):g(e)}),D("YY",function(e,t){t[Y]=f.parseTwoDigitYear(e)}),D("Y",function(e,t){t[Y]=parseInt(e,10)}),f.parseTwoDigitYear=function(e){return g(e)+(68<g(e)?1900:2e3)};var Ie=de("FullYear",!0);function je(e,t,n,s,i,r,a){var o;return e<100&&0<=e?(o=new Date(e+400,t,n,s,i,r,a),isFinite(o.getFullYear())&&o.setFullYear(e)):o=new Date(e,t,n,s,i,r,a),o}function Ze(e){var t;return e<100&&0<=e?((t=Array.prototype.slice.call(arguments))[0]=e+400,t=new Date(Date.UTC.apply(null,t)),isFinite(t.getUTCFullYear())&&t.setUTCFullYear(e)):t=new Date(Date.UTC.apply(null,arguments)),t}function ze(e,t,n){n=7+t-n;return n-(7+Ze(e,0,n).getUTCDay()-t)%7-1}function $e(e,t,n,s,i){var r,t=1+7*(t-1)+(7+n-s)%7+ze(e,s,i),n=t<=0?Ae(r=e-1)+t:t>Ae(e)?(r=e+1,t-Ae(e)):(r=e,t);return{year:r,dayOfYear:n}}function qe(e,t,n){var s,i,r=ze(e.year(),t,n),r=Math.floor((e.dayOfYear()-r-1)/7)+1;return r<1?s=r+P(i=e.year()-1,t,n):r>P(e.year(),t,n)?(s=r-P(e.year(),t,n),i=e.year()+1):(i=e.year(),s=r),{week:s,year:i}}function P(e,t,n){var s=ze(e,t,n),t=ze(e+1,t,n);return(Ae(e)-s+t)/7}s("w",["ww",2],"wo","week"),s("W",["WW",2],"Wo","isoWeek"),t("week","w"),t("isoWeek","W"),n("week",5),n("isoWeek",5),v("w",p),v("ww",p,w),v("W",p),v("WW",p,w),Te(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=g(e)});function Be(e,t){return e.slice(t,7).concat(e.slice(0,t))}s("d",0,"do","day"),s("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),s("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),s("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),s("e",0,0,"weekday"),s("E",0,0,"isoWeekday"),t("day","d"),t("weekday","e"),t("isoWeekday","E"),n("day",11),n("weekday",11),n("isoWeekday",11),v("d",p),v("e",p),v("E",p),v("dd",function(e,t){return t.weekdaysMinRegex(e)}),v("ddd",function(e,t){return t.weekdaysShortRegex(e)}),v("dddd",function(e,t){return t.weekdaysRegex(e)}),Te(["dd","ddd","dddd"],function(e,t,n,s){s=n._locale.weekdaysParse(e,s,n._strict);null!=s?t.d=s:m(n).invalidWeekday=e}),Te(["d","e","E"],function(e,t,n,s){t[s]=g(e)});var Je="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Qe="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Xe="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),Ke=k,et=k,tt=k;function nt(){function e(e,t){return t.length-e.length}for(var t,n,s,i=[],r=[],a=[],o=[],u=0;u<7;u++)s=l([2e3,1]).day(u),t=M(this.weekdaysMin(s,"")),n=M(this.weekdaysShort(s,"")),s=M(this.weekdays(s,"")),i.push(t),r.push(n),a.push(s),o.push(t),o.push(n),o.push(s);i.sort(e),r.sort(e),a.sort(e),o.sort(e),this._weekdaysRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+r.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+i.join("|")+")","i")}function st(){return this.hours()%12||12}function it(e,t){s(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function rt(e,t){return t._meridiemParse}s("H",["HH",2],0,"hour"),s("h",["hh",2],0,st),s("k",["kk",2],0,function(){return this.hours()||24}),s("hmm",0,0,function(){return""+st.apply(this)+r(this.minutes(),2)}),s("hmmss",0,0,function(){return""+st.apply(this)+r(this.minutes(),2)+r(this.seconds(),2)}),s("Hmm",0,0,function(){return""+this.hours()+r(this.minutes(),2)}),s("Hmmss",0,0,function(){return""+this.hours()+r(this.minutes(),2)+r(this.seconds(),2)}),it("a",!0),it("A",!1),t("hour","h"),n("hour",13),v("a",rt),v("A",rt),v("H",p),v("h",p),v("k",p),v("HH",p,w),v("hh",p,w),v("kk",p,w),v("hmm",ge),v("hmmss",we),v("Hmm",ge),v("Hmmss",we),D(["H","HH"],x),D(["k","kk"],function(e,t,n){e=g(e);t[x]=24===e?0:e}),D(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),D(["h","hh"],function(e,t,n){t[x]=g(e),m(n).bigHour=!0}),D("hmm",function(e,t,n){var s=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s)),m(n).bigHour=!0}),D("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s,2)),t[N]=g(e.substr(i)),m(n).bigHour=!0}),D("Hmm",function(e,t,n){var s=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s))}),D("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s,2)),t[N]=g(e.substr(i))});k=de("Hours",!0);var at,ot={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Ce,monthsShort:Ue,week:{dow:0,doy:6},weekdays:Je,weekdaysMin:Xe,weekdaysShort:Qe,meridiemParse:/[ap]\.?m?\.?/i},R={},ut={};function lt(e){return e&&e.toLowerCase().replace("_","-")}function ht(e){for(var t,n,s,i,r=0;r<e.length;){for(t=(i=lt(e[r]).split("-")).length,n=(n=lt(e[r+1]))?n.split("-"):null;0<t;){if(s=dt(i.slice(0,t).join("-")))return s;if(n&&n.length>=t&&function(e,t){for(var n=Math.min(e.length,t.length),s=0;s<n;s+=1)if(e[s]!==t[s])return s;return n}(i,n)>=t-1)break;t--}r++}return at}function dt(t){var e;if(void 0===R[t]&&"undefined"!=typeof module&&module&&module.exports&&null!=t.match("^[^/\\\\]*$"))try{e=at._abbr,require("./locale/"+t),ct(e)}catch(e){R[t]=null}return R[t]}function ct(e,t){return e&&((t=o(t)?mt(e):ft(e,t))?at=t:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),at._abbr}function ft(e,t){if(null===t)return delete R[e],null;var n,s=ot;if(t.abbr=e,null!=R[e])Q("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=R[e]._config;else if(null!=t.parentLocale)if(null!=R[t.parentLocale])s=R[t.parentLocale]._config;else{if(null==(n=dt(t.parentLocale)))return ut[t.parentLocale]||(ut[t.parentLocale]=[]),ut[t.parentLocale].push({name:e,config:t}),null;s=n._config}return R[e]=new K(X(s,t)),ut[e]&&ut[e].forEach(function(e){ft(e.name,e.config)}),ct(e),R[e]}function mt(e){var t;if(!(e=e&&e._locale&&e._locale._abbr?e._locale._abbr:e))return at;if(!a(e)){if(t=dt(e))return t;e=[e]}return ht(e)}function _t(e){var t=e._a;return t&&-2===m(e).overflow&&(t=t[O]<0||11<t[O]?O:t[b]<1||t[b]>We(t[Y],t[O])?b:t[x]<0||24<t[x]||24===t[x]&&(0!==t[T]||0!==t[N]||0!==t[Ne])?x:t[T]<0||59<t[T]?T:t[N]<0||59<t[N]?N:t[Ne]<0||999<t[Ne]?Ne:-1,m(e)._overflowDayOfYear&&(t<Y||b<t)&&(t=b),m(e)._overflowWeeks&&-1===t&&(t=Pe),m(e)._overflowWeekday&&-1===t&&(t=Re),m(e).overflow=t),e}var yt=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,gt=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,wt=/Z|[+-]\d\d(?::?\d\d)?/,pt=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/],["YYYYMM",/\d{6}/,!1],["YYYY",/\d{4}/,!1]],kt=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],vt=/^\/?Date\((-?\d+)/i,Mt=/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/,Dt={UT:0,GMT:0,EDT:-240,EST:-300,CDT:-300,CST:-360,MDT:-360,MST:-420,PDT:-420,PST:-480};function St(e){var t,n,s,i,r,a,o=e._i,u=yt.exec(o)||gt.exec(o),o=pt.length,l=kt.length;if(u){for(m(e).iso=!0,t=0,n=o;t<n;t++)if(pt[t][1].exec(u[1])){i=pt[t][0],s=!1!==pt[t][2];break}if(null==i)e._isValid=!1;else{if(u[3]){for(t=0,n=l;t<n;t++)if(kt[t][1].exec(u[3])){r=(u[2]||" ")+kt[t][0];break}if(null==r)return void(e._isValid=!1)}if(s||null==r){if(u[4]){if(!wt.exec(u[4]))return void(e._isValid=!1);a="Z"}e._f=i+(r||"")+(a||""),Tt(e)}else e._isValid=!1}}else e._isValid=!1}function Yt(e,t,n,s,i,r){e=[function(e){e=parseInt(e,10);{if(e<=49)return 2e3+e;if(e<=999)return 1900+e}return e}(e),Ue.indexOf(t),parseInt(n,10),parseInt(s,10),parseInt(i,10)];return r&&e.push(parseInt(r,10)),e}function Ot(e){var t,n,s,i,r=Mt.exec(e._i.replace(/\([^)]*\)|[\n\t]/g," ").replace(/(\s\s+)/g," ").replace(/^\s\s*/,"").replace(/\s\s*$/,""));r?(t=Yt(r[4],r[3],r[2],r[5],r[6],r[7]),n=r[1],s=t,i=e,n&&Qe.indexOf(n)!==new Date(s[0],s[1],s[2]).getDay()?(m(i).weekdayMismatch=!0,i._isValid=!1):(e._a=t,e._tzm=(n=r[8],s=r[9],i=r[10],n?Dt[n]:s?0:60*(((n=parseInt(i,10))-(s=n%100))/100)+s),e._d=Ze.apply(null,e._a),e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),m(e).rfc2822=!0)):e._isValid=!1}function bt(e,t,n){return null!=e?e:null!=t?t:n}function xt(e){var t,n,s,i,r,a,o,u,l,h,d,c=[];if(!e._d){for(s=e,i=new Date(f.now()),n=s._useUTC?[i.getUTCFullYear(),i.getUTCMonth(),i.getUTCDate()]:[i.getFullYear(),i.getMonth(),i.getDate()],e._w&&null==e._a[b]&&null==e._a[O]&&(null!=(i=(s=e)._w).GG||null!=i.W||null!=i.E?(u=1,l=4,r=bt(i.GG,s._a[Y],qe(W(),1,4).year),a=bt(i.W,1),((o=bt(i.E,1))<1||7<o)&&(h=!0)):(u=s._locale._week.dow,l=s._locale._week.doy,d=qe(W(),u,l),r=bt(i.gg,s._a[Y],d.year),a=bt(i.w,d.week),null!=i.d?((o=i.d)<0||6<o)&&(h=!0):null!=i.e?(o=i.e+u,(i.e<0||6<i.e)&&(h=!0)):o=u),a<1||a>P(r,u,l)?m(s)._overflowWeeks=!0:null!=h?m(s)._overflowWeekday=!0:(d=$e(r,a,o,u,l),s._a[Y]=d.year,s._dayOfYear=d.dayOfYear)),null!=e._dayOfYear&&(i=bt(e._a[Y],n[Y]),(e._dayOfYear>Ae(i)||0===e._dayOfYear)&&(m(e)._overflowDayOfYear=!0),h=Ze(i,0,e._dayOfYear),e._a[O]=h.getUTCMonth(),e._a[b]=h.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=c[t]=n[t];for(;t<7;t++)e._a[t]=c[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[x]&&0===e._a[T]&&0===e._a[N]&&0===e._a[Ne]&&(e._nextDay=!0,e._a[x]=0),e._d=(e._useUTC?Ze:je).apply(null,c),r=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[x]=24),e._w&&void 0!==e._w.d&&e._w.d!==r&&(m(e).weekdayMismatch=!0)}}function Tt(e){if(e._f===f.ISO_8601)St(e);else if(e._f===f.RFC_2822)Ot(e);else{e._a=[],m(e).empty=!0;for(var t,n,s,i,r,a=""+e._i,o=a.length,u=0,l=ae(e._f,e._locale).match(te)||[],h=l.length,d=0;d<h;d++)n=l[d],(t=(a.match(Oe(n,e))||[])[0])&&(0<(s=a.substr(0,a.indexOf(t))).length&&m(e).unusedInput.push(s),a=a.slice(a.indexOf(t)+t.length),u+=t.length),ie[n]?(t?m(e).empty=!1:m(e).unusedTokens.push(n),s=n,r=e,null!=(i=t)&&c(xe,s)&&xe[s](i,r._a,r,s)):e._strict&&!t&&m(e).unusedTokens.push(n);m(e).charsLeftOver=o-u,0<a.length&&m(e).unusedInput.push(a),e._a[x]<=12&&!0===m(e).bigHour&&0<e._a[x]&&(m(e).bigHour=void 0),m(e).parsedDateParts=e._a.slice(0),m(e).meridiem=e._meridiem,e._a[x]=function(e,t,n){if(null==n)return t;return null!=e.meridiemHour?e.meridiemHour(t,n):null!=e.isPM?((e=e.isPM(n))&&t<12&&(t+=12),t=e||12!==t?t:0):t}(e._locale,e._a[x],e._meridiem),null!==(o=m(e).era)&&(e._a[Y]=e._locale.erasConvertYear(o,e._a[Y])),xt(e),_t(e)}}function Nt(e){var t,n,s,i=e._i,r=e._f;if(e._locale=e._locale||mt(e._l),null===i||void 0===r&&""===i)return I({nullInput:!0});if("string"==typeof i&&(e._i=i=e._locale.preparse(i)),h(i))return new q(_t(i));if(V(i))e._d=i;else if(a(r))!function(e){var t,n,s,i,r,a,o=!1,u=e._f.length;if(0===u)return m(e).invalidFormat=!0,e._d=new Date(NaN);for(i=0;i<u;i++)r=0,a=!1,t=$({},e),null!=e._useUTC&&(t._useUTC=e._useUTC),t._f=e._f[i],Tt(t),A(t)&&(a=!0),r=(r+=m(t).charsLeftOver)+10*m(t).unusedTokens.length,m(t).score=r,o?r<s&&(s=r,n=t):(null==s||r<s||a)&&(s=r,n=t,a&&(o=!0));E(e,n||t)}(e);else if(r)Tt(e);else if(o(r=(i=e)._i))i._d=new Date(f.now());else V(r)?i._d=new Date(r.valueOf()):"string"==typeof r?(n=i,null!==(t=vt.exec(n._i))?n._d=new Date(+t[1]):(St(n),!1===n._isValid&&(delete n._isValid,Ot(n),!1===n._isValid&&(delete n._isValid,n._strict?n._isValid=!1:f.createFromInputFallback(n))))):a(r)?(i._a=G(r.slice(0),function(e){return parseInt(e,10)}),xt(i)):F(r)?(t=i)._d||(s=void 0===(n=ue(t._i)).day?n.date:n.day,t._a=G([n.year,n.month,s,n.hour,n.minute,n.second,n.millisecond],function(e){return e&&parseInt(e,10)}),xt(t)):u(r)?i._d=new Date(r):f.createFromInputFallback(i);return A(e)||(e._d=null),e}function Pt(e,t,n,s,i){var r={};return!0!==t&&!1!==t||(s=t,t=void 0),!0!==n&&!1!==n||(s=n,n=void 0),(F(e)&&L(e)||a(e)&&0===e.length)&&(e=void 0),r._isAMomentObject=!0,r._useUTC=r._isUTC=i,r._l=n,r._i=e,r._f=t,r._strict=s,(i=new q(_t(Nt(i=r))))._nextDay&&(i.add(1,"d"),i._nextDay=void 0),i}function W(e,t,n,s){return Pt(e,t,n,s,!1)}f.createFromInputFallback=e("value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are discouraged. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.",function(e){e._d=new Date(e._i+(e._useUTC?" UTC":""))}),f.ISO_8601=function(){},f.RFC_2822=function(){};ge=e("moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var e=W.apply(null,arguments);return this.isValid()&&e.isValid()?e<this?this:e:I()}),we=e("moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var e=W.apply(null,arguments);return this.isValid()&&e.isValid()?this<e?this:e:I()});function Rt(e,t){var n,s;if(!(t=1===t.length&&a(t[0])?t[0]:t).length)return W();for(n=t[0],s=1;s<t.length;++s)t[s].isValid()&&!t[s][e](n)||(n=t[s]);return n}var Wt=["year","quarter","month","week","day","hour","minute","second","millisecond"];function Ct(e){var e=ue(e),t=e.year||0,n=e.quarter||0,s=e.month||0,i=e.week||e.isoWeek||0,r=e.day||0,a=e.hour||0,o=e.minute||0,u=e.second||0,l=e.millisecond||0;this._isValid=function(e){var t,n,s=!1,i=Wt.length;for(t in e)if(c(e,t)&&(-1===S.call(Wt,t)||null!=e[t]&&isNaN(e[t])))return!1;for(n=0;n<i;++n)if(e[Wt[n]]){if(s)return!1;parseFloat(e[Wt[n]])!==g(e[Wt[n]])&&(s=!0)}return!0}(e),this._milliseconds=+l+1e3*u+6e4*o+1e3*a*60*60,this._days=+r+7*i,this._months=+s+3*n+12*t,this._data={},this._locale=mt(),this._bubble()}function Ut(e){return e instanceof Ct}function Ht(e){return e<0?-1*Math.round(-1*e):Math.round(e)}function Ft(e,n){s(e,0,0,function(){var e=this.utcOffset(),t="+";return e<0&&(e=-e,t="-"),t+r(~~(e/60),2)+n+r(~~e%60,2)})}Ft("Z",":"),Ft("ZZ",""),v("Z",Ye),v("ZZ",Ye),D(["Z","ZZ"],function(e,t,n){n._useUTC=!0,n._tzm=Vt(Ye,e)});var Lt=/([\+\-]|\d\d)/gi;function Vt(e,t){var t=(t||"").match(e);return null===t?null:0===(t=60*(e=((t[t.length-1]||[])+"").match(Lt)||["-",0,0])[1]+g(e[2]))?0:"+"===e[0]?t:-t}function Gt(e,t){var n;return t._isUTC?(t=t.clone(),n=(h(e)||V(e)?e:W(e)).valueOf()-t.valueOf(),t._d.setTime(t._d.valueOf()+n),f.updateOffset(t,!1),t):W(e).local()}function Et(e){return-Math.round(e._d.getTimezoneOffset())}function At(){return!!this.isValid()&&(this._isUTC&&0===this._offset)}f.updateOffset=function(){};var It=/^(-|\+)?(?:(\d*)[. ])?(\d+):(\d+)(?::(\d+)(\.\d*)?)?$/,jt=/^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;function C(e,t){var n,s=e,i=null;return Ut(e)?s={ms:e._milliseconds,d:e._days,M:e._months}:u(e)||!isNaN(+e)?(s={},t?s[t]=+e:s.milliseconds=+e):(i=It.exec(e))?(n="-"===i[1]?-1:1,s={y:0,d:g(i[b])*n,h:g(i[x])*n,m:g(i[T])*n,s:g(i[N])*n,ms:g(Ht(1e3*i[Ne]))*n}):(i=jt.exec(e))?(n="-"===i[1]?-1:1,s={y:Zt(i[2],n),M:Zt(i[3],n),w:Zt(i[4],n),d:Zt(i[5],n),h:Zt(i[6],n),m:Zt(i[7],n),s:Zt(i[8],n)}):null==s?s={}:"object"==typeof s&&("from"in s||"to"in s)&&(t=function(e,t){var n;if(!e.isValid()||!t.isValid())return{milliseconds:0,months:0};t=Gt(t,e),e.isBefore(t)?n=zt(e,t):((n=zt(t,e)).milliseconds=-n.milliseconds,n.months=-n.months);return n}(W(s.from),W(s.to)),(s={}).ms=t.milliseconds,s.M=t.months),i=new Ct(s),Ut(e)&&c(e,"_locale")&&(i._locale=e._locale),Ut(e)&&c(e,"_isValid")&&(i._isValid=e._isValid),i}function Zt(e,t){e=e&&parseFloat(e.replace(",","."));return(isNaN(e)?0:e)*t}function zt(e,t){var n={};return n.months=t.month()-e.month()+12*(t.year()-e.year()),e.clone().add(n.months,"M").isAfter(t)&&--n.months,n.milliseconds=+t-+e.clone().add(n.months,"M"),n}function $t(s,i){return function(e,t){var n;return null===t||isNaN(+t)||(Q(i,"moment()."+i+"(period, number) is deprecated. Please use moment()."+i+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),n=e,e=t,t=n),qt(this,C(e,t),s),this}}function qt(e,t,n,s){var i=t._milliseconds,r=Ht(t._days),t=Ht(t._months);e.isValid()&&(s=null==s||s,t&&Ve(e,ce(e,"Month")+t*n),r&&fe(e,"Date",ce(e,"Date")+r*n),i&&e._d.setTime(e._d.valueOf()+i*n),s&&f.updateOffset(e,r||t))}C.fn=Ct.prototype,C.invalid=function(){return C(NaN)};Ce=$t(1,"add"),Je=$t(-1,"subtract");function Bt(e){return"string"==typeof e||e instanceof String}function Jt(e){return h(e)||V(e)||Bt(e)||u(e)||function(t){var e=a(t),n=!1;e&&(n=0===t.filter(function(e){return!u(e)&&Bt(t)}).length);return e&&n}(e)||function(e){var t,n,s=F(e)&&!L(e),i=!1,r=["years","year","y","months","month","M","days","day","d","dates","date","D","hours","hour","h","minutes","minute","m","seconds","second","s","milliseconds","millisecond","ms"],a=r.length;for(t=0;t<a;t+=1)n=r[t],i=i||c(e,n);return s&&i}(e)||null==e}function Qt(e,t){if(e.date()<t.date())return-Qt(t,e);var n=12*(t.year()-e.year())+(t.month()-e.month()),s=e.clone().add(n,"months"),t=t-s<0?(t-s)/(s-e.clone().add(n-1,"months")):(t-s)/(e.clone().add(1+n,"months")-s);return-(n+t)||0}function Xt(e){return void 0===e?this._locale._abbr:(null!=(e=mt(e))&&(this._locale=e),this)}f.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",f.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";Xe=e("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(e){return void 0===e?this.localeData():this.locale(e)});function Kt(){return this._locale}var en=126227808e5;function tn(e,t){return(e%t+t)%t}function nn(e,t,n){return e<100&&0<=e?new Date(e+400,t,n)-en:new Date(e,t,n).valueOf()}function sn(e,t,n){return e<100&&0<=e?Date.UTC(e+400,t,n)-en:Date.UTC(e,t,n)}function rn(e,t){return t.erasAbbrRegex(e)}function an(){for(var e=[],t=[],n=[],s=[],i=this.eras(),r=0,a=i.length;r<a;++r)t.push(M(i[r].name)),e.push(M(i[r].abbr)),n.push(M(i[r].narrow)),s.push(M(i[r].name)),s.push(M(i[r].abbr)),s.push(M(i[r].narrow));this._erasRegex=new RegExp("^("+s.join("|")+")","i"),this._erasNameRegex=new RegExp("^("+t.join("|")+")","i"),this._erasAbbrRegex=new RegExp("^("+e.join("|")+")","i"),this._erasNarrowRegex=new RegExp("^("+n.join("|")+")","i")}function on(e,t){s(0,[e,e.length],0,t)}function un(e,t,n,s,i){var r;return null==e?qe(this,s,i).year:(r=P(e,s,i),function(e,t,n,s,i){e=$e(e,t,n,s,i),t=Ze(e.year,0,e.dayOfYear);return this.year(t.getUTCFullYear()),this.month(t.getUTCMonth()),this.date(t.getUTCDate()),this}.call(this,e,t=r<t?r:t,n,s,i))}s("N",0,0,"eraAbbr"),s("NN",0,0,"eraAbbr"),s("NNN",0,0,"eraAbbr"),s("NNNN",0,0,"eraName"),s("NNNNN",0,0,"eraNarrow"),s("y",["y",1],"yo","eraYear"),s("y",["yy",2],0,"eraYear"),s("y",["yyy",3],0,"eraYear"),s("y",["yyyy",4],0,"eraYear"),v("N",rn),v("NN",rn),v("NNN",rn),v("NNNN",function(e,t){return t.erasNameRegex(e)}),v("NNNNN",function(e,t){return t.erasNarrowRegex(e)}),D(["N","NN","NNN","NNNN","NNNNN"],function(e,t,n,s){s=n._locale.erasParse(e,s,n._strict);s?m(n).era=s:m(n).invalidEra=e}),v("y",Me),v("yy",Me),v("yyy",Me),v("yyyy",Me),v("yo",function(e,t){return t._eraYearOrdinalRegex||Me}),D(["y","yy","yyy","yyyy"],Y),D(["yo"],function(e,t,n,s){var i;n._locale._eraYearOrdinalRegex&&(i=e.match(n._locale._eraYearOrdinalRegex)),n._locale.eraYearOrdinalParse?t[Y]=n._locale.eraYearOrdinalParse(e,i):t[Y]=parseInt(e,10)}),s(0,["gg",2],0,function(){return this.weekYear()%100}),s(0,["GG",2],0,function(){return this.isoWeekYear()%100}),on("gggg","weekYear"),on("ggggg","weekYear"),on("GGGG","isoWeekYear"),on("GGGGG","isoWeekYear"),t("weekYear","gg"),t("isoWeekYear","GG"),n("weekYear",1),n("isoWeekYear",1),v("G",De),v("g",De),v("GG",p,w),v("gg",p,w),v("GGGG",ke,_e),v("gggg",ke,_e),v("GGGGG",ve,ye),v("ggggg",ve,ye),Te(["gggg","ggggg","GGGG","GGGGG"],function(e,t,n,s){t[s.substr(0,2)]=g(e)}),Te(["gg","GG"],function(e,t,n,s){t[s]=f.parseTwoDigitYear(e)}),s("Q",0,"Qo","quarter"),t("quarter","Q"),n("quarter",7),v("Q",i),D("Q",function(e,t){t[O]=3*(g(e)-1)}),s("D",["DD",2],"Do","date"),t("date","D"),n("date",9),v("D",p),v("DD",p,w),v("Do",function(e,t){return e?t._dayOfMonthOrdinalParse||t._ordinalParse:t._dayOfMonthOrdinalParseLenient}),D(["D","DD"],b),D("Do",function(e,t){t[b]=g(e.match(p)[0])});ke=de("Date",!0);s("DDD",["DDDD",3],"DDDo","dayOfYear"),t("dayOfYear","DDD"),n("dayOfYear",4),v("DDD",pe),v("DDDD",me),D(["DDD","DDDD"],function(e,t,n){n._dayOfYear=g(e)}),s("m",["mm",2],0,"minute"),t("minute","m"),n("minute",14),v("m",p),v("mm",p,w),D(["m","mm"],T);var ln,_e=de("Minutes",!1),ve=(s("s",["ss",2],0,"second"),t("second","s"),n("second",15),v("s",p),v("ss",p,w),D(["s","ss"],N),de("Seconds",!1));for(s("S",0,0,function(){return~~(this.millisecond()/100)}),s(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),s(0,["SSS",3],0,"millisecond"),s(0,["SSSS",4],0,function(){return 10*this.millisecond()}),s(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),s(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),s(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),s(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),s(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),t("millisecond","ms"),n("millisecond",16),v("S",pe,i),v("SS",pe,w),v("SSS",pe,me),ln="SSSS";ln.length<=9;ln+="S")v(ln,Me);function hn(e,t){t[Ne]=g(1e3*("0."+e))}for(ln="S";ln.length<=9;ln+="S")D(ln,hn);ye=de("Milliseconds",!1),s("z",0,0,"zoneAbbr"),s("zz",0,0,"zoneName");i=q.prototype;function dn(e){return e}i.add=Ce,i.calendar=function(e,t){1===arguments.length&&(arguments[0]?Jt(arguments[0])?(e=arguments[0],t=void 0):function(e){for(var t=F(e)&&!L(e),n=!1,s=["sameDay","nextDay","lastDay","nextWeek","lastWeek","sameElse"],i=0;i<s.length;i+=1)n=n||c(e,s[i]);return t&&n}(arguments[0])&&(t=arguments[0],e=void 0):t=e=void 0);var e=e||W(),n=Gt(e,this).startOf("day"),n=f.calendarFormat(this,n)||"sameElse",t=t&&(d(t[n])?t[n].call(this,e):t[n]);return this.format(t||this.localeData().calendar(n,this,W(e)))},i.clone=function(){return new q(this)},i.diff=function(e,t,n){var s,i,r;if(!this.isValid())return NaN;if(!(s=Gt(e,this)).isValid())return NaN;switch(i=6e4*(s.utcOffset()-this.utcOffset()),t=_(t)){case"year":r=Qt(this,s)/12;break;case"month":r=Qt(this,s);break;case"quarter":r=Qt(this,s)/3;break;case"second":r=(this-s)/1e3;break;case"minute":r=(this-s)/6e4;break;case"hour":r=(this-s)/36e5;break;case"day":r=(this-s-i)/864e5;break;case"week":r=(this-s-i)/6048e5;break;default:r=this-s}return n?r:y(r)},i.endOf=function(e){var t,n;if(void 0===(e=_(e))||"millisecond"===e||!this.isValid())return this;switch(n=this._isUTC?sn:nn,e){case"year":t=n(this.year()+1,0,1)-1;break;case"quarter":t=n(this.year(),this.month()-this.month()%3+3,1)-1;break;case"month":t=n(this.year(),this.month()+1,1)-1;break;case"week":t=n(this.year(),this.month(),this.date()-this.weekday()+7)-1;break;case"isoWeek":t=n(this.year(),this.month(),this.date()-(this.isoWeekday()-1)+7)-1;break;case"day":case"date":t=n(this.year(),this.month(),this.date()+1)-1;break;case"hour":t=this._d.valueOf(),t+=36e5-tn(t+(this._isUTC?0:6e4*this.utcOffset()),36e5)-1;break;case"minute":t=this._d.valueOf(),t+=6e4-tn(t,6e4)-1;break;case"second":t=this._d.valueOf(),t+=1e3-tn(t,1e3)-1;break}return this._d.setTime(t),f.updateOffset(this,!0),this},i.format=function(e){return e=e||(this.isUtc()?f.defaultFormatUtc:f.defaultFormat),e=re(this,e),this.localeData().postformat(e)},i.from=function(e,t){return this.isValid()&&(h(e)&&e.isValid()||W(e).isValid())?C({to:this,from:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()},i.fromNow=function(e){return this.from(W(),e)},i.to=function(e,t){return this.isValid()&&(h(e)&&e.isValid()||W(e).isValid())?C({from:this,to:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()},i.toNow=function(e){return this.to(W(),e)},i.get=function(e){return d(this[e=_(e)])?this[e]():this},i.invalidAt=function(){return m(this).overflow},i.isAfter=function(e,t){return e=h(e)?e:W(e),!(!this.isValid()||!e.isValid())&&("millisecond"===(t=_(t)||"millisecond")?this.valueOf()>e.valueOf():e.valueOf()<this.clone().startOf(t).valueOf())},i.isBefore=function(e,t){return e=h(e)?e:W(e),!(!this.isValid()||!e.isValid())&&("millisecond"===(t=_(t)||"millisecond")?this.valueOf()<e.valueOf():this.clone().endOf(t).valueOf()<e.valueOf())},i.isBetween=function(e,t,n,s){return e=h(e)?e:W(e),t=h(t)?t:W(t),!!(this.isValid()&&e.isValid()&&t.isValid())&&(("("===(s=s||"()")[0]?this.isAfter(e,n):!this.isBefore(e,n))&&(")"===s[1]?this.isBefore(t,n):!this.isAfter(t,n)))},i.isSame=function(e,t){var e=h(e)?e:W(e);return!(!this.isValid()||!e.isValid())&&("millisecond"===(t=_(t)||"millisecond")?this.valueOf()===e.valueOf():(e=e.valueOf(),this.clone().startOf(t).valueOf()<=e&&e<=this.clone().endOf(t).valueOf()))},i.isSameOrAfter=function(e,t){return this.isSame(e,t)||this.isAfter(e,t)},i.isSameOrBefore=function(e,t){return this.isSame(e,t)||this.isBefore(e,t)},i.isValid=function(){return A(this)},i.lang=Xe,i.locale=Xt,i.localeData=Kt,i.max=we,i.min=ge,i.parsingFlags=function(){return E({},m(this))},i.set=function(e,t){if("object"==typeof e)for(var n=function(e){var t,n=[];for(t in e)c(e,t)&&n.push({unit:t,priority:le[t]});return n.sort(function(e,t){return e.priority-t.priority}),n}(e=ue(e)),s=n.length,i=0;i<s;i++)this[n[i].unit](e[n[i].unit]);else if(d(this[e=_(e)]))return this[e](t);return this},i.startOf=function(e){var t,n;if(void 0===(e=_(e))||"millisecond"===e||!this.isValid())return this;switch(n=this._isUTC?sn:nn,e){case"year":t=n(this.year(),0,1);break;case"quarter":t=n(this.year(),this.month()-this.month()%3,1);break;case"month":t=n(this.year(),this.month(),1);break;case"week":t=n(this.year(),this.month(),this.date()-this.weekday());break;case"isoWeek":t=n(this.year(),this.month(),this.date()-(this.isoWeekday()-1));break;case"day":case"date":t=n(this.year(),this.month(),this.date());break;case"hour":t=this._d.valueOf(),t-=tn(t+(this._isUTC?0:6e4*this.utcOffset()),36e5);break;case"minute":t=this._d.valueOf(),t-=tn(t,6e4);break;case"second":t=this._d.valueOf(),t-=tn(t,1e3);break}return this._d.setTime(t),f.updateOffset(this,!0),this},i.subtract=Je,i.toArray=function(){var e=this;return[e.year(),e.month(),e.date(),e.hour(),e.minute(),e.second(),e.millisecond()]},i.toObject=function(){var e=this;return{years:e.year(),months:e.month(),date:e.date(),hours:e.hours(),minutes:e.minutes(),seconds:e.seconds(),milliseconds:e.milliseconds()}},i.toDate=function(){return new Date(this.valueOf())},i.toISOString=function(e){if(!this.isValid())return null;var t=(e=!0!==e)?this.clone().utc():this;return t.year()<0||9999<t.year()?re(t,e?"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYYYY-MM-DD[T]HH:mm:ss.SSSZ"):d(Date.prototype.toISOString)?e?this.toDate().toISOString():new Date(this.valueOf()+60*this.utcOffset()*1e3).toISOString().replace("Z",re(t,"Z")):re(t,e?"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYY-MM-DD[T]HH:mm:ss.SSSZ")},i.inspect=function(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var e,t="moment",n="";return this.isLocal()||(t=0===this.utcOffset()?"moment.utc":"moment.parseZone",n="Z"),t="["+t+'("]',e=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY",this.format(t+e+"-MM-DD[T]HH:mm:ss.SSS"+(n+'[")]'))},"undefined"!=typeof Symbol&&null!=Symbol.for&&(i[Symbol.for("nodejs.util.inspect.custom")]=function(){return"Moment<"+this.format()+">"}),i.toJSON=function(){return this.isValid()?this.toISOString():null},i.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},i.unix=function(){return Math.floor(this.valueOf()/1e3)},i.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},i.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},i.eraName=function(){for(var e,t=this.localeData().eras(),n=0,s=t.length;n<s;++n){if(e=this.clone().startOf("day").valueOf(),t[n].since<=e&&e<=t[n].until)return t[n].name;if(t[n].until<=e&&e<=t[n].since)return t[n].name}return""},i.eraNarrow=function(){for(var e,t=this.localeData().eras(),n=0,s=t.length;n<s;++n){if(e=this.clone().startOf("day").valueOf(),t[n].since<=e&&e<=t[n].until)return t[n].narrow;if(t[n].until<=e&&e<=t[n].since)return t[n].narrow}return""},i.eraAbbr=function(){for(var e,t=this.localeData().eras(),n=0,s=t.length;n<s;++n){if(e=this.clone().startOf("day").valueOf(),t[n].since<=e&&e<=t[n].until)return t[n].abbr;if(t[n].until<=e&&e<=t[n].since)return t[n].abbr}return""},i.eraYear=function(){for(var e,t,n=this.localeData().eras(),s=0,i=n.length;s<i;++s)if(e=n[s].since<=n[s].until?1:-1,t=this.clone().startOf("day").valueOf(),n[s].since<=t&&t<=n[s].until||n[s].until<=t&&t<=n[s].since)return(this.year()-f(n[s].since).year())*e+n[s].offset;return this.year()},i.year=Ie,i.isLeapYear=function(){return he(this.year())},i.weekYear=function(e){return un.call(this,e,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)},i.isoWeekYear=function(e){return un.call(this,e,this.isoWeek(),this.isoWeekday(),1,4)},i.quarter=i.quarters=function(e){return null==e?Math.ceil((this.month()+1)/3):this.month(3*(e-1)+this.month()%3)},i.month=Ge,i.daysInMonth=function(){return We(this.year(),this.month())},i.week=i.weeks=function(e){var t=this.localeData().week(this);return null==e?t:this.add(7*(e-t),"d")},i.isoWeek=i.isoWeeks=function(e){var t=qe(this,1,4).week;return null==e?t:this.add(7*(e-t),"d")},i.weeksInYear=function(){var e=this.localeData()._week;return P(this.year(),e.dow,e.doy)},i.weeksInWeekYear=function(){var e=this.localeData()._week;return P(this.weekYear(),e.dow,e.doy)},i.isoWeeksInYear=function(){return P(this.year(),1,4)},i.isoWeeksInISOWeekYear=function(){return P(this.isoWeekYear(),1,4)},i.date=ke,i.day=i.days=function(e){if(!this.isValid())return null!=e?this:NaN;var t,n,s=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=e?(t=e,n=this.localeData(),e="string"!=typeof t?t:isNaN(t)?"number"==typeof(t=n.weekdaysParse(t))?t:null:parseInt(t,10),this.add(e-s,"d")):s},i.weekday=function(e){if(!this.isValid())return null!=e?this:NaN;var t=(this.day()+7-this.localeData()._week.dow)%7;return null==e?t:this.add(e-t,"d")},i.isoWeekday=function(e){return this.isValid()?null!=e?(t=e,n=this.localeData(),n="string"==typeof t?n.weekdaysParse(t)%7||7:isNaN(t)?null:t,this.day(this.day()%7?n:n-7)):this.day()||7:null!=e?this:NaN;var t,n},i.dayOfYear=function(e){var t=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==e?t:this.add(e-t,"d")},i.hour=i.hours=k,i.minute=i.minutes=_e,i.second=i.seconds=ve,i.millisecond=i.milliseconds=ye,i.utcOffset=function(e,t,n){var s,i=this._offset||0;if(!this.isValid())return null!=e?this:NaN;if(null==e)return this._isUTC?i:Et(this);if("string"==typeof e){if(null===(e=Vt(Ye,e)))return this}else Math.abs(e)<16&&!n&&(e*=60);return!this._isUTC&&t&&(s=Et(this)),this._offset=e,this._isUTC=!0,null!=s&&this.add(s,"m"),i!==e&&(!t||this._changeInProgress?qt(this,C(e-i,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,f.updateOffset(this,!0),this._changeInProgress=null)),this},i.utc=function(e){return this.utcOffset(0,e)},i.local=function(e){return this._isUTC&&(this.utcOffset(0,e),this._isUTC=!1,e&&this.subtract(Et(this),"m")),this},i.parseZone=function(){var e;return null!=this._tzm?this.utcOffset(this._tzm,!1,!0):"string"==typeof this._i&&(null!=(e=Vt(Se,this._i))?this.utcOffset(e):this.utcOffset(0,!0)),this},i.hasAlignedHourOffset=function(e){return!!this.isValid()&&(e=e?W(e).utcOffset():0,(this.utcOffset()-e)%60==0)},i.isDST=function(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},i.isLocal=function(){return!!this.isValid()&&!this._isUTC},i.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},i.isUtc=At,i.isUTC=At,i.zoneAbbr=function(){return this._isUTC?"UTC":""},i.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},i.dates=e("dates accessor is deprecated. Use date instead.",ke),i.months=e("months accessor is deprecated. Use month instead",Ge),i.years=e("years accessor is deprecated. Use year instead",Ie),i.zone=e("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?(this.utcOffset(e="string"!=typeof e?-e:e,t),this):-this.utcOffset()}),i.isDSTShifted=e("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!o(this._isDSTShifted))return this._isDSTShifted;var e,t={};return $(t,this),(t=Nt(t))._a?(e=(t._isUTC?l:W)(t._a),this._isDSTShifted=this.isValid()&&0<function(e,t,n){for(var s=Math.min(e.length,t.length),i=Math.abs(e.length-t.length),r=0,a=0;a<s;a++)(n&&e[a]!==t[a]||!n&&g(e[a])!==g(t[a]))&&r++;return r+i}(t._a,e.toArray())):this._isDSTShifted=!1,this._isDSTShifted});w=K.prototype;function cn(e,t,n,s){var i=mt(),s=l().set(s,t);return i[n](s,e)}function fn(e,t,n){if(u(e)&&(t=e,e=void 0),e=e||"",null!=t)return cn(e,t,n,"month");for(var s=[],i=0;i<12;i++)s[i]=cn(e,i,n,"month");return s}function mn(e,t,n,s){t=("boolean"==typeof e?u(t)&&(n=t,t=void 0):(t=e,e=!1,u(n=t)&&(n=t,t=void 0)),t||"");var i,r=mt(),a=e?r._week.dow:0,o=[];if(null!=n)return cn(t,(n+a)%7,s,"day");for(i=0;i<7;i++)o[i]=cn(t,(i+a)%7,s,"day");return o}w.calendar=function(e,t,n){return d(e=this._calendar[e]||this._calendar.sameElse)?e.call(t,n):e},w.longDateFormat=function(e){var t=this._longDateFormat[e],n=this._longDateFormat[e.toUpperCase()];return t||!n?t:(this._longDateFormat[e]=n.match(te).map(function(e){return"MMMM"===e||"MM"===e||"DD"===e||"dddd"===e?e.slice(1):e}).join(""),this._longDateFormat[e])},w.invalidDate=function(){return this._invalidDate},w.ordinal=function(e){return this._ordinal.replace("%d",e)},w.preparse=dn,w.postformat=dn,w.relativeTime=function(e,t,n,s){var i=this._relativeTime[n];return d(i)?i(e,t,n,s):i.replace(/%d/i,e)},w.pastFuture=function(e,t){return d(e=this._relativeTime[0<e?"future":"past"])?e(t):e.replace(/%s/i,t)},w.set=function(e){var t,n;for(n in e)c(e,n)&&(d(t=e[n])?this[n]=t:this["_"+n]=t);this._config=e,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+"|"+/\d{1,2}/.source)},w.eras=function(e,t){for(var n,s=this._eras||mt("en")._eras,i=0,r=s.length;i<r;++i){switch(typeof s[i].since){case"string":n=f(s[i].since).startOf("day"),s[i].since=n.valueOf();break}switch(typeof s[i].until){case"undefined":s[i].until=1/0;break;case"string":n=f(s[i].until).startOf("day").valueOf(),s[i].until=n.valueOf();break}}return s},w.erasParse=function(e,t,n){var s,i,r,a,o,u=this.eras();for(e=e.toUpperCase(),s=0,i=u.length;s<i;++s)if(r=u[s].name.toUpperCase(),a=u[s].abbr.toUpperCase(),o=u[s].narrow.toUpperCase(),n)switch(t){case"N":case"NN":case"NNN":if(a===e)return u[s];break;case"NNNN":if(r===e)return u[s];break;case"NNNNN":if(o===e)return u[s];break}else if(0<=[r,a,o].indexOf(e))return u[s]},w.erasConvertYear=function(e,t){var n=e.since<=e.until?1:-1;return void 0===t?f(e.since).year():f(e.since).year()+(t-e.offset)*n},w.erasAbbrRegex=function(e){return c(this,"_erasAbbrRegex")||an.call(this),e?this._erasAbbrRegex:this._erasRegex},w.erasNameRegex=function(e){return c(this,"_erasNameRegex")||an.call(this),e?this._erasNameRegex:this._erasRegex},w.erasNarrowRegex=function(e){return c(this,"_erasNarrowRegex")||an.call(this),e?this._erasNarrowRegex:this._erasRegex},w.months=function(e,t){return e?(a(this._months)?this._months:this._months[(this._months.isFormat||He).test(t)?"format":"standalone"])[e.month()]:a(this._months)?this._months:this._months.standalone},w.monthsShort=function(e,t){return e?(a(this._monthsShort)?this._monthsShort:this._monthsShort[He.test(t)?"format":"standalone"])[e.month()]:a(this._monthsShort)?this._monthsShort:this._monthsShort.standalone},w.monthsParse=function(e,t,n){var s,i;if(this._monthsParseExact)return function(e,t,n){var s,i,r,e=e.toLocaleLowerCase();if(!this._monthsParse)for(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[],s=0;s<12;++s)r=l([2e3,s]),this._shortMonthsParse[s]=this.monthsShort(r,"").toLocaleLowerCase(),this._longMonthsParse[s]=this.months(r,"").toLocaleLowerCase();return n?"MMM"===t?-1!==(i=S.call(this._shortMonthsParse,e))?i:null:-1!==(i=S.call(this._longMonthsParse,e))?i:null:"MMM"===t?-1!==(i=S.call(this._shortMonthsParse,e))||-1!==(i=S.call(this._longMonthsParse,e))?i:null:-1!==(i=S.call(this._longMonthsParse,e))||-1!==(i=S.call(this._shortMonthsParse,e))?i:null}.call(this,e,t,n);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),s=0;s<12;s++){if(i=l([2e3,s]),n&&!this._longMonthsParse[s]&&(this._longMonthsParse[s]=new RegExp("^"+this.months(i,"").replace(".","")+"$","i"),this._shortMonthsParse[s]=new RegExp("^"+this.monthsShort(i,"").replace(".","")+"$","i")),n||this._monthsParse[s]||(i="^"+this.months(i,"")+"|^"+this.monthsShort(i,""),this._monthsParse[s]=new RegExp(i.replace(".",""),"i")),n&&"MMMM"===t&&this._longMonthsParse[s].test(e))return s;if(n&&"MMM"===t&&this._shortMonthsParse[s].test(e))return s;if(!n&&this._monthsParse[s].test(e))return s}},w.monthsRegex=function(e){return this._monthsParseExact?(c(this,"_monthsRegex")||Ee.call(this),e?this._monthsStrictRegex:this._monthsRegex):(c(this,"_monthsRegex")||(this._monthsRegex=Le),this._monthsStrictRegex&&e?this._monthsStrictRegex:this._monthsRegex)},w.monthsShortRegex=function(e){return this._monthsParseExact?(c(this,"_monthsRegex")||Ee.call(this),e?this._monthsShortStrictRegex:this._monthsShortRegex):(c(this,"_monthsShortRegex")||(this._monthsShortRegex=Fe),this._monthsShortStrictRegex&&e?this._monthsShortStrictRegex:this._monthsShortRegex)},w.week=function(e){return qe(e,this._week.dow,this._week.doy).week},w.firstDayOfYear=function(){return this._week.doy},w.firstDayOfWeek=function(){return this._week.dow},w.weekdays=function(e,t){return t=a(this._weekdays)?this._weekdays:this._weekdays[e&&!0!==e&&this._weekdays.isFormat.test(t)?"format":"standalone"],!0===e?Be(t,this._week.dow):e?t[e.day()]:t},w.weekdaysMin=function(e){return!0===e?Be(this._weekdaysMin,this._week.dow):e?this._weekdaysMin[e.day()]:this._weekdaysMin},w.weekdaysShort=function(e){return!0===e?Be(this._weekdaysShort,this._week.dow):e?this._weekdaysShort[e.day()]:this._weekdaysShort},w.weekdaysParse=function(e,t,n){var s,i;if(this._weekdaysParseExact)return function(e,t,n){var s,i,r,e=e.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],s=0;s<7;++s)r=l([2e3,1]).day(s),this._minWeekdaysParse[s]=this.weekdaysMin(r,"").toLocaleLowerCase(),this._shortWeekdaysParse[s]=this.weekdaysShort(r,"").toLocaleLowerCase(),this._weekdaysParse[s]=this.weekdays(r,"").toLocaleLowerCase();return n?"dddd"===t?-1!==(i=S.call(this._weekdaysParse,e))?i:null:"ddd"===t?-1!==(i=S.call(this._shortWeekdaysParse,e))?i:null:-1!==(i=S.call(this._minWeekdaysParse,e))?i:null:"dddd"===t?-1!==(i=S.call(this._weekdaysParse,e))||-1!==(i=S.call(this._shortWeekdaysParse,e))||-1!==(i=S.call(this._minWeekdaysParse,e))?i:null:"ddd"===t?-1!==(i=S.call(this._shortWeekdaysParse,e))||-1!==(i=S.call(this._weekdaysParse,e))||-1!==(i=S.call(this._minWeekdaysParse,e))?i:null:-1!==(i=S.call(this._minWeekdaysParse,e))||-1!==(i=S.call(this._weekdaysParse,e))||-1!==(i=S.call(this._shortWeekdaysParse,e))?i:null}.call(this,e,t,n);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),s=0;s<7;s++){if(i=l([2e3,1]).day(s),n&&!this._fullWeekdaysParse[s]&&(this._fullWeekdaysParse[s]=new RegExp("^"+this.weekdays(i,"").replace(".","\\.?")+"$","i"),this._shortWeekdaysParse[s]=new RegExp("^"+this.weekdaysShort(i,"").replace(".","\\.?")+"$","i"),this._minWeekdaysParse[s]=new RegExp("^"+this.weekdaysMin(i,"").replace(".","\\.?")+"$","i")),this._weekdaysParse[s]||(i="^"+this.weekdays(i,"")+"|^"+this.weekdaysShort(i,"")+"|^"+this.weekdaysMin(i,""),this._weekdaysParse[s]=new RegExp(i.replace(".",""),"i")),n&&"dddd"===t&&this._fullWeekdaysParse[s].test(e))return s;if(n&&"ddd"===t&&this._shortWeekdaysParse[s].test(e))return s;if(n&&"dd"===t&&this._minWeekdaysParse[s].test(e))return s;if(!n&&this._weekdaysParse[s].test(e))return s}},w.weekdaysRegex=function(e){return this._weekdaysParseExact?(c(this,"_weekdaysRegex")||nt.call(this),e?this._weekdaysStrictRegex:this._weekdaysRegex):(c(this,"_weekdaysRegex")||(this._weekdaysRegex=Ke),this._weekdaysStrictRegex&&e?this._weekdaysStrictRegex:this._weekdaysRegex)},w.weekdaysShortRegex=function(e){return this._weekdaysParseExact?(c(this,"_weekdaysRegex")||nt.call(this),e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(c(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=et),this._weekdaysShortStrictRegex&&e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)},w.weekdaysMinRegex=function(e){return this._weekdaysParseExact?(c(this,"_weekdaysRegex")||nt.call(this),e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(c(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=tt),this._weekdaysMinStrictRegex&&e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)},w.isPM=function(e){return"p"===(e+"").toLowerCase().charAt(0)},w.meridiem=function(e,t,n){return 11<e?n?"pm":"PM":n?"am":"AM"},ct("en",{eras:[{since:"0001-01-01",until:1/0,offset:1,name:"Anno Domini",narrow:"AD",abbr:"AD"},{since:"0000-12-31",until:-1/0,offset:1,name:"Before Christ",narrow:"BC",abbr:"BC"}],dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var t=e%10;return e+(1===g(e%100/10)?"th":1==t?"st":2==t?"nd":3==t?"rd":"th")}}),f.lang=e("moment.lang is deprecated. Use moment.locale instead.",ct),f.langData=e("moment.langData is deprecated. Use moment.localeData instead.",mt);var _n=Math.abs;function yn(e,t,n,s){t=C(t,n);return e._milliseconds+=s*t._milliseconds,e._days+=s*t._days,e._months+=s*t._months,e._bubble()}function gn(e){return e<0?Math.floor(e):Math.ceil(e)}function wn(e){return 4800*e/146097}function pn(e){return 146097*e/4800}function kn(e){return function(){return this.as(e)}}pe=kn("ms"),me=kn("s"),Ce=kn("m"),we=kn("h"),ge=kn("d"),Je=kn("w"),k=kn("M"),_e=kn("Q"),ve=kn("y");function vn(e){return function(){return this.isValid()?this._data[e]:NaN}}var ye=vn("milliseconds"),ke=vn("seconds"),Ie=vn("minutes"),w=vn("hours"),Mn=vn("days"),Dn=vn("months"),Sn=vn("years");var Yn=Math.round,On={ss:44,s:45,m:45,h:22,d:26,w:null,M:11};function bn(e,t,n,s){var i=C(e).abs(),r=Yn(i.as("s")),a=Yn(i.as("m")),o=Yn(i.as("h")),u=Yn(i.as("d")),l=Yn(i.as("M")),h=Yn(i.as("w")),i=Yn(i.as("y")),r=(r<=n.ss?["s",r]:r<n.s&&["ss",r])||a<=1&&["m"]||a<n.m&&["mm",a]||o<=1&&["h"]||o<n.h&&["hh",o]||u<=1&&["d"]||u<n.d&&["dd",u];return(r=(r=null!=n.w?r||h<=1&&["w"]||h<n.w&&["ww",h]:r)||l<=1&&["M"]||l<n.M&&["MM",l]||i<=1&&["y"]||["yy",i])[2]=t,r[3]=0<+e,r[4]=s,function(e,t,n,s,i){return i.relativeTime(t||1,!!n,e,s)}.apply(null,r)}var xn=Math.abs;function Tn(e){return(0<e)-(e<0)||+e}function Nn(){if(!this.isValid())return this.localeData().invalidDate();var e,t,n,s,i,r,a,o=xn(this._milliseconds)/1e3,u=xn(this._days),l=xn(this._months),h=this.asSeconds();return h?(e=y(o/60),t=y(e/60),o%=60,e%=60,n=y(l/12),l%=12,s=o?o.toFixed(3).replace(/\.?0+$/,""):"",i=Tn(this._months)!==Tn(h)?"-":"",r=Tn(this._days)!==Tn(h)?"-":"",a=Tn(this._milliseconds)!==Tn(h)?"-":"",(h<0?"-":"")+"P"+(n?i+n+"Y":"")+(l?i+l+"M":"")+(u?r+u+"D":"")+(t||e||o?"T":"")+(t?a+t+"H":"")+(e?a+e+"M":"")+(o?a+s+"S":"")):"P0D"}var U=Ct.prototype;return U.isValid=function(){return this._isValid},U.abs=function(){var e=this._data;return this._milliseconds=_n(this._milliseconds),this._days=_n(this._days),this._months=_n(this._months),e.milliseconds=_n(e.milliseconds),e.seconds=_n(e.seconds),e.minutes=_n(e.minutes),e.hours=_n(e.hours),e.months=_n(e.months),e.years=_n(e.years),this},U.add=function(e,t){return yn(this,e,t,1)},U.subtract=function(e,t){return yn(this,e,t,-1)},U.as=function(e){if(!this.isValid())return NaN;var t,n,s=this._milliseconds;if("month"===(e=_(e))||"quarter"===e||"year"===e)switch(t=this._days+s/864e5,n=this._months+wn(t),e){case"month":return n;case"quarter":return n/3;case"year":return n/12}else switch(t=this._days+Math.round(pn(this._months)),e){case"week":return t/7+s/6048e5;case"day":return t+s/864e5;case"hour":return 24*t+s/36e5;case"minute":return 1440*t+s/6e4;case"second":return 86400*t+s/1e3;case"millisecond":return Math.floor(864e5*t)+s;default:throw new Error("Unknown unit "+e)}},U.asMilliseconds=pe,U.asSeconds=me,U.asMinutes=Ce,U.asHours=we,U.asDays=ge,U.asWeeks=Je,U.asMonths=k,U.asQuarters=_e,U.asYears=ve,U.valueOf=function(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*g(this._months/12):NaN},U._bubble=function(){var e=this._milliseconds,t=this._days,n=this._months,s=this._data;return 0<=e&&0<=t&&0<=n||e<=0&&t<=0&&n<=0||(e+=864e5*gn(pn(n)+t),n=t=0),s.milliseconds=e%1e3,e=y(e/1e3),s.seconds=e%60,e=y(e/60),s.minutes=e%60,e=y(e/60),s.hours=e%24,t+=y(e/24),n+=e=y(wn(t)),t-=gn(pn(e)),e=y(n/12),n%=12,s.days=t,s.months=n,s.years=e,this},U.clone=function(){return C(this)},U.get=function(e){return e=_(e),this.isValid()?this[e+"s"]():NaN},U.milliseconds=ye,U.seconds=ke,U.minutes=Ie,U.hours=w,U.days=Mn,U.weeks=function(){return y(this.days()/7)},U.months=Dn,U.years=Sn,U.humanize=function(e,t){if(!this.isValid())return this.localeData().invalidDate();var n=!1,s=On;return"object"==typeof e&&(t=e,e=!1),"boolean"==typeof e&&(n=e),"object"==typeof t&&(s=Object.assign({},On,t),null!=t.s&&null==t.ss&&(s.ss=t.s-1)),e=this.localeData(),t=bn(this,!n,s,e),n&&(t=e.pastFuture(+this,t)),e.postformat(t)},U.toISOString=Nn,U.toString=Nn,U.toJSON=Nn,U.locale=Xt,U.localeData=Kt,U.toIsoString=e("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Nn),U.lang=Xe,s("X",0,0,"unix"),s("x",0,0,"valueOf"),v("x",De),v("X",/[+-]?\d+(\.\d{1,3})?/),D("X",function(e,t,n){n._d=new Date(1e3*parseFloat(e))}),D("x",function(e,t,n){n._d=new Date(g(e))}),f.version="2.29.2",H=W,f.fn=i,f.min=function(){return Rt("isBefore",[].slice.call(arguments,0))},f.max=function(){return Rt("isAfter",[].slice.call(arguments,0))},f.now=function(){return Date.now?Date.now():+new Date},f.utc=l,f.unix=function(e){return W(1e3*e)},f.months=function(e,t){return fn(e,t,"months")},f.isDate=V,f.locale=ct,f.invalid=I,f.duration=C,f.isMoment=h,f.weekdays=function(e,t,n){return mn(e,t,n,"weekdays")},f.parseZone=function(){return W.apply(null,arguments).parseZone()},f.localeData=mt,f.isDuration=Ut,f.monthsShort=function(e,t){return fn(e,t,"monthsShort")},f.weekdaysMin=function(e,t,n){return mn(e,t,n,"weekdaysMin")},f.defineLocale=ft,f.updateLocale=function(e,t){var n,s;return null!=t?(s=ot,null!=R[e]&&null!=R[e].parentLocale?R[e].set(X(R[e]._config,t)):(t=X(s=null!=(n=dt(e))?n._config:s,t),null==n&&(t.abbr=e),(s=new K(t)).parentLocale=R[e],R[e]=s),ct(e)):null!=R[e]&&(null!=R[e].parentLocale?(R[e]=R[e].parentLocale,e===ct()&&ct(e)):null!=R[e]&&delete R[e]),R[e]},f.locales=function(){return ee(R)},f.weekdaysShort=function(e,t,n){return mn(e,t,n,"weekdaysShort")},f.normalizeUnits=_,f.relativeTimeRounding=function(e){return void 0===e?Yn:"function"==typeof e&&(Yn=e,!0)},f.relativeTimeThreshold=function(e,t){return void 0!==On[e]&&(void 0===t?On[e]:(On[e]=t,"s"===e&&(On.ss=t-1),!0))},f.calendarFormat=function(e,t){return(e=e.diff(t,"days",!0))<-6?"sameElse":e<-1?"lastWeek":e<0?"lastDay":e<1?"sameDay":e<2?"nextDay":e<7?"nextWeek":"sameElse"},f.prototype=i,f.HTML5_FMT={DATETIME_LOCAL:"YYYY-MM-DDTHH:mm",DATETIME_LOCAL_SECONDS:"YYYY-MM-DDTHH:mm:ss",DATETIME_LOCAL_MS:"YYYY-MM-DDTHH:mm:ss.SSS",DATE:"YYYY-MM-DD",TIME:"HH:mm",TIME_SECONDS:"HH:mm:ss",TIME_MS:"HH:mm:ss.SSS",WEEK:"GGGG-[W]WW",MONTH:"YYYY-MM"},f});
+//# sourceMappingURL=moment.min.js.map
\ No newline at end of file
index cbf9534063688fb19570be155d54136fe12ce9d5..4f9f125509934fd5333d356510eb3eb63a21a853 100644 (file)
@@ -151,6 +151,10 @@ $(document).ready(function() {
                 $("#rule-drops").text(data["rule-drop"]);
                 $("#uptime").text(moment.duration(data["uptime"]*1000.0).humanize());
                 $("#latency").text((data["latency-avg10000"]/1000.0).toFixed(2));
+                $("#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;
 
@@ -190,20 +194,21 @@ $(document).ready(function() {
                      $("#version").text(data["daemon_type"]+" "+data["version"]);
                      $("#acl").text(data["acl"]);
                      $("#local").text(data["local"]);
-                     var bouw='<table width="100%"><tr align=right><th>#</th><th align=left>Name</th><th align=left>Address</th><th>Status</th><th>Latency</th><th>Queries</th><th>Drops</th><th>QPS</th><th>Out</th><th>Weight</th><th>Order</th><th align=left>Pools</th></tr>';
+                     var bouw='<table width="100%"><tr align=right><th>#</th><th align=left>Name</th><th align=left>Address</th><th>Status</th><th>UDP Latency</th><th>TCP Latency</th><th>Queries</th><th>Drops</th><th>QPS</th><th>Out</th><th>Weight</th><th>Order</th><th align=left>Pools</th></tr>';
                      $.each(data["servers"], function(a,b) {
                          bouw = bouw + ("<tr align=right><td>"+b["id"]+"</td><td align=left>"+b["name"]+"</td><td align=left>"+b["address"]+"</td><td>"+b["state"]+"</td>");
-                         var latency = (b["latency"] === null) ? 0.0 : b["latency"];
-                         bouw = bouw + ("<td>"+latency.toFixed(2)+"</td><td>"+b["queries"]+"</td><td>"+b["reuseds"]+"</td><td>"+(b["qps"]).toFixed(2)+"</td><td>"+b["outstanding"]+"</td>");
+                         var latency = (b["latency"] === null || b["latency"] === 0.0) ? "-" : b["latency"].toFixed(2);
+                         var tcpLatency = (b["tcpLatency"] === null || b["tcpLatency"] === 0.0) ? "-" : b["tcpLatency"].toFixed(2);
+                         bouw = bouw + ("<td>"+latency+"</td><td>"+tcpLatency+"</td><td>"+b["queries"]+"</td><td>"+b["reuseds"]+"</td><td>"+(b["qps"]).toFixed(2)+"</td><td>"+b["outstanding"]+"</td>");
                          bouw = bouw + ("<td>"+b["weight"]+"</td><td>"+b["order"]+"</td><td align=left>"+b["pools"]+"</td></tr>");
                      }); 
                      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>");
                          }); 
                      }
@@ -212,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>");
                          });
                      }
@@ -233,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;
                      });
                      
diff --git a/pdns/dnsdistdist/logging.hh b/pdns/dnsdistdist/logging.hh
new file mode 120000 (symlink)
index 0000000..020d82c
--- /dev/null
@@ -0,0 +1 @@
+../logging.hh
\ No newline at end of file
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])
+  ])
+])
diff --git a/pdns/dnsdistdist/m4/dnsdist_enable_tls_providers.m4 b/pdns/dnsdistdist/m4/dnsdist_enable_tls_providers.m4
new file mode 100644 (file)
index 0000000..1e58025
--- /dev/null
@@ -0,0 +1,22 @@
+AC_DEFUN([DNSDIST_ENABLE_TLS_PROVIDERS], [
+  AC_MSG_CHECKING([whether to enable OpenSSL >= 3.0 TLS providers (experimental)])
+  AC_ARG_ENABLE([tls-providers],
+    AS_HELP_STRING([--enable-tls-providers], [enable TLS providers (experimental and requires OpenSSL >= 3.0) @<:@default=no@:>@]),
+    [enable_tls_providers=$enableval],
+    [enable_tls_providers=no]
+  )
+  AC_MSG_RESULT([$enable_tls_providers])
+  AM_CONDITIONAL([HAVE_TLS_PROVIDERS], [test "x$enable_tls_providers" != "xno"])
+
+  PKG_CHECK_MODULES([LIBSSL], [libssl >= 3.0], [
+    [HAVE_LIBSSL_3_PLUS=1]
+    AC_DEFINE([HAVE_LIBSSL_3_PLUS], [1], [Define to 1 if you have OpenSSL >= 3.0])
+  ], [ : ])
+
+  AM_COND_IF([HAVE_TLS_PROVIDERS], [
+    AC_DEFINE([HAVE_TLS_PROVIDERS], [1], [Define to 1 if you enable OpenSSL >= 3.0 TLS providers])
+    AS_IF([test "x$HAVE_LIBSSL_3_PLUS" != "x1"], [
+      AC_MSG_ERROR([TLS providers support requires OpenSSL >= 3.0])
+    ])
+  ])
+])
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
diff --git a/pdns/dnsdistdist/m4/pdns_enable_lto.m4 b/pdns/dnsdistdist/m4/pdns_enable_lto.m4
new file mode 120000 (symlink)
index 0000000..0165907
--- /dev/null
@@ -0,0 +1 @@
+../../../m4/pdns_enable_lto.m4
\ No newline at end of file
diff --git a/pdns/dnsdistdist/m4/pdns_init_auto_vars.m4 b/pdns/dnsdistdist/m4/pdns_init_auto_vars.m4
new file mode 120000 (symlink)
index 0000000..c4384ff
--- /dev/null
@@ -0,0 +1 @@
+../../../m4/pdns_init_auto_vars.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])
+    ])
+  ])
+])
index c003e95fcf272ca56a82c5ee3c8d29b9f6e73bad..2e559835a0acf9bc03ffef58fc824739bbb1f7f1 100644 (file)
@@ -1,5 +1,5 @@
 //! moment.js
-//! version : 2.11.1
+//! version : 2.29.2
 //! authors : Tim Wood, Iskren Chernev, Moment.js contributors
 //! license : MIT
 //! momentjs.com
@@ -8,40 +8,82 @@
     typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
     typeof define === 'function' && define.amd ? define(factory) :
     global.moment = factory()
-}(this, function () { 'use strict';
+}(this, (function () { 'use strict';
 
     var hookCallback;
 
-    function utils_hooks__hooks () {
+    function hooks() {
         return hookCallback.apply(null, arguments);
     }
 
     // This is done to register the method called with moment()
     // without creating circular dependencies.
-    function setHookCallback (callback) {
+    function setHookCallback(callback) {
         hookCallback = callback;
     }
 
     function isArray(input) {
-        return Object.prototype.toString.call(input) === '[object Array]';
+        return (
+            input instanceof Array ||
+            Object.prototype.toString.call(input) === '[object Array]'
+        );
+    }
+
+    function isObject(input) {
+        // IE8 will treat undefined and null as object if it wasn't for
+        // input != null
+        return (
+            input != null &&
+            Object.prototype.toString.call(input) === '[object Object]'
+        );
+    }
+
+    function hasOwnProp(a, b) {
+        return Object.prototype.hasOwnProperty.call(a, b);
+    }
+
+    function isObjectEmpty(obj) {
+        if (Object.getOwnPropertyNames) {
+            return Object.getOwnPropertyNames(obj).length === 0;
+        } else {
+            var k;
+            for (k in obj) {
+                if (hasOwnProp(obj, k)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    function isUndefined(input) {
+        return input === void 0;
+    }
+
+    function isNumber(input) {
+        return (
+            typeof input === 'number' ||
+            Object.prototype.toString.call(input) === '[object Number]'
+        );
     }
 
     function isDate(input) {
-        return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
+        return (
+            input instanceof Date ||
+            Object.prototype.toString.call(input) === '[object Date]'
+        );
     }
 
     function map(arr, fn) {
-        var res = [], i;
-        for (i = 0; i < arr.length; ++i) {
+        var res = [],
+            i,
+            arrLen = arr.length;
+        for (i = 0; i < arrLen; ++i) {
             res.push(fn(arr[i], i));
         }
         return res;
     }
 
-    function hasOwnProp(a, b) {
-        return Object.prototype.hasOwnProperty.call(a, b);
-    }
-
     function extend(a, b) {
         for (var i in b) {
             if (hasOwnProp(b, i)) {
         return a;
     }
 
-    function create_utc__createUTC (input, format, locale, strict) {
+    function createUTC(input, format, locale, strict) {
         return createLocalOrUTC(input, format, locale, strict, true).utc();
     }
 
     function defaultParsingFlags() {
         // We need to deep clone this object.
         return {
-            empty           : false,
-            unusedTokens    : [],
-            unusedInput     : [],
-            overflow        : -2,
-            charsLeftOver   : 0,
-            nullInput       : false,
-            invalidMonth    : null,
-            invalidFormat   : false,
-            userInvalidated : false,
-            iso             : false
+            empty: false,
+            unusedTokens: [],
+            unusedInput: [],
+            overflow: -2,
+            charsLeftOver: 0,
+            nullInput: false,
+            invalidEra: null,
+            invalidMonth: null,
+            invalidFormat: false,
+            userInvalidated: false,
+            iso: false,
+            parsedDateParts: [],
+            era: null,
+            meridiem: null,
+            rfc2822: false,
+            weekdayMismatch: false,
         };
     }
 
         return m._pf;
     }
 
-    function valid__isValid(m) {
+    var some;
+    if (Array.prototype.some) {
+        some = Array.prototype.some;
+    } else {
+        some = function (fun) {
+            var t = Object(this),
+                len = t.length >>> 0,
+                i;
+
+            for (i = 0; i < len; i++) {
+                if (i in t && fun.call(this, t[i], i, t)) {
+                    return true;
+                }
+            }
+
+            return false;
+        };
+    }
+
+    function isValid(m) {
         if (m._isValid == null) {
-            var flags = getParsingFlags(m);
-            m._isValid = !isNaN(m._d.getTime()) &&
-                flags.overflow < 0 &&
-                !flags.empty &&
-                !flags.invalidMonth &&
-                !flags.invalidWeekday &&
-                !flags.nullInput &&
-                !flags.invalidFormat &&
-                !flags.userInvalidated;
+            var flags = getParsingFlags(m),
+                parsedParts = some.call(flags.parsedDateParts, function (i) {
+                    return i != null;
+                }),
+                isNowValid =
+                    !isNaN(m._d.getTime()) &&
+                    flags.overflow < 0 &&
+                    !flags.empty &&
+                    !flags.invalidEra &&
+                    !flags.invalidMonth &&
+                    !flags.invalidWeekday &&
+                    !flags.weekdayMismatch &&
+                    !flags.nullInput &&
+                    !flags.invalidFormat &&
+                    !flags.userInvalidated &&
+                    (!flags.meridiem || (flags.meridiem && parsedParts));
 
             if (m._strict) {
-                m._isValid = m._isValid &&
+                isNowValid =
+                    isNowValid &&
                     flags.charsLeftOver === 0 &&
                     flags.unusedTokens.length === 0 &&
                     flags.bigHour === undefined;
             }
+
+            if (Object.isFrozen == null || !Object.isFrozen(m)) {
+                m._isValid = isNowValid;
+            } else {
+                return isNowValid;
+            }
         }
         return m._isValid;
     }
 
-    function valid__createInvalid (flags) {
-        var m = create_utc__createUTC(NaN);
+    function createInvalid(flags) {
+        var m = createUTC(NaN);
         if (flags != null) {
             extend(getParsingFlags(m), flags);
-        }
-        else {
+        } else {
             getParsingFlags(m).userInvalidated = true;
         }
 
         return m;
     }
 
-    function isUndefined(input) {
-        return input === void 0;
-    }
-
     // Plugins that add properties should also add the key here (null value),
     // so we can properly clone ourselves.
-    var momentProperties = utils_hooks__hooks.momentProperties = [];
+    var momentProperties = (hooks.momentProperties = []),
+        updateInProgress = false;
 
     function copyConfig(to, from) {
-        var i, prop, val;
+        var i,
+            prop,
+            val,
+            momentPropertiesLen = momentProperties.length;
 
         if (!isUndefined(from._isAMomentObject)) {
             to._isAMomentObject = from._isAMomentObject;
             to._locale = from._locale;
         }
 
-        if (momentProperties.length > 0) {
-            for (i in momentProperties) {
+        if (momentPropertiesLen > 0) {
+            for (i = 0; i < momentPropertiesLen; i++) {
                 prop = momentProperties[i];
                 val = from[prop];
                 if (!isUndefined(val)) {
         return to;
     }
 
-    var updateInProgress = false;
-
     // Moment prototype object
     function Moment(config) {
         copyConfig(this, config);
         this._d = new Date(config._d != null ? config._d.getTime() : NaN);
+        if (!this.isValid()) {
+            this._d = new Date(NaN);
+        }
         // Prevent infinite loop in case updateOffset creates new moment
         // objects.
         if (updateInProgress === false) {
             updateInProgress = true;
-            utils_hooks__hooks.updateOffset(this);
+            hooks.updateOffset(this);
             updateInProgress = false;
         }
     }
 
-    function isMoment (obj) {
-        return obj instanceof Moment || (obj != null && obj._isAMomentObject != null);
+    function isMoment(obj) {
+        return (
+            obj instanceof Moment || (obj != null && obj._isAMomentObject != null)
+        );
     }
 
-    function absFloor (number) {
-        if (number < 0) {
-            return Math.ceil(number);
-        } else {
-            return Math.floor(number);
+    function warn(msg) {
+        if (
+            hooks.suppressDeprecationWarnings === false &&
+            typeof console !== 'undefined' &&
+            console.warn
+        ) {
+            console.warn('Deprecation warning: ' + msg);
         }
     }
 
-    function toInt(argumentForCoercion) {
-        var coercedNumber = +argumentForCoercion,
-            value = 0;
-
-        if (coercedNumber !== 0 && isFinite(coercedNumber)) {
-            value = absFloor(coercedNumber);
-        }
-
-        return value;
-    }
+    function deprecate(msg, fn) {
+        var firstTime = true;
 
-    // compare two arrays, return the number of differences
-    function compareArrays(array1, array2, dontConvert) {
-        var len = Math.min(array1.length, array2.length),
-            lengthDiff = Math.abs(array1.length - array2.length),
-            diffs = 0,
-            i;
-        for (i = 0; i < len; i++) {
-            if ((dontConvert && array1[i] !== array2[i]) ||
-                (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
-                diffs++;
+        return extend(function () {
+            if (hooks.deprecationHandler != null) {
+                hooks.deprecationHandler(null, msg);
             }
-        }
-        return diffs + lengthDiff;
-    }
-
-    function Locale() {
-    }
-
-    // internal storage for locale config files
-    var locales = {};
-    var globalLocale;
-
-    function normalizeLocale(key) {
-        return key ? key.toLowerCase().replace('_', '-') : key;
-    }
-
-    // pick the locale from the array
-    // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
-    // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
-    function chooseLocale(names) {
-        var i = 0, j, next, locale, split;
-
-        while (i < names.length) {
-            split = normalizeLocale(names[i]).split('-');
-            j = split.length;
-            next = normalizeLocale(names[i + 1]);
-            next = next ? next.split('-') : null;
-            while (j > 0) {
-                locale = loadLocale(split.slice(0, j).join('-'));
-                if (locale) {
-                    return locale;
-                }
-                if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
-                    //the next array item is better than a shallower substring of this one
-                    break;
+            if (firstTime) {
+                var args = [],
+                    arg,
+                    i,
+                    key,
+                    argLen = arguments.length;
+                for (i = 0; i < argLen; i++) {
+                    arg = '';
+                    if (typeof arguments[i] === 'object') {
+                        arg += '\n[' + i + '] ';
+                        for (key in arguments[0]) {
+                            if (hasOwnProp(arguments[0], key)) {
+                                arg += key + ': ' + arguments[0][key] + ', ';
+                            }
+                        }
+                        arg = arg.slice(0, -2); // Remove trailing comma and space
+                    } else {
+                        arg = arguments[i];
+                    }
+                    args.push(arg);
                 }
-                j--;
+                warn(
+                    msg +
+                        '\nArguments: ' +
+                        Array.prototype.slice.call(args).join('') +
+                        '\n' +
+                        new Error().stack
+                );
+                firstTime = false;
             }
-            i++;
-        }
-        return null;
-    }
-
-    function loadLocale(name) {
-        var oldLocale = null;
-        // TODO: Find a better way to register and load all the locales in Node
-        if (!locales[name] && (typeof module !== 'undefined') &&
-                module && module.exports) {
-            try {
-                oldLocale = globalLocale._abbr;
-                require('./locale/' + name);
-                // because defineLocale currently also sets the global locale, we
-                // want to undo that for lazy loaded locales
-                locale_locales__getSetGlobalLocale(oldLocale);
-            } catch (e) { }
-        }
-        return locales[name];
+            return fn.apply(this, arguments);
+        }, fn);
     }
 
-    // This function will load locale and then set the global locale.  If
-    // no arguments are passed in, it will simply return the current global
-    // locale key.
-    function locale_locales__getSetGlobalLocale (key, values) {
-        var data;
-        if (key) {
-            if (isUndefined(values)) {
-                data = locale_locales__getLocale(key);
-            }
-            else {
-                data = defineLocale(key, values);
-            }
+    var deprecations = {};
 
-            if (data) {
-                // moment.duration._locale = moment._locale = data;
-                globalLocale = data;
-            }
+    function deprecateSimple(name, msg) {
+        if (hooks.deprecationHandler != null) {
+            hooks.deprecationHandler(name, msg);
         }
-
-        return globalLocale._abbr;
-    }
-
-    function defineLocale (name, values) {
-        if (values !== null) {
-            values.abbr = name;
-            locales[name] = locales[name] || new Locale();
-            locales[name].set(values);
-
-            // backwards compat for now: also set the locale
-            locale_locales__getSetGlobalLocale(name);
-
-            return locales[name];
-        } else {
-            // useful for testing
-            delete locales[name];
-            return null;
+        if (!deprecations[name]) {
+            warn(msg);
+            deprecations[name] = true;
         }
     }
 
-    // returns locale data
-    function locale_locales__getLocale (key) {
-        var locale;
-
-        if (key && key._locale && key._locale._abbr) {
-            key = key._locale._abbr;
-        }
+    hooks.suppressDeprecationWarnings = false;
+    hooks.deprecationHandler = null;
 
-        if (!key) {
-            return globalLocale;
-        }
+    function isFunction(input) {
+        return (
+            (typeof Function !== 'undefined' && input instanceof Function) ||
+            Object.prototype.toString.call(input) === '[object Function]'
+        );
+    }
 
-        if (!isArray(key)) {
-            //short-circuit everything else
-            locale = loadLocale(key);
-            if (locale) {
-                return locale;
+    function set(config) {
+        var prop, i;
+        for (i in config) {
+            if (hasOwnProp(config, i)) {
+                prop = config[i];
+                if (isFunction(prop)) {
+                    this[i] = prop;
+                } else {
+                    this['_' + i] = prop;
+                }
             }
-            key = [key];
         }
-
-        return chooseLocale(key);
-    }
-
-    var aliases = {};
-
-    function addUnitAlias (unit, shorthand) {
-        var lowerCase = unit.toLowerCase();
-        aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
-    }
-
-    function normalizeUnits(units) {
-        return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined;
+        this._config = config;
+        // Lenient ordinal parsing accepts just a number in addition to
+        // number + (possibly) stuff coming from _dayOfMonthOrdinalParse.
+        // TODO: Remove "ordinalParse" fallback in next major release.
+        this._dayOfMonthOrdinalParseLenient = new RegExp(
+            (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) +
+                '|' +
+                /\d{1,2}/.source
+        );
     }
 
-    function normalizeObjectUnits(inputObject) {
-        var normalizedInput = {},
-            normalizedProp,
+    function mergeConfigs(parentConfig, childConfig) {
+        var res = extend({}, parentConfig),
             prop;
-
-        for (prop in inputObject) {
-            if (hasOwnProp(inputObject, prop)) {
-                normalizedProp = normalizeUnits(prop);
-                if (normalizedProp) {
-                    normalizedInput[normalizedProp] = inputObject[prop];
+        for (prop in childConfig) {
+            if (hasOwnProp(childConfig, prop)) {
+                if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {
+                    res[prop] = {};
+                    extend(res[prop], parentConfig[prop]);
+                    extend(res[prop], childConfig[prop]);
+                } else if (childConfig[prop] != null) {
+                    res[prop] = childConfig[prop];
+                } else {
+                    delete res[prop];
                 }
             }
         }
-
-        return normalizedInput;
+        for (prop in parentConfig) {
+            if (
+                hasOwnProp(parentConfig, prop) &&
+                !hasOwnProp(childConfig, prop) &&
+                isObject(parentConfig[prop])
+            ) {
+                // make sure changes to properties don't modify parent config
+                res[prop] = extend({}, res[prop]);
+            }
+        }
+        return res;
     }
 
-    function isFunction(input) {
-        return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]';
+    function Locale(config) {
+        if (config != null) {
+            this.set(config);
+        }
     }
 
-    function makeGetSet (unit, keepTime) {
-        return function (value) {
-            if (value != null) {
-                get_set__set(this, unit, value);
-                utils_hooks__hooks.updateOffset(this, keepTime);
-                return this;
-            } else {
-                return get_set__get(this, unit);
+    var keys;
+
+    if (Object.keys) {
+        keys = Object.keys;
+    } else {
+        keys = function (obj) {
+            var i,
+                res = [];
+            for (i in obj) {
+                if (hasOwnProp(obj, i)) {
+                    res.push(i);
+                }
             }
+            return res;
         };
     }
 
-    function get_set__get (mom, unit) {
-        return mom.isValid() ?
-            mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN;
-    }
-
-    function get_set__set (mom, unit, value) {
-        if (mom.isValid()) {
-            mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
-        }
-    }
-
-    // MOMENTS
+    var defaultCalendar = {
+        sameDay: '[Today at] LT',
+        nextDay: '[Tomorrow at] LT',
+        nextWeek: 'dddd [at] LT',
+        lastDay: '[Yesterday at] LT',
+        lastWeek: '[Last] dddd [at] LT',
+        sameElse: 'L',
+    };
 
-    function getSet (units, value) {
-        var unit;
-        if (typeof units === 'object') {
-            for (unit in units) {
-                this.set(unit, units[unit]);
-            }
-        } else {
-            units = normalizeUnits(units);
-            if (isFunction(this[units])) {
-                return this[units](value);
-            }
-        }
-        return this;
+    function calendar(key, mom, now) {
+        var output = this._calendar[key] || this._calendar['sameElse'];
+        return isFunction(output) ? output.call(mom, now) : output;
     }
 
     function zeroFill(number, targetLength, forceSign) {
         var absNumber = '' + Math.abs(number),
             zerosToFill = targetLength - absNumber.length,
             sign = number >= 0;
-        return (sign ? (forceSign ? '+' : '') : '-') +
-            Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber;
+        return (
+            (sign ? (forceSign ? '+' : '') : '-') +
+            Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) +
+            absNumber
+        );
     }
 
-    var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g;
-
-    var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g;
-
-    var formatFunctions = {};
-
-    var formatTokenFunctions = {};
+    var formattingTokens =
+            /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,
+        localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,
+        formatFunctions = {},
+        formatTokenFunctions = {};
 
     // token:    'M'
     // padded:   ['MM', 2]
     // ordinal:  'Mo'
     // callback: function () { this.month() + 1 }
-    function addFormatToken (token, padded, ordinal, callback) {
+    function addFormatToken(token, padded, ordinal, callback) {
         var func = callback;
         if (typeof callback === 'string') {
             func = function () {
         }
         if (ordinal) {
             formatTokenFunctions[ordinal] = function () {
-                return this.localeData().ordinal(func.apply(this, arguments), token);
+                return this.localeData().ordinal(
+                    func.apply(this, arguments),
+                    token
+                );
             };
         }
     }
     }
 
     function makeFormatFunction(format) {
-        var array = format.match(formattingTokens), i, length;
+        var array = format.match(formattingTokens),
+            i,
+            length;
 
         for (i = 0, length = array.length; i < length; i++) {
             if (formatTokenFunctions[array[i]]) {
         }
 
         return function (mom) {
-            var output = '';
+            var output = '',
+                i;
             for (i = 0; i < length; i++) {
-                output += array[i] instanceof Function ? array[i].call(mom, format) : array[i];
+                output += isFunction(array[i])
+                    ? array[i].call(mom, format)
+                    : array[i];
             }
             return output;
         };
         }
 
         format = expandFormat(format, m.localeData());
-        formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format);
+        formatFunctions[format] =
+            formatFunctions[format] || makeFormatFunction(format);
 
         return formatFunctions[format](m);
     }
 
         localFormattingTokens.lastIndex = 0;
         while (i >= 0 && localFormattingTokens.test(format)) {
-            format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
+            format = format.replace(
+                localFormattingTokens,
+                replaceLongDateFormatTokens
+            );
             localFormattingTokens.lastIndex = 0;
             i -= 1;
         }
         return format;
     }
 
-    var match1         = /\d/;            //       0 - 9
-    var match2         = /\d\d/;          //      00 - 99
-    var match3         = /\d{3}/;         //     000 - 999
-    var match4         = /\d{4}/;         //    0000 - 9999
-    var match6         = /[+-]?\d{6}/;    // -999999 - 999999
-    var match1to2      = /\d\d?/;         //       0 - 99
-    var match3to4      = /\d\d\d\d?/;     //     999 - 9999
-    var match5to6      = /\d\d\d\d\d\d?/; //   99999 - 999999
-    var match1to3      = /\d{1,3}/;       //       0 - 999
-    var match1to4      = /\d{1,4}/;       //       0 - 9999
-    var match1to6      = /[+-]?\d{1,6}/;  // -999999 - 999999
+    var defaultLongDateFormat = {
+        LTS: 'h:mm:ss A',
+        LT: 'h:mm A',
+        L: 'MM/DD/YYYY',
+        LL: 'MMMM D, YYYY',
+        LLL: 'MMMM D, YYYY h:mm A',
+        LLLL: 'dddd, MMMM D, YYYY h:mm A',
+    };
+
+    function longDateFormat(key) {
+        var format = this._longDateFormat[key],
+            formatUpper = this._longDateFormat[key.toUpperCase()];
 
-    var matchUnsigned  = /\d+/;           //       0 - inf
-    var matchSigned    = /[+-]?\d+/;      //    -inf - inf
+        if (format || !formatUpper) {
+            return format;
+        }
 
-    var matchOffset    = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z
-    var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z
+        this._longDateFormat[key] = formatUpper
+            .match(formattingTokens)
+            .map(function (tok) {
+                if (
+                    tok === 'MMMM' ||
+                    tok === 'MM' ||
+                    tok === 'DD' ||
+                    tok === 'dddd'
+                ) {
+                    return tok.slice(1);
+                }
+                return tok;
+            })
+            .join('');
 
-    var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123
+        return this._longDateFormat[key];
+    }
 
-    // any word (or two) characters or numbers including two/three word month in arabic.
-    // includes scottish gaelic two word and hyphenated months
-    var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i;
+    var defaultInvalidDate = 'Invalid date';
 
+    function invalidDate() {
+        return this._invalidDate;
+    }
 
-    var regexes = {};
+    var defaultOrdinal = '%d',
+        defaultDayOfMonthOrdinalParse = /\d{1,2}/;
 
-    function addRegexToken (token, regex, strictRegex) {
-        regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) {
-            return (isStrict && strictRegex) ? strictRegex : regex;
-        };
+    function ordinal(number) {
+        return this._ordinal.replace('%d', number);
     }
 
-    function getParseRegexForToken (token, config) {
+    var defaultRelativeTime = {
+        future: 'in %s',
+        past: '%s ago',
+        s: 'a few seconds',
+        ss: '%d seconds',
+        m: 'a minute',
+        mm: '%d minutes',
+        h: 'an hour',
+        hh: '%d hours',
+        d: 'a day',
+        dd: '%d days',
+        w: 'a week',
+        ww: '%d weeks',
+        M: 'a month',
+        MM: '%d months',
+        y: 'a year',
+        yy: '%d years',
+    };
+
+    function relativeTime(number, withoutSuffix, string, isFuture) {
+        var output = this._relativeTime[string];
+        return isFunction(output)
+            ? output(number, withoutSuffix, string, isFuture)
+            : output.replace(/%d/i, number);
+    }
+
+    function pastFuture(diff, output) {
+        var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
+        return isFunction(format) ? format(output) : format.replace(/%s/i, output);
+    }
+
+    var aliases = {};
+
+    function addUnitAlias(unit, shorthand) {
+        var lowerCase = unit.toLowerCase();
+        aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
+    }
+
+    function normalizeUnits(units) {
+        return typeof units === 'string'
+            ? aliases[units] || aliases[units.toLowerCase()]
+            : undefined;
+    }
+
+    function normalizeObjectUnits(inputObject) {
+        var normalizedInput = {},
+            normalizedProp,
+            prop;
+
+        for (prop in inputObject) {
+            if (hasOwnProp(inputObject, prop)) {
+                normalizedProp = normalizeUnits(prop);
+                if (normalizedProp) {
+                    normalizedInput[normalizedProp] = inputObject[prop];
+                }
+            }
+        }
+
+        return normalizedInput;
+    }
+
+    var priorities = {};
+
+    function addUnitPriority(unit, priority) {
+        priorities[unit] = priority;
+    }
+
+    function getPrioritizedUnits(unitsObj) {
+        var units = [],
+            u;
+        for (u in unitsObj) {
+            if (hasOwnProp(unitsObj, u)) {
+                units.push({ unit: u, priority: priorities[u] });
+            }
+        }
+        units.sort(function (a, b) {
+            return a.priority - b.priority;
+        });
+        return units;
+    }
+
+    function isLeapYear(year) {
+        return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+    }
+
+    function absFloor(number) {
+        if (number < 0) {
+            // -0 -> 0
+            return Math.ceil(number) || 0;
+        } else {
+            return Math.floor(number);
+        }
+    }
+
+    function toInt(argumentForCoercion) {
+        var coercedNumber = +argumentForCoercion,
+            value = 0;
+
+        if (coercedNumber !== 0 && isFinite(coercedNumber)) {
+            value = absFloor(coercedNumber);
+        }
+
+        return value;
+    }
+
+    function makeGetSet(unit, keepTime) {
+        return function (value) {
+            if (value != null) {
+                set$1(this, unit, value);
+                hooks.updateOffset(this, keepTime);
+                return this;
+            } else {
+                return get(this, unit);
+            }
+        };
+    }
+
+    function get(mom, unit) {
+        return mom.isValid()
+            ? mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]()
+            : NaN;
+    }
+
+    function set$1(mom, unit, value) {
+        if (mom.isValid() && !isNaN(value)) {
+            if (
+                unit === 'FullYear' &&
+                isLeapYear(mom.year()) &&
+                mom.month() === 1 &&
+                mom.date() === 29
+            ) {
+                value = toInt(value);
+                mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](
+                    value,
+                    mom.month(),
+                    daysInMonth(value, mom.month())
+                );
+            } else {
+                mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
+            }
+        }
+    }
+
+    // MOMENTS
+
+    function stringGet(units) {
+        units = normalizeUnits(units);
+        if (isFunction(this[units])) {
+            return this[units]();
+        }
+        return this;
+    }
+
+    function stringSet(units, value) {
+        if (typeof units === 'object') {
+            units = normalizeObjectUnits(units);
+            var prioritized = getPrioritizedUnits(units),
+                i,
+                prioritizedLen = prioritized.length;
+            for (i = 0; i < prioritizedLen; i++) {
+                this[prioritized[i].unit](units[prioritized[i].unit]);
+            }
+        } else {
+            units = normalizeUnits(units);
+            if (isFunction(this[units])) {
+                return this[units](value);
+            }
+        }
+        return this;
+    }
+
+    var match1 = /\d/, //       0 - 9
+        match2 = /\d\d/, //      00 - 99
+        match3 = /\d{3}/, //     000 - 999
+        match4 = /\d{4}/, //    0000 - 9999
+        match6 = /[+-]?\d{6}/, // -999999 - 999999
+        match1to2 = /\d\d?/, //       0 - 99
+        match3to4 = /\d\d\d\d?/, //     999 - 9999
+        match5to6 = /\d\d\d\d\d\d?/, //   99999 - 999999
+        match1to3 = /\d{1,3}/, //       0 - 999
+        match1to4 = /\d{1,4}/, //       0 - 9999
+        match1to6 = /[+-]?\d{1,6}/, // -999999 - 999999
+        matchUnsigned = /\d+/, //       0 - inf
+        matchSigned = /[+-]?\d+/, //    -inf - inf
+        matchOffset = /Z|[+-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z
+        matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi, // +00 -00 +00:00 -00:00 +0000 -0000 or Z
+        matchTimestamp = /[+-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
+        // any word (or two) characters or numbers including two/three word month in arabic.
+        // includes scottish gaelic two word and hyphenated months
+        matchWord =
+            /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i,
+        regexes;
+
+    regexes = {};
+
+    function addRegexToken(token, regex, strictRegex) {
+        regexes[token] = isFunction(regex)
+            ? regex
+            : function (isStrict, localeData) {
+                  return isStrict && strictRegex ? strictRegex : regex;
+              };
+    }
+
+    function getParseRegexForToken(token, config) {
         if (!hasOwnProp(regexes, token)) {
             return new RegExp(unescapeFormat(token));
         }
 
     // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
     function unescapeFormat(s) {
-        return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
-            return p1 || p2 || p3 || p4;
-        }));
+        return regexEscape(
+            s
+                .replace('\\', '')
+                .replace(
+                    /\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,
+                    function (matched, p1, p2, p3, p4) {
+                        return p1 || p2 || p3 || p4;
+                    }
+                )
+        );
     }
 
     function regexEscape(s) {
 
     var tokens = {};
 
-    function addParseToken (token, callback) {
-        var i, func = callback;
+    function addParseToken(token, callback) {
+        var i,
+            func = callback,
+            tokenLen;
         if (typeof token === 'string') {
             token = [token];
         }
-        if (typeof callback === 'number') {
+        if (isNumber(callback)) {
             func = function (input, array) {
                 array[callback] = toInt(input);
             };
         }
-        for (i = 0; i < token.length; i++) {
+        tokenLen = token.length;
+        for (i = 0; i < tokenLen; i++) {
             tokens[token[i]] = func;
         }
     }
 
-    function addWeekParseToken (token, callback) {
+    function addWeekParseToken(token, callback) {
         addParseToken(token, function (input, array, config, token) {
             config._w = config._w || {};
             callback(input, config._w, config, token);
         }
     }
 
-    var YEAR = 0;
-    var MONTH = 1;
-    var DATE = 2;
-    var HOUR = 3;
-    var MINUTE = 4;
-    var SECOND = 5;
-    var MILLISECOND = 6;
-    var WEEK = 7;
-    var WEEKDAY = 8;
+    var YEAR = 0,
+        MONTH = 1,
+        DATE = 2,
+        HOUR = 3,
+        MINUTE = 4,
+        SECOND = 5,
+        MILLISECOND = 6,
+        WEEK = 7,
+        WEEKDAY = 8;
+
+    function mod(n, x) {
+        return ((n % x) + x) % x;
+    }
+
+    var indexOf;
+
+    if (Array.prototype.indexOf) {
+        indexOf = Array.prototype.indexOf;
+    } else {
+        indexOf = function (o) {
+            // I know
+            var i;
+            for (i = 0; i < this.length; ++i) {
+                if (this[i] === o) {
+                    return i;
+                }
+            }
+            return -1;
+        };
+    }
 
     function daysInMonth(year, month) {
-        return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
+        if (isNaN(year) || isNaN(month)) {
+            return NaN;
+        }
+        var modMonth = mod(month, 12);
+        year += (month - modMonth) / 12;
+        return modMonth === 1
+            ? isLeapYear(year)
+                ? 29
+                : 28
+            : 31 - ((modMonth % 7) % 2);
     }
 
     // FORMATTING
 
     addUnitAlias('month', 'M');
 
+    // PRIORITY
+
+    addUnitPriority('month', 8);
+
     // PARSING
 
-    addRegexToken('M',    match1to2);
-    addRegexToken('MM',   match1to2, match2);
-    addRegexToken('MMM',  function (isStrict, locale) {
+    addRegexToken('M', match1to2);
+    addRegexToken('MM', match1to2, match2);
+    addRegexToken('MMM', function (isStrict, locale) {
         return locale.monthsShortRegex(isStrict);
     });
     addRegexToken('MMMM', function (isStrict, locale) {
 
     // LOCALES
 
-    var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/;
-    var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_');
-    function localeMonths (m, format) {
-        return isArray(this._months) ? this._months[m.month()] :
-            this._months[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()];
-    }
+    var defaultLocaleMonths =
+            'January_February_March_April_May_June_July_August_September_October_November_December'.split(
+                '_'
+            ),
+        defaultLocaleMonthsShort =
+            'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),
+        MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/,
+        defaultMonthsShortRegex = matchWord,
+        defaultMonthsRegex = matchWord;
+
+    function localeMonths(m, format) {
+        if (!m) {
+            return isArray(this._months)
+                ? this._months
+                : this._months['standalone'];
+        }
+        return isArray(this._months)
+            ? this._months[m.month()]
+            : this._months[
+                  (this._months.isFormat || MONTHS_IN_FORMAT).test(format)
+                      ? 'format'
+                      : 'standalone'
+              ][m.month()];
+    }
+
+    function localeMonthsShort(m, format) {
+        if (!m) {
+            return isArray(this._monthsShort)
+                ? this._monthsShort
+                : this._monthsShort['standalone'];
+        }
+        return isArray(this._monthsShort)
+            ? this._monthsShort[m.month()]
+            : this._monthsShort[
+                  MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'
+              ][m.month()];
+    }
+
+    function handleStrictParse(monthName, format, strict) {
+        var i,
+            ii,
+            mom,
+            llc = monthName.toLocaleLowerCase();
+        if (!this._monthsParse) {
+            // this is not used
+            this._monthsParse = [];
+            this._longMonthsParse = [];
+            this._shortMonthsParse = [];
+            for (i = 0; i < 12; ++i) {
+                mom = createUTC([2000, i]);
+                this._shortMonthsParse[i] = this.monthsShort(
+                    mom,
+                    ''
+                ).toLocaleLowerCase();
+                this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();
+            }
+        }
 
-    var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_');
-    function localeMonthsShort (m, format) {
-        return isArray(this._monthsShort) ? this._monthsShort[m.month()] :
-            this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()];
+        if (strict) {
+            if (format === 'MMM') {
+                ii = indexOf.call(this._shortMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._longMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            }
+        } else {
+            if (format === 'MMM') {
+                ii = indexOf.call(this._shortMonthsParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._longMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._longMonthsParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._shortMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            }
+        }
     }
 
-    function localeMonthsParse (monthName, format, strict) {
+    function localeMonthsParse(monthName, format, strict) {
         var i, mom, regex;
 
+        if (this._monthsParseExact) {
+            return handleStrictParse.call(this, monthName, format, strict);
+        }
+
         if (!this._monthsParse) {
             this._monthsParse = [];
             this._longMonthsParse = [];
             this._shortMonthsParse = [];
         }
 
+        // TODO: add sorting
+        // Sorting makes sure if one month (or abbr) is a prefix of another
+        // see sorting in computeMonthsParse
         for (i = 0; i < 12; i++) {
             // make the regex if we don't have it already
-            mom = create_utc__createUTC([2000, i]);
+            mom = createUTC([2000, i]);
             if (strict && !this._longMonthsParse[i]) {
-                this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i');
-                this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i');
+                this._longMonthsParse[i] = new RegExp(
+                    '^' + this.months(mom, '').replace('.', '') + '$',
+                    'i'
+                );
+                this._shortMonthsParse[i] = new RegExp(
+                    '^' + this.monthsShort(mom, '').replace('.', '') + '$',
+                    'i'
+                );
             }
             if (!strict && !this._monthsParse[i]) {
-                regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
+                regex =
+                    '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
                 this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
             }
             // test the regex
-            if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) {
+            if (
+                strict &&
+                format === 'MMMM' &&
+                this._longMonthsParse[i].test(monthName)
+            ) {
                 return i;
-            } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) {
+            } else if (
+                strict &&
+                format === 'MMM' &&
+                this._shortMonthsParse[i].test(monthName)
+            ) {
                 return i;
             } else if (!strict && this._monthsParse[i].test(monthName)) {
                 return i;
 
     // MOMENTS
 
-    function setMonth (mom, value) {
+    function setMonth(mom, value) {
         var dayOfMonth;
 
         if (!mom.isValid()) {
             return mom;
         }
 
-        // TODO: Move this out of here!
         if (typeof value === 'string') {
-            value = mom.localeData().monthsParse(value);
-            // TODO: Another silent failure?
-            if (typeof value !== 'number') {
-                return mom;
+            if (/^\d+$/.test(value)) {
+                value = toInt(value);
+            } else {
+                value = mom.localeData().monthsParse(value);
+                // TODO: Another silent failure?
+                if (!isNumber(value)) {
+                    return mom;
+                }
             }
         }
 
         return mom;
     }
 
-    function getSetMonth (value) {
+    function getSetMonth(value) {
         if (value != null) {
             setMonth(this, value);
-            utils_hooks__hooks.updateOffset(this, true);
+            hooks.updateOffset(this, true);
             return this;
         } else {
-            return get_set__get(this, 'Month');
+            return get(this, 'Month');
         }
     }
 
-    function getDaysInMonth () {
+    function getDaysInMonth() {
         return daysInMonth(this.year(), this.month());
     }
 
-    var defaultMonthsShortRegex = matchWord;
-    function monthsShortRegex (isStrict) {
+    function monthsShortRegex(isStrict) {
         if (this._monthsParseExact) {
             if (!hasOwnProp(this, '_monthsRegex')) {
                 computeMonthsParse.call(this);
                 return this._monthsShortRegex;
             }
         } else {
-            return this._monthsShortStrictRegex && isStrict ?
-                this._monthsShortStrictRegex : this._monthsShortRegex;
+            if (!hasOwnProp(this, '_monthsShortRegex')) {
+                this._monthsShortRegex = defaultMonthsShortRegex;
+            }
+            return this._monthsShortStrictRegex && isStrict
+                ? this._monthsShortStrictRegex
+                : this._monthsShortRegex;
         }
     }
 
-    var defaultMonthsRegex = matchWord;
-    function monthsRegex (isStrict) {
+    function monthsRegex(isStrict) {
         if (this._monthsParseExact) {
             if (!hasOwnProp(this, '_monthsRegex')) {
                 computeMonthsParse.call(this);
                 return this._monthsRegex;
             }
         } else {
-            return this._monthsStrictRegex && isStrict ?
-                this._monthsStrictRegex : this._monthsRegex;
+            if (!hasOwnProp(this, '_monthsRegex')) {
+                this._monthsRegex = defaultMonthsRegex;
+            }
+            return this._monthsStrictRegex && isStrict
+                ? this._monthsStrictRegex
+                : this._monthsRegex;
         }
     }
 
-    function computeMonthsParse () {
+    function computeMonthsParse() {
         function cmpLenRev(a, b) {
             return b.length - a.length;
         }
 
-        var shortPieces = [], longPieces = [], mixedPieces = [],
-            i, mom;
+        var shortPieces = [],
+            longPieces = [],
+            mixedPieces = [],
+            i,
+            mom;
         for (i = 0; i < 12; i++) {
             // make the regex if we don't have it already
-            mom = create_utc__createUTC([2000, i]);
+            mom = createUTC([2000, i]);
             shortPieces.push(this.monthsShort(mom, ''));
             longPieces.push(this.months(mom, ''));
             mixedPieces.push(this.months(mom, ''));
         for (i = 0; i < 12; i++) {
             shortPieces[i] = regexEscape(shortPieces[i]);
             longPieces[i] = regexEscape(longPieces[i]);
+        }
+        for (i = 0; i < 24; i++) {
             mixedPieces[i] = regexEscape(mixedPieces[i]);
         }
 
         this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
         this._monthsShortRegex = this._monthsRegex;
-        this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')$', 'i');
-        this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')$', 'i');
+        this._monthsStrictRegex = new RegExp(
+            '^(' + longPieces.join('|') + ')',
+            'i'
+        );
+        this._monthsShortStrictRegex = new RegExp(
+            '^(' + shortPieces.join('|') + ')',
+            'i'
+        );
     }
 
-    function checkOverflow (m) {
-        var overflow;
-        var a = m._a;
+    // FORMATTING
 
-        if (a && getParsingFlags(m).overflow === -2) {
-            overflow =
-                a[MONTH]       < 0 || a[MONTH]       > 11  ? MONTH :
-                a[DATE]        < 1 || a[DATE]        > daysInMonth(a[YEAR], a[MONTH]) ? DATE :
-                a[HOUR]        < 0 || a[HOUR]        > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR :
-                a[MINUTE]      < 0 || a[MINUTE]      > 59  ? MINUTE :
-                a[SECOND]      < 0 || a[SECOND]      > 59  ? SECOND :
-                a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND :
-                -1;
-
-            if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
-                overflow = DATE;
-            }
-            if (getParsingFlags(m)._overflowWeeks && overflow === -1) {
-                overflow = WEEK;
-            }
-            if (getParsingFlags(m)._overflowWeekday && overflow === -1) {
-                overflow = WEEKDAY;
-            }
+    addFormatToken('Y', 0, 0, function () {
+        var y = this.year();
+        return y <= 9999 ? zeroFill(y, 4) : '+' + y;
+    });
 
-            getParsingFlags(m).overflow = overflow;
-        }
+    addFormatToken(0, ['YY', 2], 0, function () {
+        return this.year() % 100;
+    });
 
-        return m;
-    }
+    addFormatToken(0, ['YYYY', 4], 0, 'year');
+    addFormatToken(0, ['YYYYY', 5], 0, 'year');
+    addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');
 
-    function warn(msg) {
-        if (utils_hooks__hooks.suppressDeprecationWarnings === false &&
-                (typeof console !==  'undefined') && console.warn) {
-            console.warn('Deprecation warning: ' + msg);
-        }
-    }
+    // ALIASES
 
-    function deprecate(msg, fn) {
-        var firstTime = true;
+    addUnitAlias('year', 'y');
 
-        return extend(function () {
-            if (firstTime) {
-                warn(msg + '\nArguments: ' + Array.prototype.slice.call(arguments).join(', ') + '\n' + (new Error()).stack);
-                firstTime = false;
-            }
-            return fn.apply(this, arguments);
-        }, fn);
-    }
+    // PRIORITIES
 
-    var deprecations = {};
+    addUnitPriority('year', 1);
 
-    function deprecateSimple(name, msg) {
-        if (!deprecations[name]) {
-            warn(msg);
-            deprecations[name] = true;
-        }
-    }
+    // PARSING
 
-    utils_hooks__hooks.suppressDeprecationWarnings = false;
+    addRegexToken('Y', matchSigned);
+    addRegexToken('YY', match1to2, match2);
+    addRegexToken('YYYY', match1to4, match4);
+    addRegexToken('YYYYY', match1to6, match6);
+    addRegexToken('YYYYYY', match1to6, match6);
 
-    // iso 8601 regex
-    // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
-    var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/;
-    var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/;
-
-    var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/;
-
-    var isoDates = [
-        ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/],
-        ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/],
-        ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/],
-        ['GGGG-[W]WW', /\d{4}-W\d\d/, false],
-        ['YYYY-DDD', /\d{4}-\d{3}/],
-        ['YYYY-MM', /\d{4}-\d\d/, false],
-        ['YYYYYYMMDD', /[+-]\d{10}/],
-        ['YYYYMMDD', /\d{8}/],
-        // YYYYMM is NOT allowed by the standard
-        ['GGGG[W]WWE', /\d{4}W\d{3}/],
-        ['GGGG[W]WW', /\d{4}W\d{2}/, false],
-        ['YYYYDDD', /\d{7}/]
-    ];
+    addParseToken(['YYYYY', 'YYYYYY'], YEAR);
+    addParseToken('YYYY', function (input, array) {
+        array[YEAR] =
+            input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input);
+    });
+    addParseToken('YY', function (input, array) {
+        array[YEAR] = hooks.parseTwoDigitYear(input);
+    });
+    addParseToken('Y', function (input, array) {
+        array[YEAR] = parseInt(input, 10);
+    });
 
-    // iso time formats and regexes
-    var isoTimes = [
-        ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/],
-        ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/],
-        ['HH:mm:ss', /\d\d:\d\d:\d\d/],
-        ['HH:mm', /\d\d:\d\d/],
-        ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/],
-        ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/],
-        ['HHmmss', /\d\d\d\d\d\d/],
-        ['HHmm', /\d\d\d\d/],
-        ['HH', /\d\d/]
-    ];
+    // HELPERS
 
-    var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i;
+    function daysInYear(year) {
+        return isLeapYear(year) ? 366 : 365;
+    }
 
-    // date from iso format
-    function configFromISO(config) {
-        var i, l,
-            string = config._i,
-            match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),
-            allowTime, dateFormat, timeFormat, tzFormat;
+    // HOOKS
 
-        if (match) {
-            getParsingFlags(config).iso = true;
+    hooks.parseTwoDigitYear = function (input) {
+        return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
+    };
 
-            for (i = 0, l = isoDates.length; i < l; i++) {
-                if (isoDates[i][1].exec(match[1])) {
-                    dateFormat = isoDates[i][0];
-                    allowTime = isoDates[i][2] !== false;
-                    break;
-                }
-            }
-            if (dateFormat == null) {
-                config._isValid = false;
-                return;
-            }
-            if (match[3]) {
-                for (i = 0, l = isoTimes.length; i < l; i++) {
-                    if (isoTimes[i][1].exec(match[3])) {
-                        // match[2] should be 'T' or space
-                        timeFormat = (match[2] || ' ') + isoTimes[i][0];
-                        break;
-                    }
-                }
-                if (timeFormat == null) {
-                    config._isValid = false;
-                    return;
-                }
-            }
-            if (!allowTime && timeFormat != null) {
-                config._isValid = false;
-                return;
-            }
-            if (match[4]) {
-                if (tzRegex.exec(match[4])) {
-                    tzFormat = 'Z';
-                } else {
-                    config._isValid = false;
-                    return;
-                }
+    // MOMENTS
+
+    var getSetYear = makeGetSet('FullYear', true);
+
+    function getIsLeapYear() {
+        return isLeapYear(this.year());
+    }
+
+    function createDate(y, m, d, h, M, s, ms) {
+        // can't just apply() to create a date:
+        // https://stackoverflow.com/q/181348
+        var date;
+        // the date constructor remaps years 0-99 to 1900-1999
+        if (y < 100 && y >= 0) {
+            // preserve leap years using a full 400 year cycle, then reset
+            date = new Date(y + 400, m, d, h, M, s, ms);
+            if (isFinite(date.getFullYear())) {
+                date.setFullYear(y);
             }
-            config._f = dateFormat + (timeFormat || '') + (tzFormat || '');
-            configFromStringAndFormat(config);
         } else {
-            config._isValid = false;
+            date = new Date(y, m, d, h, M, s, ms);
         }
-    }
 
-    // date from iso format or fallback
-    function configFromString(config) {
-        var matched = aspNetJsonRegex.exec(config._i);
+        return date;
+    }
 
-        if (matched !== null) {
-            config._d = new Date(+matched[1]);
-            return;
+    function createUTCDate(y) {
+        var date, args;
+        // the Date.UTC function remaps years 0-99 to 1900-1999
+        if (y < 100 && y >= 0) {
+            args = Array.prototype.slice.call(arguments);
+            // preserve leap years using a full 400 year cycle, then reset
+            args[0] = y + 400;
+            date = new Date(Date.UTC.apply(null, args));
+            if (isFinite(date.getUTCFullYear())) {
+                date.setUTCFullYear(y);
+            }
+        } else {
+            date = new Date(Date.UTC.apply(null, arguments));
         }
 
-        configFromISO(config);
-        if (config._isValid === false) {
-            delete config._isValid;
-            utils_hooks__hooks.createFromInputFallback(config);
-        }
+        return date;
     }
 
-    utils_hooks__hooks.createFromInputFallback = deprecate(
-        'moment construction falls back to js Date. This is ' +
-        'discouraged and will be removed in upcoming major ' +
-        'release. Please refer to ' +
-        'https://github.com/moment/moment/issues/1407 for more info.',
-        function (config) {
-            config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
+    // start-of-first-week - start-of-year
+    function firstWeekOffset(year, dow, doy) {
+        var // first-week day -- which january is always in the first week (4 for iso, 1 for other)
+            fwd = 7 + dow - doy,
+            // first-week day local weekday -- which local weekday is fwd
+            fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;
+
+        return -fwdlw + fwd - 1;
+    }
+
+    // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
+    function dayOfYearFromWeeks(year, week, weekday, dow, doy) {
+        var localWeekday = (7 + weekday - dow) % 7,
+            weekOffset = firstWeekOffset(year, dow, doy),
+            dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,
+            resYear,
+            resDayOfYear;
+
+        if (dayOfYear <= 0) {
+            resYear = year - 1;
+            resDayOfYear = daysInYear(resYear) + dayOfYear;
+        } else if (dayOfYear > daysInYear(year)) {
+            resYear = year + 1;
+            resDayOfYear = dayOfYear - daysInYear(year);
+        } else {
+            resYear = year;
+            resDayOfYear = dayOfYear;
         }
-    );
 
-    function createDate (y, m, d, h, M, s, ms) {
-        //can't just apply() to create a date:
-        //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply
-        var date = new Date(y, m, d, h, M, s, ms);
+        return {
+            year: resYear,
+            dayOfYear: resDayOfYear,
+        };
+    }
+
+    function weekOfYear(mom, dow, doy) {
+        var weekOffset = firstWeekOffset(mom.year(), dow, doy),
+            week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
+            resWeek,
+            resYear;
 
-        //the date constructor remaps years 0-99 to 1900-1999
-        if (y < 100 && y >= 0 && isFinite(date.getFullYear())) {
-            date.setFullYear(y);
+        if (week < 1) {
+            resYear = mom.year() - 1;
+            resWeek = week + weeksInYear(resYear, dow, doy);
+        } else if (week > weeksInYear(mom.year(), dow, doy)) {
+            resWeek = week - weeksInYear(mom.year(), dow, doy);
+            resYear = mom.year() + 1;
+        } else {
+            resYear = mom.year();
+            resWeek = week;
         }
-        return date;
+
+        return {
+            week: resWeek,
+            year: resYear,
+        };
+    }
+
+    function weeksInYear(year, dow, doy) {
+        var weekOffset = firstWeekOffset(year, dow, doy),
+            weekOffsetNext = firstWeekOffset(year + 1, dow, doy);
+        return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;
     }
 
-    function createUTCDate (y) {
-        var date = new Date(Date.UTC.apply(null, arguments));
+    // FORMATTING
+
+    addFormatToken('w', ['ww', 2], 'wo', 'week');
+    addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');
+
+    // ALIASES
+
+    addUnitAlias('week', 'w');
+    addUnitAlias('isoWeek', 'W');
+
+    // PRIORITIES
+
+    addUnitPriority('week', 5);
+    addUnitPriority('isoWeek', 5);
+
+    // PARSING
+
+    addRegexToken('w', match1to2);
+    addRegexToken('ww', match1to2, match2);
+    addRegexToken('W', match1to2);
+    addRegexToken('WW', match1to2, match2);
 
-        //the Date.UTC function remaps years 0-99 to 1900-1999
-        if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) {
-            date.setUTCFullYear(y);
+    addWeekParseToken(
+        ['w', 'ww', 'W', 'WW'],
+        function (input, week, config, token) {
+            week[token.substr(0, 1)] = toInt(input);
         }
-        return date;
+    );
+
+    // HELPERS
+
+    // LOCALES
+
+    function localeWeek(mom) {
+        return weekOfYear(mom, this._week.dow, this._week.doy).week;
+    }
+
+    var defaultLocaleWeek = {
+        dow: 0, // Sunday is the first day of the week.
+        doy: 6, // The week that contains Jan 6th is the first week of the year.
+    };
+
+    function localeFirstDayOfWeek() {
+        return this._week.dow;
+    }
+
+    function localeFirstDayOfYear() {
+        return this._week.doy;
+    }
+
+    // MOMENTS
+
+    function getSetWeek(input) {
+        var week = this.localeData().week(this);
+        return input == null ? week : this.add((input - week) * 7, 'd');
+    }
+
+    function getSetISOWeek(input) {
+        var week = weekOfYear(this, 1, 4).week;
+        return input == null ? week : this.add((input - week) * 7, 'd');
     }
 
     // FORMATTING
 
-    addFormatToken('Y', 0, 0, function () {
-        var y = this.year();
-        return y <= 9999 ? '' + y : '+' + y;
+    addFormatToken('d', 0, 'do', 'day');
+
+    addFormatToken('dd', 0, 0, function (format) {
+        return this.localeData().weekdaysMin(this, format);
     });
 
-    addFormatToken(0, ['YY', 2], 0, function () {
-        return this.year() % 100;
+    addFormatToken('ddd', 0, 0, function (format) {
+        return this.localeData().weekdaysShort(this, format);
     });
 
-    addFormatToken(0, ['YYYY',   4],       0, 'year');
-    addFormatToken(0, ['YYYYY',  5],       0, 'year');
-    addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');
+    addFormatToken('dddd', 0, 0, function (format) {
+        return this.localeData().weekdays(this, format);
+    });
+
+    addFormatToken('e', 0, 0, 'weekday');
+    addFormatToken('E', 0, 0, 'isoWeekday');
 
     // ALIASES
 
-    addUnitAlias('year', 'y');
+    addUnitAlias('day', 'd');
+    addUnitAlias('weekday', 'e');
+    addUnitAlias('isoWeekday', 'E');
 
-    // PARSING
+    // PRIORITY
+    addUnitPriority('day', 11);
+    addUnitPriority('weekday', 11);
+    addUnitPriority('isoWeekday', 11);
 
-    addRegexToken('Y',      matchSigned);
-    addRegexToken('YY',     match1to2, match2);
-    addRegexToken('YYYY',   match1to4, match4);
-    addRegexToken('YYYYY',  match1to6, match6);
-    addRegexToken('YYYYYY', match1to6, match6);
+    // PARSING
 
-    addParseToken(['YYYYY', 'YYYYYY'], YEAR);
-    addParseToken('YYYY', function (input, array) {
-        array[YEAR] = input.length === 2 ? utils_hooks__hooks.parseTwoDigitYear(input) : toInt(input);
+    addRegexToken('d', match1to2);
+    addRegexToken('e', match1to2);
+    addRegexToken('E', match1to2);
+    addRegexToken('dd', function (isStrict, locale) {
+        return locale.weekdaysMinRegex(isStrict);
     });
-    addParseToken('YY', function (input, array) {
-        array[YEAR] = utils_hooks__hooks.parseTwoDigitYear(input);
+    addRegexToken('ddd', function (isStrict, locale) {
+        return locale.weekdaysShortRegex(isStrict);
     });
-    addParseToken('Y', function (input, array) {
-        array[YEAR] = parseInt(input, 10);
+    addRegexToken('dddd', function (isStrict, locale) {
+        return locale.weekdaysRegex(isStrict);
+    });
+
+    addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {
+        var weekday = config._locale.weekdaysParse(input, token, config._strict);
+        // if we didn't get a weekday name, mark the date as invalid
+        if (weekday != null) {
+            week.d = weekday;
+        } else {
+            getParsingFlags(config).invalidWeekday = input;
+        }
+    });
+
+    addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
+        week[token] = toInt(input);
     });
 
     // HELPERS
 
-    function daysInYear(year) {
-        return isLeapYear(year) ? 366 : 365;
+    function parseWeekday(input, locale) {
+        if (typeof input !== 'string') {
+            return input;
+        }
+
+        if (!isNaN(input)) {
+            return parseInt(input, 10);
+        }
+
+        input = locale.weekdaysParse(input);
+        if (typeof input === 'number') {
+            return input;
+        }
+
+        return null;
+    }
+
+    function parseIsoWeekday(input, locale) {
+        if (typeof input === 'string') {
+            return locale.weekdaysParse(input) % 7 || 7;
+        }
+        return isNaN(input) ? null : input;
+    }
+
+    // LOCALES
+    function shiftWeekdays(ws, n) {
+        return ws.slice(n, 7).concat(ws.slice(0, n));
+    }
+
+    var defaultLocaleWeekdays =
+            'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),
+        defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
+        defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
+        defaultWeekdaysRegex = matchWord,
+        defaultWeekdaysShortRegex = matchWord,
+        defaultWeekdaysMinRegex = matchWord;
+
+    function localeWeekdays(m, format) {
+        var weekdays = isArray(this._weekdays)
+            ? this._weekdays
+            : this._weekdays[
+                  m && m !== true && this._weekdays.isFormat.test(format)
+                      ? 'format'
+                      : 'standalone'
+              ];
+        return m === true
+            ? shiftWeekdays(weekdays, this._week.dow)
+            : m
+            ? weekdays[m.day()]
+            : weekdays;
+    }
+
+    function localeWeekdaysShort(m) {
+        return m === true
+            ? shiftWeekdays(this._weekdaysShort, this._week.dow)
+            : m
+            ? this._weekdaysShort[m.day()]
+            : this._weekdaysShort;
+    }
+
+    function localeWeekdaysMin(m) {
+        return m === true
+            ? shiftWeekdays(this._weekdaysMin, this._week.dow)
+            : m
+            ? this._weekdaysMin[m.day()]
+            : this._weekdaysMin;
+    }
+
+    function handleStrictParse$1(weekdayName, format, strict) {
+        var i,
+            ii,
+            mom,
+            llc = weekdayName.toLocaleLowerCase();
+        if (!this._weekdaysParse) {
+            this._weekdaysParse = [];
+            this._shortWeekdaysParse = [];
+            this._minWeekdaysParse = [];
+
+            for (i = 0; i < 7; ++i) {
+                mom = createUTC([2000, 1]).day(i);
+                this._minWeekdaysParse[i] = this.weekdaysMin(
+                    mom,
+                    ''
+                ).toLocaleLowerCase();
+                this._shortWeekdaysParse[i] = this.weekdaysShort(
+                    mom,
+                    ''
+                ).toLocaleLowerCase();
+                this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();
+            }
+        }
+
+        if (strict) {
+            if (format === 'dddd') {
+                ii = indexOf.call(this._weekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else if (format === 'ddd') {
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            }
+        } else {
+            if (format === 'dddd') {
+                ii = indexOf.call(this._weekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else if (format === 'ddd') {
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._weekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._weekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            }
+        }
+    }
+
+    function localeWeekdaysParse(weekdayName, format, strict) {
+        var i, mom, regex;
+
+        if (this._weekdaysParseExact) {
+            return handleStrictParse$1.call(this, weekdayName, format, strict);
+        }
+
+        if (!this._weekdaysParse) {
+            this._weekdaysParse = [];
+            this._minWeekdaysParse = [];
+            this._shortWeekdaysParse = [];
+            this._fullWeekdaysParse = [];
+        }
+
+        for (i = 0; i < 7; i++) {
+            // make the regex if we don't have it already
+
+            mom = createUTC([2000, 1]).day(i);
+            if (strict && !this._fullWeekdaysParse[i]) {
+                this._fullWeekdaysParse[i] = new RegExp(
+                    '^' + this.weekdays(mom, '').replace('.', '\\.?') + '$',
+                    'i'
+                );
+                this._shortWeekdaysParse[i] = new RegExp(
+                    '^' + this.weekdaysShort(mom, '').replace('.', '\\.?') + '$',
+                    'i'
+                );
+                this._minWeekdaysParse[i] = new RegExp(
+                    '^' + this.weekdaysMin(mom, '').replace('.', '\\.?') + '$',
+                    'i'
+                );
+            }
+            if (!this._weekdaysParse[i]) {
+                regex =
+                    '^' +
+                    this.weekdays(mom, '') +
+                    '|^' +
+                    this.weekdaysShort(mom, '') +
+                    '|^' +
+                    this.weekdaysMin(mom, '');
+                this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
+            }
+            // test the regex
+            if (
+                strict &&
+                format === 'dddd' &&
+                this._fullWeekdaysParse[i].test(weekdayName)
+            ) {
+                return i;
+            } else if (
+                strict &&
+                format === 'ddd' &&
+                this._shortWeekdaysParse[i].test(weekdayName)
+            ) {
+                return i;
+            } else if (
+                strict &&
+                format === 'dd' &&
+                this._minWeekdaysParse[i].test(weekdayName)
+            ) {
+                return i;
+            } else if (!strict && this._weekdaysParse[i].test(weekdayName)) {
+                return i;
+            }
+        }
+    }
+
+    // MOMENTS
+
+    function getSetDayOfWeek(input) {
+        if (!this.isValid()) {
+            return input != null ? this : NaN;
+        }
+        var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
+        if (input != null) {
+            input = parseWeekday(input, this.localeData());
+            return this.add(input - day, 'd');
+        } else {
+            return day;
+        }
+    }
+
+    function getSetLocaleDayOfWeek(input) {
+        if (!this.isValid()) {
+            return input != null ? this : NaN;
+        }
+        var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
+        return input == null ? weekday : this.add(input - weekday, 'd');
+    }
+
+    function getSetISODayOfWeek(input) {
+        if (!this.isValid()) {
+            return input != null ? this : NaN;
+        }
+
+        // behaves the same as moment#day except
+        // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
+        // as a setter, sunday should belong to the previous week.
+
+        if (input != null) {
+            var weekday = parseIsoWeekday(input, this.localeData());
+            return this.day(this.day() % 7 ? weekday : weekday - 7);
+        } else {
+            return this.day() || 7;
+        }
+    }
+
+    function weekdaysRegex(isStrict) {
+        if (this._weekdaysParseExact) {
+            if (!hasOwnProp(this, '_weekdaysRegex')) {
+                computeWeekdaysParse.call(this);
+            }
+            if (isStrict) {
+                return this._weekdaysStrictRegex;
+            } else {
+                return this._weekdaysRegex;
+            }
+        } else {
+            if (!hasOwnProp(this, '_weekdaysRegex')) {
+                this._weekdaysRegex = defaultWeekdaysRegex;
+            }
+            return this._weekdaysStrictRegex && isStrict
+                ? this._weekdaysStrictRegex
+                : this._weekdaysRegex;
+        }
+    }
+
+    function weekdaysShortRegex(isStrict) {
+        if (this._weekdaysParseExact) {
+            if (!hasOwnProp(this, '_weekdaysRegex')) {
+                computeWeekdaysParse.call(this);
+            }
+            if (isStrict) {
+                return this._weekdaysShortStrictRegex;
+            } else {
+                return this._weekdaysShortRegex;
+            }
+        } else {
+            if (!hasOwnProp(this, '_weekdaysShortRegex')) {
+                this._weekdaysShortRegex = defaultWeekdaysShortRegex;
+            }
+            return this._weekdaysShortStrictRegex && isStrict
+                ? this._weekdaysShortStrictRegex
+                : this._weekdaysShortRegex;
+        }
+    }
+
+    function weekdaysMinRegex(isStrict) {
+        if (this._weekdaysParseExact) {
+            if (!hasOwnProp(this, '_weekdaysRegex')) {
+                computeWeekdaysParse.call(this);
+            }
+            if (isStrict) {
+                return this._weekdaysMinStrictRegex;
+            } else {
+                return this._weekdaysMinRegex;
+            }
+        } else {
+            if (!hasOwnProp(this, '_weekdaysMinRegex')) {
+                this._weekdaysMinRegex = defaultWeekdaysMinRegex;
+            }
+            return this._weekdaysMinStrictRegex && isStrict
+                ? this._weekdaysMinStrictRegex
+                : this._weekdaysMinRegex;
+        }
+    }
+
+    function computeWeekdaysParse() {
+        function cmpLenRev(a, b) {
+            return b.length - a.length;
+        }
+
+        var minPieces = [],
+            shortPieces = [],
+            longPieces = [],
+            mixedPieces = [],
+            i,
+            mom,
+            minp,
+            shortp,
+            longp;
+        for (i = 0; i < 7; i++) {
+            // make the regex if we don't have it already
+            mom = createUTC([2000, 1]).day(i);
+            minp = regexEscape(this.weekdaysMin(mom, ''));
+            shortp = regexEscape(this.weekdaysShort(mom, ''));
+            longp = regexEscape(this.weekdays(mom, ''));
+            minPieces.push(minp);
+            shortPieces.push(shortp);
+            longPieces.push(longp);
+            mixedPieces.push(minp);
+            mixedPieces.push(shortp);
+            mixedPieces.push(longp);
+        }
+        // Sorting makes sure if one weekday (or abbr) is a prefix of another it
+        // will match the longer piece.
+        minPieces.sort(cmpLenRev);
+        shortPieces.sort(cmpLenRev);
+        longPieces.sort(cmpLenRev);
+        mixedPieces.sort(cmpLenRev);
+
+        this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+        this._weekdaysShortRegex = this._weekdaysRegex;
+        this._weekdaysMinRegex = this._weekdaysRegex;
+
+        this._weekdaysStrictRegex = new RegExp(
+            '^(' + longPieces.join('|') + ')',
+            'i'
+        );
+        this._weekdaysShortStrictRegex = new RegExp(
+            '^(' + shortPieces.join('|') + ')',
+            'i'
+        );
+        this._weekdaysMinStrictRegex = new RegExp(
+            '^(' + minPieces.join('|') + ')',
+            'i'
+        );
+    }
+
+    // FORMATTING
+
+    function hFormat() {
+        return this.hours() % 12 || 12;
+    }
+
+    function kFormat() {
+        return this.hours() || 24;
+    }
+
+    addFormatToken('H', ['HH', 2], 0, 'hour');
+    addFormatToken('h', ['hh', 2], 0, hFormat);
+    addFormatToken('k', ['kk', 2], 0, kFormat);
+
+    addFormatToken('hmm', 0, 0, function () {
+        return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);
+    });
+
+    addFormatToken('hmmss', 0, 0, function () {
+        return (
+            '' +
+            hFormat.apply(this) +
+            zeroFill(this.minutes(), 2) +
+            zeroFill(this.seconds(), 2)
+        );
+    });
+
+    addFormatToken('Hmm', 0, 0, function () {
+        return '' + this.hours() + zeroFill(this.minutes(), 2);
+    });
+
+    addFormatToken('Hmmss', 0, 0, function () {
+        return (
+            '' +
+            this.hours() +
+            zeroFill(this.minutes(), 2) +
+            zeroFill(this.seconds(), 2)
+        );
+    });
+
+    function meridiem(token, lowercase) {
+        addFormatToken(token, 0, 0, function () {
+            return this.localeData().meridiem(
+                this.hours(),
+                this.minutes(),
+                lowercase
+            );
+        });
+    }
+
+    meridiem('a', true);
+    meridiem('A', false);
+
+    // ALIASES
+
+    addUnitAlias('hour', 'h');
+
+    // PRIORITY
+    addUnitPriority('hour', 13);
+
+    // PARSING
+
+    function matchMeridiem(isStrict, locale) {
+        return locale._meridiemParse;
+    }
+
+    addRegexToken('a', matchMeridiem);
+    addRegexToken('A', matchMeridiem);
+    addRegexToken('H', match1to2);
+    addRegexToken('h', match1to2);
+    addRegexToken('k', match1to2);
+    addRegexToken('HH', match1to2, match2);
+    addRegexToken('hh', match1to2, match2);
+    addRegexToken('kk', match1to2, match2);
+
+    addRegexToken('hmm', match3to4);
+    addRegexToken('hmmss', match5to6);
+    addRegexToken('Hmm', match3to4);
+    addRegexToken('Hmmss', match5to6);
+
+    addParseToken(['H', 'HH'], HOUR);
+    addParseToken(['k', 'kk'], function (input, array, config) {
+        var kInput = toInt(input);
+        array[HOUR] = kInput === 24 ? 0 : kInput;
+    });
+    addParseToken(['a', 'A'], function (input, array, config) {
+        config._isPm = config._locale.isPM(input);
+        config._meridiem = input;
+    });
+    addParseToken(['h', 'hh'], function (input, array, config) {
+        array[HOUR] = toInt(input);
+        getParsingFlags(config).bigHour = true;
+    });
+    addParseToken('hmm', function (input, array, config) {
+        var pos = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos));
+        array[MINUTE] = toInt(input.substr(pos));
+        getParsingFlags(config).bigHour = true;
+    });
+    addParseToken('hmmss', function (input, array, config) {
+        var pos1 = input.length - 4,
+            pos2 = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos1));
+        array[MINUTE] = toInt(input.substr(pos1, 2));
+        array[SECOND] = toInt(input.substr(pos2));
+        getParsingFlags(config).bigHour = true;
+    });
+    addParseToken('Hmm', function (input, array, config) {
+        var pos = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos));
+        array[MINUTE] = toInt(input.substr(pos));
+    });
+    addParseToken('Hmmss', function (input, array, config) {
+        var pos1 = input.length - 4,
+            pos2 = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos1));
+        array[MINUTE] = toInt(input.substr(pos1, 2));
+        array[SECOND] = toInt(input.substr(pos2));
+    });
+
+    // LOCALES
+
+    function localeIsPM(input) {
+        // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
+        // Using charAt should be more compatible.
+        return (input + '').toLowerCase().charAt(0) === 'p';
+    }
+
+    var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i,
+        // Setting the hour should keep the time, because the user explicitly
+        // specified which hour they want. So trying to maintain the same hour (in
+        // a new timezone) makes sense. Adding/subtracting hours does not follow
+        // this rule.
+        getSetHour = makeGetSet('Hours', true);
+
+    function localeMeridiem(hours, minutes, isLower) {
+        if (hours > 11) {
+            return isLower ? 'pm' : 'PM';
+        } else {
+            return isLower ? 'am' : 'AM';
+        }
+    }
+
+    var baseConfig = {
+        calendar: defaultCalendar,
+        longDateFormat: defaultLongDateFormat,
+        invalidDate: defaultInvalidDate,
+        ordinal: defaultOrdinal,
+        dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse,
+        relativeTime: defaultRelativeTime,
+
+        months: defaultLocaleMonths,
+        monthsShort: defaultLocaleMonthsShort,
+
+        week: defaultLocaleWeek,
+
+        weekdays: defaultLocaleWeekdays,
+        weekdaysMin: defaultLocaleWeekdaysMin,
+        weekdaysShort: defaultLocaleWeekdaysShort,
+
+        meridiemParse: defaultLocaleMeridiemParse,
+    };
+
+    // internal storage for locale config files
+    var locales = {},
+        localeFamilies = {},
+        globalLocale;
+
+    function commonPrefix(arr1, arr2) {
+        var i,
+            minl = Math.min(arr1.length, arr2.length);
+        for (i = 0; i < minl; i += 1) {
+            if (arr1[i] !== arr2[i]) {
+                return i;
+            }
+        }
+        return minl;
+    }
+
+    function normalizeLocale(key) {
+        return key ? key.toLowerCase().replace('_', '-') : key;
+    }
+
+    // pick the locale from the array
+    // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
+    // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
+    function chooseLocale(names) {
+        var i = 0,
+            j,
+            next,
+            locale,
+            split;
+
+        while (i < names.length) {
+            split = normalizeLocale(names[i]).split('-');
+            j = split.length;
+            next = normalizeLocale(names[i + 1]);
+            next = next ? next.split('-') : null;
+            while (j > 0) {
+                locale = loadLocale(split.slice(0, j).join('-'));
+                if (locale) {
+                    return locale;
+                }
+                if (
+                    next &&
+                    next.length >= j &&
+                    commonPrefix(split, next) >= j - 1
+                ) {
+                    //the next array item is better than a shallower substring of this one
+                    break;
+                }
+                j--;
+            }
+            i++;
+        }
+        return globalLocale;
+    }
+
+    function isLocaleNameSane(name) {
+        // Prevent names that look like filesystem paths, i.e contain '/' or '\'
+        return name.match('^[^/\\\\]*$') != null;
+    }
+
+    function loadLocale(name) {
+        var oldLocale = null,
+            aliasedRequire;
+        // TODO: Find a better way to register and load all the locales in Node
+        if (
+            locales[name] === undefined &&
+            typeof module !== 'undefined' &&
+            module &&
+            module.exports &&
+            isLocaleNameSane(name)
+        ) {
+            try {
+                oldLocale = globalLocale._abbr;
+                aliasedRequire = require;
+                aliasedRequire('./locale/' + name);
+                getSetGlobalLocale(oldLocale);
+            } catch (e) {
+                // mark as not found to avoid repeating expensive file require call causing high CPU
+                // when trying to find en-US, en_US, en-us for every format call
+                locales[name] = null; // null means not found
+            }
+        }
+        return locales[name];
+    }
+
+    // This function will load locale and then set the global locale.  If
+    // no arguments are passed in, it will simply return the current global
+    // locale key.
+    function getSetGlobalLocale(key, values) {
+        var data;
+        if (key) {
+            if (isUndefined(values)) {
+                data = getLocale(key);
+            } else {
+                data = defineLocale(key, values);
+            }
+
+            if (data) {
+                // moment.duration._locale = moment._locale = data;
+                globalLocale = data;
+            } else {
+                if (typeof console !== 'undefined' && console.warn) {
+                    //warn user if arguments are passed but the locale could not be set
+                    console.warn(
+                        'Locale ' + key + ' not found. Did you forget to load it?'
+                    );
+                }
+            }
+        }
+
+        return globalLocale._abbr;
+    }
+
+    function defineLocale(name, config) {
+        if (config !== null) {
+            var locale,
+                parentConfig = baseConfig;
+            config.abbr = name;
+            if (locales[name] != null) {
+                deprecateSimple(
+                    'defineLocaleOverride',
+                    'use moment.updateLocale(localeName, config) to change ' +
+                        'an existing locale. moment.defineLocale(localeName, ' +
+                        'config) should only be used for creating a new locale ' +
+                        'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.'
+                );
+                parentConfig = locales[name]._config;
+            } else if (config.parentLocale != null) {
+                if (locales[config.parentLocale] != null) {
+                    parentConfig = locales[config.parentLocale]._config;
+                } else {
+                    locale = loadLocale(config.parentLocale);
+                    if (locale != null) {
+                        parentConfig = locale._config;
+                    } else {
+                        if (!localeFamilies[config.parentLocale]) {
+                            localeFamilies[config.parentLocale] = [];
+                        }
+                        localeFamilies[config.parentLocale].push({
+                            name: name,
+                            config: config,
+                        });
+                        return null;
+                    }
+                }
+            }
+            locales[name] = new Locale(mergeConfigs(parentConfig, config));
+
+            if (localeFamilies[name]) {
+                localeFamilies[name].forEach(function (x) {
+                    defineLocale(x.name, x.config);
+                });
+            }
+
+            // backwards compat for now: also set the locale
+            // make sure we set the locale AFTER all child locales have been
+            // created, so we won't end up with the child locale set.
+            getSetGlobalLocale(name);
+
+            return locales[name];
+        } else {
+            // useful for testing
+            delete locales[name];
+            return null;
+        }
+    }
+
+    function updateLocale(name, config) {
+        if (config != null) {
+            var locale,
+                tmpLocale,
+                parentConfig = baseConfig;
+
+            if (locales[name] != null && locales[name].parentLocale != null) {
+                // Update existing child locale in-place to avoid memory-leaks
+                locales[name].set(mergeConfigs(locales[name]._config, config));
+            } else {
+                // MERGE
+                tmpLocale = loadLocale(name);
+                if (tmpLocale != null) {
+                    parentConfig = tmpLocale._config;
+                }
+                config = mergeConfigs(parentConfig, config);
+                if (tmpLocale == null) {
+                    // updateLocale is called for creating a new locale
+                    // Set abbr so it will have a name (getters return
+                    // undefined otherwise).
+                    config.abbr = name;
+                }
+                locale = new Locale(config);
+                locale.parentLocale = locales[name];
+                locales[name] = locale;
+            }
+
+            // backwards compat for now: also set the locale
+            getSetGlobalLocale(name);
+        } else {
+            // pass null for config to unupdate, useful for tests
+            if (locales[name] != null) {
+                if (locales[name].parentLocale != null) {
+                    locales[name] = locales[name].parentLocale;
+                    if (name === getSetGlobalLocale()) {
+                        getSetGlobalLocale(name);
+                    }
+                } else if (locales[name] != null) {
+                    delete locales[name];
+                }
+            }
+        }
+        return locales[name];
+    }
+
+    // returns locale data
+    function getLocale(key) {
+        var locale;
+
+        if (key && key._locale && key._locale._abbr) {
+            key = key._locale._abbr;
+        }
+
+        if (!key) {
+            return globalLocale;
+        }
+
+        if (!isArray(key)) {
+            //short-circuit everything else
+            locale = loadLocale(key);
+            if (locale) {
+                return locale;
+            }
+            key = [key];
+        }
+
+        return chooseLocale(key);
+    }
+
+    function listLocales() {
+        return keys(locales);
+    }
+
+    function checkOverflow(m) {
+        var overflow,
+            a = m._a;
+
+        if (a && getParsingFlags(m).overflow === -2) {
+            overflow =
+                a[MONTH] < 0 || a[MONTH] > 11
+                    ? MONTH
+                    : a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH])
+                    ? DATE
+                    : a[HOUR] < 0 ||
+                      a[HOUR] > 24 ||
+                      (a[HOUR] === 24 &&
+                          (a[MINUTE] !== 0 ||
+                              a[SECOND] !== 0 ||
+                              a[MILLISECOND] !== 0))
+                    ? HOUR
+                    : a[MINUTE] < 0 || a[MINUTE] > 59
+                    ? MINUTE
+                    : a[SECOND] < 0 || a[SECOND] > 59
+                    ? SECOND
+                    : a[MILLISECOND] < 0 || a[MILLISECOND] > 999
+                    ? MILLISECOND
+                    : -1;
+
+            if (
+                getParsingFlags(m)._overflowDayOfYear &&
+                (overflow < YEAR || overflow > DATE)
+            ) {
+                overflow = DATE;
+            }
+            if (getParsingFlags(m)._overflowWeeks && overflow === -1) {
+                overflow = WEEK;
+            }
+            if (getParsingFlags(m)._overflowWeekday && overflow === -1) {
+                overflow = WEEKDAY;
+            }
+
+            getParsingFlags(m).overflow = overflow;
+        }
+
+        return m;
+    }
+
+    // iso 8601 regex
+    // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
+    var extendedIsoRegex =
+            /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,
+        basicIsoRegex =
+            /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,
+        tzRegex = /Z|[+-]\d\d(?::?\d\d)?/,
+        isoDates = [
+            ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/],
+            ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/],
+            ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/],
+            ['GGGG-[W]WW', /\d{4}-W\d\d/, false],
+            ['YYYY-DDD', /\d{4}-\d{3}/],
+            ['YYYY-MM', /\d{4}-\d\d/, false],
+            ['YYYYYYMMDD', /[+-]\d{10}/],
+            ['YYYYMMDD', /\d{8}/],
+            ['GGGG[W]WWE', /\d{4}W\d{3}/],
+            ['GGGG[W]WW', /\d{4}W\d{2}/, false],
+            ['YYYYDDD', /\d{7}/],
+            ['YYYYMM', /\d{6}/, false],
+            ['YYYY', /\d{4}/, false],
+        ],
+        // iso time formats and regexes
+        isoTimes = [
+            ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/],
+            ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/],
+            ['HH:mm:ss', /\d\d:\d\d:\d\d/],
+            ['HH:mm', /\d\d:\d\d/],
+            ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/],
+            ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/],
+            ['HHmmss', /\d\d\d\d\d\d/],
+            ['HHmm', /\d\d\d\d/],
+            ['HH', /\d\d/],
+        ],
+        aspNetJsonRegex = /^\/?Date\((-?\d+)/i,
+        // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3
+        rfc2822 =
+            /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/,
+        obsOffsets = {
+            UT: 0,
+            GMT: 0,
+            EDT: -4 * 60,
+            EST: -5 * 60,
+            CDT: -5 * 60,
+            CST: -6 * 60,
+            MDT: -6 * 60,
+            MST: -7 * 60,
+            PDT: -7 * 60,
+            PST: -8 * 60,
+        };
+
+    // date from iso format
+    function configFromISO(config) {
+        var i,
+            l,
+            string = config._i,
+            match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),
+            allowTime,
+            dateFormat,
+            timeFormat,
+            tzFormat,
+            isoDatesLen = isoDates.length,
+            isoTimesLen = isoTimes.length;
+
+        if (match) {
+            getParsingFlags(config).iso = true;
+            for (i = 0, l = isoDatesLen; i < l; i++) {
+                if (isoDates[i][1].exec(match[1])) {
+                    dateFormat = isoDates[i][0];
+                    allowTime = isoDates[i][2] !== false;
+                    break;
+                }
+            }
+            if (dateFormat == null) {
+                config._isValid = false;
+                return;
+            }
+            if (match[3]) {
+                for (i = 0, l = isoTimesLen; i < l; i++) {
+                    if (isoTimes[i][1].exec(match[3])) {
+                        // match[2] should be 'T' or space
+                        timeFormat = (match[2] || ' ') + isoTimes[i][0];
+                        break;
+                    }
+                }
+                if (timeFormat == null) {
+                    config._isValid = false;
+                    return;
+                }
+            }
+            if (!allowTime && timeFormat != null) {
+                config._isValid = false;
+                return;
+            }
+            if (match[4]) {
+                if (tzRegex.exec(match[4])) {
+                    tzFormat = 'Z';
+                } else {
+                    config._isValid = false;
+                    return;
+                }
+            }
+            config._f = dateFormat + (timeFormat || '') + (tzFormat || '');
+            configFromStringAndFormat(config);
+        } else {
+            config._isValid = false;
+        }
     }
 
-    function isLeapYear(year) {
-        return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+    function extractFromRFC2822Strings(
+        yearStr,
+        monthStr,
+        dayStr,
+        hourStr,
+        minuteStr,
+        secondStr
+    ) {
+        var result = [
+            untruncateYear(yearStr),
+            defaultLocaleMonthsShort.indexOf(monthStr),
+            parseInt(dayStr, 10),
+            parseInt(hourStr, 10),
+            parseInt(minuteStr, 10),
+        ];
+
+        if (secondStr) {
+            result.push(parseInt(secondStr, 10));
+        }
+
+        return result;
+    }
+
+    function untruncateYear(yearStr) {
+        var year = parseInt(yearStr, 10);
+        if (year <= 49) {
+            return 2000 + year;
+        } else if (year <= 999) {
+            return 1900 + year;
+        }
+        return year;
+    }
+
+    function preprocessRFC2822(s) {
+        // Remove comments and folding whitespace and replace multiple-spaces with a single space
+        return s
+            .replace(/\([^)]*\)|[\n\t]/g, ' ')
+            .replace(/(\s\s+)/g, ' ')
+            .replace(/^\s\s*/, '')
+            .replace(/\s\s*$/, '');
+    }
+
+    function checkWeekday(weekdayStr, parsedInput, config) {
+        if (weekdayStr) {
+            // TODO: Replace the vanilla JS Date object with an independent day-of-week check.
+            var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr),
+                weekdayActual = new Date(
+                    parsedInput[0],
+                    parsedInput[1],
+                    parsedInput[2]
+                ).getDay();
+            if (weekdayProvided !== weekdayActual) {
+                getParsingFlags(config).weekdayMismatch = true;
+                config._isValid = false;
+                return false;
+            }
+        }
+        return true;
     }
 
-    // HOOKS
-
-    utils_hooks__hooks.parseTwoDigitYear = function (input) {
-        return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
-    };
-
-    // MOMENTS
-
-    var getSetYear = makeGetSet('FullYear', false);
-
-    function getIsLeapYear () {
-        return isLeapYear(this.year());
+    function calculateOffset(obsOffset, militaryOffset, numOffset) {
+        if (obsOffset) {
+            return obsOffsets[obsOffset];
+        } else if (militaryOffset) {
+            // the only allowed military tz is Z
+            return 0;
+        } else {
+            var hm = parseInt(numOffset, 10),
+                m = hm % 100,
+                h = (hm - m) / 100;
+            return h * 60 + m;
+        }
     }
 
-    // start-of-first-week - start-of-year
-    function firstWeekOffset(year, dow, doy) {
-        var // first-week day -- which january is always in the first week (4 for iso, 1 for other)
-            fwd = 7 + dow - doy,
-            // first-week day local weekday -- which local weekday is fwd
-            fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;
+    // date and time from ref 2822 format
+    function configFromRFC2822(config) {
+        var match = rfc2822.exec(preprocessRFC2822(config._i)),
+            parsedArray;
+        if (match) {
+            parsedArray = extractFromRFC2822Strings(
+                match[4],
+                match[3],
+                match[2],
+                match[5],
+                match[6],
+                match[7]
+            );
+            if (!checkWeekday(match[1], parsedArray, config)) {
+                return;
+            }
 
-        return -fwdlw + fwd - 1;
-    }
+            config._a = parsedArray;
+            config._tzm = calculateOffset(match[8], match[9], match[10]);
 
-    //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
-    function dayOfYearFromWeeks(year, week, weekday, dow, doy) {
-        var localWeekday = (7 + weekday - dow) % 7,
-            weekOffset = firstWeekOffset(year, dow, doy),
-            dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,
-            resYear, resDayOfYear;
+            config._d = createUTCDate.apply(null, config._a);
+            config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
 
-        if (dayOfYear <= 0) {
-            resYear = year - 1;
-            resDayOfYear = daysInYear(resYear) + dayOfYear;
-        } else if (dayOfYear > daysInYear(year)) {
-            resYear = year + 1;
-            resDayOfYear = dayOfYear - daysInYear(year);
+            getParsingFlags(config).rfc2822 = true;
         } else {
-            resYear = year;
-            resDayOfYear = dayOfYear;
+            config._isValid = false;
         }
-
-        return {
-            year: resYear,
-            dayOfYear: resDayOfYear
-        };
     }
 
-    function weekOfYear(mom, dow, doy) {
-        var weekOffset = firstWeekOffset(mom.year(), dow, doy),
-            week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
-            resWeek, resYear;
+    // date from 1) ASP.NET, 2) ISO, 3) RFC 2822 formats, or 4) optional fallback if parsing isn't strict
+    function configFromString(config) {
+        var matched = aspNetJsonRegex.exec(config._i);
+        if (matched !== null) {
+            config._d = new Date(+matched[1]);
+            return;
+        }
 
-        if (week < 1) {
-            resYear = mom.year() - 1;
-            resWeek = week + weeksInYear(resYear, dow, doy);
-        } else if (week > weeksInYear(mom.year(), dow, doy)) {
-            resWeek = week - weeksInYear(mom.year(), dow, doy);
-            resYear = mom.year() + 1;
+        configFromISO(config);
+        if (config._isValid === false) {
+            delete config._isValid;
         } else {
-            resYear = mom.year();
-            resWeek = week;
+            return;
         }
 
-        return {
-            week: resWeek,
-            year: resYear
-        };
-    }
+        configFromRFC2822(config);
+        if (config._isValid === false) {
+            delete config._isValid;
+        } else {
+            return;
+        }
 
-    function weeksInYear(year, dow, doy) {
-        var weekOffset = firstWeekOffset(year, dow, doy),
-            weekOffsetNext = firstWeekOffset(year + 1, dow, doy);
-        return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;
+        if (config._strict) {
+            config._isValid = false;
+        } else {
+            // Final attempt, use Input Fallback
+            hooks.createFromInputFallback(config);
+        }
     }
 
+    hooks.createFromInputFallback = deprecate(
+        'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' +
+            'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' +
+            'discouraged. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.',
+        function (config) {
+            config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
+        }
+    );
+
     // Pick the first defined of two or three arguments.
     function defaults(a, b, c) {
         if (a != null) {
 
     function currentDateArray(config) {
         // hooks is actually the exported moment object
-        var nowValue = new Date(utils_hooks__hooks.now());
+        var nowValue = new Date(hooks.now());
         if (config._useUTC) {
-            return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()];
+            return [
+                nowValue.getUTCFullYear(),
+                nowValue.getUTCMonth(),
+                nowValue.getUTCDate(),
+            ];
         }
         return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];
     }
     // the array should mirror the parameters below
     // note: all values past the year are optional and will default to the lowest possible value.
     // [year, month, day , hour, minute, second, millisecond]
-    function configFromArray (config) {
-        var i, date, input = [], currentDate, yearToUse;
+    function configFromArray(config) {
+        var i,
+            date,
+            input = [],
+            currentDate,
+            expectedWeekday,
+            yearToUse;
 
         if (config._d) {
             return;
         }
 
         //if the day of the year is set, figure out what it is
-        if (config._dayOfYear) {
+        if (config._dayOfYear != null) {
             yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);
 
-            if (config._dayOfYear > daysInYear(yearToUse)) {
+            if (
+                config._dayOfYear > daysInYear(yearToUse) ||
+                config._dayOfYear === 0
+            ) {
                 getParsingFlags(config)._overflowDayOfYear = true;
             }
 
 
         // Zero out whatever was not defaulted, including time
         for (; i < 7; i++) {
-            config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
+            config._a[i] = input[i] =
+                config._a[i] == null ? (i === 2 ? 1 : 0) : config._a[i];
         }
 
         // Check for 24:00:00.000
-        if (config._a[HOUR] === 24 &&
-                config._a[MINUTE] === 0 &&
-                config._a[SECOND] === 0 &&
-                config._a[MILLISECOND] === 0) {
+        if (
+            config._a[HOUR] === 24 &&
+            config._a[MINUTE] === 0 &&
+            config._a[SECOND] === 0 &&
+            config._a[MILLISECOND] === 0
+        ) {
             config._nextDay = true;
             config._a[HOUR] = 0;
         }
 
-        config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input);
+        config._d = (config._useUTC ? createUTCDate : createDate).apply(
+            null,
+            input
+        );
+        expectedWeekday = config._useUTC
+            ? config._d.getUTCDay()
+            : config._d.getDay();
+
         // Apply timezone offset from input. The actual utcOffset can be changed
         // with parseZone.
         if (config._tzm != null) {
         if (config._nextDay) {
             config._a[HOUR] = 24;
         }
+
+        // check for mismatching day of week
+        if (
+            config._w &&
+            typeof config._w.d !== 'undefined' &&
+            config._w.d !== expectedWeekday
+        ) {
+            getParsingFlags(config).weekdayMismatch = true;
+        }
     }
 
     function dayOfYearFromWeekInfo(config) {
-        var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow;
+        var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow, curWeek;
 
         w = config._w;
         if (w.GG != null || w.W != null || w.E != null) {
             // how we interpret now (local, utc, fixed offset). So create
             // a now version of current config (take local/utc/offset flags, and
             // create now).
-            weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(local__createLocal(), 1, 4).year);
+            weekYear = defaults(
+                w.GG,
+                config._a[YEAR],
+                weekOfYear(createLocal(), 1, 4).year
+            );
             week = defaults(w.W, 1);
             weekday = defaults(w.E, 1);
             if (weekday < 1 || weekday > 7) {
             dow = config._locale._week.dow;
             doy = config._locale._week.doy;
 
-            weekYear = defaults(w.gg, config._a[YEAR], weekOfYear(local__createLocal(), dow, doy).year);
-            week = defaults(w.w, 1);
+            curWeek = weekOfYear(createLocal(), dow, doy);
+
+            weekYear = defaults(w.gg, config._a[YEAR], curWeek.year);
+
+            // Default to current week.
+            week = defaults(w.w, curWeek.week);
 
             if (w.d != null) {
                 // weekday -- low day numbers are considered next week
                     weekdayOverflow = true;
                 }
             } else if (w.e != null) {
-                // local weekday -- counting starts from begining of week
+                // local weekday -- counting starts from beginning of week
                 weekday = w.e + dow;
                 if (w.e < 0 || w.e > 6) {
                     weekdayOverflow = true;
                 }
             } else {
-                // default to begining of week
+                // default to beginning of week
                 weekday = dow;
             }
         }
     }
 
     // constant that refers to the ISO standard
-    utils_hooks__hooks.ISO_8601 = function () {};
+    hooks.ISO_8601 = function () {};
+
+    // constant that refers to the RFC 2822 form
+    hooks.RFC_2822 = function () {};
 
     // date from string and format string
     function configFromStringAndFormat(config) {
         // TODO: Move this to another part of the creation flow to prevent circular deps
-        if (config._f === utils_hooks__hooks.ISO_8601) {
+        if (config._f === hooks.ISO_8601) {
             configFromISO(config);
             return;
         }
-
+        if (config._f === hooks.RFC_2822) {
+            configFromRFC2822(config);
+            return;
+        }
         config._a = [];
         getParsingFlags(config).empty = true;
 
         // This array is used to make a Date, either with `new Date` or `Date.UTC`
         var string = '' + config._i,
-            i, parsedInput, tokens, token, skipped,
+            i,
+            parsedInput,
+            tokens,
+            token,
+            skipped,
             stringLength = string.length,
-            totalParsedInputLength = 0;
-
-        tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
-
-        for (i = 0; i < tokens.length; i++) {
+            totalParsedInputLength = 0,
+            era,
+            tokenLen;
+
+        tokens =
+            expandFormat(config._f, config._locale).match(formattingTokens) || [];
+        tokenLen = tokens.length;
+        for (i = 0; i < tokenLen; i++) {
             token = tokens[i];
-            parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
-            // console.log('token', token, 'parsedInput', parsedInput,
-            //         'regex', getParseRegexForToken(token, config));
+            parsedInput = (string.match(getParseRegexForToken(token, config)) ||
+                [])[0];
             if (parsedInput) {
                 skipped = string.substr(0, string.indexOf(parsedInput));
                 if (skipped.length > 0) {
                     getParsingFlags(config).unusedInput.push(skipped);
                 }
-                string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
+                string = string.slice(
+                    string.indexOf(parsedInput) + parsedInput.length
+                );
                 totalParsedInputLength += parsedInput.length;
             }
             // don't parse if it's not a known token
             if (formatTokenFunctions[token]) {
                 if (parsedInput) {
                     getParsingFlags(config).empty = false;
-                }
-                else {
+                } else {
                     getParsingFlags(config).unusedTokens.push(token);
                 }
                 addTimeToArrayFromToken(token, parsedInput, config);
-            }
-            else if (config._strict && !parsedInput) {
+            } else if (config._strict && !parsedInput) {
                 getParsingFlags(config).unusedTokens.push(token);
             }
         }
 
         // add remaining unparsed input length to the string
-        getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength;
+        getParsingFlags(config).charsLeftOver =
+            stringLength - totalParsedInputLength;
         if (string.length > 0) {
             getParsingFlags(config).unusedInput.push(string);
         }
 
         // clear _12h flag if hour is <= 12
-        if (getParsingFlags(config).bigHour === true &&
-                config._a[HOUR] <= 12 &&
-                config._a[HOUR] > 0) {
+        if (
+            config._a[HOUR] <= 12 &&
+            getParsingFlags(config).bigHour === true &&
+            config._a[HOUR] > 0
+        ) {
             getParsingFlags(config).bigHour = undefined;
         }
+
+        getParsingFlags(config).parsedDateParts = config._a.slice(0);
+        getParsingFlags(config).meridiem = config._meridiem;
         // handle meridiem
-        config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem);
+        config._a[HOUR] = meridiemFixWrap(
+            config._locale,
+            config._a[HOUR],
+            config._meridiem
+        );
+
+        // handle era
+        era = getParsingFlags(config).era;
+        if (era !== null) {
+            config._a[YEAR] = config._locale.erasConvertYear(era, config._a[YEAR]);
+        }
 
         configFromArray(config);
         checkOverflow(config);
     }
 
-
-    function meridiemFixWrap (locale, hour, meridiem) {
+    function meridiemFixWrap(locale, hour, meridiem) {
         var isPm;
 
         if (meridiem == null) {
     function configFromStringAndArray(config) {
         var tempConfig,
             bestMoment,
-
             scoreToBeat,
             i,
-            currentScore;
+            currentScore,
+            validFormatFound,
+            bestFormatIsValid = false,
+            configfLen = config._f.length;
 
-        if (config._f.length === 0) {
+        if (configfLen === 0) {
             getParsingFlags(config).invalidFormat = true;
             config._d = new Date(NaN);
             return;
         }
 
-        for (i = 0; i < config._f.length; i++) {
+        for (i = 0; i < configfLen; i++) {
             currentScore = 0;
+            validFormatFound = false;
             tempConfig = copyConfig({}, config);
             if (config._useUTC != null) {
                 tempConfig._useUTC = config._useUTC;
             tempConfig._f = config._f[i];
             configFromStringAndFormat(tempConfig);
 
-            if (!valid__isValid(tempConfig)) {
-                continue;
+            if (isValid(tempConfig)) {
+                validFormatFound = true;
             }
 
             // if there is any input that was not parsed add a penalty for that format
 
             getParsingFlags(tempConfig).score = currentScore;
 
-            if (scoreToBeat == null || currentScore < scoreToBeat) {
-                scoreToBeat = currentScore;
-                bestMoment = tempConfig;
+            if (!bestFormatIsValid) {
+                if (
+                    scoreToBeat == null ||
+                    currentScore < scoreToBeat ||
+                    validFormatFound
+                ) {
+                    scoreToBeat = currentScore;
+                    bestMoment = tempConfig;
+                    if (validFormatFound) {
+                        bestFormatIsValid = true;
+                    }
+                }
+            } else {
+                if (currentScore < scoreToBeat) {
+                    scoreToBeat = currentScore;
+                    bestMoment = tempConfig;
+                }
             }
         }
 
             return;
         }
 
-        var i = normalizeObjectUnits(config._i);
-        config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) {
-            return obj && parseInt(obj, 10);
-        });
+        var i = normalizeObjectUnits(config._i),
+            dayOrDate = i.day === undefined ? i.date : i.day;
+        config._a = map(
+            [i.year, i.month, dayOrDate, i.hour, i.minute, i.second, i.millisecond],
+            function (obj) {
+                return obj && parseInt(obj, 10);
+            }
+        );
 
         configFromArray(config);
     }
 
-    function createFromConfig (config) {
+    function createFromConfig(config) {
         var res = new Moment(checkOverflow(prepareConfig(config)));
         if (res._nextDay) {
             // Adding is smart enough around DST
         return res;
     }
 
-    function prepareConfig (config) {
+    function prepareConfig(config) {
         var input = config._i,
             format = config._f;
 
-        config._locale = config._locale || locale_locales__getLocale(config._l);
+        config._locale = config._locale || getLocale(config._l);
 
         if (input === null || (format === undefined && input === '')) {
-            return valid__createInvalid({nullInput: true});
+            return createInvalid({ nullInput: true });
         }
 
         if (typeof input === 'string') {
 
         if (isMoment(input)) {
             return new Moment(checkOverflow(input));
+        } else if (isDate(input)) {
+            config._d = input;
         } else if (isArray(format)) {
             configFromStringAndArray(config);
         } else if (format) {
             configFromStringAndFormat(config);
-        } else if (isDate(input)) {
-            config._d = input;
         } else {
             configFromInput(config);
         }
 
-        if (!valid__isValid(config)) {
+        if (!isValid(config)) {
             config._d = null;
         }
 
 
     function configFromInput(config) {
         var input = config._i;
-        if (input === undefined) {
-            config._d = new Date(utils_hooks__hooks.now());
+        if (isUndefined(input)) {
+            config._d = new Date(hooks.now());
         } else if (isDate(input)) {
-            config._d = new Date(+input);
+            config._d = new Date(input.valueOf());
         } else if (typeof input === 'string') {
             configFromString(config);
         } else if (isArray(input)) {
                 return parseInt(obj, 10);
             });
             configFromArray(config);
-        } else if (typeof(input) === 'object') {
+        } else if (isObject(input)) {
             configFromObject(config);
-        } else if (typeof(input) === 'number') {
+        } else if (isNumber(input)) {
             // from milliseconds
             config._d = new Date(input);
         } else {
-            utils_hooks__hooks.createFromInputFallback(config);
+            hooks.createFromInputFallback(config);
         }
     }
 
-    function createLocalOrUTC (input, format, locale, strict, isUTC) {
+    function createLocalOrUTC(input, format, locale, strict, isUTC) {
         var c = {};
 
-        if (typeof(locale) === 'boolean') {
+        if (format === true || format === false) {
+            strict = format;
+            format = undefined;
+        }
+
+        if (locale === true || locale === false) {
             strict = locale;
             locale = undefined;
         }
+
+        if (
+            (isObject(input) && isObjectEmpty(input)) ||
+            (isArray(input) && input.length === 0)
+        ) {
+            input = undefined;
+        }
         // object construction must be done this way.
         // https://github.com/moment/moment/issues/1423
         c._isAMomentObject = true;
         return createFromConfig(c);
     }
 
-    function local__createLocal (input, format, locale, strict) {
+    function createLocal(input, format, locale, strict) {
         return createLocalOrUTC(input, format, locale, strict, false);
     }
 
     var prototypeMin = deprecate(
-         'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548',
-         function () {
-             var other = local__createLocal.apply(null, arguments);
-             if (this.isValid() && other.isValid()) {
-                 return other < this ? this : other;
-             } else {
-                 return valid__createInvalid();
-             }
-         }
-     );
-
-    var prototypeMax = deprecate(
-        'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548',
-        function () {
-            var other = local__createLocal.apply(null, arguments);
-            if (this.isValid() && other.isValid()) {
-                return other > this ? this : other;
-            } else {
-                return valid__createInvalid();
+            'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/',
+            function () {
+                var other = createLocal.apply(null, arguments);
+                if (this.isValid() && other.isValid()) {
+                    return other < this ? this : other;
+                } else {
+                    return createInvalid();
+                }
             }
-        }
-    );
+        ),
+        prototypeMax = deprecate(
+            'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/',
+            function () {
+                var other = createLocal.apply(null, arguments);
+                if (this.isValid() && other.isValid()) {
+                    return other > this ? this : other;
+                } else {
+                    return createInvalid();
+                }
+            }
+        );
 
     // Pick a moment m from moments so that m[fn](other) is true for all
     // other. This relies on the function fn to be transitive.
             moments = moments[0];
         }
         if (!moments.length) {
-            return local__createLocal();
+            return createLocal();
         }
         res = moments[0];
         for (i = 1; i < moments.length; ++i) {
     }
 
     // TODO: Use [].sort instead?
-    function min () {
+    function min() {
         var args = [].slice.call(arguments, 0);
 
         return pickBy('isBefore', args);
     }
 
-    function max () {
+    function max() {
         var args = [].slice.call(arguments, 0);
 
         return pickBy('isAfter', args);
     }
 
     var now = function () {
-        return Date.now ? Date.now() : +(new Date());
+        return Date.now ? Date.now() : +new Date();
     };
 
-    function Duration (duration) {
+    var ordering = [
+        'year',
+        'quarter',
+        'month',
+        'week',
+        'day',
+        'hour',
+        'minute',
+        'second',
+        'millisecond',
+    ];
+
+    function isDurationValid(m) {
+        var key,
+            unitHasDecimal = false,
+            i,
+            orderLen = ordering.length;
+        for (key in m) {
+            if (
+                hasOwnProp(m, key) &&
+                !(
+                    indexOf.call(ordering, key) !== -1 &&
+                    (m[key] == null || !isNaN(m[key]))
+                )
+            ) {
+                return false;
+            }
+        }
+
+        for (i = 0; i < orderLen; ++i) {
+            if (m[ordering[i]]) {
+                if (unitHasDecimal) {
+                    return false; // only allow non-integers for smallest unit
+                }
+                if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) {
+                    unitHasDecimal = true;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    function isValid$1() {
+        return this._isValid;
+    }
+
+    function createInvalid$1() {
+        return createDuration(NaN);
+    }
+
+    function Duration(duration) {
         var normalizedInput = normalizeObjectUnits(duration),
             years = normalizedInput.year || 0,
             quarters = normalizedInput.quarter || 0,
             months = normalizedInput.month || 0,
-            weeks = normalizedInput.week || 0,
+            weeks = normalizedInput.week || normalizedInput.isoWeek || 0,
             days = normalizedInput.day || 0,
             hours = normalizedInput.hour || 0,
             minutes = normalizedInput.minute || 0,
             seconds = normalizedInput.second || 0,
             milliseconds = normalizedInput.millisecond || 0;
 
+        this._isValid = isDurationValid(normalizedInput);
+
         // representation for dateAddRemove
-        this._milliseconds = +milliseconds +
+        this._milliseconds =
+            +milliseconds +
             seconds * 1e3 + // 1000
             minutes * 6e4 + // 1000 * 60
-            hours * 36e5; // 1000 * 60 * 60
+            hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978
         // Because of dateAddRemove treats 24 hours as different from a
         // day when working around DST, we need to store them separately
-        this._days = +days +
-            weeks * 7;
-        // It is impossible translate months into days without knowing
+        this._days = +days + weeks * 7;
+        // It is impossible to translate months into days without knowing
         // which months you are are talking about, so we have to store
         // it separately.
-        this._months = +months +
-            quarters * 3 +
-            years * 12;
+        this._months = +months + quarters * 3 + years * 12;
 
         this._data = {};
 
-        this._locale = locale_locales__getLocale();
+        this._locale = getLocale();
 
         this._bubble();
     }
 
-    function isDuration (obj) {
+    function isDuration(obj) {
         return obj instanceof Duration;
     }
 
+    function absRound(number) {
+        if (number < 0) {
+            return Math.round(-1 * number) * -1;
+        } else {
+            return Math.round(number);
+        }
+    }
+
+    // compare two arrays, return the number of differences
+    function compareArrays(array1, array2, dontConvert) {
+        var len = Math.min(array1.length, array2.length),
+            lengthDiff = Math.abs(array1.length - array2.length),
+            diffs = 0,
+            i;
+        for (i = 0; i < len; i++) {
+            if (
+                (dontConvert && array1[i] !== array2[i]) ||
+                (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))
+            ) {
+                diffs++;
+            }
+        }
+        return diffs + lengthDiff;
+    }
+
     // FORMATTING
 
-    function offset (token, separator) {
+    function offset(token, separator) {
         addFormatToken(token, 0, 0, function () {
-            var offset = this.utcOffset();
-            var sign = '+';
+            var offset = this.utcOffset(),
+                sign = '+';
             if (offset < 0) {
                 offset = -offset;
                 sign = '-';
             }
-            return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2);
+            return (
+                sign +
+                zeroFill(~~(offset / 60), 2) +
+                separator +
+                zeroFill(~~offset % 60, 2)
+            );
         });
     }
 
 
     // PARSING
 
-    addRegexToken('Z',  matchShortOffset);
+    addRegexToken('Z', matchShortOffset);
     addRegexToken('ZZ', matchShortOffset);
     addParseToken(['Z', 'ZZ'], function (input, array, config) {
         config._useUTC = true;
     var chunkOffset = /([\+\-]|\d\d)/gi;
 
     function offsetFromString(matcher, string) {
-        var matches = ((string || '').match(matcher) || []);
-        var chunk   = matches[matches.length - 1] || [];
-        var parts   = (chunk + '').match(chunkOffset) || ['-', 0, 0];
-        var minutes = +(parts[1] * 60) + toInt(parts[2]);
+        var matches = (string || '').match(matcher),
+            chunk,
+            parts,
+            minutes;
+
+        if (matches === null) {
+            return null;
+        }
 
-        return parts[0] === '+' ? minutes : -minutes;
+        chunk = matches[matches.length - 1] || [];
+        parts = (chunk + '').match(chunkOffset) || ['-', 0, 0];
+        minutes = +(parts[1] * 60) + toInt(parts[2]);
+
+        return minutes === 0 ? 0 : parts[0] === '+' ? minutes : -minutes;
     }
 
     // Return a moment from input, that is local/utc/zone equivalent to model.
         var res, diff;
         if (model._isUTC) {
             res = model.clone();
-            diff = (isMoment(input) || isDate(input) ? +input : +local__createLocal(input)) - (+res);
+            diff =
+                (isMoment(input) || isDate(input)
+                    ? input.valueOf()
+                    : createLocal(input).valueOf()) - res.valueOf();
             // Use low-level api, because this fn is low-level api.
-            res._d.setTime(+res._d + diff);
-            utils_hooks__hooks.updateOffset(res, false);
+            res._d.setTime(res._d.valueOf() + diff);
+            hooks.updateOffset(res, false);
             return res;
         } else {
-            return local__createLocal(input).local();
+            return createLocal(input).local();
         }
     }
 
-    function getDateOffset (m) {
+    function getDateOffset(m) {
         // On Firefox.24 Date#getTimezoneOffset returns a floating point.
         // https://github.com/moment/moment/pull/1871
-        return -Math.round(m._d.getTimezoneOffset() / 15) * 15;
+        return -Math.round(m._d.getTimezoneOffset());
     }
 
     // HOOKS
 
     // This function will be called whenever a moment is mutated.
     // It is intended to keep the offset in sync with the timezone.
-    utils_hooks__hooks.updateOffset = function () {};
+    hooks.updateOffset = function () {};
 
     // MOMENTS
 
     // a second time. In case it wants us to change the offset again
     // _changeInProgress == true case, then we have to adjust, because
     // there is no such time in the given timezone.
-    function getSetOffset (input, keepLocalTime) {
+    function getSetOffset(input, keepLocalTime, keepMinutes) {
         var offset = this._offset || 0,
             localAdjust;
         if (!this.isValid()) {
         if (input != null) {
             if (typeof input === 'string') {
                 input = offsetFromString(matchShortOffset, input);
-            } else if (Math.abs(input) < 16) {
+                if (input === null) {
+                    return this;
+                }
+            } else if (Math.abs(input) < 16 && !keepMinutes) {
                 input = input * 60;
             }
             if (!this._isUTC && keepLocalTime) {
             }
             if (offset !== input) {
                 if (!keepLocalTime || this._changeInProgress) {
-                    add_subtract__addSubtract(this, create__createDuration(input - offset, 'm'), 1, false);
+                    addSubtract(
+                        this,
+                        createDuration(input - offset, 'm'),
+                        1,
+                        false
+                    );
                 } else if (!this._changeInProgress) {
                     this._changeInProgress = true;
-                    utils_hooks__hooks.updateOffset(this, true);
+                    hooks.updateOffset(this, true);
                     this._changeInProgress = null;
                 }
             }
         }
     }
 
-    function getSetZone (input, keepLocalTime) {
+    function getSetZone(input, keepLocalTime) {
         if (input != null) {
             if (typeof input !== 'string') {
                 input = -input;
         }
     }
 
-    function setOffsetToUTC (keepLocalTime) {
+    function setOffsetToUTC(keepLocalTime) {
         return this.utcOffset(0, keepLocalTime);
     }
 
-    function setOffsetToLocal (keepLocalTime) {
+    function setOffsetToLocal(keepLocalTime) {
         if (this._isUTC) {
             this.utcOffset(0, keepLocalTime);
             this._isUTC = false;
         return this;
     }
 
-    function setOffsetToParsedOffset () {
-        if (this._tzm) {
-            this.utcOffset(this._tzm);
+    function setOffsetToParsedOffset() {
+        if (this._tzm != null) {
+            this.utcOffset(this._tzm, false, true);
         } else if (typeof this._i === 'string') {
-            this.utcOffset(offsetFromString(matchOffset, this._i));
+            var tZone = offsetFromString(matchOffset, this._i);
+            if (tZone != null) {
+                this.utcOffset(tZone);
+            } else {
+                this.utcOffset(0, true);
+            }
         }
         return this;
     }
 
-    function hasAlignedHourOffset (input) {
+    function hasAlignedHourOffset(input) {
         if (!this.isValid()) {
             return false;
         }
-        input = input ? local__createLocal(input).utcOffset() : 0;
+        input = input ? createLocal(input).utcOffset() : 0;
 
         return (this.utcOffset() - input) % 60 === 0;
     }
 
-    function isDaylightSavingTime () {
+    function isDaylightSavingTime() {
         return (
             this.utcOffset() > this.clone().month(0).utcOffset() ||
             this.utcOffset() > this.clone().month(5).utcOffset()
         );
     }
 
-    function isDaylightSavingTimeShifted () {
+    function isDaylightSavingTimeShifted() {
         if (!isUndefined(this._isDSTShifted)) {
             return this._isDSTShifted;
         }
 
-        var c = {};
+        var c = {},
+            other;
 
         copyConfig(c, this);
         c = prepareConfig(c);
 
         if (c._a) {
-            var other = c._isUTC ? create_utc__createUTC(c._a) : local__createLocal(c._a);
-            this._isDSTShifted = this.isValid() &&
-                compareArrays(c._a, other.toArray()) > 0;
+            other = c._isUTC ? createUTC(c._a) : createLocal(c._a);
+            this._isDSTShifted =
+                this.isValid() && compareArrays(c._a, other.toArray()) > 0;
         } else {
             this._isDSTShifted = false;
         }
         return this._isDSTShifted;
     }
 
-    function isLocal () {
+    function isLocal() {
         return this.isValid() ? !this._isUTC : false;
     }
 
-    function isUtcOffset () {
+    function isUtcOffset() {
         return this.isValid() ? this._isUTC : false;
     }
 
-    function isUtc () {
+    function isUtc() {
         return this.isValid() ? this._isUTC && this._offset === 0 : false;
     }
 
     // ASP.NET json date format regex
-    var aspNetRegex = /(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/;
-
-    // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
-    // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
-    var isoRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;
-
-    function create__createDuration (input, key) {
+    var aspNetRegex = /^(-|\+)?(?:(\d*)[. ])?(\d+):(\d+)(?::(\d+)(\.\d*)?)?$/,
+        // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
+        // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
+        // and further modified to allow for strings containing both week and day
+        isoRegex =
+            /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;
+
+    function createDuration(input, key) {
         var duration = input,
             // matching against regexp is expensive, do it on demand
             match = null,
 
         if (isDuration(input)) {
             duration = {
-                ms : input._milliseconds,
-                d  : input._days,
-                M  : input._months
+                ms: input._milliseconds,
+                d: input._days,
+                M: input._months,
             };
-        } else if (typeof input === 'number') {
+        } else if (isNumber(input) || !isNaN(+input)) {
             duration = {};
             if (key) {
-                duration[key] = input;
+                duration[key] = +input;
             } else {
-                duration.milliseconds = input;
+                duration.milliseconds = +input;
             }
-        } else if (!!(match = aspNetRegex.exec(input))) {
-            sign = (match[1] === '-') ? -1 : 1;
+        } else if ((match = aspNetRegex.exec(input))) {
+            sign = match[1] === '-' ? -1 : 1;
             duration = {
-                y  : 0,
-                d  : toInt(match[DATE])        * sign,
-                h  : toInt(match[HOUR])        * sign,
-                m  : toInt(match[MINUTE])      * sign,
-                s  : toInt(match[SECOND])      * sign,
-                ms : toInt(match[MILLISECOND]) * sign
+                y: 0,
+                d: toInt(match[DATE]) * sign,
+                h: toInt(match[HOUR]) * sign,
+                m: toInt(match[MINUTE]) * sign,
+                s: toInt(match[SECOND]) * sign,
+                ms: toInt(absRound(match[MILLISECOND] * 1000)) * sign, // the millisecond decimal point is included in the match
             };
-        } else if (!!(match = isoRegex.exec(input))) {
-            sign = (match[1] === '-') ? -1 : 1;
+        } else if ((match = isoRegex.exec(input))) {
+            sign = match[1] === '-' ? -1 : 1;
             duration = {
-                y : parseIso(match[2], sign),
-                M : parseIso(match[3], sign),
-                : parseIso(match[4], sign),
-                : parseIso(match[5], sign),
-                : parseIso(match[6], sign),
-                : parseIso(match[7], sign),
-                w : parseIso(match[8], sign)
+                y: parseIso(match[2], sign),
+                M: parseIso(match[3], sign),
+                w: parseIso(match[4], sign),
+                d: parseIso(match[5], sign),
+                h: parseIso(match[6], sign),
+                m: parseIso(match[7], sign),
+                s: parseIso(match[8], sign),
             };
-        } else if (duration == null) {// checks for null or undefined
+        } else if (duration == null) {
+            // checks for null or undefined
             duration = {};
-        } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) {
-            diffRes = momentsDifference(local__createLocal(duration.from), local__createLocal(duration.to));
+        } else if (
+            typeof duration === 'object' &&
+            ('from' in duration || 'to' in duration)
+        ) {
+            diffRes = momentsDifference(
+                createLocal(duration.from),
+                createLocal(duration.to)
+            );
 
             duration = {};
             duration.ms = diffRes.milliseconds;
             ret._locale = input._locale;
         }
 
+        if (isDuration(input) && hasOwnProp(input, '_isValid')) {
+            ret._isValid = input._isValid;
+        }
+
         return ret;
     }
 
-    create__createDuration.fn = Duration.prototype;
+    createDuration.fn = Duration.prototype;
+    createDuration.invalid = createInvalid$1;
 
-    function parseIso (inp, sign) {
+    function parseIso(inp, sign) {
         // We'd normally use ~~inp for this, but unfortunately it also
         // converts floats to ints.
         // inp may be undefined, so careful calling replace on it.
     }
 
     function positiveMomentsDifference(base, other) {
-        var res = {milliseconds: 0, months: 0};
+        var res = {};
 
-        res.months = other.month() - base.month() +
-            (other.year() - base.year()) * 12;
+        res.months =
+            other.month() - base.month() + (other.year() - base.year()) * 12;
         if (base.clone().add(res.months, 'M').isAfter(other)) {
             --res.months;
         }
 
-        res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
+        res.milliseconds = +other - +base.clone().add(res.months, 'M');
 
         return res;
     }
     function momentsDifference(base, other) {
         var res;
         if (!(base.isValid() && other.isValid())) {
-            return {milliseconds: 0, months: 0};
+            return { milliseconds: 0, months: 0 };
         }
 
         other = cloneWithOffset(other, base);
             var dur, tmp;
             //invert the arguments, but complain about it
             if (period !== null && !isNaN(+period)) {
-                deprecateSimple(name, 'moment().' + name  + '(period, number) is deprecated. Please use moment().' + name + '(number, period).');
-                tmp = val; val = period; period = tmp;
+                deprecateSimple(
+                    name,
+                    'moment().' +
+                        name +
+                        '(period, number) is deprecated. Please use moment().' +
+                        name +
+                        '(number, period). ' +
+                        'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.'
+                );
+                tmp = val;
+                val = period;
+                period = tmp;
             }
 
-            val = typeof val === 'string' ? +val : val;
-            dur = create__createDuration(val, period);
-            add_subtract__addSubtract(this, dur, direction);
+            dur = createDuration(val, period);
+            addSubtract(this, dur, direction);
             return this;
         };
     }
 
-    function add_subtract__addSubtract (mom, duration, isAdding, updateOffset) {
+    function addSubtract(mom, duration, isAdding, updateOffset) {
         var milliseconds = duration._milliseconds,
-            days = duration._days,
-            months = duration._months;
+            days = absRound(duration._days),
+            months = absRound(duration._months);
 
         if (!mom.isValid()) {
             // No op
 
         updateOffset = updateOffset == null ? true : updateOffset;
 
-        if (milliseconds) {
-            mom._d.setTime(+mom._d + milliseconds * isAdding);
+        if (months) {
+            setMonth(mom, get(mom, 'Month') + months * isAdding);
         }
         if (days) {
-            get_set__set(mom, 'Date', get_set__get(mom, 'Date') + days * isAdding);
+            set$1(mom, 'Date', get(mom, 'Date') + days * isAdding);
         }
-        if (months) {
-            setMonth(mom, get_set__get(mom, 'Month') + months * isAdding);
+        if (milliseconds) {
+            mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);
         }
         if (updateOffset) {
-            utils_hooks__hooks.updateOffset(mom, days || months);
+            hooks.updateOffset(mom, days || months);
+        }
+    }
+
+    var add = createAdder(1, 'add'),
+        subtract = createAdder(-1, 'subtract');
+
+    function isString(input) {
+        return typeof input === 'string' || input instanceof String;
+    }
+
+    // type MomentInput = Moment | Date | string | number | (number | string)[] | MomentInputObject | void; // null | undefined
+    function isMomentInput(input) {
+        return (
+            isMoment(input) ||
+            isDate(input) ||
+            isString(input) ||
+            isNumber(input) ||
+            isNumberOrStringArray(input) ||
+            isMomentInputObject(input) ||
+            input === null ||
+            input === undefined
+        );
+    }
+
+    function isMomentInputObject(input) {
+        var objectTest = isObject(input) && !isObjectEmpty(input),
+            propertyTest = false,
+            properties = [
+                'years',
+                'year',
+                'y',
+                'months',
+                'month',
+                'M',
+                'days',
+                'day',
+                'd',
+                'dates',
+                'date',
+                'D',
+                'hours',
+                'hour',
+                'h',
+                'minutes',
+                'minute',
+                'm',
+                'seconds',
+                'second',
+                's',
+                'milliseconds',
+                'millisecond',
+                'ms',
+            ],
+            i,
+            property,
+            propertyLen = properties.length;
+
+        for (i = 0; i < propertyLen; i += 1) {
+            property = properties[i];
+            propertyTest = propertyTest || hasOwnProp(input, property);
         }
+
+        return objectTest && propertyTest;
     }
 
-    var add_subtract__add      = createAdder(1, 'add');
-    var add_subtract__subtract = createAdder(-1, 'subtract');
+    function isNumberOrStringArray(input) {
+        var arrayTest = isArray(input),
+            dataTypeTest = false;
+        if (arrayTest) {
+            dataTypeTest =
+                input.filter(function (item) {
+                    return !isNumber(item) && isString(input);
+                }).length === 0;
+        }
+        return arrayTest && dataTypeTest;
+    }
 
-    function moment_calendar__calendar (time, formats) {
+    function isCalendarSpec(input) {
+        var objectTest = isObject(input) && !isObjectEmpty(input),
+            propertyTest = false,
+            properties = [
+                'sameDay',
+                'nextDay',
+                'lastDay',
+                'nextWeek',
+                'lastWeek',
+                'sameElse',
+            ],
+            i,
+            property;
+
+        for (i = 0; i < properties.length; i += 1) {
+            property = properties[i];
+            propertyTest = propertyTest || hasOwnProp(input, property);
+        }
+
+        return objectTest && propertyTest;
+    }
+
+    function getCalendarFormat(myMoment, now) {
+        var diff = myMoment.diff(now, 'days', true);
+        return diff < -6
+            ? 'sameElse'
+            : diff < -1
+            ? 'lastWeek'
+            : diff < 0
+            ? 'lastDay'
+            : diff < 1
+            ? 'sameDay'
+            : diff < 2
+            ? 'nextDay'
+            : diff < 7
+            ? 'nextWeek'
+            : 'sameElse';
+    }
+
+    function calendar$1(time, formats) {
+        // Support for single parameter, formats only overload to the calendar function
+        if (arguments.length === 1) {
+            if (!arguments[0]) {
+                time = undefined;
+                formats = undefined;
+            } else if (isMomentInput(arguments[0])) {
+                time = arguments[0];
+                formats = undefined;
+            } else if (isCalendarSpec(arguments[0])) {
+                formats = arguments[0];
+                time = undefined;
+            }
+        }
         // We want to compare the start of today, vs this.
         // Getting start-of-today depends on whether we're local/utc/offset or not.
-        var now = time || local__createLocal(),
+        var now = time || createLocal(),
             sod = cloneWithOffset(now, this).startOf('day'),
-            diff = this.diff(sod, 'days', true),
-            format = diff < -6 ? 'sameElse' :
-                diff < -1 ? 'lastWeek' :
-                diff < 0 ? 'lastDay' :
-                diff < 1 ? 'sameDay' :
-                diff < 2 ? 'nextDay' :
-                diff < 7 ? 'nextWeek' : 'sameElse';
-
-        var output = formats && (isFunction(formats[format]) ? formats[format]() : formats[format]);
-
-        return this.format(output || this.localeData().calendar(format, this, local__createLocal(now)));
+            format = hooks.calendarFormat(this, sod) || 'sameElse',
+            output =
+                formats &&
+                (isFunction(formats[format])
+                    ? formats[format].call(this, now)
+                    : formats[format]);
+
+        return this.format(
+            output || this.localeData().calendar(format, this, createLocal(now))
+        );
     }
 
-    function clone () {
+    function clone() {
         return new Moment(this);
     }
 
-    function isAfter (input, units) {
-        var localInput = isMoment(input) ? input : local__createLocal(input);
+    function isAfter(input, units) {
+        var localInput = isMoment(input) ? input : createLocal(input);
         if (!(this.isValid() && localInput.isValid())) {
             return false;
         }
-        units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
+        units = normalizeUnits(units) || 'millisecond';
         if (units === 'millisecond') {
-            return +this > +localInput;
+            return this.valueOf() > localInput.valueOf();
         } else {
-            return +localInput < +this.clone().startOf(units);
+            return localInput.valueOf() < this.clone().startOf(units).valueOf();
         }
     }
 
-    function isBefore (input, units) {
-        var localInput = isMoment(input) ? input : local__createLocal(input);
+    function isBefore(input, units) {
+        var localInput = isMoment(input) ? input : createLocal(input);
         if (!(this.isValid() && localInput.isValid())) {
             return false;
         }
-        units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
+        units = normalizeUnits(units) || 'millisecond';
         if (units === 'millisecond') {
-            return +this < +localInput;
+            return this.valueOf() < localInput.valueOf();
         } else {
-            return +this.clone().endOf(units) < +localInput;
+            return this.clone().endOf(units).valueOf() < localInput.valueOf();
         }
     }
 
-    function isBetween (from, to, units) {
-        return this.isAfter(from, units) && this.isBefore(to, units);
+    function isBetween(from, to, units, inclusivity) {
+        var localFrom = isMoment(from) ? from : createLocal(from),
+            localTo = isMoment(to) ? to : createLocal(to);
+        if (!(this.isValid() && localFrom.isValid() && localTo.isValid())) {
+            return false;
+        }
+        inclusivity = inclusivity || '()';
+        return (
+            (inclusivity[0] === '('
+                ? this.isAfter(localFrom, units)
+                : !this.isBefore(localFrom, units)) &&
+            (inclusivity[1] === ')'
+                ? this.isBefore(localTo, units)
+                : !this.isAfter(localTo, units))
+        );
     }
 
-    function isSame (input, units) {
-        var localInput = isMoment(input) ? input : local__createLocal(input),
+    function isSame(input, units) {
+        var localInput = isMoment(input) ? input : createLocal(input),
             inputMs;
         if (!(this.isValid() && localInput.isValid())) {
             return false;
         }
-        units = normalizeUnits(units || 'millisecond');
+        units = normalizeUnits(units) || 'millisecond';
         if (units === 'millisecond') {
-            return +this === +localInput;
+            return this.valueOf() === localInput.valueOf();
         } else {
-            inputMs = +localInput;
-            return +(this.clone().startOf(units)) <= inputMs && inputMs <= +(this.clone().endOf(units));
+            inputMs = localInput.valueOf();
+            return (
+                this.clone().startOf(units).valueOf() <= inputMs &&
+                inputMs <= this.clone().endOf(units).valueOf()
+            );
         }
     }
 
-    function isSameOrAfter (input, units) {
-        return this.isSame(input, units) || this.isAfter(input,units);
+    function isSameOrAfter(input, units) {
+        return this.isSame(input, units) || this.isAfter(input, units);
     }
 
-    function isSameOrBefore (input, units) {
-        return this.isSame(input, units) || this.isBefore(input,units);
+    function isSameOrBefore(input, units) {
+        return this.isSame(input, units) || this.isBefore(input, units);
     }
 
-    function diff (input, units, asFloat) {
-        var that,
-            zoneDelta,
-            delta, output;
+    function diff(input, units, asFloat) {
+        var that, zoneDelta, output;
 
         if (!this.isValid()) {
             return NaN;
 
         units = normalizeUnits(units);
 
-        if (units === 'year' || units === 'month' || units === 'quarter') {
-            output = monthDiff(this, that);
-            if (units === 'quarter') {
-                output = output / 3;
-            } else if (units === 'year') {
-                output = output / 12;
-            }
-        } else {
-            delta = this - that;
-            output = units === 'second' ? delta / 1e3 : // 1000
-                units === 'minute' ? delta / 6e4 : // 1000 * 60
-                units === 'hour' ? delta / 36e5 : // 1000 * 60 * 60
-                units === 'day' ? (delta - zoneDelta) / 864e5 : // 1000 * 60 * 60 * 24, negate dst
-                units === 'week' ? (delta - zoneDelta) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst
-                delta;
+        switch (units) {
+            case 'year':
+                output = monthDiff(this, that) / 12;
+                break;
+            case 'month':
+                output = monthDiff(this, that);
+                break;
+            case 'quarter':
+                output = monthDiff(this, that) / 3;
+                break;
+            case 'second':
+                output = (this - that) / 1e3;
+                break; // 1000
+            case 'minute':
+                output = (this - that) / 6e4;
+                break; // 1000 * 60
+            case 'hour':
+                output = (this - that) / 36e5;
+                break; // 1000 * 60 * 60
+            case 'day':
+                output = (this - that - zoneDelta) / 864e5;
+                break; // 1000 * 60 * 60 * 24, negate dst
+            case 'week':
+                output = (this - that - zoneDelta) / 6048e5;
+                break; // 1000 * 60 * 60 * 24 * 7, negate dst
+            default:
+                output = this - that;
         }
+
         return asFloat ? output : absFloor(output);
     }
 
-    function monthDiff (a, b) {
+    function monthDiff(a, b) {
+        if (a.date() < b.date()) {
+            // end-of-month calculations work correct when the start month has more
+            // days than the end month.
+            return -monthDiff(b, a);
+        }
         // difference in months
-        var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()),
+        var wholeMonthDiff = (b.year() - a.year()) * 12 + (b.month() - a.month()),
             // b is in (anchor - 1 month, anchor + 1 month)
             anchor = a.clone().add(wholeMonthDiff, 'months'),
-            anchor2, adjust;
+            anchor2,
+            adjust;
 
         if (b - anchor < 0) {
             anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
             adjust = (b - anchor) / (anchor2 - anchor);
         }
 
-        return -(wholeMonthDiff + adjust);
+        //check for negative zero, return zero if negative zero
+        return -(wholeMonthDiff + adjust) || 0;
     }
 
-    utils_hooks__hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
+    hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
+    hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';
 
-    function toString () {
+    function toString() {
         return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
     }
 
-    function moment_format__toISOString () {
-        var m = this.clone().utc();
-        if (0 < m.year() && m.year() <= 9999) {
-            if (isFunction(Date.prototype.toISOString)) {
-                // native implementation is ~50x faster, use it when we can
+    function toISOString(keepOffset) {
+        if (!this.isValid()) {
+            return null;
+        }
+        var utc = keepOffset !== true,
+            m = utc ? this.clone().utc() : this;
+        if (m.year() < 0 || m.year() > 9999) {
+            return formatMoment(
+                m,
+                utc
+                    ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'
+                    : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ'
+            );
+        }
+        if (isFunction(Date.prototype.toISOString)) {
+            // native implementation is ~50x faster, use it when we can
+            if (utc) {
                 return this.toDate().toISOString();
             } else {
-                return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
+                return new Date(this.valueOf() + this.utcOffset() * 60 * 1000)
+                    .toISOString()
+                    .replace('Z', formatMoment(m, 'Z'));
             }
-        } else {
-            return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
         }
+        return formatMoment(
+            m,
+            utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ'
+        );
+    }
+
+    /**
+     * Return a human readable representation of a moment that can
+     * also be evaluated to get a new moment which is the same
+     *
+     * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects
+     */
+    function inspect() {
+        if (!this.isValid()) {
+            return 'moment.invalid(/* ' + this._i + ' */)';
+        }
+        var func = 'moment',
+            zone = '',
+            prefix,
+            year,
+            datetime,
+            suffix;
+        if (!this.isLocal()) {
+            func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone';
+            zone = 'Z';
+        }
+        prefix = '[' + func + '("]';
+        year = 0 <= this.year() && this.year() <= 9999 ? 'YYYY' : 'YYYYYY';
+        datetime = '-MM-DD[T]HH:mm:ss.SSS';
+        suffix = zone + '[")]';
+
+        return this.format(prefix + year + datetime + suffix);
     }
 
-    function format (inputString) {
-        var output = formatMoment(this, inputString || utils_hooks__hooks.defaultFormat);
+    function format(inputString) {
+        if (!inputString) {
+            inputString = this.isUtc()
+                ? hooks.defaultFormatUtc
+                : hooks.defaultFormat;
+        }
+        var output = formatMoment(this, inputString);
         return this.localeData().postformat(output);
     }
 
-    function from (time, withoutSuffix) {
-        if (this.isValid() &&
-                ((isMoment(time) && time.isValid()) ||
-                 local__createLocal(time).isValid())) {
-            return create__createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
+    function from(time, withoutSuffix) {
+        if (
+            this.isValid() &&
+            ((isMoment(time) && time.isValid()) || createLocal(time).isValid())
+        ) {
+            return createDuration({ to: this, from: time })
+                .locale(this.locale())
+                .humanize(!withoutSuffix);
         } else {
             return this.localeData().invalidDate();
         }
     }
 
-    function fromNow (withoutSuffix) {
-        return this.from(local__createLocal(), withoutSuffix);
+    function fromNow(withoutSuffix) {
+        return this.from(createLocal(), withoutSuffix);
     }
 
-    function to (time, withoutSuffix) {
-        if (this.isValid() &&
-                ((isMoment(time) && time.isValid()) ||
-                 local__createLocal(time).isValid())) {
-            return create__createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix);
+    function to(time, withoutSuffix) {
+        if (
+            this.isValid() &&
+            ((isMoment(time) && time.isValid()) || createLocal(time).isValid())
+        ) {
+            return createDuration({ from: this, to: time })
+                .locale(this.locale())
+                .humanize(!withoutSuffix);
         } else {
             return this.localeData().invalidDate();
         }
     }
 
-    function toNow (withoutSuffix) {
-        return this.to(local__createLocal(), withoutSuffix);
+    function toNow(withoutSuffix) {
+        return this.to(createLocal(), withoutSuffix);
     }
 
     // If passed a locale key, it will set the locale for this
     // instance.  Otherwise, it will return the locale configuration
     // variables for this instance.
-    function locale (key) {
+    function locale(key) {
         var newLocaleData;
 
         if (key === undefined) {
             return this._locale._abbr;
         } else {
-            newLocaleData = locale_locales__getLocale(key);
+            newLocaleData = getLocale(key);
             if (newLocaleData != null) {
                 this._locale = newLocaleData;
             }
         }
     );
 
-    function localeData () {
+    function localeData() {
         return this._locale;
     }
 
-    function startOf (units) {
+    var MS_PER_SECOND = 1000,
+        MS_PER_MINUTE = 60 * MS_PER_SECOND,
+        MS_PER_HOUR = 60 * MS_PER_MINUTE,
+        MS_PER_400_YEARS = (365 * 400 + 97) * 24 * MS_PER_HOUR;
+
+    // actual modulo - handles negative numbers (for dates before 1970):
+    function mod$1(dividend, divisor) {
+        return ((dividend % divisor) + divisor) % divisor;
+    }
+
+    function localStartOfDate(y, m, d) {
+        // the date constructor remaps years 0-99 to 1900-1999
+        if (y < 100 && y >= 0) {
+            // preserve leap years using a full 400 year cycle, then reset
+            return new Date(y + 400, m, d) - MS_PER_400_YEARS;
+        } else {
+            return new Date(y, m, d).valueOf();
+        }
+    }
+
+    function utcStartOfDate(y, m, d) {
+        // Date.UTC remaps years 0-99 to 1900-1999
+        if (y < 100 && y >= 0) {
+            // preserve leap years using a full 400 year cycle, then reset
+            return Date.UTC(y + 400, m, d) - MS_PER_400_YEARS;
+        } else {
+            return Date.UTC(y, m, d);
+        }
+    }
+
+    function startOf(units) {
+        var time, startOfDate;
         units = normalizeUnits(units);
-        // the following switch intentionally omits break keywords
-        // to utilize falling through the cases.
-        switch (units) {
-        case 'year':
-            this.month(0);
-            /* falls through */
-        case 'quarter':
-        case 'month':
-            this.date(1);
-            /* falls through */
-        case 'week':
-        case 'isoWeek':
-        case 'day':
-            this.hours(0);
-            /* falls through */
-        case 'hour':
-            this.minutes(0);
-            /* falls through */
-        case 'minute':
-            this.seconds(0);
-            /* falls through */
-        case 'second':
-            this.milliseconds(0);
-        }
-
-        // weeks are a special case
-        if (units === 'week') {
-            this.weekday(0);
-        }
-        if (units === 'isoWeek') {
-            this.isoWeekday(1);
-        }
-
-        // quarters are also special
-        if (units === 'quarter') {
-            this.month(Math.floor(this.month() / 3) * 3);
+        if (units === undefined || units === 'millisecond' || !this.isValid()) {
+            return this;
         }
 
+        startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;
+
+        switch (units) {
+            case 'year':
+                time = startOfDate(this.year(), 0, 1);
+                break;
+            case 'quarter':
+                time = startOfDate(
+                    this.year(),
+                    this.month() - (this.month() % 3),
+                    1
+                );
+                break;
+            case 'month':
+                time = startOfDate(this.year(), this.month(), 1);
+                break;
+            case 'week':
+                time = startOfDate(
+                    this.year(),
+                    this.month(),
+                    this.date() - this.weekday()
+                );
+                break;
+            case 'isoWeek':
+                time = startOfDate(
+                    this.year(),
+                    this.month(),
+                    this.date() - (this.isoWeekday() - 1)
+                );
+                break;
+            case 'day':
+            case 'date':
+                time = startOfDate(this.year(), this.month(), this.date());
+                break;
+            case 'hour':
+                time = this._d.valueOf();
+                time -= mod$1(
+                    time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE),
+                    MS_PER_HOUR
+                );
+                break;
+            case 'minute':
+                time = this._d.valueOf();
+                time -= mod$1(time, MS_PER_MINUTE);
+                break;
+            case 'second':
+                time = this._d.valueOf();
+                time -= mod$1(time, MS_PER_SECOND);
+                break;
+        }
+
+        this._d.setTime(time);
+        hooks.updateOffset(this, true);
         return this;
     }
 
-    function endOf (units) {
+    function endOf(units) {
+        var time, startOfDate;
         units = normalizeUnits(units);
-        if (units === undefined || units === 'millisecond') {
+        if (units === undefined || units === 'millisecond' || !this.isValid()) {
             return this;
         }
-        return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms');
-    }
 
-    function to_type__valueOf () {
-        return +this._d - ((this._offset || 0) * 60000);
+        startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;
+
+        switch (units) {
+            case 'year':
+                time = startOfDate(this.year() + 1, 0, 1) - 1;
+                break;
+            case 'quarter':
+                time =
+                    startOfDate(
+                        this.year(),
+                        this.month() - (this.month() % 3) + 3,
+                        1
+                    ) - 1;
+                break;
+            case 'month':
+                time = startOfDate(this.year(), this.month() + 1, 1) - 1;
+                break;
+            case 'week':
+                time =
+                    startOfDate(
+                        this.year(),
+                        this.month(),
+                        this.date() - this.weekday() + 7
+                    ) - 1;
+                break;
+            case 'isoWeek':
+                time =
+                    startOfDate(
+                        this.year(),
+                        this.month(),
+                        this.date() - (this.isoWeekday() - 1) + 7
+                    ) - 1;
+                break;
+            case 'day':
+            case 'date':
+                time = startOfDate(this.year(), this.month(), this.date() + 1) - 1;
+                break;
+            case 'hour':
+                time = this._d.valueOf();
+                time +=
+                    MS_PER_HOUR -
+                    mod$1(
+                        time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE),
+                        MS_PER_HOUR
+                    ) -
+                    1;
+                break;
+            case 'minute':
+                time = this._d.valueOf();
+                time += MS_PER_MINUTE - mod$1(time, MS_PER_MINUTE) - 1;
+                break;
+            case 'second':
+                time = this._d.valueOf();
+                time += MS_PER_SECOND - mod$1(time, MS_PER_SECOND) - 1;
+                break;
+        }
+
+        this._d.setTime(time);
+        hooks.updateOffset(this, true);
+        return this;
     }
 
-    function unix () {
-        return Math.floor(+this / 1000);
+    function valueOf() {
+        return this._d.valueOf() - (this._offset || 0) * 60000;
     }
 
-    function toDate () {
-        return this._offset ? new Date(+this) : this._d;
+    function unix() {
+        return Math.floor(this.valueOf() / 1000);
     }
 
-    function toArray () {
-        var m = this;
-        return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()];
+    function toDate() {
+        return new Date(this.valueOf());
     }
 
-    function toObject () {
+    function toArray() {
+        var m = this;
+        return [
+            m.year(),
+            m.month(),
+            m.date(),
+            m.hour(),
+            m.minute(),
+            m.second(),
+            m.millisecond(),
+        ];
+    }
+
+    function toObject() {
         var m = this;
         return {
             years: m.year(),
             hours: m.hours(),
             minutes: m.minutes(),
             seconds: m.seconds(),
-            milliseconds: m.milliseconds()
+            milliseconds: m.milliseconds(),
         };
     }
 
-    function toJSON () {
-        // JSON.stringify(new Date(NaN)) === 'null'
-        return this.isValid() ? this.toISOString() : 'null';
+    function toJSON() {
+        // new Date(NaN).toJSON() === null
+        return this.isValid() ? this.toISOString() : null;
     }
 
-    function moment_valid__isValid () {
-        return valid__isValid(this);
+    function isValid$2() {
+        return isValid(this);
     }
 
-    function parsingFlags () {
+    function parsingFlags() {
         return extend({}, getParsingFlags(this));
     }
 
-    function invalidAt () {
+    function invalidAt() {
         return getParsingFlags(this).overflow;
     }
 
             format: this._f,
             locale: this._locale,
             isUTC: this._isUTC,
-            strict: this._strict
+            strict: this._strict,
         };
     }
 
-    // FORMATTING
-
-    addFormatToken(0, ['gg', 2], 0, function () {
-        return this.weekYear() % 100;
-    });
-
-    addFormatToken(0, ['GG', 2], 0, function () {
-        return this.isoWeekYear() % 100;
-    });
-
-    function addWeekYearFormatToken (token, getter) {
-        addFormatToken(0, [token, token.length], 0, getter);
-    }
-
-    addWeekYearFormatToken('gggg',     'weekYear');
-    addWeekYearFormatToken('ggggg',    'weekYear');
-    addWeekYearFormatToken('GGGG',  'isoWeekYear');
-    addWeekYearFormatToken('GGGGG', 'isoWeekYear');
-
-    // ALIASES
+    addFormatToken('N', 0, 0, 'eraAbbr');
+    addFormatToken('NN', 0, 0, 'eraAbbr');
+    addFormatToken('NNN', 0, 0, 'eraAbbr');
+    addFormatToken('NNNN', 0, 0, 'eraName');
+    addFormatToken('NNNNN', 0, 0, 'eraNarrow');
+
+    addFormatToken('y', ['y', 1], 'yo', 'eraYear');
+    addFormatToken('y', ['yy', 2], 0, 'eraYear');
+    addFormatToken('y', ['yyy', 3], 0, 'eraYear');
+    addFormatToken('y', ['yyyy', 4], 0, 'eraYear');
+
+    addRegexToken('N', matchEraAbbr);
+    addRegexToken('NN', matchEraAbbr);
+    addRegexToken('NNN', matchEraAbbr);
+    addRegexToken('NNNN', matchEraName);
+    addRegexToken('NNNNN', matchEraNarrow);
+
+    addParseToken(
+        ['N', 'NN', 'NNN', 'NNNN', 'NNNNN'],
+        function (input, array, config, token) {
+            var era = config._locale.erasParse(input, token, config._strict);
+            if (era) {
+                getParsingFlags(config).era = era;
+            } else {
+                getParsingFlags(config).invalidEra = input;
+            }
+        }
+    );
 
-    addUnitAlias('weekYear', 'gg');
-    addUnitAlias('isoWeekYear', 'GG');
+    addRegexToken('y', matchUnsigned);
+    addRegexToken('yy', matchUnsigned);
+    addRegexToken('yyy', matchUnsigned);
+    addRegexToken('yyyy', matchUnsigned);
+    addRegexToken('yo', matchEraYearOrdinal);
 
-    // PARSING
+    addParseToken(['y', 'yy', 'yyy', 'yyyy'], YEAR);
+    addParseToken(['yo'], function (input, array, config, token) {
+        var match;
+        if (config._locale._eraYearOrdinalRegex) {
+            match = input.match(config._locale._eraYearOrdinalRegex);
+        }
 
-    addRegexToken('G',      matchSigned);
-    addRegexToken('g',      matchSigned);
-    addRegexToken('GG',     match1to2, match2);
-    addRegexToken('gg',     match1to2, match2);
-    addRegexToken('GGGG',   match1to4, match4);
-    addRegexToken('gggg',   match1to4, match4);
-    addRegexToken('GGGGG',  match1to6, match6);
-    addRegexToken('ggggg',  match1to6, match6);
-
-    addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) {
-        week[token.substr(0, 2)] = toInt(input);
+        if (config._locale.eraYearOrdinalParse) {
+            array[YEAR] = config._locale.eraYearOrdinalParse(input, match);
+        } else {
+            array[YEAR] = parseInt(input, 10);
+        }
     });
 
-    addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
-        week[token] = utils_hooks__hooks.parseTwoDigitYear(input);
-    });
+    function localeEras(m, format) {
+        var i,
+            l,
+            date,
+            eras = this._eras || getLocale('en')._eras;
+        for (i = 0, l = eras.length; i < l; ++i) {
+            switch (typeof eras[i].since) {
+                case 'string':
+                    // truncate time
+                    date = hooks(eras[i].since).startOf('day');
+                    eras[i].since = date.valueOf();
+                    break;
+            }
 
-    // MOMENTS
+            switch (typeof eras[i].until) {
+                case 'undefined':
+                    eras[i].until = +Infinity;
+                    break;
+                case 'string':
+                    // truncate time
+                    date = hooks(eras[i].until).startOf('day').valueOf();
+                    eras[i].until = date.valueOf();
+                    break;
+            }
+        }
+        return eras;
+    }
+
+    function localeErasParse(eraName, format, strict) {
+        var i,
+            l,
+            eras = this.eras(),
+            name,
+            abbr,
+            narrow;
+        eraName = eraName.toUpperCase();
+
+        for (i = 0, l = eras.length; i < l; ++i) {
+            name = eras[i].name.toUpperCase();
+            abbr = eras[i].abbr.toUpperCase();
+            narrow = eras[i].narrow.toUpperCase();
+
+            if (strict) {
+                switch (format) {
+                    case 'N':
+                    case 'NN':
+                    case 'NNN':
+                        if (abbr === eraName) {
+                            return eras[i];
+                        }
+                        break;
 
-    function getSetWeekYear (input) {
-        return getSetWeekYearHelper.call(this,
-                input,
-                this.week(),
-                this.weekday(),
-                this.localeData()._week.dow,
-                this.localeData()._week.doy);
-    }
+                    case 'NNNN':
+                        if (name === eraName) {
+                            return eras[i];
+                        }
+                        break;
 
-    function getSetISOWeekYear (input) {
-        return getSetWeekYearHelper.call(this,
-                input, this.isoWeek(), this.isoWeekday(), 1, 4);
+                    case 'NNNNN':
+                        if (narrow === eraName) {
+                            return eras[i];
+                        }
+                        break;
+                }
+            } else if ([name, abbr, narrow].indexOf(eraName) >= 0) {
+                return eras[i];
+            }
+        }
     }
 
-    function getISOWeeksInYear () {
-        return weeksInYear(this.year(), 1, 4);
+    function localeErasConvertYear(era, year) {
+        var dir = era.since <= era.until ? +1 : -1;
+        if (year === undefined) {
+            return hooks(era.since).year();
+        } else {
+            return hooks(era.since).year() + (year - era.offset) * dir;
+        }
     }
 
-    function getWeeksInYear () {
-        var weekInfo = this.localeData()._week;
-        return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
-    }
+    function getEraName() {
+        var i,
+            l,
+            val,
+            eras = this.localeData().eras();
+        for (i = 0, l = eras.length; i < l; ++i) {
+            // truncate time
+            val = this.clone().startOf('day').valueOf();
 
-    function getSetWeekYearHelper(input, week, weekday, dow, doy) {
-        var weeksTarget;
-        if (input == null) {
-            return weekOfYear(this, dow, doy).year;
-        } else {
-            weeksTarget = weeksInYear(input, dow, doy);
-            if (week > weeksTarget) {
-                week = weeksTarget;
+            if (eras[i].since <= val && val <= eras[i].until) {
+                return eras[i].name;
+            }
+            if (eras[i].until <= val && val <= eras[i].since) {
+                return eras[i].name;
             }
-            return setWeekAll.call(this, input, week, weekday, dow, doy);
         }
-    }
-
-    function setWeekAll(weekYear, week, weekday, dow, doy) {
-        var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),
-            date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);
 
-        // console.log("got", weekYear, week, weekday, "set", date.toISOString());
-        this.year(date.getUTCFullYear());
-        this.month(date.getUTCMonth());
-        this.date(date.getUTCDate());
-        return this;
+        return '';
     }
 
-    // FORMATTING
-
-    addFormatToken('Q', 0, 'Qo', 'quarter');
-
-    // ALIASES
-
-    addUnitAlias('quarter', 'Q');
-
-    // PARSING
-
-    addRegexToken('Q', match1);
-    addParseToken('Q', function (input, array) {
-        array[MONTH] = (toInt(input) - 1) * 3;
-    });
+    function getEraNarrow() {
+        var i,
+            l,
+            val,
+            eras = this.localeData().eras();
+        for (i = 0, l = eras.length; i < l; ++i) {
+            // truncate time
+            val = this.clone().startOf('day').valueOf();
 
-    // MOMENTS
+            if (eras[i].since <= val && val <= eras[i].until) {
+                return eras[i].narrow;
+            }
+            if (eras[i].until <= val && val <= eras[i].since) {
+                return eras[i].narrow;
+            }
+        }
 
-    function getSetQuarter (input) {
-        return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
+        return '';
     }
 
-    // FORMATTING
-
-    addFormatToken('w', ['ww', 2], 'wo', 'week');
-    addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');
-
-    // ALIASES
-
-    addUnitAlias('week', 'w');
-    addUnitAlias('isoWeek', 'W');
+    function getEraAbbr() {
+        var i,
+            l,
+            val,
+            eras = this.localeData().eras();
+        for (i = 0, l = eras.length; i < l; ++i) {
+            // truncate time
+            val = this.clone().startOf('day').valueOf();
 
-    // PARSING
+            if (eras[i].since <= val && val <= eras[i].until) {
+                return eras[i].abbr;
+            }
+            if (eras[i].until <= val && val <= eras[i].since) {
+                return eras[i].abbr;
+            }
+        }
 
-    addRegexToken('w',  match1to2);
-    addRegexToken('ww', match1to2, match2);
-    addRegexToken('W',  match1to2);
-    addRegexToken('WW', match1to2, match2);
+        return '';
+    }
 
-    addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) {
-        week[token.substr(0, 1)] = toInt(input);
-    });
+    function getEraYear() {
+        var i,
+            l,
+            dir,
+            val,
+            eras = this.localeData().eras();
+        for (i = 0, l = eras.length; i < l; ++i) {
+            dir = eras[i].since <= eras[i].until ? +1 : -1;
 
-    // HELPERS
+            // truncate time
+            val = this.clone().startOf('day').valueOf();
 
-    // LOCALES
+            if (
+                (eras[i].since <= val && val <= eras[i].until) ||
+                (eras[i].until <= val && val <= eras[i].since)
+            ) {
+                return (
+                    (this.year() - hooks(eras[i].since).year()) * dir +
+                    eras[i].offset
+                );
+            }
+        }
 
-    function localeWeek (mom) {
-        return weekOfYear(mom, this._week.dow, this._week.doy).week;
+        return this.year();
     }
 
-    var defaultLocaleWeek = {
-        dow : 0, // Sunday is the first day of the week.
-        doy : 6  // The week that contains Jan 1st is the first week of the year.
-    };
-
-    function localeFirstDayOfWeek () {
-        return this._week.dow;
+    function erasNameRegex(isStrict) {
+        if (!hasOwnProp(this, '_erasNameRegex')) {
+            computeErasParse.call(this);
+        }
+        return isStrict ? this._erasNameRegex : this._erasRegex;
     }
 
-    function localeFirstDayOfYear () {
-        return this._week.doy;
+    function erasAbbrRegex(isStrict) {
+        if (!hasOwnProp(this, '_erasAbbrRegex')) {
+            computeErasParse.call(this);
+        }
+        return isStrict ? this._erasAbbrRegex : this._erasRegex;
     }
 
-    // MOMENTS
-
-    function getSetWeek (input) {
-        var week = this.localeData().week(this);
-        return input == null ? week : this.add((input - week) * 7, 'd');
+    function erasNarrowRegex(isStrict) {
+        if (!hasOwnProp(this, '_erasNarrowRegex')) {
+            computeErasParse.call(this);
+        }
+        return isStrict ? this._erasNarrowRegex : this._erasRegex;
     }
 
-    function getSetISOWeek (input) {
-        var week = weekOfYear(this, 1, 4).week;
-        return input == null ? week : this.add((input - week) * 7, 'd');
+    function matchEraAbbr(isStrict, locale) {
+        return locale.erasAbbrRegex(isStrict);
     }
 
-    // FORMATTING
-
-    addFormatToken('D', ['DD', 2], 'Do', 'date');
-
-    // ALIASES
+    function matchEraName(isStrict, locale) {
+        return locale.erasNameRegex(isStrict);
+    }
 
-    addUnitAlias('date', 'D');
+    function matchEraNarrow(isStrict, locale) {
+        return locale.erasNarrowRegex(isStrict);
+    }
 
-    // PARSING
+    function matchEraYearOrdinal(isStrict, locale) {
+        return locale._eraYearOrdinalRegex || matchUnsigned;
+    }
 
-    addRegexToken('D',  match1to2);
-    addRegexToken('DD', match1to2, match2);
-    addRegexToken('Do', function (isStrict, locale) {
-        return isStrict ? locale._ordinalParse : locale._ordinalParseLenient;
-    });
+    function computeErasParse() {
+        var abbrPieces = [],
+            namePieces = [],
+            narrowPieces = [],
+            mixedPieces = [],
+            i,
+            l,
+            eras = this.eras();
 
-    addParseToken(['D', 'DD'], DATE);
-    addParseToken('Do', function (input, array) {
-        array[DATE] = toInt(input.match(match1to2)[0], 10);
-    });
+        for (i = 0, l = eras.length; i < l; ++i) {
+            namePieces.push(regexEscape(eras[i].name));
+            abbrPieces.push(regexEscape(eras[i].abbr));
+            narrowPieces.push(regexEscape(eras[i].narrow));
 
-    // MOMENTS
+            mixedPieces.push(regexEscape(eras[i].name));
+            mixedPieces.push(regexEscape(eras[i].abbr));
+            mixedPieces.push(regexEscape(eras[i].narrow));
+        }
 
-    var getSetDayOfMonth = makeGetSet('Date', true);
+        this._erasRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+        this._erasNameRegex = new RegExp('^(' + namePieces.join('|') + ')', 'i');
+        this._erasAbbrRegex = new RegExp('^(' + abbrPieces.join('|') + ')', 'i');
+        this._erasNarrowRegex = new RegExp(
+            '^(' + narrowPieces.join('|') + ')',
+            'i'
+        );
+    }
 
     // FORMATTING
 
-    addFormatToken('d', 0, 'do', 'day');
-
-    addFormatToken('dd', 0, 0, function (format) {
-        return this.localeData().weekdaysMin(this, format);
+    addFormatToken(0, ['gg', 2], 0, function () {
+        return this.weekYear() % 100;
     });
 
-    addFormatToken('ddd', 0, 0, function (format) {
-        return this.localeData().weekdaysShort(this, format);
+    addFormatToken(0, ['GG', 2], 0, function () {
+        return this.isoWeekYear() % 100;
     });
 
-    addFormatToken('dddd', 0, 0, function (format) {
-        return this.localeData().weekdays(this, format);
-    });
+    function addWeekYearFormatToken(token, getter) {
+        addFormatToken(0, [token, token.length], 0, getter);
+    }
 
-    addFormatToken('e', 0, 0, 'weekday');
-    addFormatToken('E', 0, 0, 'isoWeekday');
+    addWeekYearFormatToken('gggg', 'weekYear');
+    addWeekYearFormatToken('ggggg', 'weekYear');
+    addWeekYearFormatToken('GGGG', 'isoWeekYear');
+    addWeekYearFormatToken('GGGGG', 'isoWeekYear');
 
     // ALIASES
 
-    addUnitAlias('day', 'd');
-    addUnitAlias('weekday', 'e');
-    addUnitAlias('isoWeekday', 'E');
-
-    // PARSING
+    addUnitAlias('weekYear', 'gg');
+    addUnitAlias('isoWeekYear', 'GG');
 
-    addRegexToken('d',    match1to2);
-    addRegexToken('e',    match1to2);
-    addRegexToken('E',    match1to2);
-    addRegexToken('dd',   matchWord);
-    addRegexToken('ddd',  matchWord);
-    addRegexToken('dddd', matchWord);
+    // PRIORITY
 
-    addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {
-        var weekday = config._locale.weekdaysParse(input, token, config._strict);
-        // if we didn't get a weekday name, mark the date as invalid
-        if (weekday != null) {
-            week.d = weekday;
-        } else {
-            getParsingFlags(config).invalidWeekday = input;
-        }
-    });
+    addUnitPriority('weekYear', 1);
+    addUnitPriority('isoWeekYear', 1);
 
-    addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
-        week[token] = toInt(input);
-    });
+    // PARSING
 
-    // HELPERS
+    addRegexToken('G', matchSigned);
+    addRegexToken('g', matchSigned);
+    addRegexToken('GG', match1to2, match2);
+    addRegexToken('gg', match1to2, match2);
+    addRegexToken('GGGG', match1to4, match4);
+    addRegexToken('gggg', match1to4, match4);
+    addRegexToken('GGGGG', match1to6, match6);
+    addRegexToken('ggggg', match1to6, match6);
 
-    function parseWeekday(input, locale) {
-        if (typeof input !== 'string') {
-            return input;
+    addWeekParseToken(
+        ['gggg', 'ggggg', 'GGGG', 'GGGGG'],
+        function (input, week, config, token) {
+            week[token.substr(0, 2)] = toInt(input);
         }
+    );
 
-        if (!isNaN(input)) {
-            return parseInt(input, 10);
-        }
+    addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
+        week[token] = hooks.parseTwoDigitYear(input);
+    });
 
-        input = locale.weekdaysParse(input);
-        if (typeof input === 'number') {
-            return input;
-        }
+    // MOMENTS
 
-        return null;
+    function getSetWeekYear(input) {
+        return getSetWeekYearHelper.call(
+            this,
+            input,
+            this.week(),
+            this.weekday(),
+            this.localeData()._week.dow,
+            this.localeData()._week.doy
+        );
     }
 
-    // LOCALES
-
-    var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_');
-    function localeWeekdays (m, format) {
-        return isArray(this._weekdays) ? this._weekdays[m.day()] :
-            this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()];
+    function getSetISOWeekYear(input) {
+        return getSetWeekYearHelper.call(
+            this,
+            input,
+            this.isoWeek(),
+            this.isoWeekday(),
+            1,
+            4
+        );
     }
 
-    var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_');
-    function localeWeekdaysShort (m) {
-        return this._weekdaysShort[m.day()];
+    function getISOWeeksInYear() {
+        return weeksInYear(this.year(), 1, 4);
     }
 
-    var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_');
-    function localeWeekdaysMin (m) {
-        return this._weekdaysMin[m.day()];
+    function getISOWeeksInISOWeekYear() {
+        return weeksInYear(this.isoWeekYear(), 1, 4);
     }
 
-    function localeWeekdaysParse (weekdayName, format, strict) {
-        var i, mom, regex;
-
-        if (!this._weekdaysParse) {
-            this._weekdaysParse = [];
-            this._minWeekdaysParse = [];
-            this._shortWeekdaysParse = [];
-            this._fullWeekdaysParse = [];
-        }
-
-        for (i = 0; i < 7; i++) {
-            // make the regex if we don't have it already
-
-            mom = local__createLocal([2000, 1]).day(i);
-            if (strict && !this._fullWeekdaysParse[i]) {
-                this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i');
-                this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i');
-                this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i');
-            }
-            if (!this._weekdaysParse[i]) {
-                regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
-                this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
-            }
-            // test the regex
-            if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) {
-                return i;
-            } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) {
-                return i;
-            } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) {
-                return i;
-            } else if (!strict && this._weekdaysParse[i].test(weekdayName)) {
-                return i;
-            }
-        }
+    function getWeeksInYear() {
+        var weekInfo = this.localeData()._week;
+        return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
     }
 
-    // MOMENTS
+    function getWeeksInWeekYear() {
+        var weekInfo = this.localeData()._week;
+        return weeksInYear(this.weekYear(), weekInfo.dow, weekInfo.doy);
+    }
 
-    function getSetDayOfWeek (input) {
-        if (!this.isValid()) {
-            return input != null ? this : NaN;
-        }
-        var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
-        if (input != null) {
-            input = parseWeekday(input, this.localeData());
-            return this.add(input - day, 'd');
+    function getSetWeekYearHelper(input, week, weekday, dow, doy) {
+        var weeksTarget;
+        if (input == null) {
+            return weekOfYear(this, dow, doy).year;
         } else {
-            return day;
+            weeksTarget = weeksInYear(input, dow, doy);
+            if (week > weeksTarget) {
+                week = weeksTarget;
+            }
+            return setWeekAll.call(this, input, week, weekday, dow, doy);
         }
     }
 
-    function getSetLocaleDayOfWeek (input) {
-        if (!this.isValid()) {
-            return input != null ? this : NaN;
-        }
-        var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
-        return input == null ? weekday : this.add(input - weekday, 'd');
-    }
+    function setWeekAll(weekYear, week, weekday, dow, doy) {
+        var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),
+            date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);
 
-    function getSetISODayOfWeek (input) {
-        if (!this.isValid()) {
-            return input != null ? this : NaN;
-        }
-        // behaves the same as moment#day except
-        // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
-        // as a setter, sunday should belong to the previous week.
-        return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7);
+        this.year(date.getUTCFullYear());
+        this.month(date.getUTCMonth());
+        this.date(date.getUTCDate());
+        return this;
     }
 
     // FORMATTING
 
-    addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');
+    addFormatToken('Q', 0, 'Qo', 'quarter');
 
     // ALIASES
 
-    addUnitAlias('dayOfYear', 'DDD');
+    addUnitAlias('quarter', 'Q');
+
+    // PRIORITY
+
+    addUnitPriority('quarter', 7);
 
     // PARSING
 
-    addRegexToken('DDD',  match1to3);
-    addRegexToken('DDDD', match3);
-    addParseToken(['DDD', 'DDDD'], function (input, array, config) {
-        config._dayOfYear = toInt(input);
+    addRegexToken('Q', match1);
+    addParseToken('Q', function (input, array) {
+        array[MONTH] = (toInt(input) - 1) * 3;
     });
 
-    // HELPERS
-
     // MOMENTS
 
-    function getSetDayOfYear (input) {
-        var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1;
-        return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
+    function getSetQuarter(input) {
+        return input == null
+            ? Math.ceil((this.month() + 1) / 3)
+            : this.month((input - 1) * 3 + (this.month() % 3));
     }
 
     // FORMATTING
 
-    function hFormat() {
-        return this.hours() % 12 || 12;
-    }
+    addFormatToken('D', ['DD', 2], 'Do', 'date');
 
-    addFormatToken('H', ['HH', 2], 0, 'hour');
-    addFormatToken('h', ['hh', 2], 0, hFormat);
+    // ALIASES
 
-    addFormatToken('hmm', 0, 0, function () {
-        return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);
-    });
+    addUnitAlias('date', 'D');
 
-    addFormatToken('hmmss', 0, 0, function () {
-        return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) +
-            zeroFill(this.seconds(), 2);
-    });
+    // PRIORITY
+    addUnitPriority('date', 9);
 
-    addFormatToken('Hmm', 0, 0, function () {
-        return '' + this.hours() + zeroFill(this.minutes(), 2);
+    // PARSING
+
+    addRegexToken('D', match1to2);
+    addRegexToken('DD', match1to2, match2);
+    addRegexToken('Do', function (isStrict, locale) {
+        // TODO: Remove "ordinalParse" fallback in next major release.
+        return isStrict
+            ? locale._dayOfMonthOrdinalParse || locale._ordinalParse
+            : locale._dayOfMonthOrdinalParseLenient;
     });
 
-    addFormatToken('Hmmss', 0, 0, function () {
-        return '' + this.hours() + zeroFill(this.minutes(), 2) +
-            zeroFill(this.seconds(), 2);
+    addParseToken(['D', 'DD'], DATE);
+    addParseToken('Do', function (input, array) {
+        array[DATE] = toInt(input.match(match1to2)[0]);
     });
 
-    function meridiem (token, lowercase) {
-        addFormatToken(token, 0, 0, function () {
-            return this.localeData().meridiem(this.hours(), this.minutes(), lowercase);
-        });
-    }
+    // MOMENTS
 
-    meridiem('a', true);
-    meridiem('A', false);
+    var getSetDayOfMonth = makeGetSet('Date', true);
 
-    // ALIASES
+    // FORMATTING
 
-    addUnitAlias('hour', 'h');
+    addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');
 
-    // PARSING
+    // ALIASES
 
-    function matchMeridiem (isStrict, locale) {
-        return locale._meridiemParse;
-    }
+    addUnitAlias('dayOfYear', 'DDD');
 
-    addRegexToken('a',  matchMeridiem);
-    addRegexToken('A',  matchMeridiem);
-    addRegexToken('H',  match1to2);
-    addRegexToken('h',  match1to2);
-    addRegexToken('HH', match1to2, match2);
-    addRegexToken('hh', match1to2, match2);
+    // PRIORITY
+    addUnitPriority('dayOfYear', 4);
 
-    addRegexToken('hmm', match3to4);
-    addRegexToken('hmmss', match5to6);
-    addRegexToken('Hmm', match3to4);
-    addRegexToken('Hmmss', match5to6);
+    // PARSING
 
-    addParseToken(['H', 'HH'], HOUR);
-    addParseToken(['a', 'A'], function (input, array, config) {
-        config._isPm = config._locale.isPM(input);
-        config._meridiem = input;
-    });
-    addParseToken(['h', 'hh'], function (input, array, config) {
-        array[HOUR] = toInt(input);
-        getParsingFlags(config).bigHour = true;
-    });
-    addParseToken('hmm', function (input, array, config) {
-        var pos = input.length - 2;
-        array[HOUR] = toInt(input.substr(0, pos));
-        array[MINUTE] = toInt(input.substr(pos));
-        getParsingFlags(config).bigHour = true;
-    });
-    addParseToken('hmmss', function (input, array, config) {
-        var pos1 = input.length - 4;
-        var pos2 = input.length - 2;
-        array[HOUR] = toInt(input.substr(0, pos1));
-        array[MINUTE] = toInt(input.substr(pos1, 2));
-        array[SECOND] = toInt(input.substr(pos2));
-        getParsingFlags(config).bigHour = true;
-    });
-    addParseToken('Hmm', function (input, array, config) {
-        var pos = input.length - 2;
-        array[HOUR] = toInt(input.substr(0, pos));
-        array[MINUTE] = toInt(input.substr(pos));
-    });
-    addParseToken('Hmmss', function (input, array, config) {
-        var pos1 = input.length - 4;
-        var pos2 = input.length - 2;
-        array[HOUR] = toInt(input.substr(0, pos1));
-        array[MINUTE] = toInt(input.substr(pos1, 2));
-        array[SECOND] = toInt(input.substr(pos2));
+    addRegexToken('DDD', match1to3);
+    addRegexToken('DDDD', match3);
+    addParseToken(['DDD', 'DDDD'], function (input, array, config) {
+        config._dayOfYear = toInt(input);
     });
 
-    // LOCALES
-
-    function localeIsPM (input) {
-        // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
-        // Using charAt should be more compatible.
-        return ((input + '').toLowerCase().charAt(0) === 'p');
-    }
-
-    var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i;
-    function localeMeridiem (hours, minutes, isLower) {
-        if (hours > 11) {
-            return isLower ? 'pm' : 'PM';
-        } else {
-            return isLower ? 'am' : 'AM';
-        }
-    }
-
+    // HELPERS
 
     // MOMENTS
 
-    // Setting the hour should keep the time, because the user explicitly
-    // specified which hour he wants. So trying to maintain the same hour (in
-    // a new timezone) makes sense. Adding/subtracting hours does not follow
-    // this rule.
-    var getSetHour = makeGetSet('Hours', true);
+    function getSetDayOfYear(input) {
+        var dayOfYear =
+            Math.round(
+                (this.clone().startOf('day') - this.clone().startOf('year')) / 864e5
+            ) + 1;
+        return input == null ? dayOfYear : this.add(input - dayOfYear, 'd');
+    }
 
     // FORMATTING
 
 
     addUnitAlias('minute', 'm');
 
+    // PRIORITY
+
+    addUnitPriority('minute', 14);
+
     // PARSING
 
-    addRegexToken('m',  match1to2);
+    addRegexToken('m', match1to2);
     addRegexToken('mm', match1to2, match2);
     addParseToken(['m', 'mm'], MINUTE);
 
 
     addUnitAlias('second', 's');
 
+    // PRIORITY
+
+    addUnitPriority('second', 15);
+
     // PARSING
 
-    addRegexToken('s',  match1to2);
+    addRegexToken('s', match1to2);
     addRegexToken('ss', match1to2, match2);
     addParseToken(['s', 'ss'], SECOND);
 
         return this.millisecond() * 1000000;
     });
 
-
     // ALIASES
 
     addUnitAlias('millisecond', 'ms');
 
+    // PRIORITY
+
+    addUnitPriority('millisecond', 16);
+
     // PARSING
 
-    addRegexToken('S',    match1to3, match1);
-    addRegexToken('SS',   match1to3, match2);
-    addRegexToken('SSS',  match1to3, match3);
+    addRegexToken('S', match1to3, match1);
+    addRegexToken('SS', match1to3, match2);
+    addRegexToken('SSS', match1to3, match3);
 
-    var token;
+    var token, getSetMillisecond;
     for (token = 'SSSS'; token.length <= 9; token += 'S') {
         addRegexToken(token, matchUnsigned);
     }
     for (token = 'S'; token.length <= 9; token += 'S') {
         addParseToken(token, parseMs);
     }
-    // MOMENTS
 
-    var getSetMillisecond = makeGetSet('Milliseconds', false);
+    getSetMillisecond = makeGetSet('Milliseconds', false);
 
     // FORMATTING
 
-    addFormatToken('z',  0, 0, 'zoneAbbr');
+    addFormatToken('z', 0, 0, 'zoneAbbr');
     addFormatToken('zz', 0, 0, 'zoneName');
 
     // MOMENTS
 
-    function getZoneAbbr () {
+    function getZoneAbbr() {
         return this._isUTC ? 'UTC' : '';
     }
 
-    function getZoneName () {
+    function getZoneName() {
         return this._isUTC ? 'Coordinated Universal Time' : '';
     }
 
-    var momentPrototype__proto = Moment.prototype;
-
-    momentPrototype__proto.add               = add_subtract__add;
-    momentPrototype__proto.calendar          = moment_calendar__calendar;
-    momentPrototype__proto.clone             = clone;
-    momentPrototype__proto.diff              = diff;
-    momentPrototype__proto.endOf             = endOf;
-    momentPrototype__proto.format            = format;
-    momentPrototype__proto.from              = from;
-    momentPrototype__proto.fromNow           = fromNow;
-    momentPrototype__proto.to                = to;
-    momentPrototype__proto.toNow             = toNow;
-    momentPrototype__proto.get               = getSet;
-    momentPrototype__proto.invalidAt         = invalidAt;
-    momentPrototype__proto.isAfter           = isAfter;
-    momentPrototype__proto.isBefore          = isBefore;
-    momentPrototype__proto.isBetween         = isBetween;
-    momentPrototype__proto.isSame            = isSame;
-    momentPrototype__proto.isSameOrAfter     = isSameOrAfter;
-    momentPrototype__proto.isSameOrBefore    = isSameOrBefore;
-    momentPrototype__proto.isValid           = moment_valid__isValid;
-    momentPrototype__proto.lang              = lang;
-    momentPrototype__proto.locale            = locale;
-    momentPrototype__proto.localeData        = localeData;
-    momentPrototype__proto.max               = prototypeMax;
-    momentPrototype__proto.min               = prototypeMin;
-    momentPrototype__proto.parsingFlags      = parsingFlags;
-    momentPrototype__proto.set               = getSet;
-    momentPrototype__proto.startOf           = startOf;
-    momentPrototype__proto.subtract          = add_subtract__subtract;
-    momentPrototype__proto.toArray           = toArray;
-    momentPrototype__proto.toObject          = toObject;
-    momentPrototype__proto.toDate            = toDate;
-    momentPrototype__proto.toISOString       = moment_format__toISOString;
-    momentPrototype__proto.toJSON            = toJSON;
-    momentPrototype__proto.toString          = toString;
-    momentPrototype__proto.unix              = unix;
-    momentPrototype__proto.valueOf           = to_type__valueOf;
-    momentPrototype__proto.creationData      = creationData;
-
-    // Year
-    momentPrototype__proto.year       = getSetYear;
-    momentPrototype__proto.isLeapYear = getIsLeapYear;
-
-    // Week Year
-    momentPrototype__proto.weekYear    = getSetWeekYear;
-    momentPrototype__proto.isoWeekYear = getSetISOWeekYear;
-
-    // Quarter
-    momentPrototype__proto.quarter = momentPrototype__proto.quarters = getSetQuarter;
-
-    // Month
-    momentPrototype__proto.month       = getSetMonth;
-    momentPrototype__proto.daysInMonth = getDaysInMonth;
-
-    // Week
-    momentPrototype__proto.week           = momentPrototype__proto.weeks        = getSetWeek;
-    momentPrototype__proto.isoWeek        = momentPrototype__proto.isoWeeks     = getSetISOWeek;
-    momentPrototype__proto.weeksInYear    = getWeeksInYear;
-    momentPrototype__proto.isoWeeksInYear = getISOWeeksInYear;
-
-    // Day
-    momentPrototype__proto.date       = getSetDayOfMonth;
-    momentPrototype__proto.day        = momentPrototype__proto.days             = getSetDayOfWeek;
-    momentPrototype__proto.weekday    = getSetLocaleDayOfWeek;
-    momentPrototype__proto.isoWeekday = getSetISODayOfWeek;
-    momentPrototype__proto.dayOfYear  = getSetDayOfYear;
-
-    // Hour
-    momentPrototype__proto.hour = momentPrototype__proto.hours = getSetHour;
-
-    // Minute
-    momentPrototype__proto.minute = momentPrototype__proto.minutes = getSetMinute;
-
-    // Second
-    momentPrototype__proto.second = momentPrototype__proto.seconds = getSetSecond;
-
-    // Millisecond
-    momentPrototype__proto.millisecond = momentPrototype__proto.milliseconds = getSetMillisecond;
-
-    // Offset
-    momentPrototype__proto.utcOffset            = getSetOffset;
-    momentPrototype__proto.utc                  = setOffsetToUTC;
-    momentPrototype__proto.local                = setOffsetToLocal;
-    momentPrototype__proto.parseZone            = setOffsetToParsedOffset;
-    momentPrototype__proto.hasAlignedHourOffset = hasAlignedHourOffset;
-    momentPrototype__proto.isDST                = isDaylightSavingTime;
-    momentPrototype__proto.isDSTShifted         = isDaylightSavingTimeShifted;
-    momentPrototype__proto.isLocal              = isLocal;
-    momentPrototype__proto.isUtcOffset          = isUtcOffset;
-    momentPrototype__proto.isUtc                = isUtc;
-    momentPrototype__proto.isUTC                = isUtc;
-
-    // Timezone
-    momentPrototype__proto.zoneAbbr = getZoneAbbr;
-    momentPrototype__proto.zoneName = getZoneName;
-
-    // Deprecations
-    momentPrototype__proto.dates  = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth);
-    momentPrototype__proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth);
-    momentPrototype__proto.years  = deprecate('years accessor is deprecated. Use year instead', getSetYear);
-    momentPrototype__proto.zone   = deprecate('moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779', getSetZone);
-
-    var momentPrototype = momentPrototype__proto;
-
-    function moment__createUnix (input) {
-        return local__createLocal(input * 1000);
-    }
-
-    function moment__createInZone () {
-        return local__createLocal.apply(null, arguments).parseZone();
+    var proto = Moment.prototype;
+
+    proto.add = add;
+    proto.calendar = calendar$1;
+    proto.clone = clone;
+    proto.diff = diff;
+    proto.endOf = endOf;
+    proto.format = format;
+    proto.from = from;
+    proto.fromNow = fromNow;
+    proto.to = to;
+    proto.toNow = toNow;
+    proto.get = stringGet;
+    proto.invalidAt = invalidAt;
+    proto.isAfter = isAfter;
+    proto.isBefore = isBefore;
+    proto.isBetween = isBetween;
+    proto.isSame = isSame;
+    proto.isSameOrAfter = isSameOrAfter;
+    proto.isSameOrBefore = isSameOrBefore;
+    proto.isValid = isValid$2;
+    proto.lang = lang;
+    proto.locale = locale;
+    proto.localeData = localeData;
+    proto.max = prototypeMax;
+    proto.min = prototypeMin;
+    proto.parsingFlags = parsingFlags;
+    proto.set = stringSet;
+    proto.startOf = startOf;
+    proto.subtract = subtract;
+    proto.toArray = toArray;
+    proto.toObject = toObject;
+    proto.toDate = toDate;
+    proto.toISOString = toISOString;
+    proto.inspect = inspect;
+    if (typeof Symbol !== 'undefined' && Symbol.for != null) {
+        proto[Symbol.for('nodejs.util.inspect.custom')] = function () {
+            return 'Moment<' + this.format() + '>';
+        };
     }
+    proto.toJSON = toJSON;
+    proto.toString = toString;
+    proto.unix = unix;
+    proto.valueOf = valueOf;
+    proto.creationData = creationData;
+    proto.eraName = getEraName;
+    proto.eraNarrow = getEraNarrow;
+    proto.eraAbbr = getEraAbbr;
+    proto.eraYear = getEraYear;
+    proto.year = getSetYear;
+    proto.isLeapYear = getIsLeapYear;
+    proto.weekYear = getSetWeekYear;
+    proto.isoWeekYear = getSetISOWeekYear;
+    proto.quarter = proto.quarters = getSetQuarter;
+    proto.month = getSetMonth;
+    proto.daysInMonth = getDaysInMonth;
+    proto.week = proto.weeks = getSetWeek;
+    proto.isoWeek = proto.isoWeeks = getSetISOWeek;
+    proto.weeksInYear = getWeeksInYear;
+    proto.weeksInWeekYear = getWeeksInWeekYear;
+    proto.isoWeeksInYear = getISOWeeksInYear;
+    proto.isoWeeksInISOWeekYear = getISOWeeksInISOWeekYear;
+    proto.date = getSetDayOfMonth;
+    proto.day = proto.days = getSetDayOfWeek;
+    proto.weekday = getSetLocaleDayOfWeek;
+    proto.isoWeekday = getSetISODayOfWeek;
+    proto.dayOfYear = getSetDayOfYear;
+    proto.hour = proto.hours = getSetHour;
+    proto.minute = proto.minutes = getSetMinute;
+    proto.second = proto.seconds = getSetSecond;
+    proto.millisecond = proto.milliseconds = getSetMillisecond;
+    proto.utcOffset = getSetOffset;
+    proto.utc = setOffsetToUTC;
+    proto.local = setOffsetToLocal;
+    proto.parseZone = setOffsetToParsedOffset;
+    proto.hasAlignedHourOffset = hasAlignedHourOffset;
+    proto.isDST = isDaylightSavingTime;
+    proto.isLocal = isLocal;
+    proto.isUtcOffset = isUtcOffset;
+    proto.isUtc = isUtc;
+    proto.isUTC = isUtc;
+    proto.zoneAbbr = getZoneAbbr;
+    proto.zoneName = getZoneName;
+    proto.dates = deprecate(
+        'dates accessor is deprecated. Use date instead.',
+        getSetDayOfMonth
+    );
+    proto.months = deprecate(
+        'months accessor is deprecated. Use month instead',
+        getSetMonth
+    );
+    proto.years = deprecate(
+        'years accessor is deprecated. Use year instead',
+        getSetYear
+    );
+    proto.zone = deprecate(
+        'moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/',
+        getSetZone
+    );
+    proto.isDSTShifted = deprecate(
+        'isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information',
+        isDaylightSavingTimeShifted
+    );
 
-    var defaultCalendar = {
-        sameDay : '[Today at] LT',
-        nextDay : '[Tomorrow at] LT',
-        nextWeek : 'dddd [at] LT',
-        lastDay : '[Yesterday at] LT',
-        lastWeek : '[Last] dddd [at] LT',
-        sameElse : 'L'
-    };
-
-    function locale_calendar__calendar (key, mom, now) {
-        var output = this._calendar[key];
-        return isFunction(output) ? output.call(mom, now) : output;
+    function createUnix(input) {
+        return createLocal(input * 1000);
     }
 
-    var defaultLongDateFormat = {
-        LTS  : 'h:mm:ss A',
-        LT   : 'h:mm A',
-        L    : 'MM/DD/YYYY',
-        LL   : 'MMMM D, YYYY',
-        LLL  : 'MMMM D, YYYY h:mm A',
-        LLLL : 'dddd, MMMM D, YYYY h:mm A'
-    };
-
-    function longDateFormat (key) {
-        var format = this._longDateFormat[key],
-            formatUpper = this._longDateFormat[key.toUpperCase()];
-
-        if (format || !formatUpper) {
-            return format;
-        }
-
-        this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) {
-            return val.slice(1);
-        });
-
-        return this._longDateFormat[key];
+    function createInZone() {
+        return createLocal.apply(null, arguments).parseZone();
     }
 
-    var defaultInvalidDate = 'Invalid date';
-
-    function invalidDate () {
-        return this._invalidDate;
+    function preParsePostFormat(string) {
+        return string;
     }
 
-    var defaultOrdinal = '%d';
-    var defaultOrdinalParse = /\d{1,2}/;
-
-    function ordinal (number) {
-        return this._ordinal.replace('%d', number);
+    var proto$1 = Locale.prototype;
+
+    proto$1.calendar = calendar;
+    proto$1.longDateFormat = longDateFormat;
+    proto$1.invalidDate = invalidDate;
+    proto$1.ordinal = ordinal;
+    proto$1.preparse = preParsePostFormat;
+    proto$1.postformat = preParsePostFormat;
+    proto$1.relativeTime = relativeTime;
+    proto$1.pastFuture = pastFuture;
+    proto$1.set = set;
+    proto$1.eras = localeEras;
+    proto$1.erasParse = localeErasParse;
+    proto$1.erasConvertYear = localeErasConvertYear;
+    proto$1.erasAbbrRegex = erasAbbrRegex;
+    proto$1.erasNameRegex = erasNameRegex;
+    proto$1.erasNarrowRegex = erasNarrowRegex;
+
+    proto$1.months = localeMonths;
+    proto$1.monthsShort = localeMonthsShort;
+    proto$1.monthsParse = localeMonthsParse;
+    proto$1.monthsRegex = monthsRegex;
+    proto$1.monthsShortRegex = monthsShortRegex;
+    proto$1.week = localeWeek;
+    proto$1.firstDayOfYear = localeFirstDayOfYear;
+    proto$1.firstDayOfWeek = localeFirstDayOfWeek;
+
+    proto$1.weekdays = localeWeekdays;
+    proto$1.weekdaysMin = localeWeekdaysMin;
+    proto$1.weekdaysShort = localeWeekdaysShort;
+    proto$1.weekdaysParse = localeWeekdaysParse;
+
+    proto$1.weekdaysRegex = weekdaysRegex;
+    proto$1.weekdaysShortRegex = weekdaysShortRegex;
+    proto$1.weekdaysMinRegex = weekdaysMinRegex;
+
+    proto$1.isPM = localeIsPM;
+    proto$1.meridiem = localeMeridiem;
+
+    function get$1(format, index, field, setter) {
+        var locale = getLocale(),
+            utc = createUTC().set(setter, index);
+        return locale[field](utc, format);
     }
 
-    function preParsePostFormat (string) {
-        return string;
-    }
+    function listMonthsImpl(format, index, field) {
+        if (isNumber(format)) {
+            index = format;
+            format = undefined;
+        }
 
-    var defaultRelativeTime = {
-        future : 'in %s',
-        past   : '%s ago',
-        s  : 'a few seconds',
-        m  : 'a minute',
-        mm : '%d minutes',
-        h  : 'an hour',
-        hh : '%d hours',
-        d  : 'a day',
-        dd : '%d days',
-        M  : 'a month',
-        MM : '%d months',
-        y  : 'a year',
-        yy : '%d years'
-    };
+        format = format || '';
 
-    function relative__relativeTime (number, withoutSuffix, string, isFuture) {
-        var output = this._relativeTime[string];
-        return (isFunction(output)) ?
-            output(number, withoutSuffix, string, isFuture) :
-            output.replace(/%d/i, number);
-    }
+        if (index != null) {
+            return get$1(format, index, field, 'month');
+        }
 
-    function pastFuture (diff, output) {
-        var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
-        return isFunction(format) ? format(output) : format.replace(/%s/i, output);
+        var i,
+            out = [];
+        for (i = 0; i < 12; i++) {
+            out[i] = get$1(format, i, field, 'month');
+        }
+        return out;
     }
 
-    function locale_set__set (config) {
-        var prop, i;
-        for (i in config) {
-            prop = config[i];
-            if (isFunction(prop)) {
-                this[i] = prop;
-            } else {
-                this['_' + i] = prop;
+    // ()
+    // (5)
+    // (fmt, 5)
+    // (fmt)
+    // (true)
+    // (true, 5)
+    // (true, fmt, 5)
+    // (true, fmt)
+    function listWeekdaysImpl(localeSorted, format, index, field) {
+        if (typeof localeSorted === 'boolean') {
+            if (isNumber(format)) {
+                index = format;
+                format = undefined;
             }
-        }
-        // Lenient ordinal parsing accepts just a number in addition to
-        // number + (possibly) stuff coming from _ordinalParseLenient.
-        this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + (/\d{1,2}/).source);
-    }
-
-    var prototype__proto = Locale.prototype;
-
-    prototype__proto._calendar       = defaultCalendar;
-    prototype__proto.calendar        = locale_calendar__calendar;
-    prototype__proto._longDateFormat = defaultLongDateFormat;
-    prototype__proto.longDateFormat  = longDateFormat;
-    prototype__proto._invalidDate    = defaultInvalidDate;
-    prototype__proto.invalidDate     = invalidDate;
-    prototype__proto._ordinal        = defaultOrdinal;
-    prototype__proto.ordinal         = ordinal;
-    prototype__proto._ordinalParse   = defaultOrdinalParse;
-    prototype__proto.preparse        = preParsePostFormat;
-    prototype__proto.postformat      = preParsePostFormat;
-    prototype__proto._relativeTime   = defaultRelativeTime;
-    prototype__proto.relativeTime    = relative__relativeTime;
-    prototype__proto.pastFuture      = pastFuture;
-    prototype__proto.set             = locale_set__set;
-
-    // Month
-    prototype__proto.months            =        localeMonths;
-    prototype__proto._months           = defaultLocaleMonths;
-    prototype__proto.monthsShort       =        localeMonthsShort;
-    prototype__proto._monthsShort      = defaultLocaleMonthsShort;
-    prototype__proto.monthsParse       =        localeMonthsParse;
-    prototype__proto._monthsRegex      = defaultMonthsRegex;
-    prototype__proto.monthsRegex       = monthsRegex;
-    prototype__proto._monthsShortRegex = defaultMonthsShortRegex;
-    prototype__proto.monthsShortRegex  = monthsShortRegex;
-
-    // Week
-    prototype__proto.week = localeWeek;
-    prototype__proto._week = defaultLocaleWeek;
-    prototype__proto.firstDayOfYear = localeFirstDayOfYear;
-    prototype__proto.firstDayOfWeek = localeFirstDayOfWeek;
-
-    // Day of Week
-    prototype__proto.weekdays       =        localeWeekdays;
-    prototype__proto._weekdays      = defaultLocaleWeekdays;
-    prototype__proto.weekdaysMin    =        localeWeekdaysMin;
-    prototype__proto._weekdaysMin   = defaultLocaleWeekdaysMin;
-    prototype__proto.weekdaysShort  =        localeWeekdaysShort;
-    prototype__proto._weekdaysShort = defaultLocaleWeekdaysShort;
-    prototype__proto.weekdaysParse  =        localeWeekdaysParse;
-
-    // Hours
-    prototype__proto.isPM = localeIsPM;
-    prototype__proto._meridiemParse = defaultLocaleMeridiemParse;
-    prototype__proto.meridiem = localeMeridiem;
-
-    function lists__get (format, index, field, setter) {
-        var locale = locale_locales__getLocale();
-        var utc = create_utc__createUTC().set(setter, index);
-        return locale[field](utc, format);
-    }
 
-    function list (format, index, field, count, setter) {
-        if (typeof format === 'number') {
+            format = format || '';
+        } else {
+            format = localeSorted;
             index = format;
-            format = undefined;
+            localeSorted = false;
+
+            if (isNumber(format)) {
+                index = format;
+                format = undefined;
+            }
+
+            format = format || '';
         }
 
-        format = format || '';
+        var locale = getLocale(),
+            shift = localeSorted ? locale._week.dow : 0,
+            i,
+            out = [];
 
         if (index != null) {
-            return lists__get(format, index, field, setter);
+            return get$1(format, (index + shift) % 7, field, 'day');
         }
 
-        var i;
-        var out = [];
-        for (i = 0; i < count; i++) {
-            out[i] = lists__get(format, i, field, setter);
+        for (i = 0; i < 7; i++) {
+            out[i] = get$1(format, (i + shift) % 7, field, 'day');
         }
         return out;
     }
 
-    function lists__listMonths (format, index) {
-        return list(format, index, 'months', 12, 'month');
+    function listMonths(format, index) {
+        return listMonthsImpl(format, index, 'months');
     }
 
-    function lists__listMonthsShort (format, index) {
-        return list(format, index, 'monthsShort', 12, 'month');
+    function listMonthsShort(format, index) {
+        return listMonthsImpl(format, index, 'monthsShort');
     }
 
-    function lists__listWeekdays (format, index) {
-        return list(format, index, 'weekdays', 7, 'day');
+    function listWeekdays(localeSorted, format, index) {
+        return listWeekdaysImpl(localeSorted, format, index, 'weekdays');
     }
 
-    function lists__listWeekdaysShort (format, index) {
-        return list(format, index, 'weekdaysShort', 7, 'day');
+    function listWeekdaysShort(localeSorted, format, index) {
+        return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');
     }
 
-    function lists__listWeekdaysMin (format, index) {
-        return list(format, index, 'weekdaysMin', 7, 'day');
+    function listWeekdaysMin(localeSorted, format, index) {
+        return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');
     }
 
-    locale_locales__getSetGlobalLocale('en', {
-        ordinalParse: /\d{1,2}(th|st|nd|rd)/,
-        ordinal : function (number) {
+    getSetGlobalLocale('en', {
+        eras: [
+            {
+                since: '0001-01-01',
+                until: +Infinity,
+                offset: 1,
+                name: 'Anno Domini',
+                narrow: 'AD',
+                abbr: 'AD',
+            },
+            {
+                since: '0000-12-31',
+                until: -Infinity,
+                offset: 1,
+                name: 'Before Christ',
+                narrow: 'BC',
+                abbr: 'BC',
+            },
+        ],
+        dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/,
+        ordinal: function (number) {
             var b = number % 10,
-                output = (toInt(number % 100 / 10) === 1) ? 'th' :
-                (b === 1) ? 'st' :
-                (b === 2) ? 'nd' :
-                (b === 3) ? 'rd' : 'th';
+                output =
+                    toInt((number % 100) / 10) === 1
+                        ? 'th'
+                        : b === 1
+                        ? 'st'
+                        : b === 2
+                        ? 'nd'
+                        : b === 3
+                        ? 'rd'
+                        : 'th';
             return number + output;
-        }
+        },
     });
 
     // Side effect imports
-    utils_hooks__hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', locale_locales__getSetGlobalLocale);
-    utils_hooks__hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', locale_locales__getLocale);
+
+    hooks.lang = deprecate(
+        'moment.lang is deprecated. Use moment.locale instead.',
+        getSetGlobalLocale
+    );
+    hooks.langData = deprecate(
+        'moment.langData is deprecated. Use moment.localeData instead.',
+        getLocale
+    );
 
     var mathAbs = Math.abs;
 
-    function duration_abs__abs () {
-        var data           = this._data;
+    function abs() {
+        var data = this._data;
 
         this._milliseconds = mathAbs(this._milliseconds);
-        this._days         = mathAbs(this._days);
-        this._months       = mathAbs(this._months);
+        this._days = mathAbs(this._days);
+        this._months = mathAbs(this._months);
 
-        data.milliseconds  = mathAbs(data.milliseconds);
-        data.seconds       = mathAbs(data.seconds);
-        data.minutes       = mathAbs(data.minutes);
-        data.hours         = mathAbs(data.hours);
-        data.months        = mathAbs(data.months);
-        data.years         = mathAbs(data.years);
+        data.milliseconds = mathAbs(data.milliseconds);
+        data.seconds = mathAbs(data.seconds);
+        data.minutes = mathAbs(data.minutes);
+        data.hours = mathAbs(data.hours);
+        data.months = mathAbs(data.months);
+        data.years = mathAbs(data.years);
 
         return this;
     }
 
-    function duration_add_subtract__addSubtract (duration, input, value, direction) {
-        var other = create__createDuration(input, value);
+    function addSubtract$1(duration, input, value, direction) {
+        var other = createDuration(input, value);
 
         duration._milliseconds += direction * other._milliseconds;
-        duration._days         += direction * other._days;
-        duration._months       += direction * other._months;
+        duration._days += direction * other._days;
+        duration._months += direction * other._months;
 
         return duration._bubble();
     }
 
     // supports only 2.0-style add(1, 's') or add(duration)
-    function duration_add_subtract__add (input, value) {
-        return duration_add_subtract__addSubtract(this, input, value, 1);
+    function add$1(input, value) {
+        return addSubtract$1(this, input, value, 1);
     }
 
     // supports only 2.0-style subtract(1, 's') or subtract(duration)
-    function duration_add_subtract__subtract (input, value) {
-        return duration_add_subtract__addSubtract(this, input, value, -1);
+    function subtract$1(input, value) {
+        return addSubtract$1(this, input, value, -1);
     }
 
-    function absCeil (number) {
+    function absCeil(number) {
         if (number < 0) {
             return Math.floor(number);
         } else {
         }
     }
 
-    function bubble () {
-        var milliseconds = this._milliseconds;
-        var days         = this._days;
-        var months       = this._months;
-        var data         = this._data;
-        var seconds, minutes, hours, years, monthsFromDays;
+    function bubble() {
+        var milliseconds = this._milliseconds,
+            days = this._days,
+            months = this._months,
+            data = this._data,
+            seconds,
+            minutes,
+            hours,
+            years,
+            monthsFromDays;
 
         // if we have a mix of positive and negative values, bubble down first
         // check: https://github.com/moment/moment/issues/2166
-        if (!((milliseconds >= 0 && days >= 0 && months >= 0) ||
-                (milliseconds <= 0 && days <= 0 && months <= 0))) {
+        if (
+            !(
+                (milliseconds >= 0 && days >= 0 && months >= 0) ||
+                (milliseconds <= 0 && days <= 0 && months <= 0)
+            )
+        ) {
             milliseconds += absCeil(monthsToDays(months) + days) * 864e5;
             days = 0;
             months = 0;
         // examples of what that means.
         data.milliseconds = milliseconds % 1000;
 
-        seconds           = absFloor(milliseconds / 1000);
-        data.seconds      = seconds % 60;
+        seconds = absFloor(milliseconds / 1000);
+        data.seconds = seconds % 60;
 
-        minutes           = absFloor(seconds / 60);
-        data.minutes      = minutes % 60;
+        minutes = absFloor(seconds / 60);
+        data.minutes = minutes % 60;
 
-        hours             = absFloor(minutes / 60);
-        data.hours        = hours % 24;
+        hours = absFloor(minutes / 60);
+        data.hours = hours % 24;
 
         days += absFloor(hours / 24);
 
         years = absFloor(months / 12);
         months %= 12;
 
-        data.days   = days;
+        data.days = days;
         data.months = months;
-        data.years  = years;
+        data.years = years;
 
         return this;
     }
 
-    function daysToMonths (days) {
+    function daysToMonths(days) {
         // 400 years have 146097 days (taking into account leap year rules)
         // 400 years have 12 months === 4800
-        return days * 4800 / 146097;
+        return (days * 4800) / 146097;
     }
 
-    function monthsToDays (months) {
+    function monthsToDays(months) {
         // the reverse of daysToMonths
-        return months * 146097 / 4800;
+        return (months * 146097) / 4800;
     }
 
-    function as (units) {
-        var days;
-        var months;
-        var milliseconds = this._milliseconds;
+    function as(units) {
+        if (!this.isValid()) {
+            return NaN;
+        }
+        var days,
+            months,
+            milliseconds = this._milliseconds;
 
         units = normalizeUnits(units);
 
-        if (units === 'month' || units === 'year') {
-            days   = this._days   + milliseconds / 864e5;
+        if (units === 'month' || units === 'quarter' || units === 'year') {
+            days = this._days + milliseconds / 864e5;
             months = this._months + daysToMonths(days);
-            return units === 'month' ? months : months / 12;
+            switch (units) {
+                case 'month':
+                    return months;
+                case 'quarter':
+                    return months / 3;
+                case 'year':
+                    return months / 12;
+            }
         } else {
             // handle milliseconds separately because of floating point math errors (issue #1867)
             days = this._days + Math.round(monthsToDays(this._months));
             switch (units) {
-                case 'week'   : return days / 7     + milliseconds / 6048e5;
-                case 'day'    : return days         + milliseconds / 864e5;
-                case 'hour'   : return days * 24    + milliseconds / 36e5;
-                case 'minute' : return days * 1440  + milliseconds / 6e4;
-                case 'second' : return days * 86400 + milliseconds / 1000;
+                case 'week':
+                    return days / 7 + milliseconds / 6048e5;
+                case 'day':
+                    return days + milliseconds / 864e5;
+                case 'hour':
+                    return days * 24 + milliseconds / 36e5;
+                case 'minute':
+                    return days * 1440 + milliseconds / 6e4;
+                case 'second':
+                    return days * 86400 + milliseconds / 1000;
                 // Math.floor prevents floating point math errors here
-                case 'millisecond': return Math.floor(days * 864e5) + milliseconds;
-                default: throw new Error('Unknown unit ' + units);
+                case 'millisecond':
+                    return Math.floor(days * 864e5) + milliseconds;
+                default:
+                    throw new Error('Unknown unit ' + units);
             }
         }
     }
 
     // TODO: Use this.as('ms')?
-    function duration_as__valueOf () {
+    function valueOf$1() {
+        if (!this.isValid()) {
+            return NaN;
+        }
         return (
             this._milliseconds +
             this._days * 864e5 +
         );
     }
 
-    function makeAs (alias) {
+    function makeAs(alias) {
         return function () {
             return this.as(alias);
         };
     }
 
-    var asMilliseconds = makeAs('ms');
-    var asSeconds      = makeAs('s');
-    var asMinutes      = makeAs('m');
-    var asHours        = makeAs('h');
-    var asDays         = makeAs('d');
-    var asWeeks        = makeAs('w');
-    var asMonths       = makeAs('M');
-    var asYears        = makeAs('y');
+    var asMilliseconds = makeAs('ms'),
+        asSeconds = makeAs('s'),
+        asMinutes = makeAs('m'),
+        asHours = makeAs('h'),
+        asDays = makeAs('d'),
+        asWeeks = makeAs('w'),
+        asMonths = makeAs('M'),
+        asQuarters = makeAs('Q'),
+        asYears = makeAs('y');
+
+    function clone$1() {
+        return createDuration(this);
+    }
 
-    function duration_get__get (units) {
+    function get$2(units) {
         units = normalizeUnits(units);
-        return this[units + 's']();
+        return this.isValid() ? this[units + 's']() : NaN;
     }
 
     function makeGetter(name) {
         return function () {
-            return this._data[name];
+            return this.isValid() ? this._data[name] : NaN;
         };
     }
 
-    var milliseconds = makeGetter('milliseconds');
-    var seconds      = makeGetter('seconds');
-    var minutes      = makeGetter('minutes');
-    var hours        = makeGetter('hours');
-    var days         = makeGetter('days');
-    var months       = makeGetter('months');
-    var years        = makeGetter('years');
+    var milliseconds = makeGetter('milliseconds'),
+        seconds = makeGetter('seconds'),
+        minutes = makeGetter('minutes'),
+        hours = makeGetter('hours'),
+        days = makeGetter('days'),
+        months = makeGetter('months'),
+        years = makeGetter('years');
 
-    function weeks () {
+    function weeks() {
         return absFloor(this.days() / 7);
     }
 
-    var round = Math.round;
-    var thresholds = {
-        s: 45,  // seconds to minute
-        m: 45,  // minutes to hour
-        h: 22,  // hours to day
-        d: 26,  // days to month
-        M: 11   // months to year
-    };
+    var round = Math.round,
+        thresholds = {
+            ss: 44, // a few seconds to seconds
+            s: 45, // seconds to minute
+            m: 45, // minutes to hour
+            h: 22, // hours to day
+            d: 26, // days to month/week
+            w: null, // weeks to month
+            M: 11, // months to year
+        };
 
     // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
     function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
         return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
     }
 
-    function duration_humanize__relativeTime (posNegDuration, withoutSuffix, locale) {
-        var duration = create__createDuration(posNegDuration).abs();
-        var seconds  = round(duration.as('s'));
-        var minutes  = round(duration.as('m'));
-        var hours    = round(duration.as('h'));
-        var days     = round(duration.as('d'));
-        var months   = round(duration.as('M'));
-        var years    = round(duration.as('y'));
-
-        var a = seconds < thresholds.s && ['s', seconds]  ||
-                minutes <= 1           && ['m']           ||
-                minutes < thresholds.m && ['mm', minutes] ||
-                hours   <= 1           && ['h']           ||
-                hours   < thresholds.h && ['hh', hours]   ||
-                days    <= 1           && ['d']           ||
-                days    < thresholds.d && ['dd', days]    ||
-                months  <= 1           && ['M']           ||
-                months  < thresholds.M && ['MM', months]  ||
-                years   <= 1           && ['y']           || ['yy', years];
+    function relativeTime$1(posNegDuration, withoutSuffix, thresholds, locale) {
+        var duration = createDuration(posNegDuration).abs(),
+            seconds = round(duration.as('s')),
+            minutes = round(duration.as('m')),
+            hours = round(duration.as('h')),
+            days = round(duration.as('d')),
+            months = round(duration.as('M')),
+            weeks = round(duration.as('w')),
+            years = round(duration.as('y')),
+            a =
+                (seconds <= thresholds.ss && ['s', seconds]) ||
+                (seconds < thresholds.s && ['ss', seconds]) ||
+                (minutes <= 1 && ['m']) ||
+                (minutes < thresholds.m && ['mm', minutes]) ||
+                (hours <= 1 && ['h']) ||
+                (hours < thresholds.h && ['hh', hours]) ||
+                (days <= 1 && ['d']) ||
+                (days < thresholds.d && ['dd', days]);
+
+        if (thresholds.w != null) {
+            a =
+                a ||
+                (weeks <= 1 && ['w']) ||
+                (weeks < thresholds.w && ['ww', weeks]);
+        }
+        a = a ||
+            (months <= 1 && ['M']) ||
+            (months < thresholds.M && ['MM', months]) ||
+            (years <= 1 && ['y']) || ['yy', years];
 
         a[2] = withoutSuffix;
         a[3] = +posNegDuration > 0;
         return substituteTimeAgo.apply(null, a);
     }
 
+    // This function allows you to set the rounding function for relative time strings
+    function getSetRelativeTimeRounding(roundingFunction) {
+        if (roundingFunction === undefined) {
+            return round;
+        }
+        if (typeof roundingFunction === 'function') {
+            round = roundingFunction;
+            return true;
+        }
+        return false;
+    }
+
     // This function allows you to set a threshold for relative time strings
-    function duration_humanize__getSetRelativeTimeThreshold (threshold, limit) {
+    function getSetRelativeTimeThreshold(threshold, limit) {
         if (thresholds[threshold] === undefined) {
             return false;
         }
             return thresholds[threshold];
         }
         thresholds[threshold] = limit;
+        if (threshold === 's') {
+            thresholds.ss = limit - 1;
+        }
         return true;
     }
 
-    function humanize (withSuffix) {
-        var locale = this.localeData();
-        var output = duration_humanize__relativeTime(this, !withSuffix, locale);
+    function humanize(argWithSuffix, argThresholds) {
+        if (!this.isValid()) {
+            return this.localeData().invalidDate();
+        }
+
+        var withSuffix = false,
+            th = thresholds,
+            locale,
+            output;
+
+        if (typeof argWithSuffix === 'object') {
+            argThresholds = argWithSuffix;
+            argWithSuffix = false;
+        }
+        if (typeof argWithSuffix === 'boolean') {
+            withSuffix = argWithSuffix;
+        }
+        if (typeof argThresholds === 'object') {
+            th = Object.assign({}, thresholds, argThresholds);
+            if (argThresholds.s != null && argThresholds.ss == null) {
+                th.ss = argThresholds.s - 1;
+            }
+        }
+
+        locale = this.localeData();
+        output = relativeTime$1(this, !withSuffix, th, locale);
 
         if (withSuffix) {
             output = locale.pastFuture(+this, output);
         return locale.postformat(output);
     }
 
-    var iso_string__abs = Math.abs;
+    var abs$1 = Math.abs;
+
+    function sign(x) {
+        return (x > 0) - (x < 0) || +x;
+    }
 
-    function iso_string__toISOString() {
+    function toISOString$1() {
         // for ISO strings we do not use the normal bubbling rules:
         //  * milliseconds bubble up until they become hours
         //  * days do not bubble at all
         // This is because there is no context-free conversion between hours and days
         // (think of clock changes)
         // and also not between days and months (28-31 days per month)
-        var seconds = iso_string__abs(this._milliseconds) / 1000;
-        var days         = iso_string__abs(this._days);
-        var months       = iso_string__abs(this._months);
-        var minutes, hours, years;
+        if (!this.isValid()) {
+            return this.localeData().invalidDate();
+        }
+
+        var seconds = abs$1(this._milliseconds) / 1000,
+            days = abs$1(this._days),
+            months = abs$1(this._months),
+            minutes,
+            hours,
+            years,
+            s,
+            total = this.asSeconds(),
+            totalSign,
+            ymSign,
+            daysSign,
+            hmsSign;
+
+        if (!total) {
+            // this is the same as C#'s (Noda) and python (isodate)...
+            // but not other JS (goog.date)
+            return 'P0D';
+        }
 
         // 3600 seconds -> 60 minutes -> 1 hour
-        minutes           = absFloor(seconds / 60);
-        hours             = absFloor(minutes / 60);
+        minutes = absFloor(seconds / 60);
+        hours = absFloor(minutes / 60);
         seconds %= 60;
         minutes %= 60;
 
         // 12 months -> 1 year
-        years  = absFloor(months / 12);
+        years = absFloor(months / 12);
         months %= 12;
 
-
         // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
-        var Y = years;
-        var M = months;
-        var D = days;
-        var h = hours;
-        var m = minutes;
-        var s = seconds;
-        var total = this.asSeconds();
+        s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : '';
 
-        if (!total) {
-            // this is the same as C#'s (Noda) and python (isodate)...
-            // but not other JS (goog.date)
-            return 'P0D';
-        }
+        totalSign = total < 0 ? '-' : '';
+        ymSign = sign(this._months) !== sign(total) ? '-' : '';
+        daysSign = sign(this._days) !== sign(total) ? '-' : '';
+        hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : '';
 
-        return (total < 0 ? '-' : '') +
+        return (
+            totalSign +
             'P' +
-            (Y ? Y + 'Y' : '') +
-            (M ? M + 'M' : '') +
-            (D ? D + 'D' : '') +
-            ((h || m || s) ? 'T' : '') +
-            (h ? h + 'H' : '') +
-            (m ? m + 'M' : '') +
-            (s ? s + 'S' : '');
-    }
-
-    var duration_prototype__proto = Duration.prototype;
-
-    duration_prototype__proto.abs            = duration_abs__abs;
-    duration_prototype__proto.add            = duration_add_subtract__add;
-    duration_prototype__proto.subtract       = duration_add_subtract__subtract;
-    duration_prototype__proto.as             = as;
-    duration_prototype__proto.asMilliseconds = asMilliseconds;
-    duration_prototype__proto.asSeconds      = asSeconds;
-    duration_prototype__proto.asMinutes      = asMinutes;
-    duration_prototype__proto.asHours        = asHours;
-    duration_prototype__proto.asDays         = asDays;
-    duration_prototype__proto.asWeeks        = asWeeks;
-    duration_prototype__proto.asMonths       = asMonths;
-    duration_prototype__proto.asYears        = asYears;
-    duration_prototype__proto.valueOf        = duration_as__valueOf;
-    duration_prototype__proto._bubble        = bubble;
-    duration_prototype__proto.get            = duration_get__get;
-    duration_prototype__proto.milliseconds   = milliseconds;
-    duration_prototype__proto.seconds        = seconds;
-    duration_prototype__proto.minutes        = minutes;
-    duration_prototype__proto.hours          = hours;
-    duration_prototype__proto.days           = days;
-    duration_prototype__proto.weeks          = weeks;
-    duration_prototype__proto.months         = months;
-    duration_prototype__proto.years          = years;
-    duration_prototype__proto.humanize       = humanize;
-    duration_prototype__proto.toISOString    = iso_string__toISOString;
-    duration_prototype__proto.toString       = iso_string__toISOString;
-    duration_prototype__proto.toJSON         = iso_string__toISOString;
-    duration_prototype__proto.locale         = locale;
-    duration_prototype__proto.localeData     = localeData;
-
-    // Deprecations
-    duration_prototype__proto.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', iso_string__toISOString);
-    duration_prototype__proto.lang = lang;
+            (years ? ymSign + years + 'Y' : '') +
+            (months ? ymSign + months + 'M' : '') +
+            (days ? daysSign + days + 'D' : '') +
+            (hours || minutes || seconds ? 'T' : '') +
+            (hours ? hmsSign + hours + 'H' : '') +
+            (minutes ? hmsSign + minutes + 'M' : '') +
+            (seconds ? hmsSign + s + 'S' : '')
+        );
+    }
 
-    // Side effect imports
+    var proto$2 = Duration.prototype;
+
+    proto$2.isValid = isValid$1;
+    proto$2.abs = abs;
+    proto$2.add = add$1;
+    proto$2.subtract = subtract$1;
+    proto$2.as = as;
+    proto$2.asMilliseconds = asMilliseconds;
+    proto$2.asSeconds = asSeconds;
+    proto$2.asMinutes = asMinutes;
+    proto$2.asHours = asHours;
+    proto$2.asDays = asDays;
+    proto$2.asWeeks = asWeeks;
+    proto$2.asMonths = asMonths;
+    proto$2.asQuarters = asQuarters;
+    proto$2.asYears = asYears;
+    proto$2.valueOf = valueOf$1;
+    proto$2._bubble = bubble;
+    proto$2.clone = clone$1;
+    proto$2.get = get$2;
+    proto$2.milliseconds = milliseconds;
+    proto$2.seconds = seconds;
+    proto$2.minutes = minutes;
+    proto$2.hours = hours;
+    proto$2.days = days;
+    proto$2.weeks = weeks;
+    proto$2.months = months;
+    proto$2.years = years;
+    proto$2.humanize = humanize;
+    proto$2.toISOString = toISOString$1;
+    proto$2.toString = toISOString$1;
+    proto$2.toJSON = toISOString$1;
+    proto$2.locale = locale;
+    proto$2.localeData = localeData;
+
+    proto$2.toIsoString = deprecate(
+        'toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)',
+        toISOString$1
+    );
+    proto$2.lang = lang;
 
     // FORMATTING
 
     addRegexToken('x', matchSigned);
     addRegexToken('X', matchTimestamp);
     addParseToken('X', function (input, array, config) {
-        config._d = new Date(parseFloat(input, 10) * 1000);
+        config._d = new Date(parseFloat(input) * 1000);
     });
     addParseToken('x', function (input, array, config) {
         config._d = new Date(toInt(input));
     });
 
-    // Side effect imports
+    //! moment.js
+
+    hooks.version = '2.29.2';
+
+    setHookCallback(createLocal);
+
+    hooks.fn = proto;
+    hooks.min = min;
+    hooks.max = max;
+    hooks.now = now;
+    hooks.utc = createUTC;
+    hooks.unix = createUnix;
+    hooks.months = listMonths;
+    hooks.isDate = isDate;
+    hooks.locale = getSetGlobalLocale;
+    hooks.invalid = createInvalid;
+    hooks.duration = createDuration;
+    hooks.isMoment = isMoment;
+    hooks.weekdays = listWeekdays;
+    hooks.parseZone = createInZone;
+    hooks.localeData = getLocale;
+    hooks.isDuration = isDuration;
+    hooks.monthsShort = listMonthsShort;
+    hooks.weekdaysMin = listWeekdaysMin;
+    hooks.defineLocale = defineLocale;
+    hooks.updateLocale = updateLocale;
+    hooks.locales = listLocales;
+    hooks.weekdaysShort = listWeekdaysShort;
+    hooks.normalizeUnits = normalizeUnits;
+    hooks.relativeTimeRounding = getSetRelativeTimeRounding;
+    hooks.relativeTimeThreshold = getSetRelativeTimeThreshold;
+    hooks.calendarFormat = getCalendarFormat;
+    hooks.prototype = proto;
+
+    // currently HTML5 input type only supports 24-hour formats
+    hooks.HTML5_FMT = {
+        DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // <input type="datetime-local" />
+        DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // <input type="datetime-local" step="1" />
+        DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // <input type="datetime-local" step="0.001" />
+        DATE: 'YYYY-MM-DD', // <input type="date" />
+        TIME: 'HH:mm', // <input type="time" />
+        TIME_SECONDS: 'HH:mm:ss', // <input type="time" step="1" />
+        TIME_MS: 'HH:mm:ss.SSS', // <input type="time" step="0.001" />
+        WEEK: 'GGGG-[W]WW', // <input type="week" />
+        MONTH: 'YYYY-MM', // <input type="month" />
+    };
 
+    return hooks;
 
-    utils_hooks__hooks.version = '2.11.1';
-
-    setHookCallback(local__createLocal);
-
-    utils_hooks__hooks.fn                    = momentPrototype;
-    utils_hooks__hooks.min                   = min;
-    utils_hooks__hooks.max                   = max;
-    utils_hooks__hooks.now                   = now;
-    utils_hooks__hooks.utc                   = create_utc__createUTC;
-    utils_hooks__hooks.unix                  = moment__createUnix;
-    utils_hooks__hooks.months                = lists__listMonths;
-    utils_hooks__hooks.isDate                = isDate;
-    utils_hooks__hooks.locale                = locale_locales__getSetGlobalLocale;
-    utils_hooks__hooks.invalid               = valid__createInvalid;
-    utils_hooks__hooks.duration              = create__createDuration;
-    utils_hooks__hooks.isMoment              = isMoment;
-    utils_hooks__hooks.weekdays              = lists__listWeekdays;
-    utils_hooks__hooks.parseZone             = moment__createInZone;
-    utils_hooks__hooks.localeData            = locale_locales__getLocale;
-    utils_hooks__hooks.isDuration            = isDuration;
-    utils_hooks__hooks.monthsShort           = lists__listMonthsShort;
-    utils_hooks__hooks.weekdaysMin           = lists__listWeekdaysMin;
-    utils_hooks__hooks.defineLocale          = defineLocale;
-    utils_hooks__hooks.weekdaysShort         = lists__listWeekdaysShort;
-    utils_hooks__hooks.normalizeUnits        = normalizeUnits;
-    utils_hooks__hooks.relativeTimeThreshold = duration_humanize__getSetRelativeTimeThreshold;
-    utils_hooks__hooks.prototype             = momentPrototype;
-
-    var _moment = utils_hooks__hooks;
-
-    return _moment;
-
-}));
\ 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 34432f2902b60225721139f8ba52c67471547eda..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>
@@ -29,6 +31,7 @@ BOOST_AUTO_TEST_CASE(test_ConnectionManagementEnabled) {
   /* raise the number of slots */
   maxConns = 12;
   manager.setMaxConcurrentConnections(maxConns);
+  BOOST_CHECK_EQUAL(manager.getMaxConcurrentConnections(), maxConns);
   BOOST_CHECK_EQUAL(manager.registerConnection(), true);
   BOOST_CHECK_EQUAL(manager.registerConnection(), true);
   BOOST_CHECK_EQUAL(manager.registerConnection(), false);
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 a59163b039f6bbe1abaa30567dbbe105f8b3bbd8..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>
 
 #include "dnsdist-tcp-downstream.hh"
+#include "dnsdist-downstream-connection.hh"
 
 class MockupConnection
 {
@@ -67,6 +71,10 @@ public:
   {
   }
 
+  void release()
+  {
+  }
+
   std::shared_ptr<DownstreamState> getDS() const
   {
     return d_ds;
diff --git a/pdns/dnsdistdist/test-dnsdist-dnsparser.cc b/pdns/dnsdistdist/test-dnsdist-dnsparser.cc
new file mode 100644 (file)
index 0000000..a7bb4e3
--- /dev/null
@@ -0,0 +1,465 @@
+/*
+ * 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-dnsparser.hh"
+#include "dnswriter.hh"
+#include "dnsparser.hh"
+
+BOOST_AUTO_TEST_SUITE(test_dnsdist_dnsparser)
+
+BOOST_AUTO_TEST_CASE(test_Query)
+{
+  const DNSName target("powerdns.com.");
+  const DNSName newTarget("dnsdist.org.");
+  const DNSName notTheTarget("not-powerdns.com.");
+
+  {
+    /* query for the target */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pw(query, target, QType::A, QClass::IN, 0);
+    pw.getHeader()->rd = 1;
+    pw.getHeader()->id = htons(42);
+    pw.commit();
+
+    BOOST_CHECK(dnsdist::changeNameInDNSPacket(query, target, newTarget));
+
+    MOADNSParser mdp(false, reinterpret_cast<const char*>(query.data()), query.size());
+    BOOST_CHECK_EQUAL(mdp.d_qname, newTarget);
+    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+  }
+
+  {
+    /* query smaller than a DNS header */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pw(query, target, QType::A, QClass::IN, 0);
+    pw.getHeader()->rd = 1;
+    pw.getHeader()->id = htons(42);
+    pw.commit();
+
+    query.resize(sizeof(dnsheader) - 1);
+    BOOST_CHECK(!dnsdist::changeNameInDNSPacket(query, target, newTarget));
+  }
+
+  {
+    /* query for a different name than the target */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pw(query, notTheTarget, QType::A, QClass::IN, 0);
+    pw.getHeader()->rd = 1;
+    pw.getHeader()->id = htons(42);
+    pw.commit();
+
+    BOOST_CHECK(dnsdist::changeNameInDNSPacket(query, target, newTarget));
+
+    MOADNSParser mdp(false, reinterpret_cast<const char*>(query.data()), query.size());
+    BOOST_CHECK_EQUAL(mdp.d_qname, notTheTarget);
+    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_Response)
+{
+  const DNSName target("powerdns.com.");
+  const DNSName newTarget("dnsdist.org.");
+  const DNSName notTheTarget("not-powerdns.com.");
+
+  {
+    /* response for the target, A and AAAA */
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, target, QType::A, QClass::IN, 0);
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->id = htons(42);
+    pwR.startRecord(target, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    ComboAddress v4("192.0.2.1");
+    pwR.xfrCAWithoutPort(4, v4);
+    pwR.commit();
+    pwR.startRecord(target, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
+    ComboAddress v6("2001:db8::1");
+    pwR.xfrCAWithoutPort(6, v6);
+    pwR.commit();
+    pwR.addOpt(4096, 0, 0);
+    pwR.commit();
+
+    BOOST_CHECK(dnsdist::changeNameInDNSPacket(response, target, newTarget));
+
+    MOADNSParser mdp(false, reinterpret_cast<const char*>(response.data()), response.size());
+    BOOST_CHECK_EQUAL(mdp.d_qname, newTarget);
+    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, 2U);
+
+    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 3U);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::A));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, newTarget);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::AAAA));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_class, QClass::IN);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, newTarget);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(2).first.d_type, static_cast<uint16_t>(QType::OPT));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(2).first.d_name, g_rootdnsname);
+  }
+
+  {
+    /* response with A for the target, AAAA for another name */
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, target, QType::A, QClass::IN, 0);
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->id = htons(42);
+    pwR.startRecord(target, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    ComboAddress v4("192.0.2.1");
+    pwR.xfrCAWithoutPort(4, v4);
+    pwR.commit();
+    pwR.startRecord(notTheTarget, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
+    ComboAddress v6("2001:db8::1");
+    pwR.xfrCAWithoutPort(6, v6);
+    pwR.commit();
+    pwR.addOpt(4096, 0, 0);
+    pwR.commit();
+
+    BOOST_CHECK(dnsdist::changeNameInDNSPacket(response, target, newTarget));
+
+    MOADNSParser mdp(false, reinterpret_cast<const char*>(response.data()), response.size());
+    BOOST_CHECK_EQUAL(mdp.d_qname, newTarget);
+    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, 2U);
+
+    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 3U);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::A));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, newTarget);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::AAAA));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_class, QClass::IN);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, notTheTarget);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(2).first.d_type, static_cast<uint16_t>(QType::OPT));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(2).first.d_name, g_rootdnsname);
+  }
+
+  {
+    /* response with CNAME for the target, A for another name */
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, target, QType::A, QClass::IN, 0);
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->id = htons(42);
+    pwR.startRecord(target, QType::CNAME, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfrName(notTheTarget);
+    pwR.commit();
+    pwR.startRecord(notTheTarget, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    ComboAddress v4("192.0.2.1");
+    pwR.xfrCAWithoutPort(4, v4);
+    pwR.commit();
+    pwR.addOpt(4096, 0, 0);
+    pwR.commit();
+
+    BOOST_CHECK(dnsdist::changeNameInDNSPacket(response, target, newTarget));
+
+    MOADNSParser mdp(false, reinterpret_cast<const char*>(response.data()), response.size());
+    BOOST_CHECK_EQUAL(mdp.d_qname, newTarget);
+    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 2U);
+    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
+    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
+
+    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 3U);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::CNAME));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, newTarget);
+    auto content = getRR<UnknownRecordContent>(mdp.d_answers.at(0).first);
+    BOOST_REQUIRE(content != nullptr);
+    BOOST_CHECK_EQUAL(content->getRawContent().size(), notTheTarget.getStorage().size());
+
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::A));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_class, QClass::IN);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, notTheTarget);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(2).first.d_type, static_cast<uint16_t>(QType::OPT));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(2).first.d_name, g_rootdnsname);
+  }
+
+  {
+    /* response with a lot of records for the target, all supported */
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, target, QType::ANY, QClass::IN, 0);
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->id = htons(42);
+    pwR.startRecord(target, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    ComboAddress v4("192.0.2.1");
+    pwR.xfrCAWithoutPort(4, v4);
+    pwR.commit();
+    pwR.startRecord(target, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    ComboAddress v6("2001:db8::1");
+    pwR.xfrCAWithoutPort(6, v6);
+    pwR.commit();
+    pwR.startRecord(target, QType::NS, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfrName(DNSName("pdns-public-ns1.powerdns.com."));
+    pwR.commit();
+    pwR.startRecord(target, QType::MX, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfr16BitInt(75);
+    pwR.xfrName(DNSName("download1.powerdns.com."));
+    pwR.commit();
+    pwR.startRecord(target, QType::TXT, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfrText("\"random text\"");
+    pwR.commit();
+    pwR.startRecord(target, QType::RRSIG, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfrType(QType::TXT);
+    pwR.xfr8BitInt(13);
+    pwR.xfr8BitInt(2);
+    pwR.xfr32BitInt(42);
+    pwR.xfrTime(42);
+    pwR.xfrTime(42);
+    pwR.xfr16BitInt(42);
+    pwR.xfrName(DNSName("powerdns.com."));
+    pwR.xfrBlob(std::string());
+    pwR.commit();
+    pwR.addOpt(4096, 0, 0);
+    pwR.commit();
+
+    BOOST_CHECK(dnsdist::changeNameInDNSPacket(response, target, newTarget));
+
+    MOADNSParser mdp(false, reinterpret_cast<const char*>(response.data()), response.size());
+    BOOST_CHECK_EQUAL(mdp.d_qname, newTarget);
+    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 6U);
+    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
+    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
+
+    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 7U);
+    for (const auto& answer : mdp.d_answers) {
+      if (answer.first.d_type == QType::OPT) {
+        continue;
+      }
+      BOOST_CHECK_EQUAL(answer.first.d_class, QClass::IN);
+      BOOST_CHECK_EQUAL(answer.first.d_name, newTarget);
+    }
+  }
+
+  {
+    /* response with a lot of records for the target, all supported */
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, target, QType::ANY, QClass::IN, 0);
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->id = htons(42);
+    pwR.startRecord(target, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    ComboAddress v4("192.0.2.1");
+    pwR.xfrCAWithoutPort(4, v4);
+    pwR.commit();
+    pwR.startRecord(target, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    ComboAddress v6("2001:db8::1");
+    pwR.xfrCAWithoutPort(6, v6);
+    pwR.commit();
+    pwR.startRecord(target, QType::NS, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfrName(DNSName("pdns-public-ns1.powerdns.com."));
+    pwR.commit();
+    pwR.startRecord(target, QType::SOA, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfrName(DNSName("pdns-public-ns1.powerdns.com."));
+    pwR.xfrName(DNSName("admin.powerdns.com."));
+    pwR.xfr32BitInt(1);
+    pwR.xfr32BitInt(2);
+    pwR.xfr32BitInt(3);
+    pwR.xfr32BitInt(4);
+    pwR.xfr32BitInt(5);
+    pwR.commit();
+    pwR.startRecord(target, QType::MX, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfr16BitInt(75);
+    pwR.xfrName(DNSName("download1.powerdns.com."));
+    pwR.commit();
+    pwR.startRecord(target, QType::TXT, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfrText("\"random text\"");
+    pwR.commit();
+    pwR.startRecord(target, QType::SRV, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfr16BitInt(1);
+    pwR.xfr16BitInt(2);
+    pwR.xfr16BitInt(65535);
+    pwR.xfrName(DNSName("target.powerdns.com."));
+    pwR.commit();
+    pwR.addOpt(4096, 0, 0);
+    pwR.commit();
+
+    {
+      // before
+      MOADNSParser mdp(false, reinterpret_cast<const char*>(response.data()), response.size());
+      BOOST_CHECK_EQUAL(mdp.d_qname, target);
+      BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+      BOOST_CHECK_EQUAL(mdp.d_header.ancount, 7U);
+      BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
+      BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
+
+      BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 8U);
+      for (const auto& answer : mdp.d_answers) {
+        if (answer.first.d_type == QType::OPT) {
+          continue;
+        }
+        BOOST_CHECK_EQUAL(answer.first.d_class, QClass::IN);
+        BOOST_CHECK_EQUAL(answer.first.d_name, target);
+      }
+    }
+
+    // rebasing
+    BOOST_CHECK(dnsdist::changeNameInDNSPacket(response, target, newTarget));
+
+    {
+      // after
+      MOADNSParser mdp(false, reinterpret_cast<const char*>(response.data()), response.size());
+      BOOST_CHECK_EQUAL(mdp.d_qname, newTarget);
+      BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+      BOOST_CHECK_EQUAL(mdp.d_header.ancount, 7U);
+      BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
+      BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
+
+      BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 8U);
+      for (const auto& answer : mdp.d_answers) {
+        if (answer.first.d_type == QType::OPT) {
+          continue;
+        }
+        BOOST_CHECK_EQUAL(answer.first.d_class, QClass::IN);
+        BOOST_CHECK_EQUAL(answer.first.d_name, newTarget);
+      }
+    }
+  }
+
+  {
+    /* response with an ALIAS record, which is not supported */
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, target, QType::A, QClass::IN, 0);
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->id = htons(42);
+    pwR.startRecord(target, QType::ALIAS, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfrName(notTheTarget);
+    pwR.commit();
+    pwR.addOpt(4096, 0, 0);
+    pwR.commit();
+
+    BOOST_CHECK(!dnsdist::changeNameInDNSPacket(response, target, newTarget));
+
+    MOADNSParser mdp(false, reinterpret_cast<const char*>(response.data()), response.size());
+    BOOST_CHECK_EQUAL(mdp.d_qname, target);
+    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, 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::ALIAS));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, target);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_Overlay)
+{
+  const DNSName target("powerdns.com.");
+
+  {
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, target, QType::ANY, QClass::IN, 0);
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->id = htons(42);
+    pwR.startRecord(target, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    ComboAddress v4("192.0.2.1");
+    pwR.xfrCAWithoutPort(4, v4);
+    pwR.commit();
+    pwR.startRecord(target, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    ComboAddress v6("2001:db8::1");
+    pwR.xfrCAWithoutPort(6, v6);
+    pwR.commit();
+    pwR.startRecord(target, QType::NS, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfrName(DNSName("pdns-public-ns1.powerdns.com."));
+    pwR.commit();
+    pwR.startRecord(target, QType::SOA, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfrName(DNSName("pdns-public-ns1.powerdns.com."));
+    pwR.xfrName(DNSName("admin.powerdns.com."));
+    pwR.xfr32BitInt(1);
+    pwR.xfr32BitInt(2);
+    pwR.xfr32BitInt(3);
+    pwR.xfr32BitInt(4);
+    pwR.xfr32BitInt(5);
+    pwR.commit();
+    pwR.startRecord(target, QType::MX, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfr16BitInt(75);
+    pwR.xfrName(DNSName("download1.powerdns.com."));
+    pwR.commit();
+    pwR.startRecord(target, QType::TXT, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfrText("\"random text\"");
+    pwR.commit();
+    pwR.addOpt(4096, 0, 0);
+    pwR.commit();
+
+    {
+      // check packet smaller than dnsheader
+      BOOST_CHECK_THROW(dnsdist::DNSPacketOverlay(std::string_view(reinterpret_cast<const char*>(response.data()), 11U)), std::runtime_error);
+      // check corrupted packet
+      BOOST_CHECK_THROW(dnsdist::DNSPacketOverlay(std::string_view(reinterpret_cast<const char*>(response.data()), response.size() - 1)), std::runtime_error);
+    }
+
+    dnsdist::DNSPacketOverlay overlay(std::string_view(reinterpret_cast<const char*>(response.data()), response.size()));
+    BOOST_CHECK_EQUAL(overlay.d_qname, target);
+    BOOST_CHECK_EQUAL(overlay.d_qtype, QType::ANY);
+    BOOST_CHECK_EQUAL(overlay.d_qclass, QClass::IN);
+    BOOST_CHECK_EQUAL(overlay.d_header.qr, 1U);
+    BOOST_CHECK_EQUAL(overlay.d_header.rd, 1U);
+    BOOST_CHECK_EQUAL(overlay.d_header.ra, 1U);
+    BOOST_CHECK_EQUAL(overlay.d_header.id, htons(42));
+    BOOST_CHECK_EQUAL(ntohs(overlay.d_header.qdcount), 1U);
+    BOOST_CHECK_EQUAL(ntohs(overlay.d_header.ancount), 6U);
+    BOOST_CHECK_EQUAL(ntohs(overlay.d_header.nscount), 0U);
+    BOOST_CHECK_EQUAL(ntohs(overlay.d_header.arcount), 1U);
+    BOOST_CHECK_EQUAL(overlay.d_records.size(), 7U);
+
+    /* this is off, of course, but we are only doing a sanity check here */
+    uint16_t lastOffset = sizeof(dnsheader) + target.wirelength() + sizeof(uint16_t) + sizeof(uint16_t);
+    for (const auto& record : overlay.d_records) {
+      if (record.d_type == QType::OPT) {
+        continue;
+      }
+
+      BOOST_CHECK_EQUAL(record.d_name, target);
+      BOOST_CHECK_EQUAL(record.d_class, QClass::IN);
+      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;
+    }
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END();
diff --git a/pdns/dnsdistdist/test-dnsdist-lua-ffi.cc b/pdns/dnsdistdist/test-dnsdist-lua-ffi.cc
new file mode 100644 (file)
index 0000000..79c9ef9
--- /dev/null
@@ -0,0 +1,846 @@
+/*
+ * 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-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)
+{
+  InternalQueryState ids;
+  ids.origRemote = ComboAddress("192.0.2.1:4242");
+  ids.origDest = ComboAddress("192.0.2.255:53");
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  ids.qname = DNSName("www.powerdns.com.");
+  ids.queryRealTime.start();
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
+  pwQ.getHeader()->rd = 1;
+  pwQ.getHeader()->id = htons(42);
+
+  DNSQuestion dq(ids, query);
+  dnsdist_ffi_dnsquestion_t lightDQ(&dq);
+
+  {
+    // dnsdist_ffi_dnsquestion_get_qtype
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_qtype(&lightDQ), ids.qtype);
+  }
+
+  {
+    // dnsdist_ffi_dnsquestion_get_qclass
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_qclass(&lightDQ), ids.qclass);
+  }
+
+  {
+    // dnsdist_ffi_dnsquestion_get_id
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_id(&lightDQ), ntohs(pwQ.getHeader()->id));
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_id(nullptr), 0U);
+  }
+
+  {
+    // dnsdist_ffi_dnsquestion_get_localaddr, dnsdist_ffi_dnsquestion_get_local_port
+    const char* buffer = nullptr;
+    size_t bufferSize = 0;
+    dnsdist_ffi_dnsquestion_get_localaddr(&lightDQ, reinterpret_cast<const void**>(&buffer), &bufferSize);
+    BOOST_REQUIRE(buffer != nullptr);
+    BOOST_REQUIRE_EQUAL(bufferSize, sizeof(ids.origDest.sin4.sin_addr.s_addr));
+    BOOST_CHECK(memcmp(buffer, &ids.origDest.sin4.sin_addr.s_addr, sizeof(ids.origDest.sin4.sin_addr.s_addr)) == 0);
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_local_port(&lightDQ), 53U);
+  }
+
+  {
+    // dnsdist_ffi_dnsquestion_get_remoteaddr, dnsdist_ffi_dnsquestion_get_remote_port
+    const char* buffer = nullptr;
+    size_t bufferSize = 0;
+    dnsdist_ffi_dnsquestion_get_remoteaddr(&lightDQ, reinterpret_cast<const void**>(&buffer), &bufferSize);
+    BOOST_REQUIRE(buffer != nullptr);
+    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));
+  }
+
+  {
+    // dnsdist_ffi_dnsquestion_get_masked_remoteaddr
+    const char* buffer = nullptr;
+    size_t bufferSize = 0;
+    dnsdist_ffi_dnsquestion_get_masked_remoteaddr(&lightDQ, reinterpret_cast<const void**>(&buffer), &bufferSize, 16);
+    BOOST_REQUIRE(buffer != nullptr);
+    auto masked = Netmask(ids.origRemote, 16).getMaskedNetwork();
+    BOOST_REQUIRE_EQUAL(bufferSize, sizeof(masked.sin4.sin_addr.s_addr));
+    BOOST_CHECK(memcmp(buffer, &masked.sin4.sin_addr.s_addr, sizeof(masked.sin4.sin_addr.s_addr)) == 0);
+  }
+
+  {
+    const char* buffer[6];
+    size_t bufferSize = 6;
+
+    // invalid
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_mac_addr(nullptr, buffer, 0), 0U);
+    // too small
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_mac_addr(&lightDQ, buffer, 0), 0U);
+
+    // we will not find the correspondig MAC address in /proc/net/arp, unfortunately, especially not on !linux
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_mac_addr(&lightDQ, buffer, bufferSize), 0U);
+  }
+
+  {
+    // dnsdist_ffi_dnsquestion_get_qname_raw
+    const char* buffer = nullptr;
+    size_t bufferSize = 0;
+    dnsdist_ffi_dnsquestion_get_qname_raw(&lightDQ, &buffer, &bufferSize);
+    BOOST_REQUIRE(buffer != nullptr);
+    BOOST_REQUIRE_EQUAL(bufferSize, ids.qname.getStorage().size());
+    BOOST_CHECK(memcmp(buffer, ids.qname.getStorage().data(), ids.qname.getStorage().size()) == 0);
+  }
+
+  {
+    // test V6 as well
+    ids.origRemote = ComboAddress("[2001:db8::1]:65535");
+    ids.origDest = ComboAddress("[2001:db8::2]:53");
+
+    const char* buffer = nullptr;
+    size_t bufferSize = 0;
+    dnsdist_ffi_dnsquestion_get_remoteaddr(&lightDQ, reinterpret_cast<const void**>(&buffer), &bufferSize);
+    BOOST_REQUIRE(buffer != nullptr);
+    BOOST_REQUIRE_EQUAL(bufferSize, sizeof(ids.origRemote.sin6.sin6_addr.s6_addr));
+    BOOST_CHECK(memcmp(buffer, &ids.origRemote.sin6.sin6_addr.s6_addr, sizeof(ids.origRemote.sin6.sin6_addr.s6_addr)) == 0);
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_remote_port(&lightDQ), 65535U);
+  }
+
+  {
+    // dnsdist_ffi_dnsquestion_get_qname_hash
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_qname_hash(&lightDQ, 42), ids.qname.hash(42));
+  }
+
+  {
+    // dnsdist_ffi_dnsquestion_get_rcode
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_rcode(&lightDQ), RCode::NoError);
+  }
+
+  {
+    // dnsdist_ffi_dnsquestion_get_header
+    BOOST_CHECK(memcmp(dnsdist_ffi_dnsquestion_get_header(&lightDQ), pwQ.getHeader(), sizeof(dnsheader)) == 0);
+  }
+
+  {
+    // dnsdist_ffi_dnsquestion_get_len, dnsdist_ffi_dnsquestion_get_size
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_len(&lightDQ), query.size());
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_size(&lightDQ), query.size());
+
+    auto oldSize = query.size();
+    BOOST_CHECK(dnsdist_ffi_dnsquestion_set_size(&lightDQ, oldSize + 1));
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_size(&lightDQ), oldSize + 1);
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_len(&lightDQ), oldSize + 1);
+    dnsdist_ffi_dnsquestion_set_len(&lightDQ, oldSize);
+
+    auto max = std::numeric_limits<size_t>::max();
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_set_size(&lightDQ, max), 0U);
+  }
+
+  {
+    // dnsdist_ffi_dnsquestion_get_opcode
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_opcode(&lightDQ), Opcode::Query);
+  }
+
+  {
+    // dnsdist_ffi_dnsquestion_get_tcp
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_tcp(&lightDQ), false);
+  }
+
+  {
+    // dnsdist_ffi_dnsquestion_get_protocol
+    BOOST_CHECK(static_cast<uint8_t>(dnsdist_ffi_dnsquestion_get_protocol(nullptr)) == dnsdist::Protocol(dnsdist::Protocol::DoUDP).toNumber());
+
+    BOOST_CHECK(static_cast<uint8_t>(dnsdist_ffi_dnsquestion_get_protocol(&lightDQ)) == dnsdist::Protocol(dnsdist::Protocol::DoUDP).toNumber());
+    for (const auto protocol : {dnsdist::Protocol::DoUDP, dnsdist::Protocol::DoTCP, dnsdist::Protocol::DNSCryptUDP, dnsdist::Protocol::DNSCryptTCP, dnsdist::Protocol::DoT, dnsdist::Protocol::DoH}) {
+      dq.ids.protocol = protocol;
+      BOOST_CHECK(static_cast<uint8_t>(dnsdist_ffi_dnsquestion_get_protocol(&lightDQ)) == protocol);
+    }
+  }
+
+  {
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_elapsed_us(nullptr), 0U);
+    BOOST_CHECK_GT(dnsdist_ffi_dnsquestion_get_elapsed_us(&lightDQ), 0U);
+  }
+
+  {
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_skip_cache(&lightDQ), false);
+    dnsdist_ffi_dnsquestion_set_skip_cache(&lightDQ, true);
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_skip_cache(&lightDQ), true);
+  }
+
+  {
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_use_ecs(&lightDQ), true);
+    dnsdist_ffi_dnsquestion_set_use_ecs(&lightDQ, false);
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_use_ecs(&lightDQ), false);
+  }
+
+  {
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_add_xpf(&lightDQ), true);
+  }
+
+  {
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_ecs_override(&lightDQ), false);
+    dnsdist_ffi_dnsquestion_set_ecs_override(&lightDQ, true);
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_ecs_override(&lightDQ), true);
+  }
+
+  {
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_is_temp_failure_ttl_set(&lightDQ), false);
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_temp_failure_ttl(&lightDQ), 0U);
+
+    dnsdist_ffi_dnsquestion_set_temp_failure_ttl(&lightDQ, 42);
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_is_temp_failure_ttl_set(&lightDQ), true);
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_temp_failure_ttl(&lightDQ), 42U);
+    dnsdist_ffi_dnsquestion_unset_temp_failure_ttl(&lightDQ);
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_is_temp_failure_ttl_set(&lightDQ), false);
+  }
+
+  {
+    BOOST_CHECK(!dnsdist_ffi_dnsquestion_get_do(&lightDQ));
+  }
+
+  {
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_ecs_prefix_length(&lightDQ), g_ECSSourcePrefixV4);
+    dnsdist_ffi_dnsquestion_set_ecs_prefix_length(&lightDQ, 65535);
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_ecs_prefix_length(&lightDQ), 65535U);
+  }
+
+  {
+    const char* buffer = nullptr;
+    size_t bufferSize = 0;
+    dnsdist_ffi_dnsquestion_get_sni(&lightDQ, &buffer, &bufferSize);
+    BOOST_CHECK_EQUAL(bufferSize, 0U);
+  }
+
+  {
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_trailing_data(&lightDQ, nullptr), 0U);
+#if 0
+    // DNSQuestion::setTrailingData() and DNSQuestion::getTrailingData() are currently stubs in the test runner
+    std::string garbage("thisissomegarbagetrailingdata");
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_set_trailing_data(&lightDQ, garbage.data(), garbage.size()), true);
+    const char* buffer = nullptr;
+    BOOST_REQUIRE_EQUAL(dnsdist_ffi_dnsquestion_get_trailing_data(&lightDQ, &buffer), garbage.size());
+    BOOST_CHECK_EQUAL(garbage, std::string(buffer));
+#endif
+  }
+
+  {
+#if 0
+    // SpoofAction::operator() is a stub in the test runner
+    auto oldData = dq.getData();
+    std::vector<dnsdist_ffi_raw_value> values;
+    ComboAddress v4("192.0.2.1");
+    ComboAddress v6("[2001:db8::42]");
+    values.push_back({ reinterpret_cast<const char*>(&v4.sin4.sin_addr.s_addr), sizeof(v4.sin4.sin_addr.s_addr)});
+    values.push_back({ reinterpret_cast<const char*>(&v6.sin6.sin6_addr.s6_addr), sizeof(v6.sin6.sin6_addr.s6_addr)});
+
+    dnsdist_ffi_dnsquestion_spoof_addrs(&lightDQ, values.data(), values.size());
+    BOOST_CHECK(dq.getData().size() > oldData.size());
+
+    MOADNSParser mdp(false, reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size());
+    BOOST_CHECK_EQUAL(mdp.d_qname, ids.qname);
+    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+    BOOST_CHECK_EQUAL(mdp.d_header.ancount, values.size());
+    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
+    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::A));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, ids.qname);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::AAAA));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_class, QClass::IN);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, ids.qname);
+
+    dq.getMutableData() = oldData;
+#endif
+  }
+
+  {
+    BOOST_CHECK(!dnsdist_ffi_dnsquestion_set_restartable(nullptr));
+    BOOST_CHECK(dnsdist_ffi_dnsquestion_set_restartable(&lightDQ));
+  }
+
+  {
+    BOOST_CHECK_EQUAL(ids.ttlCap, 0U);
+    dnsdist_ffi_dnsquestion_set_max_returned_ttl(&lightDQ, 42U);
+    BOOST_CHECK_EQUAL(ids.ttlCap, 42U);
+  }
+
+  {
+    const std::string tagName("my-tag");
+    const std::string tagValue("my-value");
+    const std::string tagRawValue("my-\0-binary-value");
+    std::string buffer;
+    buffer.resize(512);
+    BOOST_CHECK(dnsdist_ffi_dnsquestion_get_tag(nullptr, nullptr) == nullptr);
+    BOOST_CHECK(dnsdist_ffi_dnsquestion_get_tag(&lightDQ, nullptr) == nullptr);
+    BOOST_CHECK(dnsdist_ffi_dnsquestion_get_tag(&lightDQ, tagName.c_str()) == nullptr);
+
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_tag_raw(nullptr, nullptr, nullptr, 0), 0U);
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_tag_raw(&lightDQ, tagName.c_str(), buffer.data(), buffer.size()), 0U);
+
+    dnsdist_ffi_dnsquestion_set_tag(&lightDQ, tagName.c_str(), tagValue.c_str());
+
+    auto got = dnsdist_ffi_dnsquestion_get_tag(&lightDQ, tagName.c_str());
+    BOOST_CHECK(got != nullptr);
+    BOOST_CHECK_EQUAL(got, tagValue.c_str());
+
+    const dnsdist_ffi_tag_t* tags = nullptr;
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_tag_array(nullptr, nullptr), 0U);
+    BOOST_REQUIRE_EQUAL(dnsdist_ffi_dnsquestion_get_tag_array(&lightDQ, &tags), 1U);
+    BOOST_CHECK_EQUAL(std::string(tags[0].name), tagName.c_str());
+    BOOST_CHECK_EQUAL(std::string(tags[0].value), tagValue.c_str());
+
+    dnsdist_ffi_dnsquestion_set_tag_raw(&lightDQ, tagName.c_str(), tagRawValue.c_str(), tagRawValue.size());
+
+    // too small
+    buffer.resize(1);
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_tag_raw(&lightDQ, tagName.c_str(), buffer.data(), buffer.size()), 0U);
+
+    buffer.resize(tagRawValue.size());
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_tag_raw(&lightDQ, tagName.c_str(), buffer.data(), buffer.size()), tagRawValue.size());
+    BOOST_CHECK_EQUAL(buffer, tagRawValue);
+
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_tag_raw(&lightDQ, "wrong tag name", buffer.data(), buffer.size()), 0U);
+
+    // dnsdist_ffi_dnsquestion_get_tag_array
+
+    {
+      // no DOHUnit attached
+      BOOST_CHECK(dnsdist_ffi_dnsquestion_get_http_path(&lightDQ) == nullptr);
+      BOOST_CHECK(dnsdist_ffi_dnsquestion_get_http_query_string(&lightDQ) == nullptr);
+      BOOST_CHECK(dnsdist_ffi_dnsquestion_get_http_host(&lightDQ) == nullptr);
+      BOOST_CHECK(dnsdist_ffi_dnsquestion_get_http_scheme(&lightDQ) == nullptr);
+      BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_http_headers(&lightDQ, nullptr), 0U);
+      dnsdist_ffi_dnsquestion_set_http_response(&lightDQ, 0U, nullptr, 0U, nullptr);
+    }
+  }
+
+  const std::string deviceID{"my-device-id"};
+  const std::string deviceName{"my-device-name"};
+  const std::string requestorID{"my-requestor-ID"};
+  dnsdist_ffi_dnsquestion_set_device_id(nullptr, nullptr, 0);
+  dnsdist_ffi_dnsquestion_set_device_id(&lightDQ, nullptr, 0);
+  dnsdist_ffi_dnsquestion_set_device_id(&lightDQ, deviceID.c_str(), deviceID.size());
+  dnsdist_ffi_dnsquestion_set_device_name(nullptr, nullptr, 0);
+  dnsdist_ffi_dnsquestion_set_device_name(&lightDQ, nullptr, 0);
+  dnsdist_ffi_dnsquestion_set_device_name(&lightDQ, deviceName.c_str(), deviceName.size());
+  dnsdist_ffi_dnsquestion_set_requestor_id(nullptr, nullptr, 0);
+  dnsdist_ffi_dnsquestion_set_requestor_id(&lightDQ, nullptr, 0);
+  dnsdist_ffi_dnsquestion_set_requestor_id(&lightDQ, requestorID.c_str(), requestorID.size());
+  BOOST_REQUIRE(ids.d_protoBufData != nullptr);
+  BOOST_CHECK_EQUAL(ids.d_protoBufData->d_deviceID, deviceID);
+  BOOST_CHECK_EQUAL(ids.d_protoBufData->d_deviceName, deviceName);
+  BOOST_CHECK_EQUAL(ids.d_protoBufData->d_requestorID, requestorID);
+}
+
+BOOST_AUTO_TEST_CASE(test_Response)
+{
+  InternalQueryState ids;
+  ids.origRemote = ComboAddress("192.0.2.1:4242");
+  ids.origDest = ComboAddress("192.0.2.255:53");
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  ids.qname = DNSName("www.powerdns.com.");
+  ids.queryRealTime.start();
+
+  PacketBuffer response;
+  GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::A, QClass::IN, 0);
+  pwR.getHeader()->qr = 1;
+  pwR.getHeader()->rd = 1;
+  pwR.getHeader()->id = htons(42);
+
+  ComboAddress dsAddr("192.0.2.1:53");
+  auto ds = std::make_shared<DownstreamState>(dsAddr);
+
+  DNSResponse dr(ids, response, ds);
+  dnsdist_ffi_dnsresponse_t lightDR(&dr);
+
+  {
+    dnsdist_ffi_dnsresponse_set_min_ttl(&lightDR, 42);
+    dnsdist_ffi_dnsresponse_set_max_ttl(&lightDR, 84);
+    dnsdist_ffi_dnsresponse_limit_ttl(&lightDR, 42, 84);
+  }
+
+  {
+    BOOST_CHECK_EQUAL(ids.ttlCap, 0U);
+    dnsdist_ffi_dnsresponse_set_max_returned_ttl(&lightDR, 42);
+    BOOST_CHECK_EQUAL(ids.ttlCap, 42U);
+  }
+
+  {
+    /* invalid parameters */
+    BOOST_CHECK(!dnsdist_ffi_dnsresponse_rebase(&lightDR, nullptr, 0));
+
+    /* invalid name */
+    BOOST_CHECK(!dnsdist_ffi_dnsresponse_rebase(&lightDR, "\5AAAA", 5));
+
+    DNSName newName("not-powerdns.com.");
+    BOOST_CHECK(dnsdist_ffi_dnsresponse_rebase(&lightDR, newName.getStorage().data(), newName.getStorage().size()));
+    BOOST_CHECK_EQUAL(ids.qname.toString(), newName.toString());
+  }
+
+  {
+    dnsdist_ffi_dnsresponse_clear_records_type(nullptr, QType::A);
+    dnsdist_ffi_dnsresponse_clear_records_type(&lightDR, QType::A);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_Server)
+{
+  ComboAddress dsAddr("192.0.2.1:53");
+  auto ds = std::make_shared<DownstreamState>(dsAddr);
+  dnsdist_ffi_server_t server(ds);
+
+  BOOST_CHECK_EQUAL(dnsdist_ffi_server_get_outstanding(&server), 0U);
+  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), 1);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_server_get_order(&server), 1);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_server_get_latency(&server), 0.0);
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCache)
+{
+  auto packetCache = std::make_shared<DNSDistPacketCache>(10);
+
+  ComboAddress ipv4("192.0.2.1");
+  InternalQueryState ids;
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  ids.qname = DNSName("powerdns.com.");
+  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()->id = pwQ.getHeader()->id;
+  pwR.startRecord(ids.qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+  pwR.xfrCAWithoutPort(4, ipv4);
+  pwR.commit();
+
+  bool dnssecOK = true;
+  bool receivedOverUDP = true;
+  uint32_t key = 0;
+  boost::optional<Netmask> subnet;
+  ids.queryRealTime.start();
+  DNSQuestion dq(ids, query);
+  packetCache->get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
+  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>();
+  testPool->packetCache = packetCache;
+  std::string poolWithNoCacheName("test-pool-without-cache");
+  auto testPoolWithNoCache = std::make_shared<ServerPool>();
+  auto localPools = g_pools.getCopy();
+  localPools.emplace(poolName, testPool);
+  localPools.emplace(poolWithNoCacheName, testPoolWithNoCache);
+  g_pools.setState(localPools);
+
+  {
+    dnsdist_ffi_domain_list_t* list = nullptr;
+    {
+      // invalid parameters
+      BOOST_CHECK_EQUAL(dnsdist_ffi_packetcache_get_domain_list_by_addr(nullptr, nullptr, nullptr), 0U);
+      BOOST_CHECK_EQUAL(dnsdist_ffi_packetcache_get_domain_list_by_addr("not-existing-pool", ipv4.toString().c_str(), &list), 0U);
+      BOOST_CHECK_EQUAL(dnsdist_ffi_packetcache_get_domain_list_by_addr(poolName.c_str(), "invalid-address", &list), 0U);
+      BOOST_CHECK_EQUAL(dnsdist_ffi_packetcache_get_domain_list_by_addr(poolWithNoCacheName.c_str(), ipv4.toString().c_str(), &list), 0U);
+    }
+
+    {
+      // no match
+      BOOST_CHECK_EQUAL(dnsdist_ffi_packetcache_get_domain_list_by_addr(poolName.c_str(), ComboAddress("192.0.2.254").toString().c_str(), &list), 0U);
+    }
+
+    auto got = dnsdist_ffi_packetcache_get_domain_list_by_addr(poolName.c_str(), ipv4.toString().c_str(), &list);
+    BOOST_REQUIRE_EQUAL(got, 1U);
+    BOOST_REQUIRE(list != nullptr);
+
+    {
+      // invalid parameters
+      BOOST_CHECK(dnsdist_ffi_domain_list_get(nullptr, 0) == nullptr);
+      BOOST_CHECK(dnsdist_ffi_domain_list_get(list, 1) == nullptr);
+    }
+
+    {
+      const char* domain = dnsdist_ffi_domain_list_get(list, 0);
+      BOOST_CHECK(domain == ids.qname.toString());
+    }
+
+    dnsdist_ffi_domain_list_free(list);
+  }
+
+  {
+    dnsdist_ffi_address_list_t* addresses = nullptr;
+    {
+      // invalid parameters
+      BOOST_CHECK_EQUAL(dnsdist_ffi_packetcache_get_address_list_by_domain(nullptr, nullptr, nullptr), 0U);
+      BOOST_CHECK_EQUAL(dnsdist_ffi_packetcache_get_address_list_by_domain("not-existing-pool", ids.qname.toString().c_str(), &addresses), 0U);
+      BOOST_CHECK_EQUAL(dnsdist_ffi_packetcache_get_address_list_by_domain(poolName.c_str(), "invalid-dns...name", &addresses), 0U);
+      BOOST_CHECK_EQUAL(dnsdist_ffi_packetcache_get_address_list_by_domain(poolWithNoCacheName.c_str(), ipv4.toString().c_str(), &addresses), 0U);
+    }
+
+    {
+      // no match
+      BOOST_CHECK_EQUAL(dnsdist_ffi_packetcache_get_address_list_by_domain(poolName.c_str(), "wrong.name.", &addresses), 0U);
+    }
+
+    auto got = dnsdist_ffi_packetcache_get_address_list_by_domain(poolName.c_str(), ids.qname.toString().c_str(), &addresses);
+    BOOST_REQUIRE_EQUAL(got, 1U);
+    BOOST_REQUIRE(addresses != nullptr);
+
+    {
+      // invalid parameters
+      BOOST_CHECK(dnsdist_ffi_address_list_get(nullptr, 0) == nullptr);
+      BOOST_CHECK(dnsdist_ffi_address_list_get(addresses, 1) == nullptr);
+    }
+
+    {
+      const char* addr = dnsdist_ffi_address_list_get(addresses, 0);
+      BOOST_CHECK(addr == ipv4.toString());
+    }
+
+    dnsdist_ffi_address_list_free(addresses);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_ProxyProtocol)
+{
+  ComboAddress v4("192.0.2.1");
+  ComboAddress v6("[2001:db8::42]");
+
+  std::vector<dnsdist_ffi_proxy_protocol_value> values;
+  values.push_back({"test-value", 10U, 1U});
+
+  std::vector<uint8_t> output;
+  output.resize(4096);
+
+  {
+    // too small buffer
+    auto got = dnsdist_ffi_generate_proxy_protocol_payload(sizeof(v4.sin4.sin_addr.s_addr), &v4.sin4.sin_addr.s_addr, &v4.sin4.sin_addr.s_addr, 4242U, 53U, true, values.size(), values.data(), output.data(), 0);
+    BOOST_CHECK_EQUAL(got, 0U);
+  }
+
+  {
+    // invalid address size
+    auto got = dnsdist_ffi_generate_proxy_protocol_payload(0U, &v4.sin4.sin_addr.s_addr, &v4.sin4.sin_addr.s_addr, 4242U, 53U, true, values.size(), values.data(), output.data(), 0);
+    BOOST_CHECK_EQUAL(got, 0U);
+  }
+
+  {
+    auto got = dnsdist_ffi_generate_proxy_protocol_payload(sizeof(v4.sin4.sin_addr.s_addr), &v4.sin4.sin_addr.s_addr, &v4.sin4.sin_addr.s_addr, 4242U, 53U, true, values.size(), values.data(), output.data(), output.size());
+    BOOST_CHECK_EQUAL(got, 41U);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketOverlay)
+{
+  const DNSName target("powerdns.com.");
+  PacketBuffer response;
+  GenericDNSPacketWriter<PacketBuffer> pwR(response, target, QType::A, QClass::IN, 0);
+  pwR.getHeader()->qr = 1;
+  pwR.getHeader()->rd = 1;
+  pwR.getHeader()->ra = 1;
+  pwR.getHeader()->id = htons(42);
+  pwR.startRecord(target, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+  ComboAddress v4("192.0.2.1");
+  pwR.xfrCAWithoutPort(4, v4);
+  pwR.commit();
+  pwR.startRecord(target, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
+  ComboAddress v6("2001:db8::1");
+  pwR.xfrCAWithoutPort(6, v6);
+  pwR.commit();
+  pwR.addOpt(4096, 0, 0);
+  pwR.commit();
+
+  /* invalid parameters */
+  BOOST_CHECK(!dnsdist_ffi_dnspacket_parse(nullptr, 0, nullptr));
+
+  dnsdist_ffi_dnspacket_t* packet = nullptr;
+  // invalid packet
+  BOOST_CHECK(!dnsdist_ffi_dnspacket_parse(reinterpret_cast<const char*>(response.data()), response.size() - 1, &packet));
+  BOOST_REQUIRE(dnsdist_ffi_dnspacket_parse(reinterpret_cast<const char*>(response.data()), response.size(), &packet));
+  BOOST_REQUIRE(packet != nullptr);
+
+  const char* qname = nullptr;
+  size_t qnameSize = 0;
+
+  // invalid parameters
+  dnsdist_ffi_dnspacket_get_qname_raw(nullptr, nullptr, 0);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_qtype(nullptr), 0U);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_qclass(nullptr), 0U);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_qtype(packet), QType::A);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_qclass(packet), QClass::IN);
+
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_records_count_in_section(nullptr, 0), 0U);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_records_count_in_section(packet, 0), 0U);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_records_count_in_section(packet, 1), 1U);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_records_count_in_section(packet, 2), 0U);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_records_count_in_section(packet, 3), 2U);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_records_count_in_section(packet, 4), 0U);
+
+  dnsdist_ffi_dnspacket_get_qname_raw(packet, &qname, &qnameSize);
+  BOOST_REQUIRE(qname != nullptr);
+  BOOST_REQUIRE_EQUAL(qnameSize, target.wirelength());
+  BOOST_CHECK_EQUAL(memcmp(qname, target.getStorage().data(), target.getStorage().size()), 0);
+
+  {
+    std::string parsedName;
+    parsedName.resize(1024);
+
+    // too small
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_name_at_offset_raw(reinterpret_cast<const char*>(response.data()), response.size(), sizeof(dnsheader), parsedName.data(), 1U), 0U);
+    // invalid parameters
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_name_at_offset_raw(nullptr, 0, sizeof(dnsheader), parsedName.data(), parsedName.size()), 0U);
+    // invalid packet
+    BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_name_at_offset_raw(reinterpret_cast<const char*>(response.data()), sizeof(dnsheader) + 2, sizeof(dnsheader), parsedName.data(), parsedName.size()), 0U);
+
+    auto parsedNameSize = dnsdist_ffi_dnspacket_get_name_at_offset_raw(reinterpret_cast<const char*>(response.data()), response.size(), sizeof(dnsheader), parsedName.data(), parsedName.size());
+    BOOST_REQUIRE_GT(parsedNameSize, 0U);
+    BOOST_REQUIRE_EQUAL(parsedNameSize, target.wirelength());
+    BOOST_CHECK_EQUAL(memcmp(parsedName.c_str(), target.getStorage().data(), target.getStorage().size()), 0);
+  }
+
+  const char* name = nullptr;
+  size_t nameSize = 0;
+  dnsdist_ffi_dnspacket_get_record_name_raw(nullptr, 0, nullptr, 0);
+  BOOST_REQUIRE(name == nullptr);
+
+  // invalid parameters
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_record_type(nullptr, 0), 0U);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_record_class(nullptr, 0), 0U);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_record_ttl(nullptr, 0), 0U);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_record_content_length(nullptr, 0), 0U);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_record_content_offset(nullptr, 0), 0U);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_name_at_offset_raw(nullptr, 0, 0, nullptr, 0), 0U);
+
+  // first record */
+  dnsdist_ffi_dnspacket_get_record_name_raw(packet, 0, &name, &nameSize);
+  BOOST_REQUIRE(name != nullptr);
+  BOOST_REQUIRE_EQUAL(nameSize, target.wirelength());
+  BOOST_CHECK_EQUAL(memcmp(name, target.getStorage().data(), target.getStorage().size()), 0);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_record_type(packet, 0), QType::A);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_record_class(packet, 0), QClass::IN);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_record_ttl(packet, 0), 7200U);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_record_content_length(packet, 0), sizeof(v4.sin4.sin_addr.s_addr));
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_record_content_offset(packet, 0), 42U);
+
+  // second record
+  dnsdist_ffi_dnspacket_get_record_name_raw(packet, 1, &name, &nameSize);
+  BOOST_REQUIRE(name != nullptr);
+  BOOST_REQUIRE_EQUAL(nameSize, target.wirelength());
+  BOOST_CHECK_EQUAL(memcmp(name, target.getStorage().data(), target.getStorage().size()), 0);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_record_type(packet, 1), QType::AAAA);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_record_class(packet, 1), QClass::IN);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_record_ttl(packet, 1), 7200U);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_record_content_length(packet, 1), sizeof(v6.sin6.sin6_addr.s6_addr));
+  BOOST_CHECK_EQUAL(dnsdist_ffi_dnspacket_get_record_content_offset(packet, 1), 58U);
+
+  dnsdist_ffi_dnspacket_free(packet);
+}
+
+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");
+  uint16_t qtype = QType::AAAA;
+  uint16_t size = 42;
+  dnsdist::Protocol protocol = dnsdist::Protocol::DoUDP;
+  dnsdist::Protocol outgoingProtocol = dnsdist::Protocol::DoUDP;
+  unsigned int responseTime = 0;
+  struct timespec now;
+  gettime(&now);
+
+  g_rings.reset();
+  g_rings.init();
+  BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0U);
+
+  g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+  g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+
+  dnsdist_ffi_ring_entry_list_t* list = nullptr;
+
+  {
+    // invalid
+    BOOST_CHECK_EQUAL(dnsdist_ffi_ring_get_entries(nullptr), 0U);
+    BOOST_CHECK(list == nullptr);
+    BOOST_CHECK_EQUAL(dnsdist_ffi_ring_get_entries_by_addr(requestor1.toString().c_str(), nullptr), 0U);
+    BOOST_CHECK_EQUAL(dnsdist_ffi_ring_get_entries_by_addr(nullptr, &list), 0U);
+    BOOST_CHECK(list == nullptr);
+    BOOST_CHECK_EQUAL(dnsdist_ffi_ring_get_entries_by_addr("invalid-address", &list), 0U);
+    BOOST_CHECK(list == nullptr);
+    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);
+  }
+
+  BOOST_REQUIRE_EQUAL(dnsdist_ffi_ring_get_entries(&list), 2U);
+  BOOST_CHECK(list != nullptr);
+
+  BOOST_CHECK(!dnsdist_ffi_ring_entry_is_response(list, 0));
+  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.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());
+  }
+
+  dnsdist_ffi_ring_entry_list_free(list);
+  list = nullptr;
+
+  // not the right requestor
+  BOOST_REQUIRE_EQUAL(dnsdist_ffi_ring_get_entries_by_addr("192.0.2.2", &list), 0U);
+  BOOST_CHECK(list == nullptr);
+
+  BOOST_REQUIRE_EQUAL(dnsdist_ffi_ring_get_entries_by_addr(requestor1.toString().c_str(), &list), 2U);
+  BOOST_CHECK(list != nullptr);
+  dnsdist_ffi_ring_entry_list_free(list);
+  list = nullptr;
+}
+
+BOOST_AUTO_TEST_CASE(test_NetworkEndpoint)
+{
+  {
+    dnsdist_ffi_network_endpoint_t* endpoint = nullptr;
+    BOOST_CHECK(!dnsdist_ffi_network_endpoint_new("a", 1, nullptr));
+    BOOST_CHECK(!dnsdist_ffi_network_endpoint_new(nullptr, 1, &endpoint));
+    BOOST_CHECK(!dnsdist_ffi_network_endpoint_new("a", 0, &endpoint));
+    // the path does not exist
+    BOOST_CHECK(!dnsdist_ffi_network_endpoint_new("a", 1, &endpoint));
+  }
+
+  {
+    BOOST_CHECK(!dnsdist_ffi_network_endpoint_is_valid(nullptr));
+  }
+
+  {
+    dnsdist_ffi_network_endpoint_t* endpoint = nullptr;
+    BOOST_CHECK(!dnsdist_ffi_network_endpoint_send(nullptr, "a", 1));
+    BOOST_CHECK(!dnsdist_ffi_network_endpoint_send(endpoint, nullptr, 1));
+  }
+
+  {
+    dnsdist_ffi_network_endpoint_free(nullptr);
+  }
+}
+
+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();
diff --git a/pdns/dnsdistdist/test-dnsdistasync.cc b/pdns/dnsdistdist/test-dnsdistasync.cc
new file mode 100644 (file)
index 0000000..e535ba3
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * 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-async.hh"
+
+BOOST_AUTO_TEST_SUITE(test_dnsdistasync)
+
+class DummyQuerySender : public TCPQuerySender
+{
+public:
+  bool active() const override
+  {
+    return true;
+  }
+
+  void handleResponse([[maybe_unused]] const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
+  {
+  }
+
+  void handleXFRResponse([[maybe_unused]] const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
+  {
+  }
+
+  void notifyIOError([[maybe_unused]] const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
+  {
+    errorRaised = true;
+  }
+
+  std::atomic<bool> errorRaised{false};
+};
+
+struct DummyCrossProtocolQuery : public CrossProtocolQuery
+{
+  DummyCrossProtocolQuery() :
+    CrossProtocolQuery()
+  {
+    d_sender = std::make_shared<DummyQuerySender>();
+  }
+
+  std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
+  {
+    return d_sender;
+  }
+
+  std::shared_ptr<DummyQuerySender> d_sender;
+};
+
+BOOST_AUTO_TEST_CASE(test_Basic)
+{
+  auto holder = std::make_unique<dnsdist::AsynchronousHolder>();
+  BOOST_CHECK(holder->empty());
+
+  {
+    auto query = holder->get(0, 0);
+    BOOST_CHECK(query == nullptr);
+  }
+
+  {
+    uint16_t asyncID = 1;
+    uint16_t queryID = 42;
+    struct timeval ttd;
+    gettimeofday(&ttd, nullptr);
+    // timeout in 100 ms
+    const timeval add{0, 100000};
+    ttd = ttd + add;
+
+    holder->push(asyncID, queryID, ttd, std::make_unique<DummyCrossProtocolQuery>());
+    BOOST_CHECK(!holder->empty());
+
+    auto query = holder->get(0, 0);
+    BOOST_CHECK(query == nullptr);
+
+    query = holder->get(asyncID, queryID);
+    BOOST_CHECK(holder->empty());
+
+    query = holder->get(asyncID, queryID);
+    BOOST_CHECK(query == nullptr);
+
+    // sleep for 200 ms, to be sure the main thread has
+    // been awakened
+    usleep(200000);
+  }
+
+  holder->stop();
+}
+
+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
+  {
+  };
+
+  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());
+  }
+
+  // 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.load());
+
+  holder->stop();
+}
+
+BOOST_AUTO_TEST_CASE(test_AddingExpiredEvent)
+{
+  auto holder = std::make_unique<dnsdist::AsynchronousHolder>(false);
+  uint16_t asyncID = 1;
+  uint16_t queryID = 42;
+  struct timeval ttd;
+  gettimeofday(&ttd, nullptr);
+  // timeout was 10 ms ago, for some reason (long processing time, CPU starvation...)
+  const timeval sub{0, 10000};
+  ttd = ttd - sub;
+
+  std::shared_ptr<DummyQuerySender> sender{nullptr};
+  {
+    auto query = std::make_unique<DummyCrossProtocolQuery>();
+    sender = query->d_sender;
+    BOOST_REQUIRE(sender != nullptr);
+    holder->push(asyncID, queryID, ttd, std::move(query));
+  }
+
+  // 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.load());
+
+  holder->stop();
+}
+
+BOOST_AUTO_TEST_SUITE_END();
diff --git a/pdns/dnsdistdist/test-dnsdistbackend_cc.cc b/pdns/dnsdistdist/test-dnsdistbackend_cc.cc
new file mode 100644 (file)
index 0000000..34745f4
--- /dev/null
@@ -0,0 +1,294 @@
+
+#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"
+
+BOOST_AUTO_TEST_SUITE(dnsdistbackend_cc)
+
+BOOST_AUTO_TEST_CASE(test_Basic)
+{
+  DownstreamState::Config config;
+  DownstreamState ds(std::move(config), nullptr, false);
+  BOOST_CHECK(ds.d_config.availability == DownstreamState::Availability::Auto);
+  BOOST_CHECK_EQUAL(ds.isUp(), false);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "down");
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), true);
+
+  ds.setUp();
+  BOOST_CHECK(ds.d_config.availability == DownstreamState::Availability::Up);
+  BOOST_CHECK_EQUAL(ds.isUp(), true);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "UP");
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), false);
+
+  ds.setDown();
+  BOOST_CHECK(ds.d_config.availability == DownstreamState::Availability::Down);
+  BOOST_CHECK_EQUAL(ds.isUp(), false);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "DOWN");
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), false);
+
+  ds.setAuto();
+  BOOST_CHECK(ds.d_config.availability == DownstreamState::Availability::Auto);
+  BOOST_CHECK_EQUAL(ds.isUp(), false);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "down");
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), true);
+
+  ds.submitHealthCheckResult(true, true);
+  BOOST_CHECK(ds.d_config.availability == DownstreamState::Availability::Auto);
+  BOOST_CHECK_EQUAL(ds.isUp(), true);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "up");
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), true);
+}
+
+BOOST_AUTO_TEST_CASE(test_MaxCheckFailures)
+{
+  const size_t maxCheckFailures = 5;
+  DownstreamState::Config config;
+  config.maxCheckFailures = maxCheckFailures;
+  /* prevents a re-connection */
+  config.remote = ComboAddress("0.0.0.0");
+
+  DownstreamState ds(std::move(config), nullptr, false);
+  BOOST_CHECK(ds.d_config.availability == DownstreamState::Availability::Auto);
+  ds.setUpStatus(true);
+  BOOST_CHECK_EQUAL(ds.isUp(), true);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "up");
+
+  for (size_t idx = 0; idx < maxCheckFailures - 1; idx++) {
+    ds.submitHealthCheckResult(false, false);
+  }
+
+  /* four failed checks is not enough */
+  BOOST_CHECK(ds.d_config.availability == DownstreamState::Availability::Auto);
+  BOOST_CHECK_EQUAL(ds.isUp(), true);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "up");
+
+  /* but five is */
+  ds.submitHealthCheckResult(false, false);
+  BOOST_CHECK(ds.d_config.availability == DownstreamState::Availability::Auto);
+  BOOST_CHECK_EQUAL(ds.isUp(), false);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "down");
+
+  /* only one successful check is needed to go back up */
+  ds.submitHealthCheckResult(false, true);
+  BOOST_CHECK(ds.d_config.availability == DownstreamState::Availability::Auto);
+  BOOST_CHECK_EQUAL(ds.isUp(), true);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "up");
+}
+
+BOOST_AUTO_TEST_CASE(test_Rise)
+{
+  const size_t minRise = 5;
+  DownstreamState::Config config;
+  config.minRiseSuccesses = minRise;
+  /* prevents a re-connection */
+  config.remote = ComboAddress("0.0.0.0");
+
+  DownstreamState ds(std::move(config), nullptr, false);
+  BOOST_CHECK(ds.d_config.availability == DownstreamState::Availability::Auto);
+  BOOST_CHECK_EQUAL(ds.isUp(), false);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "down");
+
+  for (size_t idx = 0; idx < minRise - 1; idx++) {
+    ds.submitHealthCheckResult(false, true);
+  }
+
+  /* four successful checks is not enough */
+  BOOST_CHECK(ds.d_config.availability == DownstreamState::Availability::Auto);
+  BOOST_CHECK_EQUAL(ds.isUp(), false);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "down");
+
+  /* but five is */
+  ds.submitHealthCheckResult(false, true);
+  BOOST_CHECK(ds.d_config.availability == DownstreamState::Availability::Auto);
+  BOOST_CHECK_EQUAL(ds.isUp(), true);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "up");
+
+  /* only one failed check is needed to go back down */
+  ds.submitHealthCheckResult(false, false);
+  BOOST_CHECK(ds.d_config.availability == DownstreamState::Availability::Auto);
+  BOOST_CHECK_EQUAL(ds.isUp(), false);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "down");
+}
+
+BOOST_AUTO_TEST_CASE(test_Lazy)
+{
+  DownstreamState::Config config;
+  config.minRiseSuccesses = 5;
+  config.maxCheckFailures = 3;
+  config.d_lazyHealthCheckMinSampleCount = 11;
+  config.d_lazyHealthCheckThreshold = 20;
+  config.d_lazyHealthCheckUseExponentialBackOff = false;
+  config.availability = DownstreamState::Availability::Lazy;
+  /* prevents a re-connection */
+  config.remote = ComboAddress("0.0.0.0");
+
+  DownstreamState ds(std::move(config), nullptr, false);
+  BOOST_CHECK(ds.d_config.availability == DownstreamState::Availability::Lazy);
+  BOOST_CHECK_EQUAL(ds.isUp(), true);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "up");
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), false);
+
+  /* submit a few results, first successful ones */
+  for (size_t idx = 0; idx < 5; idx++) {
+    ds.reportResponse(RCode::NoError);
+  }
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), false);
+  /* then failed ones */
+  for (size_t idx = 0; idx < 5; idx++) {
+    ds.reportTimeoutOrError();
+  }
+
+  /* the threshold should be reached (50% > 20%) but we do not have enough sample yet
+     (10 < config.d_lazyHealthCheckMinSampleCount) */
+  BOOST_CHECK_EQUAL(ds.isUp(), true);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "up");
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), false);
+
+  /* reporting one valid answer put us above the minimum number of samples,
+     and we are still above the threshold */
+  ds.reportResponse(RCode::NoError);
+  BOOST_CHECK_EQUAL(ds.isUp(), true);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "up");
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), true);
+
+  /* we should be in Potential Failure mode now, and thus always returning true */
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), true);
+
+  /* even if we fill the whole circular buffer with valid answers */
+  for (size_t idx = 0; idx < config.d_lazyHealthCheckSampleSize; idx++) {
+    ds.reportResponse(RCode::NoError);
+  }
+  BOOST_CHECK_EQUAL(ds.isUp(), true);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "up");
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), true);
+
+  /* if we submit at least one valid health-check, we go back to Healthy */
+  ds.submitHealthCheckResult(false, true);
+  BOOST_CHECK_EQUAL(ds.isUp(), true);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "up");
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), false);
+
+  /* now let's reach the threshold again, this time just barely */
+  for (size_t idx = 0; idx < config.d_lazyHealthCheckThreshold; idx++) {
+    ds.reportTimeoutOrError();
+  }
+  BOOST_CHECK_EQUAL(ds.isUp(), true);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "up");
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), true);
+
+  /* we need maxCheckFailures failed health-checks to go down */
+  BOOST_REQUIRE(config.maxCheckFailures >= 1);
+  for (size_t idx = 0; idx < static_cast<size_t>(config.maxCheckFailures - 1); idx++) {
+    ds.submitHealthCheckResult(false, false);
+  }
+  BOOST_CHECK_EQUAL(ds.isUp(), true);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "up");
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), true);
+  time_t failedCheckTime = time(nullptr);
+  ds.submitHealthCheckResult(false, false);
+
+  /* now we are in Failed state */
+  BOOST_CHECK_EQUAL(ds.isUp(), false);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "down");
+  BOOST_CHECK(ds.getNextLazyHealthCheck() == (failedCheckTime + config.d_lazyHealthCheckFailedInterval));
+
+  /* let fill the buffer with successes, it does not matter */
+  for (size_t idx = 0; idx < config.d_lazyHealthCheckSampleSize; idx++) {
+    ds.reportResponse(RCode::NoError);
+  }
+
+  /* we need minRiseSuccesses successful health-checks to go up */
+  BOOST_REQUIRE(config.minRiseSuccesses >= 1);
+  for (size_t idx = 0; idx < static_cast<size_t>(config.minRiseSuccesses - 1); idx++) {
+    ds.submitHealthCheckResult(false, true);
+  }
+  BOOST_CHECK_EQUAL(ds.isUp(), false);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "down");
+
+  ds.submitHealthCheckResult(false, true);
+  BOOST_CHECK_EQUAL(ds.isUp(), true);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "up");
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), false);
+}
+
+BOOST_AUTO_TEST_CASE(test_LazyExponentialBackOff)
+{
+  DownstreamState::Config config;
+  config.minRiseSuccesses = 5;
+  config.maxCheckFailures = 3;
+  config.d_lazyHealthCheckMinSampleCount = 11;
+  config.d_lazyHealthCheckThreshold = 20;
+  config.d_lazyHealthCheckUseExponentialBackOff = true;
+  config.d_lazyHealthCheckMaxBackOff = 600;
+  config.d_lazyHealthCheckFailedInterval = 15;
+  config.availability = DownstreamState::Availability::Lazy;
+  /* prevents a re-connection */
+  config.remote = ComboAddress("0.0.0.0");
+
+  DownstreamState ds(std::move(config), nullptr, false);
+  BOOST_CHECK(ds.d_config.availability == DownstreamState::Availability::Lazy);
+  BOOST_CHECK_EQUAL(ds.isUp(), true);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "up");
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), false);
+
+  /* submit a few failed results */
+  for (size_t idx = 0; idx < config.d_lazyHealthCheckMinSampleCount; idx++) {
+    ds.reportTimeoutOrError();
+  }
+  BOOST_CHECK_EQUAL(ds.isUp(), true);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "up");
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), true);
+
+  /* we should be in Potential Failure mode now, and thus always returning true */
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), true);
+
+  /* we need maxCheckFailures failed health-checks to go down */
+  BOOST_REQUIRE(config.maxCheckFailures >= 1);
+  for (size_t idx = 0; idx < static_cast<size_t>(config.maxCheckFailures - 1); idx++) {
+    ds.submitHealthCheckResult(false, false);
+  }
+  BOOST_CHECK_EQUAL(ds.isUp(), true);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "up");
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), true);
+  time_t currentTime = time(nullptr);
+  ds.submitHealthCheckResult(false, false);
+
+  /* now we are in Failed state */
+  BOOST_CHECK_EQUAL(ds.isUp(), false);
+  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);
+
+  /* so after 5 failures */
+  const size_t nbFailures = 5;
+  for (size_t idx = 0; idx < nbFailures; idx++) {
+    currentTime = ds.getNextLazyHealthCheck();
+    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))));
+
+  /* we need minRiseSuccesses successful health-checks to go up */
+  BOOST_REQUIRE(config.minRiseSuccesses >= 1);
+  for (size_t idx = 0; idx < static_cast<size_t>(config.minRiseSuccesses - 1); idx++) {
+    ds.submitHealthCheckResult(false, true);
+  }
+  BOOST_CHECK_EQUAL(ds.isUp(), false);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "down");
+
+  ds.submitHealthCheckResult(false, true);
+  BOOST_CHECK_EQUAL(ds.isUp(), true);
+  BOOST_CHECK_EQUAL(ds.getStatus(), "up");
+  BOOST_CHECK_EQUAL(ds.healthCheckRequired(), false);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
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 bfb9c87e847a400a833cd73fa0b2c4ceb066be55..fbd24dd973e92c33fb40259b646a9a32b7fbae27 100644 (file)
@@ -1,23 +1,40 @@
 
+#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;
-GlobalStateHolder<NetmaskTree<DynBlock, AddressAndPortRange>> g_dynblockNMG;
-GlobalStateHolder<SuffixMatchTree<DynBlock>> g_dynblockSMT;
 shared_ptr<BPFFilter> g_defaultBPFFilter{nullptr};
 
+#ifndef DISABLE_DYNBLOCKS
+
 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;
@@ -1506,3 +1623,4 @@ BOOST_AUTO_TEST_CASE(test_NetmaskTreePort) {
 }
 
 BOOST_AUTO_TEST_SUITE_END()
+#endif /* DISABLE_DYNBLOCKS */
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 5ed25c2a1020a52ba5ef430f498a4218811e8c95..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>
@@ -19,7 +22,7 @@ static void doKVSChecks(std::unique_ptr<KeyValueStore>& kvs, const ComboAddress&
     /* local address is not in the db, remote is */
     BOOST_CHECK_EQUAL(kvs->getValue(std::string(reinterpret_cast<const char*>(&lc.sin4.sin_addr.s_addr), sizeof(lc.sin4.sin_addr.s_addr)), value), false);
     BOOST_CHECK_EQUAL(kvs->keyExists(std::string(reinterpret_cast<const char*>(&lc.sin4.sin_addr.s_addr), sizeof(lc.sin4.sin_addr.s_addr))), false);
-    BOOST_CHECK(kvs->keyExists(std::string(reinterpret_cast<const char*>(&dq.remote->sin4.sin_addr.s_addr), sizeof(dq.remote->sin4.sin_addr.s_addr))));
+    BOOST_CHECK(kvs->keyExists(std::string(reinterpret_cast<const char*>(&dq.ids.origRemote.sin4.sin_addr.s_addr), sizeof(dq.ids.origRemote.sin4.sin_addr.s_addr))));
 
     auto keys = lookupKey->getKeys(dq);
     BOOST_CHECK_EQUAL(keys.size(), 1U);
@@ -66,7 +69,7 @@ static void doKVSChecks(std::unique_ptr<KeyValueStore>& kvs, const ComboAddress&
     }
   }
 
-  const DNSName subdomain = DNSName("sub") + *dq.qname;
+  const DNSName subdomain = DNSName("sub") + dq.ids.qname;
   const DNSName notPDNS("not-powerdns.com.");
 
   /* qname match, in wire format */
@@ -147,9 +150,9 @@ static void doKVSChecks(std::unique_ptr<KeyValueStore>& kvs, const ComboAddress&
   {
     auto lookupKey = make_unique<KeyValueLookupKeySuffix>(0, true);
     auto keys = lookupKey->getKeys(dq);
-    BOOST_CHECK_EQUAL(keys.size(), dq.qname->countLabels());
+    BOOST_CHECK_EQUAL(keys.size(), dq.ids.qname.countLabels());
     BOOST_REQUIRE(!keys.empty());
-    BOOST_CHECK_EQUAL(keys.at(0), dq.qname->toDNSStringLC());
+    BOOST_CHECK_EQUAL(keys.at(0), dq.ids.qname.toDNSStringLC());
     std::string value;
     BOOST_CHECK_EQUAL(kvs->getValue(keys.at(0), value), true);
     BOOST_CHECK_EQUAL(value, "this is the value for the qname");
@@ -184,9 +187,9 @@ static void doKVSChecks(std::unique_ptr<KeyValueStore>& kvs, const ComboAddress&
   {
     auto lookupKey = make_unique<KeyValueLookupKeySuffix>(0, false);
     auto keys = lookupKey->getKeys(dq);
-    BOOST_CHECK_EQUAL(keys.size(), dq.qname->countLabels());
+    BOOST_CHECK_EQUAL(keys.size(), dq.ids.qname.countLabels());
     BOOST_REQUIRE(!keys.empty());
-    BOOST_CHECK_EQUAL(keys.at(0), dq.qname->toStringRootDot());
+    BOOST_CHECK_EQUAL(keys.at(0), dq.ids.qname.toStringRootDot());
     std::string value;
     BOOST_CHECK_EQUAL(kvs->getValue(keys.at(0), value), false);
     value.clear();
@@ -221,7 +224,7 @@ static void doKVSChecks(std::unique_ptr<KeyValueStore>& kvs, const ComboAddress&
     auto keys = lookupKey->getKeys(dq);
     BOOST_CHECK_EQUAL(keys.size(), 1U);
     BOOST_REQUIRE(!keys.empty());
-    BOOST_CHECK_EQUAL(keys.at(0), dq.qname->toDNSStringLC());
+    BOOST_CHECK_EQUAL(keys.at(0), dq.ids.qname.toDNSStringLC());
     std::string value;
     BOOST_CHECK_EQUAL(kvs->getValue(keys.at(0), value), true);
     BOOST_CHECK_EQUAL(value, "this is the value for the qname");
@@ -300,21 +303,21 @@ BOOST_AUTO_TEST_SUITE(dnsdistkvs_cc)
 #ifdef HAVE_LMDB
 BOOST_AUTO_TEST_CASE(test_LMDB) {
 
-  DNSName qname("powerdns.com.");
+  InternalQueryState ids;
+  ids.qname = DNSName("powerdns.com.");
   DNSName plaintextDomain("powerdns.org.");
-  uint16_t qtype = QType::A;
-  uint16_t qclass = QClass::IN;
-  ComboAddress lc("192.0.2.1:53");
-  ComboAddress rem("192.0.2.128:42");
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.origDest = ComboAddress("192.0.2.1:53");
+  ids.origRemote = ComboAddress("192.0.2.128:42");
   PacketBuffer packet(sizeof(dnsheader));
-  auto proto = dnsdist::Protocol::DoUDP;
-  struct timespec queryRealTime;
-  gettime(&queryRealTime, true);
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  ids.queryRealTime.start();
   struct timespec expiredTime;
   /* the internal QPS limiter does not use the real time */
   gettime(&expiredTime);
 
-  DNSQuestion dq(&qname, qtype, qclass, &lc, &rem, packet, proto, &queryRealTime);
+  DNSQuestion dq(ids, packet);
   ComboAddress v4Masked(v4ToMask);
   ComboAddress v6Masked(v6ToMask);
   v4Masked.truncate(25);
@@ -330,11 +333,11 @@ BOOST_AUTO_TEST_CASE(test_LMDB) {
     MDBEnv env(dbPath.c_str(), MDB_NOSUBDIR, 0600, 50);
     auto transaction = env.getRWTransaction();
     auto dbi = transaction->openDB("db-name", MDB_CREATE);
-    transaction->put(dbi, MDBInVal(std::string(reinterpret_cast<const char*>(&rem.sin4.sin_addr.s_addr), sizeof(rem.sin4.sin_addr.s_addr))), MDBInVal("this is the value for the remote addr"));
-    transaction->put(dbi, MDBInVal(std::string(reinterpret_cast<const char*>(&rem.sin4.sin_addr.s_addr), sizeof(rem.sin4.sin_addr.s_addr)) + std::string(reinterpret_cast<const char*>(&rem.sin4.sin_port), sizeof(rem.sin4.sin_port))), MDBInVal("this is the value for the remote addr + port"));
+    transaction->put(dbi, MDBInVal(std::string(reinterpret_cast<const char*>(&ids.origRemote.sin4.sin_addr.s_addr), sizeof(ids.origRemote.sin4.sin_addr.s_addr))), MDBInVal("this is the value for the remote addr"));
+    transaction->put(dbi, MDBInVal(std::string(reinterpret_cast<const char*>(&ids.origRemote.sin4.sin_addr.s_addr), sizeof(ids.origRemote.sin4.sin_addr.s_addr)) + std::string(reinterpret_cast<const char*>(&ids.origRemote.sin4.sin_port), sizeof(ids.origRemote.sin4.sin_port))), MDBInVal("this is the value for the remote addr + port"));
     transaction->put(dbi, MDBInVal(std::string(reinterpret_cast<const char*>(&v4Masked.sin4.sin_addr.s_addr), sizeof(v4Masked.sin4.sin_addr.s_addr))), MDBInVal("this is the value for the masked v4 addr"));
     transaction->put(dbi, MDBInVal(std::string(reinterpret_cast<const char*>(&v6Masked.sin6.sin6_addr.s6_addr), sizeof(v6Masked.sin6.sin6_addr.s6_addr))), MDBInVal("this is the value for the masked v6 addr"));
-    transaction->put(dbi, MDBInVal(qname.toDNSStringLC()), MDBInVal("this is the value for the qname"));
+    transaction->put(dbi, MDBInVal(dq.ids.qname.toDNSStringLC()), MDBInVal("this is the value for the qname"));
     transaction->put(dbi, MDBInVal(plaintextDomain.toStringRootDot()), MDBInVal("this is the value for the plaintext domain"));
 
     transaction->commit();
@@ -355,7 +358,7 @@ BOOST_AUTO_TEST_CASE(test_LMDB) {
   }
 
   std::unique_ptr<KeyValueStore> lmdb = std::make_unique<LMDBKVStore>(dbPath, "db-name");
-  doKVSChecks(lmdb, lc, rem, dq, plaintextDomain);
+  doKVSChecks(lmdb, ids.origDest, ids.origRemote, dq, plaintextDomain);
   lmdb.reset();
 
   lmdb = std::make_unique<LMDBKVStore>(dbPath, "range-db-name");
@@ -385,21 +388,21 @@ BOOST_AUTO_TEST_CASE(test_LMDB) {
 #ifdef HAVE_CDB
 BOOST_AUTO_TEST_CASE(test_CDB) {
 
-  DNSName qname("powerdns.com.");
+  InternalQueryState ids;
+  ids.qname = DNSName("powerdns.com.");
   DNSName plaintextDomain("powerdns.org.");
-  uint16_t qtype = QType::A;
-  uint16_t qclass = QClass::IN;
-  ComboAddress lc("192.0.2.1:53");
-  ComboAddress rem("192.0.2.128:42");
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.origDest = ComboAddress("192.0.2.1:53");
+  ids.origRemote = ComboAddress("192.0.2.128:42");
   PacketBuffer packet(sizeof(dnsheader));
-  auto proto = dnsdist::Protocol::DoUDP;
-  struct timespec queryRealTime;
-  gettime(&queryRealTime, true);
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  ids.queryRealTime.start();
   struct timespec expiredTime;
   /* the internal QPS limiter does not use the real time */
   gettime(&expiredTime);
 
-  DNSQuestion dq(&qname, qtype, qclass, &lc, &rem, packet, proto, &queryRealTime);
+  DNSQuestion dq(ids, packet);
   ComboAddress v4Masked(v4ToMask);
   ComboAddress v6Masked(v6ToMask);
   v4Masked.truncate(25);
@@ -410,17 +413,17 @@ BOOST_AUTO_TEST_CASE(test_CDB) {
     int fd = mkstemp(db);
     BOOST_REQUIRE(fd >= 0);
     CDBWriter writer(fd);
-    BOOST_REQUIRE(writer.addEntry(std::string(reinterpret_cast<const char*>(&rem.sin4.sin_addr.s_addr), sizeof(rem.sin4.sin_addr.s_addr)), "this is the value for the remote addr"));
-    BOOST_REQUIRE(writer.addEntry(std::string(reinterpret_cast<const char*>(&rem.sin4.sin_addr.s_addr), sizeof(rem.sin4.sin_addr.s_addr)) + std::string(reinterpret_cast<const char*>(&rem.sin4.sin_port), sizeof(rem.sin4.sin_port)), "this is the value for the remote addr + port"));
+    BOOST_REQUIRE(writer.addEntry(std::string(reinterpret_cast<const char*>(&ids.origRemote.sin4.sin_addr.s_addr), sizeof(ids.origRemote.sin4.sin_addr.s_addr)), "this is the value for the remote addr"));
+    BOOST_REQUIRE(writer.addEntry(std::string(reinterpret_cast<const char*>(&ids.origRemote.sin4.sin_addr.s_addr), sizeof(ids.origRemote.sin4.sin_addr.s_addr)) + std::string(reinterpret_cast<const char*>(&ids.origRemote.sin4.sin_port), sizeof(ids.origRemote.sin4.sin_port)), "this is the value for the remote addr + port"));
     BOOST_REQUIRE(writer.addEntry(std::string(reinterpret_cast<const char*>(&v4Masked.sin4.sin_addr.s_addr), sizeof(v4Masked.sin4.sin_addr.s_addr)), "this is the value for the masked v4 addr"));
     BOOST_REQUIRE(writer.addEntry(std::string(reinterpret_cast<const char*>(&v6Masked.sin6.sin6_addr.s6_addr), sizeof(v6Masked.sin6.sin6_addr.s6_addr)), "this is the value for the masked v6 addr"));
-    BOOST_REQUIRE(writer.addEntry(qname.toDNSStringLC(), "this is the value for the qname"));
+    BOOST_REQUIRE(writer.addEntry(dq.ids.qname.toDNSStringLC(), "this is the value for the qname"));
     BOOST_REQUIRE(writer.addEntry(plaintextDomain.toStringRootDot(), "this is the value for the plaintext domain"));
     writer.close();
   }
 
   std::unique_ptr<KeyValueStore> cdb = std::make_unique<CDBKVStore>(db, 0);
-  doKVSChecks(cdb, lc, rem, dq, plaintextDomain);
+  doKVSChecks(cdb, ids.origDest, ids.origRemote, dq, plaintextDomain);
 
   unlink(db);
 
index 9eee1cca51b26699897f438f8a6f2c3f40ab7608..bcb73b26529cd858245f22dd9aae0fd6e933e0c7 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>
@@ -20,8 +23,6 @@ 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
@@ -108,17 +80,17 @@ std::atomic<bool> g_configurationDone{false};
 static DNSQuestion getDQ(const DNSName* providedName = nullptr)
 {
   static const DNSName qname("powerdns.com.");
-  static const ComboAddress lc("127.0.0.1:53");
-  static const ComboAddress rem("192.0.2.1:42");
-  static struct timespec queryRealTime;
   static PacketBuffer packet(sizeof(dnsheader));
-
-  uint16_t qtype = QType::A;
-  uint16_t qclass = QClass::IN;
-  auto proto = dnsdist::Protocol::DoUDP;
-  gettime(&queryRealTime, true);
-
-  DNSQuestion dq(providedName ? providedName : &qname, qtype, qclass, &lc, &rem, packet, proto, &queryRealTime);
+  static InternalQueryState ids;
+  ids.origDest = ComboAddress("127.0.0.1:53");
+  ids.origRemote = ComboAddress("192.0.2.1:42");
+  ids.qname = providedName ? *providedName : qname;
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  ids.queryRealTime.start();
+
+  DNSQuestion dq(ids, packet);
   return dq;
 }
 
@@ -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;
diff --git a/pdns/dnsdistdist/test-dnsdistluanetwork.cc b/pdns/dnsdistdist/test-dnsdistluanetwork.cc
new file mode 100644 (file)
index 0000000..6ddc4a2
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ * 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-lua-network.hh"
+
+BOOST_AUTO_TEST_SUITE(test_dnsdistluanetwork)
+
+BOOST_AUTO_TEST_CASE(test_Basic)
+{
+  dnsdist::NetworkListener listener;
+  bool received = false;
+
+  std::string payload = {'h', 'e', 'l', 'l', 'o'};
+  char socketPath[] = "/tmp/test_dnsdistluanetwork.XXXXXX";
+  int fd = mkstemp(socketPath);
+  BOOST_REQUIRE(fd >= 0);
+
+  listener.addUnixListeningEndpoint(socketPath, 0, [&received, payload](dnsdist::NetworkListener::EndpointID endpoint, std::string&& dgram, const std::string& from) {
+    BOOST_CHECK_EQUAL(endpoint, 0U);
+    BOOST_CHECK(dgram == payload);
+    received = true;
+  });
+
+  dnsdist::NetworkEndpoint client(socketPath);
+  BOOST_CHECK(client.send(payload));
+
+  struct timeval now;
+  listener.runOnce(now, 1000);
+  BOOST_CHECK(received);
+
+  unlink(socketPath);
+  close(fd);
+}
+
+BOOST_AUTO_TEST_CASE(test_Exceptions)
+{
+  std::string payload = {'h', 'e', 'l', 'l', 'o'};
+  char socketPath[] = "/tmp/test_dnsdistluanetwork.XXXXXX";
+  int fd = mkstemp(socketPath);
+  BOOST_REQUIRE(fd >= 0);
+
+  {
+    dnsdist::NetworkListener listener;
+    /* try running while empty */
+    struct timeval now;
+    BOOST_CHECK_THROW(listener.runOnce(now, 1000), std::runtime_error);
+  }
+
+  {
+    /* invalid path */
+    dnsdist::NetworkListener listener;
+    BOOST_CHECK_THROW(listener.addUnixListeningEndpoint(std::string(), 0,
+                                                        [](dnsdist::NetworkListener::EndpointID endpoint, std::string&& dgram, const std::string& from) {}),
+                      std::runtime_error);
+
+    bool caught = false;
+    try {
+      std::string empty;
+      dnsdist::NetworkEndpoint endpoint(empty);
+    }
+    catch (const std::runtime_error& e) {
+      caught = true;
+    }
+    BOOST_CHECK(caught);
+  }
+
+  {
+    dnsdist::NetworkListener listener;
+    bool received = false;
+    listener.addUnixListeningEndpoint(socketPath, 0, [&received](dnsdist::NetworkListener::EndpointID endpoint, std::string&& dgram, const std::string& from) {
+      received = true;
+    });
+
+    dnsdist::NetworkEndpoint client(socketPath);
+    BOOST_CHECK(client.send(payload));
+
+    struct timeval now;
+    listener.runOnce(now, 1000);
+    BOOST_CHECK(received);
+
+    char otherSocketPath[] = "/tmp/test_dnsdistluanetworkOtherPath";
+    /* try binding when already running */
+    bool raised = false;
+    try {
+      listener.addUnixListeningEndpoint(otherSocketPath, 0,
+                                        [](dnsdist::NetworkListener::EndpointID endpoint, std::string&& dgram, const std::string& from) {});
+    }
+    catch (const std::runtime_error& e) {
+      raised = true;
+      BOOST_CHECK_EQUAL(e.what(), "NetworkListener should not be altered at runtime");
+    }
+    BOOST_CHECK(raised);
+  }
+
+  {
+    dnsdist::NetworkListener listener;
+    bool received = false;
+    listener.addUnixListeningEndpoint(socketPath, 0, [&received](dnsdist::NetworkListener::EndpointID endpoint, std::string&& dgram, const std::string& from) {
+      received = true;
+      throw std::runtime_error("Test exception");
+    });
+
+    dnsdist::NetworkEndpoint client(socketPath);
+    BOOST_CHECK(client.send(payload));
+
+    struct timeval now;
+    listener.runOnce(now, 1000);
+    BOOST_CHECK(received);
+  }
+
+  {
+    class UnexpectedException
+    {
+    };
+
+    dnsdist::NetworkListener listener;
+    bool received = false;
+    listener.addUnixListeningEndpoint(socketPath, 0, [&received](dnsdist::NetworkListener::EndpointID endpoint, std::string&& dgram, const std::string& from) {
+      received = true;
+      throw UnexpectedException();
+    });
+
+    dnsdist::NetworkEndpoint client(socketPath);
+    BOOST_CHECK(client.send(payload));
+
+    struct timeval now;
+    listener.runOnce(now, 1000);
+    BOOST_CHECK(received);
+  }
+
+  unlink(socketPath);
+  close(fd);
+}
+
+#ifdef __linux__
+BOOST_AUTO_TEST_CASE(test_Abstract)
+{
+  dnsdist::NetworkListener listener;
+  bool received = false;
+
+  std::string payload = {'h', 'e', 'l', 'l', 'o'};
+  std::string socketPath("test_dnsdistluanetwork");
+  socketPath.insert(0, 1, 0);
+
+  listener.addUnixListeningEndpoint(socketPath, 0, [&received, payload](dnsdist::NetworkListener::EndpointID endpoint, std::string&& dgram, const std::string& from) {
+    BOOST_CHECK_EQUAL(endpoint, 0U);
+    BOOST_CHECK(dgram == payload);
+    received = true;
+  });
+
+  dnsdist::NetworkEndpoint client(socketPath);
+  BOOST_CHECK(client.send(payload));
+
+  struct timeval now;
+  listener.runOnce(now, 1000);
+  BOOST_CHECK(received);
+}
+
+BOOST_AUTO_TEST_CASE(test_Abstract_Exceptions)
+{
+  dnsdist::NetworkListener listener;
+  std::string socketPath("test_dnsdistluanetwork");
+  socketPath.insert(0, 1, 0);
+  bool received = false;
+  listener.addUnixListeningEndpoint(socketPath, 0, [&received](dnsdist::NetworkListener::EndpointID endpoint, std::string&& dgram, const std::string& from) {
+    received = true;
+  });
+
+  /* try binding twice to the same path */
+  bool raised = false;
+  try {
+    listener.addUnixListeningEndpoint(socketPath, 0, [](dnsdist::NetworkListener::EndpointID endpoint, std::string&& dgram, const std::string& from) {});
+  }
+  catch (const std::runtime_error& e) {
+    raised = true;
+    BOOST_CHECK(boost::starts_with(e.what(), "Error binding Unix socket to path"));
+  }
+  BOOST_CHECK(raised);
+
+  {
+    /* try connecting to a non-existing path */
+    raised = false;
+    std::string nonExistingPath("test_dnsdistluanetwork_non_existing");
+    nonExistingPath.insert(0, 1, 0);
+    try {
+      dnsdist::NetworkEndpoint endpoint(nonExistingPath);
+    }
+    catch (const std::runtime_error& e) {
+      raised = true;
+    }
+    BOOST_CHECK(raised);
+  }
+}
+
+#endif /* __linux__ */
+
+BOOST_AUTO_TEST_SUITE_END();
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..3fa1eaa
--- /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 7bf4e2c8ef4820df0619c1004fa73e5570b41726..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
 {
@@ -599,11 +495,6 @@ public:
     return true;
   }
 
-  const ClientState* getClientState() const override
-  {
-    return nullptr;
-  }
-
   void handleResponse(const struct timeval& now, TCPResponse&& response) override
   {
     if (d_customHandler) {
@@ -612,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);
@@ -631,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(IDState&& query, const struct timeval& now) override
+  void notifyIOError([[maybe_unused]] const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
   {
     d_error = true;
   }
@@ -646,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()
@@ -696,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);
 
@@ -730,7 +591,7 @@ BOOST_FIXTURE_TEST_CASE(test_SingleQuery, TestFixture)
 
   auto sender = std::make_shared<MockupQuerySender>();
   sender->d_id = counter;
-  InternalQuery internalQuery(std::move(query), IDState());
+  InternalQuery internalQuery(std::move(query), InternalQueryState());
 
   s_steps = {
     {ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done},
@@ -772,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);
 
@@ -808,7 +669,7 @@ BOOST_FIXTURE_TEST_CASE(test_ConcurrentQueries, TestFixture)
 
     auto sender = std::make_shared<MockupQuerySender>();
     sender->d_id = counter;
-    InternalQuery internalQuery(std::move(query), IDState());
+    InternalQuery internalQuery(std::move(query), InternalQueryState());
     queries.push_back({std::move(sender), std::move(internalQuery)});
   }
 
@@ -861,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);
 
@@ -897,7 +758,7 @@ BOOST_FIXTURE_TEST_CASE(test_ConnectionReuse, TestFixture)
 
     auto sender = std::make_shared<MockupQuerySender>();
     sender->d_id = counter;
-    InternalQuery internalQuery(std::move(query), IDState());
+    InternalQuery internalQuery(std::move(query), InternalQueryState());
     queries.push_back({std::move(sender), std::move(internalQuery)});
   }
 
@@ -969,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);
 
@@ -1011,7 +872,7 @@ BOOST_FIXTURE_TEST_CASE(test_InvalidDNSAnswer, TestFixture)
        while TCP and DoT will first pass it back to the TCP worker thread */
     throw std::runtime_error("Invalid response");
   };
-  InternalQuery internalQuery(std::move(query), IDState());
+  InternalQuery internalQuery(std::move(query), InternalQueryState());
 
   s_steps = {
     {ExpectedStep::ExpectedRequest::connectToBackend, IOState::Done},
@@ -1050,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);
 
@@ -1086,7 +947,7 @@ BOOST_FIXTURE_TEST_CASE(test_TimeoutWhileWriting, TestFixture)
 
     auto sender = std::make_shared<MockupQuerySender>();
     sender->d_id = counter;
-    InternalQuery internalQuery(std::move(query), IDState());
+    InternalQuery internalQuery(std::move(query), InternalQueryState());
     queries.push_back({std::move(sender), std::move(internalQuery)});
   }
 
@@ -1137,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);
 
@@ -1173,7 +1034,7 @@ BOOST_FIXTURE_TEST_CASE(test_TimeoutWhileReading, TestFixture)
 
     auto sender = std::make_shared<MockupQuerySender>();
     sender->d_id = counter;
-    InternalQuery internalQuery(std::move(query), IDState());
+    InternalQuery internalQuery(std::move(query), InternalQueryState());
     queries.push_back({std::move(sender), std::move(internalQuery)});
   }
 
@@ -1224,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);
 
@@ -1260,7 +1121,7 @@ BOOST_FIXTURE_TEST_CASE(test_ShortWrite, TestFixture)
 
     auto sender = std::make_shared<MockupQuerySender>();
     sender->d_id = counter;
-    InternalQuery internalQuery(std::move(query), IDState());
+    InternalQuery internalQuery(std::move(query), InternalQueryState());
     queries.push_back({std::move(sender), std::move(internalQuery)});
   }
 
@@ -1311,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);
 
@@ -1347,7 +1208,7 @@ BOOST_FIXTURE_TEST_CASE(test_ShortRead, TestFixture)
 
     auto sender = std::make_shared<MockupQuerySender>();
     sender->d_id = counter;
-    InternalQuery internalQuery(std::move(query), IDState());
+    InternalQuery internalQuery(std::move(query), InternalQueryState());
     queries.push_back({std::move(sender), std::move(internalQuery)});
   }
 
@@ -1405,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);
 
@@ -1441,7 +1302,7 @@ BOOST_FIXTURE_TEST_CASE(test_ConnectionClosedWhileReading, TestFixture)
 
     auto sender = std::make_shared<MockupQuerySender>();
     sender->d_id = counter;
-    InternalQuery internalQuery(std::move(query), IDState());
+    InternalQuery internalQuery(std::move(query), InternalQueryState());
     queries.push_back({std::move(sender), std::move(internalQuery)});
   }
 
@@ -1491,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);
 
@@ -1527,7 +1388,7 @@ BOOST_FIXTURE_TEST_CASE(test_ConnectionClosedWhileWriting, TestFixture)
 
     auto sender = std::make_shared<MockupQuerySender>();
     sender->d_id = counter;
-    InternalQuery internalQuery(std::move(query), IDState());
+    InternalQuery internalQuery(std::move(query), InternalQueryState());
     queries.push_back({std::move(sender), std::move(internalQuery)});
   }
 
@@ -1585,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);
 
@@ -1623,7 +1484,7 @@ BOOST_FIXTURE_TEST_CASE(test_GoAwayFromServer, TestFixture)
 
     auto sender = std::make_shared<MockupQuerySender>();
     sender->d_id = counter;
-    InternalQuery internalQuery(std::move(query), IDState());
+    InternalQuery internalQuery(std::move(query), InternalQueryState());
     queries.push_back({std::move(sender), std::move(internalQuery)});
   }
 
@@ -1696,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);
 
@@ -1732,7 +1593,7 @@ BOOST_FIXTURE_TEST_CASE(test_HTTP500FromServer, TestFixture)
 
     auto sender = std::make_shared<MockupQuerySender>();
     sender->d_id = counter;
-    InternalQuery internalQuery(std::move(query), IDState());
+    InternalQuery internalQuery(std::move(query), InternalQueryState());
     queries.push_back({std::move(sender), std::move(internalQuery)});
   }
 
@@ -1789,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);
 
@@ -1825,7 +1686,7 @@ BOOST_FIXTURE_TEST_CASE(test_WrongStreamID, TestFixture)
 
     auto sender = std::make_shared<MockupQuerySender>();
     sender->d_id = counter;
-    InternalQuery internalQuery(std::move(query), IDState());
+    InternalQuery internalQuery(std::move(query), InternalQueryState());
     queries.push_back({std::move(sender), std::move(internalQuery)});
   }
 
@@ -1889,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);
@@ -1928,7 +1789,7 @@ BOOST_FIXTURE_TEST_CASE(test_ProxyProtocol, TestFixture)
     auto sender = std::make_shared<MockupQuerySender>();
     sender->d_id = counter;
     std::string payload = makeProxyHeader(counter % 2, local, local, {});
-    InternalQuery internalQuery(std::move(query), IDState());
+    InternalQuery internalQuery(std::move(query), InternalQueryState());
     internalQuery.d_proxyProtocolPayload = std::move(payload);
     queries.push_back({std::move(sender), std::move(internalQuery)});
   }
@@ -1988,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);
+}
index 8fa8dbf2e71942ed26a53487c1f02160104accf7..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>
@@ -208,7 +211,7 @@ BOOST_AUTO_TEST_CASE(test_Rings_Threaded) {
   Rings rings(numberOfEntries, numberOfShards, lockAttempts, true);
   rings.init();
 #if defined(DNSDIST_RINGS_WITH_MACADDRESS)
-  Rings::Query query({requestor, qname, now, dh, size, qtype, protocol, "", false});
+  Rings::Query query({requestor, qname, now, dh, size, qtype, protocol, dnsdist::MacAddress(), false});
 #else
   Rings::Query query({requestor, qname, now, dh, size, qtype, protocol});
 #endif
index e27e6f6afe641027d9db55c034de769f7070d9d7..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>
@@ -18,17 +21,17 @@ void checkParameterBound(const std::string& parameter, uint64_t value, size_t ma
 static DNSQuestion getDQ(const DNSName* providedName = nullptr)
 {
   static const DNSName qname("powerdns.com.");
-  static const ComboAddress lc("127.0.0.1:53");
-  static const ComboAddress rem("192.0.2.1:42");
-  static struct timespec queryRealTime;
   static PacketBuffer packet(sizeof(dnsheader));
-
-  uint16_t qtype = QType::A;
-  uint16_t qclass = QClass::IN;
-  auto proto = dnsdist::Protocol::DoUDP;
-  gettime(&queryRealTime, true);
-
-  DNSQuestion dq(providedName ? providedName : &qname, qtype, qclass, &lc, &rem, packet, proto, &queryRealTime);
+  static InternalQueryState ids;
+  ids.origDest = ComboAddress("127.0.0.1:53");
+  ids.origRemote = ComboAddress("192.0.2.1:42");
+  ids.qname = providedName ? *providedName : qname;
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  ids.queryRealTime.start();
+
+  DNSQuestion dq(ids, packet);
   return dq;
 }
 
@@ -42,24 +45,24 @@ BOOST_AUTO_TEST_CASE(test_MaxQPSIPRule) {
   unsigned int scanFraction = 10;
   MaxQPSIPRule rule(maxQPS, maxBurst, 32, 64, expiration, cleanupDelay, scanFraction);
 
-  DNSName qname("powerdns.com.");
-  uint16_t qtype = QType::A;
-  uint16_t qclass = QClass::IN;
-  ComboAddress lc("127.0.0.1:53");
-  ComboAddress rem("192.0.2.1:42");
+  InternalQueryState ids;
+  ids.qname = DNSName("powerdns.com.");
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.origDest = ComboAddress("127.0.0.1:53");
+  ids.origRemote = ComboAddress("192.0.2.1:42");
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  ids.queryRealTime.start();
   PacketBuffer packet(sizeof(dnsheader));
-  auto proto = dnsdist::Protocol::DoUDP;
-  struct timespec queryRealTime;
-  gettime(&queryRealTime, true);
   struct timespec expiredTime;
   /* the internal QPS limiter does not use the real time */
   gettime(&expiredTime);
 
-  DNSQuestion dq(&qname, qtype, qclass, &lc, &rem, packet, proto, &queryRealTime);
+  DNSQuestion dq(ids, packet);
 
   for (size_t idx = 0; idx < maxQPS; idx++) {
     /* let's use different source ports, it shouldn't matter */
-    rem = ComboAddress("192.0.2.1:" + std::to_string(idx));
+    ids.origRemote = ComboAddress("192.0.2.1:" + std::to_string(idx));
     BOOST_CHECK_EQUAL(rule.matches(&dq), false);
     BOOST_CHECK_EQUAL(rule.getEntriesCount(), 1U);
   }
@@ -87,7 +90,7 @@ BOOST_AUTO_TEST_CASE(test_MaxQPSIPRule) {
   /* Let's insert a lot of different sources now */
   for (size_t idxByte3 = 0; idxByte3 < 256; idxByte3++) {
     for (size_t idxByte4 = 0; idxByte4 < 256; idxByte4++) {
-      rem = ComboAddress("10.0." + std::to_string(idxByte3) + "." + std::to_string(idxByte4));
+      ids.origRemote = ComboAddress("10.0." + std::to_string(idxByte3) + "." + std::to_string(idxByte4));
       BOOST_CHECK_EQUAL(rule.matches(&dq), false);
     }
   }
@@ -106,7 +109,7 @@ BOOST_AUTO_TEST_CASE(test_MaxQPSIPRule) {
   auto removed = rule.cleanup(notExpiredTime, &scanned);
   BOOST_CHECK_EQUAL(removed, 0U);
   /* the first entry should still have been valid, we should not have scanned more */
-  BOOST_CHECK_EQUAL(scanned, 1U);
+  BOOST_CHECK_EQUAL(scanned, rule.getNumberOfShards());
   BOOST_CHECK_EQUAL(rule.getEntriesCount(), total);
 
   /* make sure all entries are _not_ valid anymore */
@@ -114,7 +117,7 @@ BOOST_AUTO_TEST_CASE(test_MaxQPSIPRule) {
   expiredTime.tv_sec += 1;
 
   removed = rule.cleanup(expiredTime, &scanned);
-  BOOST_CHECK_EQUAL(removed, (total / scanFraction) + 1);
+  BOOST_CHECK_EQUAL(removed, (total / scanFraction) + 1 + rule.getNumberOfShards());
   /* we should not have scanned more than scanFraction */
   BOOST_CHECK_EQUAL(scanned, removed);
   BOOST_CHECK_EQUAL(rule.getEntriesCount(), total - removed);
@@ -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 c15d07a51caac8a482e01f3ccba28092fe3f59c3..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>
@@ -14,6 +17,13 @@ BOOST_AUTO_TEST_CASE(test_Basic)
 {
   DNSName target("powerdns.com.");
 
+  {
+    // invalid priority of 0 + parameters
+    std::vector<uint8_t> payload;
+    const uint16_t priority = 0;
+    BOOST_CHECK(!generateSVCPayload(payload, priority, target, {SvcParam::SvcParamKey::port}, {"dot"}, false, 853, std::string(), {ComboAddress("192.0.2.1")}, {ComboAddress("2001:db8::1")}, {}));
+  }
+
   {
     std::vector<uint8_t> payload;
     const uint16_t priority = 1;
@@ -93,4 +103,33 @@ BOOST_AUTO_TEST_CASE(test_Basic)
   }
 }
 
+BOOST_AUTO_TEST_CASE(test_Parsing)
+{
+  svcParamsLua_t params;
+  params["mandatory"] = std::vector<std::pair<int, std::string>>({
+    {1, "port"},
+  });
+  params["alpn"] = std::vector<std::pair<int, std::string>>({
+    {1, "h2"},
+  });
+  params["noDefaultAlpn"] = static_cast<bool>(true);
+  params["port"] = static_cast<uint16_t>(443);
+  params["ipv4hint"] = std::vector<std::pair<int, std::string>>({
+    {1, "192.0.2.1"},
+  });
+  params["ipv6hint"] = std::vector<std::pair<int, std::string>>({
+    {1, "2001:db8::1"},
+  });
+  params["ech"] = std::string("test");
+
+  auto parsed = parseSVCParameters(params);
+  BOOST_CHECK(parsed.mandatoryParams == std::set<uint16_t>{SvcParam::SvcParamKey::port});
+  BOOST_CHECK(parsed.alpns == std::vector<std::string>{"h2"});
+  BOOST_CHECK(parsed.ipv4hints == std::vector<ComboAddress>{ComboAddress("192.0.2.1")});
+  BOOST_CHECK(parsed.ipv6hints == std::vector<ComboAddress>{ComboAddress("2001:db8::1")});
+  BOOST_CHECK_EQUAL(parsed.ech, "test");
+  BOOST_CHECK_EQUAL(*parsed.port, 443);
+  BOOST_CHECK_EQUAL(parsed.noDefaultAlpn, true);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
index 9457b62e36d06ba9efbd68a6c43652303b36d6ba..22a27c692c1a07c021710046547bfda7572d785f 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<servers_t> g_dstates;
 
@@ -48,7 +51,7 @@ bool checkDNSCryptQuery(const ClientState& cs, PacketBuffer& query, std::unique_
   return false;
 }
 
-bool checkQueryHeaders(const struct dnsheader* dh)
+bool checkQueryHeaders(const struct dnsheader& dnsHeader, ClientState& clientState)
 {
   return true;
 }
@@ -58,38 +61,32 @@ uint64_t uptimeOfProcess(const std::string& str)
   return 0;
 }
 
-void handleResponseSent(const IDState& ids, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol protocol)
+void handleResponseSent(const InternalQueryState& ids, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol protocol, bool fromBackend)
 {
 }
 
-static std::function<ProcessQueryResult(DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)> s_processQuery;
+std::function<ProcessQueryResult(DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend)> s_processQuery;
 
-ProcessQueryResult processQuery(DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)
+ProcessQueryResult processQuery(DNSQuestion& dq, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)
 {
   if (s_processQuery) {
-    return s_processQuery(dq, cs, holders, selectedBackend);
+    return s_processQuery(dq, selectedBackend);
   }
 
   return ProcessQueryResult::Drop;
 }
 
-static std::function<bool(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const ComboAddress& remote, unsigned int& qnameWireLength)> s_responseContentMatches;
-
-bool responseContentMatches(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const ComboAddress& 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)
 {
-  if (s_responseContentMatches) {
-    return s_responseContentMatches(response, qname, qtype, qclass, remote, qnameWireLength);
-  }
-
   return true;
 }
 
-static std::function<bool(PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted)> s_processResponse;
+static std::function<bool(PacketBuffer& response, DNSResponse& dr, bool muted)> s_processResponse;
 
-bool processResponse(PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted, bool receivedOverUDP)
+bool processResponse(PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& localRespRuleActions, const std::vector<DNSDistResponseRuleAction>& localCacheInsertedRespRuleActions, DNSResponse& dr, bool muted)
 {
   if (s_processResponse) {
-    return s_processResponse(response, localRespRuleActions, dr, muted);
+    return s_processResponse(response, dr, muted);
   }
 
   return false;
@@ -214,11 +211,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;
@@ -446,6 +438,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
@@ -454,25 +479,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,12 +517,12 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
       { ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, query.size() - 2 },
       { ExpectedStep::ExpectedRequest::closeClient, IOState::Done },
     };
-    s_processQuery = [](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       return ProcessQueryResult::Drop;
     };
 
     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);
   }
 
@@ -523,13 +539,13 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
       { ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 0 },
       { ExpectedStep::ExpectedRequest::closeClient, IOState::Done },
     };
-    s_processQuery = [](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       // Would be nicer to actually turn it into a response
       return ProcessQueryResult::SendAnswer;
     };
 
     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);
   }
@@ -555,7 +571,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
       { ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 0 },
       { ExpectedStep::ExpectedRequest::closeClient, IOState::Done },
     };
-    s_processQuery = [](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       // Would be nicer to actually turn it into a response
       return ProcessQueryResult::SendAnswer;
     };
@@ -564,7 +580,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,12 +599,12 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
       { ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, query.size() - 2 },
       { ExpectedStep::ExpectedRequest::closeClient, IOState::Done },
     };
-    s_processQuery = [](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       throw std::runtime_error("Something unexpected happened");
     };
 
     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);
   }
 
@@ -610,13 +626,13 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
     s_steps.push_back({ ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 0 });
     s_steps.push_back({ ExpectedStep::ExpectedRequest::closeClient, IOState::Done });
 
-    s_processQuery = [](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       // Would be nicer to actually turn it into a response
       return ProcessQueryResult::SendAnswer;
     };
 
     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
   }
@@ -632,7 +648,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
       { ExpectedStep::ExpectedRequest::readFromClient, IOState::NeedRead, query.size() - 2 - 2 },
       { ExpectedStep::ExpectedRequest::closeClient, IOState::Done },
     };
-    s_processQuery = [](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       /* should not be reached */
       BOOST_CHECK(false);
       return ProcessQueryResult::SendAnswer;
@@ -642,7 +658,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;
@@ -670,7 +686,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
       { ExpectedStep::ExpectedRequest::writeToClient, IOState::NeedWrite, 1 },
       { ExpectedStep::ExpectedRequest::closeClient, IOState::Done },
     };
-    s_processQuery = [](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       return ProcessQueryResult::SendAnswer;
     };
 
@@ -678,7 +694,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,20 +722,20 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
       { ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 0 },
       { ExpectedStep::ExpectedRequest::closeClient, IOState::Done },
     };
-    s_processQuery = [](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       return ProcessQueryResult::SendAnswer;
     };
 
     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);
 
@@ -764,7 +780,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionWithProxyProtocol_SelfAnswered)
       { ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 0 },
       { ExpectedStep::ExpectedRequest::closeClient, IOState::Done },
     };
-    s_processQuery = [](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       return ProcessQueryResult::SendAnswer;
     };
 
@@ -772,7 +788,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,12 +810,12 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionWithProxyProtocol_SelfAnswered)
       { ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, s_proxyProtocolMinimumHeaderSize },
       { ExpectedStep::ExpectedRequest::closeClient, IOState::Done },
     };
-    s_processQuery = [](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       return ProcessQueryResult::SendAnswer;
     };
 
     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);
   }
@@ -821,7 +837,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionWithProxyProtocol_SelfAnswered)
       { ExpectedStep::ExpectedRequest::readFromClient, IOState::NeedRead, proxyPayload.size() - s_proxyProtocolMinimumHeaderSize - 1},
       { ExpectedStep::ExpectedRequest::closeClient, IOState::Done },
     };
-    s_processQuery = [](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       return ProcessQueryResult::SendAnswer;
     };
 
@@ -829,7 +845,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;
@@ -846,10 +862,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);
 
@@ -900,16 +916,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
       /* closing a connection to the backend */
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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());
@@ -940,16 +956,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
       /* closing a connection to the backend */
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       throw std::runtime_error("Unexpected error while processing the response");
     };
 
     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);
@@ -979,16 +995,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
       /* closing a connection to the backend */
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return false;
     };
 
     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);
@@ -1022,16 +1038,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
       /* closing a connection to the backend */
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
@@ -1050,15 +1066,15 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
       /* closing client connection */
       { ExpectedStep::ExpectedRequest::closeClient, IOState::Done },
     };
-    s_processQuery = [](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       return ProcessQueryResult::SendAnswer;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
@@ -1087,16 +1103,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
       /* closing backend connection */
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
@@ -1155,18 +1171,18 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     /* 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);
     }
@@ -1217,17 +1233,17 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
       { ExpectedStep::ExpectedRequest::closeClient, IOState::Done },
     };
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
 
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
@@ -1253,17 +1269,17 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
 
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
@@ -1300,16 +1316,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
@@ -1357,16 +1373,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
@@ -1413,16 +1429,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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());
@@ -1472,16 +1488,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
       { ExpectedStep::ExpectedRequest::closeClient, IOState::Done },
     };
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
@@ -1524,16 +1540,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
@@ -1584,16 +1600,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
@@ -1625,16 +1641,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
@@ -1682,21 +1698,21 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
       /* send the response */
       s_steps.push_back({ ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, query.size() + 2 });
     };
+    /* close the connection with the backend */
+    s_steps.push_back({ ExpectedStep::ExpectedRequest::closeBackend, IOState::Done });
     /* close the connection with the client */
     s_steps.push_back({ ExpectedStep::ExpectedRequest::closeClient, IOState::Done });
-    /* eventually with the backend as well */
-    s_steps.push_back({ ExpectedStep::ExpectedRequest::closeBackend, IOState::Done });
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
 
@@ -1707,12 +1723,49 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
 #endif
   }
 
+  {
+    /* 2 queries on the same connection, asynchronously handled, check that we only read the first one (no OOOR as maxInFlight is 0) */
+    TEST_INIT("=> 2 queries on the same connection, async");
+
+    size_t count = 2;
+
+    s_readBuffer = query;
+
+    for (size_t idx = 0; idx < count; idx++) {
+      appendPayloadEditingID(s_readBuffer, query, idx);
+      appendPayloadEditingID(s_backendReadBuffer, query, idx);
+    }
+
+    s_steps = { { ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done },
+                { ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 2 },
+                { ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, query.size() - 2 },
+                /* close the connection with the client */
+                { ExpectedStep::ExpectedRequest::closeClient, IOState::Done }
+    };
+
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+      selectedBackend = backend;
+      dq.asynchronous = true;
+      /* note that we do nothing with the query, we just tell the frontend it was dealt with */
+      return ProcessQueryResult::Asynchronous;
+    };
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
+      return true;
+    };
+
+    auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, 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 */
+    IncomingTCPConnectionState::clearAllDownstreamConnections();
+  }
 }
 
-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;
 
@@ -1876,16 +1929,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
     }
@@ -1999,7 +2052,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
 
-    s_processQuery = [backend,&responses](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend,&responses](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       static size_t count = 0;
       if (count++ == 3) {
         /* self answered */
@@ -2012,12 +2065,12 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
@@ -2181,23 +2234,23 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
         timeout = true;
       } },
 
-      /* closing client connection */
-      { ExpectedStep::ExpectedRequest::closeClient, IOState::Done },
-
       /* closing a connection to the backend */
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
+
+      /* closing client connection */
+      { ExpectedStep::ExpectedRequest::closeClient, IOState::Done },
     };
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
@@ -2260,7 +2313,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
     };
 
     counter = 0;
-    s_processQuery = [backend,&counter](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend,&counter](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       if (counter == 0) {
         ++counter;
         selectedBackend = backend;
@@ -2268,12 +2321,12 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
       }
       return ProcessQueryResult::Drop;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
     }
@@ -2343,7 +2396,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
     };
 
     counter = 0;
-    s_processQuery = [backend,&counter](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend,&counter](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       if (counter == 0) {
         ++counter;
         selectedBackend = backend;
@@ -2351,12 +2404,12 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
       }
       return ProcessQueryResult::Drop;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
     }
@@ -2458,22 +2511,22 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
       { ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, responses.at(4).size(), [&timeout](int desc) {
         timeout = true;
       } },
-      /* client times out again, this time we close the connection */
-      { ExpectedStep::ExpectedRequest::closeClient, IOState::Done, 0 },
       /* closing a connection to the backend */
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
+      /* client times out again, this time we close the connection */
+      { ExpectedStep::ExpectedRequest::closeClient, IOState::Done, 0 },
     };
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
     }
@@ -2616,16 +2669,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
       { ExpectedStep::ExpectedRequest::closeClient, IOState::Done, 0 },
     };
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
     }
@@ -2823,16 +2876,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
     }
@@ -2997,16 +3050,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
 
-    s_processQuery = [proxyEnabledBackend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [proxyEnabledBackend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = proxyEnabledBackend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
     }
@@ -3261,16 +3314,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
     }
@@ -3381,22 +3434,22 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
       } },
       /* client closes the connection */
       { ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 0 },
-      /* closing the client connection */
-      { ExpectedStep::ExpectedRequest::closeClient, IOState::Done, 0 },
       /* closing the backend connection */
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done, 0 },
+      /* closing the client connection */
+      { ExpectedStep::ExpectedRequest::closeClient, IOState::Done, 0 },
     };
 
-    s_processQuery = [proxyEnabledBackend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [proxyEnabledBackend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = proxyEnabledBackend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
     }
@@ -3472,16 +3525,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
       { ExpectedStep::ExpectedRequest::closeClient, IOState::Done, 0 },
     };
 
-    s_processQuery = [proxyEnabledBackend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [proxyEnabledBackend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = proxyEnabledBackend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
     }
@@ -3537,16 +3590,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done, 0 },
     };
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
     }
@@ -3728,16 +3781,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
 
-    s_processQuery = [backend1](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend1](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend1;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
     }
@@ -3807,22 +3860,22 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
       { ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, responses.at(0).size(), [&timeout](int desc) {
         timeout = true;
       } },
-      /* closing client connection */
-      { ExpectedStep::ExpectedRequest::closeClient, IOState::Done },
       /* closing a connection to the backend */
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
+      /* closing client connection */
+      { ExpectedStep::ExpectedRequest::closeClient, IOState::Done },
     };
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
     }
@@ -3849,10 +3902,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;
 
@@ -4045,16 +4098,16 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendNotOOOR)
       { ExpectedStep::ExpectedRequest::closeBackend, IOState::Done },
     };
 
-    s_processQuery = [backend](DNSQuestion& dq, ClientState& cs, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
       selectedBackend = backend;
       return ProcessQueryResult::PassToBackend;
     };
-    s_processResponse = [](PacketBuffer& response, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRuleActions, DNSResponse& dr, bool muted) -> bool {
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
       return true;
     };
 
     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);
     }
@@ -4068,6 +4121,65 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendNotOOOR)
     /* we need to clear them now, otherwise we end up with dangling pointers to the steps via the TLS context, etc */
     BOOST_CHECK_EQUAL(IncomingTCPConnectionState::clearAllDownstreamConnections(), 5U);
   }
+
+  {
+    /* 2 queries on the same connection, asynchronously handled, check that we only read all of them (OOOR as maxInFlight is 65535) */
+    TEST_INIT("=> 2 queries on the same connection, async with OOOR");
+
+    size_t count = 2;
+
+    s_readBuffer = queries.at(0);
+
+    for (size_t idx = 0; idx < count; idx++) {
+      appendPayloadEditingID(s_readBuffer, queries.at(idx), idx);
+      appendPayloadEditingID(s_backendReadBuffer, queries.at(idx), idx);
+    }
+
+    bool timeout = false;
+    s_steps = { { ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done },
+                { ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 2 },
+                { ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, queries.at(0).size() - 2 },
+                { ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 2 },
+                { ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, queries.at(1).size() - 2 },
+                { ExpectedStep::ExpectedRequest::readFromClient, IOState::NeedRead, 0, [&timeout](int desc) {
+                  timeout = true;
+                }},
+                /* close the connection with the client */
+                { ExpectedStep::ExpectedRequest::closeClient, IOState::Done }
+    };
+
+    s_processQuery = [backend](DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+      selectedBackend = backend;
+      dq.asynchronous = true;
+      /* note that we do nothing with the query, we just tell the frontend it was dealt with */
+      return ProcessQueryResult::Asynchronous;
+    };
+    s_processResponse = [](PacketBuffer& response, DNSResponse& dr, bool muted) -> bool {
+      return true;
+    };
+
+    auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
+    state->handleIO();
+    while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
+      threadData.mplexer->run(&now);
+    }
+
+    struct timeval later = now;
+    later.tv_sec += g_tcpRecvTimeout + 1;
+    auto expiredConns = threadData.mplexer->getTimeouts(later);
+    BOOST_CHECK_EQUAL(expiredConns.size(), 1U);
+    for (const auto& cbData : expiredConns) {
+      if (cbData.second.type() == typeid(std::shared_ptr<IncomingTCPConnectionState>)) {
+        auto cbState = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(cbData.second);
+        cbState->handleTimeout(cbState, false);
+      }
+    }
+
+    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 */
+    IncomingTCPConnectionState::clearAllDownstreamConnections();
+  }
 }
 
 BOOST_AUTO_TEST_SUITE_END();
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/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 87a5799672f6eb1107deb2113e4316af98fa1f59..27f435094dc29b986b14615343cda5ac3736001d 100644 (file)
@@ -108,11 +108,11 @@ DNSName::string_t segmentDNSNameRaw(const char* realinput, size_t inputlen)
         const char* eof = pe;
         int cs;
         char val = 0;
-        char labellen=0;
+        unsigned char labellen=0;
         unsigned int lenpos=0;
         %%{
                 action labelEnd { 
-                        if (labellen < 0 || labellen > 63) {
+                        if (labellen > 63) {
                           throw runtime_error("Unable to parse DNS name '"+string(realinput)+"': invalid label length "+std::to_string(labellen));
                         }
                         ret[lenpos]=labellen;
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 834378586f82cb70a8e137518c721b4abad68c1e..c5728cc2cea245054dc21c5eb0ffdd4c38c9f59d 100644 (file)
@@ -41,22 +41,37 @@ std::ostream & operator<<(std::ostream &os, const DNSName& d)
   return os <<d.toLogString();
 }
 
-DNSName::DNSName(const char* p, size_t length)
+void DNSName::throwSafeRangeError(const std::string& msg, const char* buf, size_t length)
 {
-  if(p[0]==0 || (p[0]=='.' && p[1]==0)) {
-    d_storage.assign(1, (char)0);
+  std::string dots;
+  if (length > s_maxDNSNameLength) {
+    length = s_maxDNSNameLength;
+    dots = "...";
+  }
+  std::string label;
+  DNSName::appendEscapedLabel(label, buf, length);
+  throw std::range_error(msg + label + dots);
+}
+
+DNSName::DNSName(const std::string_view sw)
+{
+  const char* p = sw.data();
+  size_t length = sw.length();
+
+  if(length == 0 || (length == 1 && p[0]=='.')) {
+    d_storage.assign(1, '\0');
   } else {
-    if(!strchr(p, '\\')) {
+    if(!std::memchr(p, '\\', length)) {
       unsigned char lenpos=0;
       unsigned char labellen=0;
-      size_t plen=length;
-      const char* const pbegin=p, *pend=p+plen;
-      d_storage.reserve(plen+1);
+      const char* const pbegin=p, *pend=p+length;
+
+      d_storage.reserve(length+1);
       for(auto iter = pbegin; iter != pend; ) {
         lenpos = d_storage.size();
         if(*iter=='.')
-          throw std::runtime_error("Found . in wrong position in DNSName "+string(p));
-        d_storage.append(1, (char)0);
+          throwSafeRangeError("Found . in wrong position in DNSName: ", p, length);
+        d_storage.append(1, '\0');
         labellen=0;
         auto begiter=iter;
         for(; iter != pend && *iter!='.'; ++iter) {
@@ -66,26 +81,25 @@ DNSName::DNSName(const char* p, size_t length)
         if(iter != pend)
           ++iter;
         if(labellen > 63)
-          throw std::range_error("label too long to append");
+          throwSafeRangeError("label too long to append: ", p, length);
 
-        if(iter-pbegin > 254) // reserve two bytes, one for length and one for the root label
-          throw std::range_error("name too long to append");
+        if(iter-pbegin > static_cast<ptrdiff_t>(s_maxDNSNameLength - 1)) // reserve two bytes, one for length and one for the root label
+          throwSafeRangeError("name too long to append: ", p, length);
 
         d_storage[lenpos]=labellen;
       }
-      d_storage.append(1, (char)0);
+      d_storage.append(1, '\0');
     }
     else {
       d_storage=segmentDNSNameRaw(p, length);
-      if(d_storage.size() > 255) {
-        throw std::range_error("name too long");
+      if(d_storage.size() > s_maxDNSNameLength) {
+        throwSafeRangeError("name too long: ", p, length);
       }
     }
   }
 }
 
-
-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)+")");
@@ -99,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 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};
+
+  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);
   }
 }
 
@@ -210,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());
 }
@@ -235,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) {
@@ -255,8 +345,8 @@ bool DNSName::isPartOf(const DNSName& parent) const
       }
       return true;
     }
-    if (*us < 0) {
-      throw std::out_of_range("negative label length in dnsname");
+    if (static_cast<uint8_t>(*us) > 63) {
+      throw std::out_of_range("illegal label length in dnsname");
     }
   }
   return false;
@@ -269,19 +359,24 @@ 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
 {
-  DNSName result;
+  if (empty() || other.empty()) {
+    return DNSName();
+  }
+
+  DNSName result(g_rootdnsname);
 
   const std::vector<std::string> ours = getRawLabels();
   const std::vector<std::string> others = other.getRawLabels();
@@ -304,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();
@@ -324,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 > 254) // 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() > 254) // 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());
@@ -396,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 == '*');
 }
@@ -435,8 +540,9 @@ unsigned int DNSName::countLabels() const
 
 void DNSName::trimToLabels(unsigned int to)
 {
-  while(countLabels() > to && chopOff())
+  while(countLabels() > to && chopOff()) {
     ;
+  }
 }
 
 
@@ -451,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);
@@ -479,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);
@@ -491,3 +601,132 @@ bool DNSName::has8bitBytes() const
 
   return false;
 }
+
+// clang-format off
+const unsigned char dns_toupper_table[256] = {
+  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+  0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+  0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+  0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+  0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+  0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+  0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+  0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+  0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+  0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+  0x60, 'A',  'B',  'C',  'D',  'E',  'F',  'G',
+  'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
+  'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
+  'X',  'Y',  'Z',  0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+  0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+  0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+  0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+  0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
+  0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+  0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+  0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+  0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+  0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+  0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
+  0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+  0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+  0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+  0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+  0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+  0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
+};
+
+const unsigned char dns_tolower_table[256] = {
+  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+  0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+  0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+  0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+  0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+  0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+  0x40, 'a',  'b',  'c',  'd',  'e',  'f',  'g',
+  'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
+  'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
+  'x',  'y',  'z',  0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+  0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+  0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
+  0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+  0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+  0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+  0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+  0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+  0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
+  0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+  0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+  0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+  0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+  0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+  0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
+  0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+  0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+  0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+  0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+  0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+  0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
+};
+
+DNSName::RawLabelsVisitor::RawLabelsVisitor(const DNSName::string_t& storage): d_storage(storage)
+{
+  size_t position = 0;
+  while (position < storage.size()) {
+    auto labelLength = static_cast<uint8_t>(storage.at(position));
+    if (labelLength == 0) {
+      break;
+    }
+    d_labelPositions.at(d_position) = position;
+    d_position++;
+    position += labelLength + 1;
+  }
+}
+
+DNSName::RawLabelsVisitor DNSName::getRawLabelsVisitor() const
+{
+  return DNSName::RawLabelsVisitor(getStorage());
+}
+
+std::string_view DNSName::RawLabelsVisitor::front() const
+{
+  if (d_position == 0) {
+    throw std::out_of_range("trying to access the front of an empty DNSName::RawLabelsVisitor");
+  }
+  uint8_t length = d_storage.at(0);
+  if (length == 0) {
+    return std::string_view();
+  }
+  return std::string_view(&d_storage.at(1), length);
+}
+
+std::string_view DNSName::RawLabelsVisitor::back() const
+{
+  if (d_position == 0) {
+    throw std::out_of_range("trying to access the back of an empty DNSName::RawLabelsVisitor");
+  }
+  size_t offset = d_labelPositions.at(d_position-1);
+  uint8_t length = d_storage.at(offset);
+  if (length == 0) {
+    return std::string_view();
+  }
+  return std::string_view(&d_storage.at(offset + 1), length);
+}
+
+bool DNSName::RawLabelsVisitor::pop_back()
+{
+  if (d_position > 0) {
+    d_position--;
+    return true;
+  }
+  return false;
+}
+
+bool DNSName::RawLabelsVisitor::empty() const
+{
+  return d_position == 0;
+}
index 6c49d0bd8d3ce297c64b4795d676db45930b5d20..e7a9f4b4eafd1149386b5a7019a649c16235cca1 100644 (file)
@@ -20,7 +20,9 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #pragma once
+#include <array>
 #include <cstring>
+#include <optional>
 #include <string>
 #include <vector>
 #include <set>
@@ -29,6 +31,7 @@
 #include <sstream>
 #include <iterator>
 #include <unordered_set>
+#include <string_view>
 
 #include <boost/version.hpp>
 
 #include <boost/container/string.hpp>
 #endif
 
-#include "ascii.hh"
+inline bool dns_isspace(char c)
+{
+  return c == ' ' || c == '\t' || c == '\r' || c == '\n';
+}
+
+extern const unsigned char dns_toupper_table[256],  dns_tolower_table[256];
+
+inline unsigned char dns_toupper(unsigned char c)
+{
+  return dns_toupper_table[c];
+}
+
+inline unsigned char dns_tolower(unsigned char c)
+{
+  return dns_tolower_table[c];
+}
 
-uint32_t burtleCI(const unsigned char* k, uint32_t length, uint32_t init);
+#include "burtle.hh"
 
 // #include "dns.hh"
 // #include "logger.hh"
 
 //#include <ext/vstring.h>
 
-/* Quest in life: 
+/* Quest in life:
      accept escaped ascii presentations of DNS names and store them "natively"
      accept a DNS packet with an offset, and extract a DNS name from it
      build up DNSNames with prepend and append of 'raw' unescaped labels
 
    Be able to turn them into ASCII and "DNS name in a packet" again on request
 
-   Provide some common operators for comparison, detection of being part of another domain 
+   Provide some common operators for comparison, detection of being part of another domain
 
    NOTE: For now, everything MUST be . terminated, otherwise it is an error
 */
@@ -60,7 +78,9 @@ uint32_t burtleCI(const unsigned char* k, uint32_t length, uint32_t init);
 class DNSName
 {
 public:
-  DNSName()  {}          //!< Constructs an *empty* DNSName, NOT the root!
+  static const size_t s_maxDNSNameLength = 255;
+
+  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)
   {
@@ -69,7 +89,7 @@ public:
     }
     return *this;
   }
-  DNSName& operator=(DNSName&& rhs)
+  DNSName& operator=(DNSName&& rhs) noexcept
   {
     if (this != &rhs) {
       d_storage = std::move(rhs.d_storage);
@@ -78,11 +98,10 @@ public:
   }
   DNSName(const DNSName& a) = default;
   DNSName(DNSName&& a) = default;
-  explicit DNSName(const char* p): DNSName(p, std::strlen(p)) {} //!< Constructs from a human formatted, escaped presentation
-  explicit DNSName(const char* p, size_t len);      //!< Constructs from a human formatted, escaped presentation
-  explicit DNSName(const std::string& str) : DNSName(str.c_str(), str.length()) {}; //!< Constructs from a human formatted, escaped presentation
-  DNSName(const char* p, int len, int offset, bool uncompress, uint16_t* qtype=nullptr, uint16_t* qclass=nullptr, unsigned int* consumed=nullptr, uint16_t minOffset=0); //!< Construct from a DNS Packet, taking the first question if offset=12. If supplied, consumed is set to the number of bytes consumed from the packet, which will not be equal to the wire length of the resulting name in case of compression.
-  
+
+  explicit DNSName(std::string_view sw); //!< Constructs from a human formatted, escaped presentation
+  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
   bool operator!=(const DNSName& other) const { return !(*this == other); }
@@ -131,8 +150,8 @@ public:
   }
   DNSName& operator+=(const DNSName& rhs)
   {
-    if(d_storage.size() + rhs.d_storage.size() > 256) // one extra byte for the second root label
-      throw std::range_error("name too long");
+    if(d_storage.size() + rhs.d_storage.size() > s_maxDNSNameLength + 1) // one extra byte for the second root label
+      throwSafeRangeError("resulting name too long", rhs.d_storage.data(), rhs.d_storage.size());
     if(rhs.empty())
       return *this;
 
@@ -146,7 +165,7 @@ public:
 
   bool operator<(const DNSName& rhs)  const // this delivers _some_ kind of ordering, but not one useful in a DNS context. Really fast though.
   {
-    return std::lexicographical_compare(d_storage.rbegin(), d_storage.rend(), 
+    return std::lexicographical_compare(d_storage.rbegin(), d_storage.rend(),
                                 rhs.d_storage.rbegin(), rhs.d_storage.rend(),
                                 [](const unsigned char& a, const unsigned char& b) {
                                          return dns_tolower(a) < dns_tolower(b);
@@ -154,7 +173,7 @@ public:
   }
 
   inline bool canonCompare(const DNSName& rhs) const;
-  bool slowCanonCompare(const DNSName& rhs) const;  
+  bool slowCanonCompare(const DNSName& rhs) const;
 
 #if BOOST_VERSION >= 105300
   typedef boost::container::string string_t;
@@ -167,12 +186,61 @@ public:
 
   bool has8bitBytes() const; /* returns true if at least one byte of the labels forming the name is not included in [A-Za-z0-9_*./@ \\:-] */
 
+  class RawLabelsVisitor
+  {
+  public:
+    /* Zero-copy, zero-allocation raw labels visitor.
+       The general idea is that we walk the labels in the constructor,
+       filling up our array of labels position and setting the initial
+       value of d_position at the number of labels.
+       We then can easily provide string_view into the first and last label.
+       pop_back() moves d_position one label closer to the start, so we
+       can also easily walk back the labels in reverse order.
+       There is no copy because we use a reference into the DNSName storage,
+       so it is absolutely forbidden to alter the DNSName for as long as we
+       exist, and no allocation because we use a static array (there cannot
+       be more than 128 labels in a DNSName).
+    */
+    RawLabelsVisitor(const string_t& storage);
+    std::string_view front() const;
+    std::string_view back() const;
+    bool pop_back();
+    bool empty() const;
+  private:
+    std::array<uint8_t, 128> d_labelPositions;
+    const string_t& d_storage;
+    size_t d_position{0};
+  };
+  RawLabelsVisitor getRawLabelsVisitor() const;
+
 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);
+  class UnsignedCharView
+  {
+  public:
+    UnsignedCharView(const char* data_, size_t size_): view(data_, size_)
+    {
+    }
+    const unsigned char& at(std::string_view::size_type pos) const
+    {
+      return reinterpret_cast<const unsigned char&>(view.at(pos));
+    }
+
+    size_t size() const
+    {
+      return view.size();
+    }
+
+  private:
+    std::string_view view;
+  };
+
+  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 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);
 };
 
 size_t hash_value(DNSName const& d);
@@ -187,7 +255,7 @@ inline bool DNSName::canonCompare(const DNSName& rhs) const
   //
   // 0,2,6,a
   // 0,4,a
-  
+
   uint8_t ourpos[64], rhspos[64];
   uint8_t ourcount=0, rhscount=0;
   //cout<<"Asked to compare "<<toString()<<" to "<<rhs.toString()<<endl;
@@ -199,7 +267,7 @@ inline bool DNSName::canonCompare(const DNSName& rhs) const
   if(ourcount == sizeof(ourpos) || rhscount==sizeof(rhspos)) {
     return slowCanonCompare(rhs);
   }
-  
+
   for(;;) {
     if(ourcount == 0 && rhscount != 0)
       return true;
@@ -209,21 +277,21 @@ inline bool DNSName::canonCompare(const DNSName& rhs) const
     rhscount--;
 
     bool res=std::lexicographical_compare(
-                                         d_storage.c_str() + ourpos[ourcount] + 1, 
+                                         d_storage.c_str() + ourpos[ourcount] + 1,
                                          d_storage.c_str() + ourpos[ourcount] + 1 + *(d_storage.c_str() + ourpos[ourcount]),
-                                         rhs.d_storage.c_str() + rhspos[rhscount] + 1, 
+                                         rhs.d_storage.c_str() + rhspos[rhscount] + 1,
                                          rhs.d_storage.c_str() + rhspos[rhscount] + 1 + *(rhs.d_storage.c_str() + rhspos[rhscount]),
                                          [](const unsigned char& a, const unsigned char& b) {
                                            return dns_tolower(a) < dns_tolower(b);
                                          });
-    
+
     //    cout<<"Forward: "<<res<<endl;
     if(res)
       return true;
 
-    res=std::lexicographical_compare(    rhs.d_storage.c_str() + rhspos[rhscount] + 1, 
+    res=std::lexicographical_compare(    rhs.d_storage.c_str() + rhspos[rhscount] + 1,
                                          rhs.d_storage.c_str() + rhspos[rhscount] + 1 + *(rhs.d_storage.c_str() + rhspos[rhscount]),
-                                         d_storage.c_str() + ourpos[ourcount] + 1, 
+                                         d_storage.c_str() + ourpos[ourcount] + 1,
                                          d_storage.c_str() + ourpos[ourcount] + 1 + *(d_storage.c_str() + ourpos[ourcount]),
                                          [](const unsigned char& a, const unsigned char& b) {
                                            return dns_tolower(a) < dns_tolower(b);
@@ -251,6 +319,8 @@ inline DNSName operator+(const DNSName& lhs, const DNSName& rhs)
   return ret;
 }
 
+extern const DNSName g_rootdnsname, g_wildcarddnsname;
+
 template<typename T>
 struct SuffixMatchTree
 {
@@ -273,16 +343,48 @@ struct SuffixMatchTree
     }
     return *this;
   }
-  
+  bool operator<(const SuffixMatchTree& rhs) const
+  {
+    return strcasecmp(d_name.c_str(), rhs.d_name.c_str()) < 0;
+  }
+
   std::string d_name;
-  mutable std::set<SuffixMatchTree> children;
+  mutable std::set<SuffixMatchTree, std::less<>> children;
   mutable bool endNode;
   mutable T d_value;
-  bool operator<(const SuffixMatchTree& rhs) const
+
+  /* this structure is used to do a lookup without allocating and
+     copying a string, using C++14's heterogeneous lookups in ordered
+     containers */
+  struct LightKey
   {
-    return strcasecmp(d_name.c_str(), rhs.d_name.c_str()) < 0;
+    std::string_view d_name;
+    bool operator<(const SuffixMatchTree& smt) const
+    {
+      auto compareUpTo = std::min(this->d_name.size(), smt.d_name.size());
+      auto ret = strncasecmp(this->d_name.data(), smt.d_name.data(), compareUpTo);
+      if (ret != 0) {
+        return ret < 0;
+      }
+      if (this->d_name.size() == smt.d_name.size()) {
+        return ret < 0;
+      }
+      return this->d_name.size() < smt.d_name.size();
+    }
+  };
+
+  bool operator<(const LightKey& lk) const
+  {
+    auto compareUpTo = std::min(this->d_name.size(), lk.d_name.size());
+    auto ret = strncasecmp(this->d_name.data(), lk.d_name.data(), compareUpTo);
+    if (ret != 0) {
+      return ret < 0;
+    }
+    if (this->d_name.size() == lk.d_name.size()) {
+      return ret < 0;
+    }
+    return this->d_name.size() < lk.d_name.size();
   }
-  typedef SuffixMatchTree value_type;
 
   template<typename V>
   void visit(const V& v) const {
@@ -372,58 +474,106 @@ struct SuffixMatchTree
     child->remove(labels);
   }
 
-  T* lookup(const DNSName& name)  const
+  T* lookup(const DNSName& name) const
+  {
+    auto bestNode = getBestNode(name);
+    if (bestNode) {
+      return &bestNode->d_value;
+    }
+    return nullptr;
+  }
+
+  std::optional<DNSName> getBestMatch(const DNSName& name) const
+  {
+    if (children.empty()) { // speed up empty set
+      return endNode ? std::optional<DNSName>(g_rootdnsname) : std::nullopt;
+    }
+
+    auto visitor = name.getRawLabelsVisitor();
+    return getBestMatch(visitor);
+  }
+
+  // Returns all end-nodes, fully qualified (not as separate labels)
+  std::vector<DNSName> getNodes() const {
+    std::vector<DNSName> ret;
+    if (endNode) {
+      ret.push_back(DNSName(d_name));
+    }
+    for (const auto& child : children) {
+      auto nodes = child.getNodes();
+      ret.reserve(ret.size() + nodes.size());
+      for (const auto &node: nodes) {
+        ret.push_back(node + DNSName(d_name));
+      }
+    }
+    return ret;
+  }
+
+private:
+  const SuffixMatchTree* getBestNode(const DNSName& name)  const
   {
     if (children.empty()) { // speed up empty set
       if (endNode) {
-        return &d_value;
+        return this;
       }
       return nullptr;
     }
 
-    auto labels = name.getRawLabels();
-    return lookup(labels);
+    auto visitor = name.getRawLabelsVisitor();
+    return getBestNode(visitor);
   }
 
-  T* lookup(std::vector<std::string>& labels) const
+  const SuffixMatchTree* getBestNode(DNSName::RawLabelsVisitor& visitor) const
   {
-    if (labels.empty()) { // optimization
+    if (visitor.empty()) { // optimization
       if (endNode) {
-        return &d_value;
+        return this;
       }
       return nullptr;
     }
 
-    SuffixMatchTree smn(*labels.rbegin());
-    auto child = children.find(smn);
+    const LightKey lk{visitor.back()};
+    auto child = children.find(lk);
     if (child == children.end()) {
-      if(endNode) {
-        return &d_value;
+      if (endNode) {
+        return this;
       }
       return nullptr;
     }
-    labels.pop_back();
-    auto result = child->lookup(labels);
+    visitor.pop_back();
+    auto result = child->getBestNode(visitor);
     if (result) {
       return result;
     }
-    return endNode ? &d_value : nullptr;
+    return endNode ? this : nullptr;
   }
 
-  // Returns all end-nodes, fully qualified (not as separate labels)
-  std::vector<DNSName> getNodes() const {
-    std::vector<DNSName> ret;
-    if (endNode) {
-      ret.push_back(DNSName(d_name));
+  std::optional<DNSName> getBestMatch(DNSName::RawLabelsVisitor& visitor) const
+  {
+    if (visitor.empty()) { // optimization
+      if (endNode) {
+        return std::optional<DNSName>(d_name);
+      }
+      return std::nullopt;
     }
-    for (const auto& child : children) {
-      auto nodes = child.getNodes();
-      ret.reserve(ret.size() + nodes.size());
-      for (const auto &node: nodes) {
-        ret.push_back(node + DNSName(d_name));
+
+    const LightKey lk{visitor.back()};
+    auto child = children.find(lk);
+    if (child == children.end()) {
+      if (endNode) {
+        return std::optional<DNSName>(d_name);
       }
+      return std::nullopt;
     }
-    return ret;
+    visitor.pop_back();
+    auto result = child->getBestMatch(visitor);
+    if (result) {
+      if (!d_name.empty()) {
+        result->appendRawLabel(d_name);
+      }
+      return result;
+    }
+    return endNode ? std::optional<DNSName>(d_name) : std::nullopt;
   }
 };
 
@@ -432,8 +582,7 @@ struct SuffixMatchTree
 struct SuffixMatchNode
 {
   public:
-    SuffixMatchNode()
-    {}
+    SuffixMatchNode() = default;
     SuffixMatchTree<bool> d_tree;
 
     void add(const DNSName& dnsname)
@@ -480,6 +629,11 @@ struct SuffixMatchNode
       return d_tree.lookup(dnsname) != nullptr;
     }
 
+    std::optional<DNSName> getBestMatch(const DNSName& name) const
+    {
+      return d_tree.getBestMatch(name);
+    }
+
     std::string toString() const
     {
       std::string ret;
@@ -507,22 +661,23 @@ namespace std {
 }
 
 DNSName::string_t segmentDNSNameRaw(const char* input, size_t inputlen); // from ragel
+
 bool DNSName::operator==(const DNSName& rhs) const
 {
-  if(rhs.empty() != empty() || rhs.d_storage.size() != d_storage.size())
+  if (rhs.empty() != empty() || rhs.d_storage.size() != d_storage.size()) {
     return false;
+  }
 
-  auto us = d_storage.cbegin();
-  auto p = rhs.d_storage.cbegin();
-  for(; us != d_storage.cend() && p != rhs.d_storage.cend(); ++us, ++p) { 
-    if(dns_tolower(*p) != dns_tolower(*us))
+  const auto* us = d_storage.cbegin();
+  const auto* p = rhs.d_storage.cbegin();
+  for (; us != d_storage.cend() && p != rhs.d_storage.cend(); ++us, ++p) {
+    if (dns_tolower(*p) != dns_tolower(*us)) {
       return false;
+    }
   }
   return true;
 }
 
-extern const DNSName g_rootdnsname, g_wildcarddnsname;
-
 struct DNSNameSet: public std::unordered_set<DNSName> {
     std::string toString() const {
         std::ostringstream oss;
index b41949ce217d51e618b92684519f44a291f2273f..a69d9f1edb2f55390b0c3feec24c34c70e74af13 100644 (file)
@@ -49,6 +49,7 @@
 #include "dnssecinfra.hh"
 #include "base64.hh"
 #include "ednssubnet.hh"
+#include "gss_context.hh"
 #include "dns_random.hh"
 #include "shuffle.hh"
 
@@ -62,10 +63,10 @@ DNSPacket::DNSPacket(bool isQuery): d_isQuery(isQuery)
   memset(&d, 0, sizeof(d));
 }
 
-const string& DNSPacket::getString()
+const string& DNSPacket::getString(bool throwsOnTruncation)
 {
   if(!d_wrapped)
-    wrapup();
+    wrapup(throwsOnTruncation);
 
   return d_rawpacket;
 }
@@ -173,7 +174,7 @@ void DNSPacket::addRecord(DNSZoneRecord&& rr)
   // in case we are not compressing for AXFR, no such checking is performed!
 
   if(d_compress) {
-    std::string ser = const_cast<DNSZoneRecord&>(rr).dr.d_content->serialize(rr.dr.d_name);
+    std::string ser = rr.dr.getContent()->serialize(rr.dr.d_name);
     auto hash = boost::hash< std::pair<DNSName, std::string> >()({rr.dr.d_name, ser});
     if(d_dedup.count(hash)) { // might be a dup
       for(auto & i : d_rrs) {
@@ -183,7 +184,6 @@ void DNSPacket::addRecord(DNSZoneRecord&& rr)
     }
     d_dedup.insert(hash);
   }
-
   d_rrs.push_back(std::move(rr));
 }
 
@@ -262,7 +262,7 @@ bool DNSPacket::isEmpty()
 /** Must be called before attempting to access getData(). This function stuffs all resource
  *  records found in rrs into the data buffer. It also frees resource records queued for us.
  */
-void DNSPacket::wrapup()
+void DNSPacket::wrapup(bool throwsOnTruncation)
 {
   if(d_wrapped) {
     return;
@@ -332,7 +332,7 @@ void DNSPacket::wrapup()
 
   if (d_haveednscookie) {
     if (d_eco.isWellFormed()) {
-        optsize += EDNSCookiesOpt::EDNSCookieOptSize;
+        optsize += EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE + EDNSCookiesOpt::EDNSCookieOptSize;
     }
   }
 
@@ -349,12 +349,14 @@ void DNSPacket::wrapup()
     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.d_content->toPacket(pw);
+        pos->dr.getContent()->toPacket(pw);
         if(pw.size() + optsize > (d_tcp ? 65535 : getMaxReplyLen())) {
+          if (throwsOnTruncation) {
+            throw PDNSException("attempt to write an oversized chunk");
+          }
           pw.rollback();
           pw.truncate();
           pw.getHeader()->tc=1;
@@ -370,6 +372,8 @@ void DNSPacket::wrapup()
       
       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);
@@ -512,7 +516,7 @@ bool DNSPacket::getTSIGDetails(TSIGRecordContent* trc, DNSName* keyname, uint16_
   for(const auto & answer : mdp.d_answers) {
     if(answer.first.d_type == QType::TSIG && answer.first.d_class == QType::ANY) {
       // cast can fail, f.e. if d_content is an UnknownRecordContent.
-      shared_ptr<TSIGRecordContent> content = std::dynamic_pointer_cast<TSIGRecordContent>(answer.first.d_content);
+      auto content = getRR<TSIGRecordContent>(answer.first);
       if (!content) {
         g_log<<Logger::Error<<"TSIG record has no or invalid content (invalid packet)"<<endl;
         return false;
@@ -545,7 +549,7 @@ bool DNSPacket::getTKEYRecord(TKEYRecordContent *tr, DNSName *keyname) const
 
     if(answer.first.d_type == QType::TKEY) {
       // cast can fail, f.e. if d_content is an UnknownRecordContent.
-      shared_ptr<TKEYRecordContent> content = std::dynamic_pointer_cast<TKEYRecordContent>(answer.first.d_content);
+      auto content = getRR<TKEYRecordContent>(answer.first);
       if (!content) {
         g_log<<Logger::Error<<"TKEY record has no or invalid content (invalid packet)"<<endl;
         return false;
@@ -594,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) {
@@ -732,13 +736,15 @@ bool DNSPacket::checkForCorrectTSIG(UeberBackend* B, DNSName* keyname, string* s
   if (tt.algo == DNSName("hmac-md5.sig-alg.reg.int"))
     tt.algo = DNSName("hmac-md5");
 
-  string secret64;
-  if(!B->getTSIGKey(*keyname, &tt.algo, &secret64)) {
-    g_log<<Logger::Error<<"Packet for domain '"<<this->qdomain<<"' denied: can't find TSIG key with name '"<<*keyname<<"' and algorithm '"<<tt.algo<<"'"<<endl;
-    return false;
+  if (tt.algo != DNSName("gss-tsig")) {
+    string secret64;
+    if(!B->getTSIGKey(*keyname, tt.algo, secret64)) {
+      g_log << Logger::Error << "Packet for domain '" << this->qdomain << "' denied: can't find TSIG key with name '" << *keyname << "' and algorithm '" << tt.algo << "'" << endl;
+      return false;
+    }
+    B64Decode(secret64, *secret);
+    tt.secret = *secret;
   }
-  B64Decode(secret64, *secret);
-  tt.secret = *secret;
 
   bool result;
 
@@ -756,3 +762,16 @@ bool DNSPacket::checkForCorrectTSIG(UeberBackend* B, DNSName* keyname, string* s
 const DNSName& DNSPacket::getTSIGKeyname() const {
   return d_tsigkeyname;
 }
+
+#ifdef ENABLE_GSS_TSIG
+void DNSPacket::cleanupGSS(int rcode)
+{
+  // We cannot check g_doGssTSIG here, as this code is also included in other executables
+  // than pdns_server.
+  if (rcode != RCode::NoError && d_tsig_algo == TSIG_GSS && !getTSIGKeyname().empty()) {
+    GssContext ctx(getTSIGKeyname());
+    ctx.destroy();
+  }
+}
+#endif
+
index 097bb5b63f72b9f6952e81e75999eeccfcb270f1..60e3268a9ebf39d315472ec39e34512b654eadf2 100644 (file)
@@ -58,7 +58,7 @@ public:
 
   int noparse(const char *mesg, size_t len); //!< just suck the data inward
   int parse(const char *mesg, size_t len); //!< parse a raw UDP or TCP packet and suck the data inward
-  const string& getString(); //!< for serialization - just passes the whole packet
+  const string& getString(bool throwsOnTruncation=false); //!< for serialization - just passes the whole packet. If throwsOnTruncation is set, an exception will be raised if the records are too large to fit inside a single DNS payload, instead of setting the TC bit
 
   // address & socket manipulation
   void setRemote(const ComboAddress*, std::optional<ComboAddress> = std::nullopt);
@@ -105,7 +105,7 @@ public:
   void setQuestion(int op, const DNSName &qdomain, int qtype);  // wipes 'd', sets a random id, creates start of packet (domain, type, class etc)
 
   DTime d_dt; //!< the time this packet was created. replyPacket() copies this in for you, so d_dt becomes the time spent processing the question+answer
-  void wrapup();  // writes out queued rrs, and generates the binary packet. also shuffles. also rectifies dnsheader 'd', and copies it to the stringbuffer
+  void wrapup(bool throwsOnTruncation=false);  // writes out queued rrs, and generates the binary packet. also shuffles. also rectifies dnsheader 'd', and copies it to the stringbuffer. If throwsOnTruncation is set, an exception will be raised if the records are too large to fit inside a single DNS payload, instead of setting the TC bit
   void spoofQuestion(const DNSPacket& qd); //!< paste in the exact right case of the question. Useful for PacketCache
   unsigned int getMinTTL(); //!< returns lowest TTL of any record in the packet
   bool isEmpty(); //!< returns true if there are no rrs in the packet
@@ -172,6 +172,11 @@ public:
   static bool s_doEDNSSubnetProcessing;
   static bool s_doEDNSCookieProcessing;
   static string s_EDNSCookieKey;
+  EDNSSubnetOpts d_eso;
+
+#ifdef ENABLE_GSS_TSIG
+  void cleanupGSS(int rcode);
+#endif
 
 private:
   void pasteQ(const char *question, int length); //!< set the question of this packet, useful for crafting replies
@@ -183,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 2e21461208370b6a77c5cdf63f82cf8933c3cf75..5a17fc36cb929b85bcfb12401a1891ba57704521 100644 (file)
@@ -25,6 +25,7 @@
 #include <boost/format.hpp>
 
 #include "namespaces.hh"
+#include "noinitvector.hh"
 
 UnknownRecordContent::UnknownRecordContent(const string& zone)
 {
@@ -60,7 +61,7 @@ UnknownRecordContent::UnknownRecordContent(const string& zone)
   d_record.insert(d_record.end(), out.begin(), out.end());
 }
 
-string UnknownRecordContent::getZoneRepresentation(bool noDot) const
+string UnknownRecordContent::getZoneRepresentation(bool /* noDot */) const
 {
   ostringstream str;
   str<<"\\# "<<(unsigned int)d_record.size()<<" ";
@@ -72,7 +73,7 @@ string UnknownRecordContent::getZoneRepresentation(bool noDot) const
   return str.str();
 }
 
-void UnknownRecordContent::toPacket(DNSPacketWriter& pw)
+void UnknownRecordContent::toPacket(DNSPacketWriter& pw) const
 {
   pw.xfrBlob(string(d_record.begin(),d_record.end()));
 }
@@ -84,20 +85,16 @@ shared_ptr<DNSRecordContent> DNSRecordContent::deserialize(const DNSName& qname,
   dnsheader.qdcount=htons(1);
   dnsheader.ancount=htons(1);
 
-  vector<uint8_t> packet; // build pseudo packet
-
+  PacketBuffer packet; // build pseudo packet
   /* will look like: dnsheader, 5 bytes, encoded qname, dns record header, serialized data */
-
   const auto& encoded = qname.getStorage();
-
   packet.resize(sizeof(dnsheader) + 5 + encoded.size() + sizeof(struct dnsrecordheader) + serialized.size());
 
   uint16_t pos=0;
-
   memcpy(&packet[0], &dnsheader, sizeof(dnsheader)); pos+=sizeof(dnsheader);
 
-  char tmp[6]="\x0" "\x0\x1" "\x0\x1"; // root question for ns_t_a
-  memcpy(&packet[pos], &tmp, 5); pos+=5;
+  constexpr std::array<uint8_t, 5> tmp= {'\x0', '\x0', '\x1', '\x0', '\x1' }; // root question for ns_t_a
+  memcpy(&packet[pos], tmp.data(), tmp.size()); pos += tmp.size();
 
   memcpy(&packet[pos], encoded.c_str(), encoded.size()); pos+=(uint16_t)encoded.size();
 
@@ -108,19 +105,26 @@ shared_ptr<DNSRecordContent> DNSRecordContent::deserialize(const DNSName& qname,
   drh.d_clen=htons(serialized.size());
 
   memcpy(&packet[pos], &drh, sizeof(drh)); pos+=sizeof(drh);
-  if (serialized.size() > 0) {
+  if (!serialized.empty()) {
     memcpy(&packet[pos], serialized.c_str(), serialized.size());
     pos += (uint16_t) serialized.size();
     (void) pos;
   }
 
-  MOADNSParser mdp(false, (char*)&*packet.begin(), (unsigned int)packet.size());
-  shared_ptr<DNSRecordContent> ret= mdp.d_answers.begin()->first.d_content;
-  return ret;
+  DNSRecord dr;
+  dr.d_class = QClass::IN;
+  dr.d_type = qtype;
+  dr.d_name = qname;
+  dr.d_clen = serialized.size();
+  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::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
 
@@ -132,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()) {
@@ -143,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.
@@ -191,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();
@@ -198,22 +208,23 @@ 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.d_content->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 pdns_string_view& packet)
+void MOADNSParser::init(bool query, const std::string_view& packet)
 {
   if (packet.size() < sizeof(dnsheader))
     throw MOADNSException("Packet shorter than minimal header");
@@ -268,18 +279,18 @@ void MOADNSParser::init(bool query, const pdns_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
           (dr.d_place == DNSResourceRecord::ANSWER || dr.d_place == DNSResourceRecord::AUTHORITY || (dr.d_type != QType::OPT && dr.d_type != QType::TSIG && dr.d_type != QType::SIG && dr.d_type != QType::TKEY) || ((dr.d_type == QType::TSIG || dr.d_type == QType::SIG || dr.d_type == QType::TKEY) && dr.d_class != QClass::ANY))) {
 //        cerr<<"discarding RR, query is "<<query<<", place is "<<dr.d_place<<", type is "<<dr.d_type<<", class is "<<dr.d_class<<endl;
-        dr.d_content=std::make_shared<UnknownRecordContent>(dr, pr);
+        dr.setContent(std::make_shared<UnknownRecordContent>(dr, pr));
       }
       else {
 //        cerr<<"parsing RR, query is "<<query<<", place is "<<dr.d_place<<", type is "<<dr.d_type<<", class is "<<dr.d_class<<endl;
-        dr.d_content=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:
@@ -345,40 +356,46 @@ bool MOADNSParser::hasEDNS() const
 
 void PacketReader::getDnsrecordheader(struct dnsrecordheader &ah)
 {
-  unsigned int n;
-  unsigned char *p=reinterpret_cast<unsigned char*>(&ah);
+  unsigned char *p = reinterpret_cast<unsigned char*>(&ah);
 
-  for(n=0; n < sizeof(dnsrecordheader); ++n)
-    p[n]=d_content.at(d_pos++);
+  for(unsigned int n = 0; n < sizeof(dnsrecordheader); ++n) {
+    p[n] = d_content.at(d_pos++);
+  }
 
-  ah.d_type=ntohs(ah.d_type);
-  ah.d_class=ntohs(ah.d_class);
-  ah.d_clen=ntohs(ah.d_clen);
-  ah.d_ttl=ntohl(ah.d_ttl);
+  ah.d_type = ntohs(ah.d_type);
+  ah.d_class = ntohs(ah.d_class);
+  ah.d_clen = ntohs(ah.d_clen);
+  ah.d_ttl = ntohl(ah.d_ttl);
 
-  d_startrecordpos=d_pos; // needed for getBlob later on
-  d_recordlen=ah.d_clen;
+  d_startrecordpos = d_pos; // needed for getBlob later on
+  d_recordlen = ah.d_clen;
 }
 
 
 void PacketReader::copyRecord(vector<unsigned char>& dest, uint16_t len)
 {
-  dest.resize(len);
-  if(!len)
+  if (len == 0) {
     return;
+  }
+  if ((d_pos + len) > d_content.size()) {
+    throw std::out_of_range("Attempt to copy outside of packet");
+  }
 
-  for(uint16_t n=0;n<len;++n) {
-    dest.at(n)=d_content.at(d_pos++);
+  dest.resize(len);
+
+  for (uint16_t n = 0; n < len; ++n) {
+    dest.at(n) = d_content.at(d_pos++);
   }
 }
 
 void PacketReader::copyRecord(unsigned char* dest, uint16_t len)
 {
-  if(d_pos + len > d_content.size())
+  if (d_pos + len > d_content.size()) {
     throw std::out_of_range("Attempt to copy outside of packet");
+  }
 
   memcpy(dest, &d_content.at(d_pos), len);
-  d_pos+=len;
+  d_pos += len;
 }
 
 void PacketReader::xfrNodeOrLocatorID(NodeOrLocatorID& ret)
@@ -500,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;
 }
 
@@ -520,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;
 }
@@ -668,7 +689,7 @@ void PacketReader::xfrSvcParamKeyVals(set<SvcParam> &kvs) {
 }
 
 
-void PacketReader::xfrHexBlob(string& blob, bool keepReading)
+void PacketReader::xfrHexBlob(string& blob, bool /* keepReading */)
 {
   xfrBlob(blob);
 }
@@ -745,19 +766,55 @@ void editDNSPacketTTL(char* packet, size_t length, const std::function<uint32_t(
   }
 }
 
-static int rewritePacketWithoutRecordTypes(const PacketBuffer& initialPacket, PacketBuffer& newContent, const std::set<QType>& qtypes)
+static bool checkIfPacketContainsRecords(const PacketBuffer& packet, const std::unordered_set<QType>& qtypes)
 {
-  static const std::set<QType>& safeTypes{QType::A, QType::AAAA, QType::DHCID, QType::TXT, QType::OPT, QType::HINFO, QType::DNSKEY, QType::CDNSKEY, QType::DS, QType::CDS, QType::DLV, QType::SSHFP, QType::KEY, QType::CERT, QType::TLSA, QType::SMIMEA, QType::OPENPGPKEY, QType::SVCB, QType::HTTPS, QType::NSEC3, QType::CSYNC, QType::NSEC3PARAM, QType::LOC, QType::NID, QType::L32, QType::L64, QType::EUI48, QType::EUI64, QType::URI, QType::CAA};
+  auto length = packet.size();
+  if (length < sizeof(dnsheader)) {
+    return false;
+  }
+
+  try {
+    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);
+    for (size_t n = 0; n < qdcount; ++n) {
+      dpm.skipDomainName();
+      /* type and class */
+      dpm.skipBytes(4);
+    }
+    const size_t recordsCount = static_cast<size_t>(ntohs(dh->ancount)) + ntohs(dh->nscount) + ntohs(dh->arcount);
+    for (size_t n = 0; n < recordsCount; ++n) {
+      dpm.skipDomainName();
+      uint16_t dnstype = dpm.get16BitInt();
+      uint16_t dnsclass = dpm.get16BitInt();
+      if (dnsclass == QClass::IN && qtypes.count(dnstype) > 0) {
+        return true;
+      }
+      /* ttl */
+      dpm.skipBytes(4);
+      dpm.skipRData();
+    }
+  }
+  catch (...) {
+  }
+
+  return false;
+}
+
+static int rewritePacketWithoutRecordTypes(const PacketBuffer& initialPacket, PacketBuffer& newContent, const std::unordered_set<QType>& qtypes)
+{
+  static const std::unordered_set<QType>& safeTypes{QType::A, QType::AAAA, QType::DHCID, QType::TXT, QType::OPT, QType::HINFO, QType::DNSKEY, QType::CDNSKEY, QType::DS, QType::CDS, QType::DLV, QType::SSHFP, QType::KEY, QType::CERT, QType::TLSA, QType::SMIMEA, QType::OPENPGPKEY, QType::SVCB, QType::HTTPS, QType::NSEC3, QType::CSYNC, QType::NSEC3PARAM, QType::LOC, QType::NID, QType::L32, QType::L64, QType::EUI48, QType::EUI64, QType::URI, QType::CAA};
 
   if (initialPacket.size() < sizeof(dnsheader)) {
     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;
-    auto packetView = pdns_string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size());
+    auto packetView = std::string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size());
 
     PacketReader pr(packetView);
 
@@ -859,13 +916,17 @@ static int rewritePacketWithoutRecordTypes(const PacketBuffer& initialPacket, Pa
   return 0;
 }
 
-void clearDNSPacketRecordTypes(vector<uint8_t>& packet, const std::set<QType>& qtypes)
+void clearDNSPacketRecordTypes(vector<uint8_t>& packet, const std::unordered_set<QType>& qtypes)
 {
   return clearDNSPacketRecordTypes(reinterpret_cast<PacketBuffer&>(packet), qtypes);
 }
 
-void clearDNSPacketRecordTypes(PacketBuffer& packet, const std::set<QType>& qtypes)
+void clearDNSPacketRecordTypes(PacketBuffer& packet, const std::unordered_set<QType>& qtypes)
 {
+  if (!checkIfPacketContainsRecords(packet, qtypes)) {
+    return;
+  }
+
   PacketBuffer newContent;
 
   auto result = rewritePacketWithoutRecordTypes(packet, newContent, qtypes);
@@ -875,47 +936,45 @@ void clearDNSPacketRecordTypes(PacketBuffer& packet, const std::set<QType>& qtyp
 }
 
 // method of operation: silently fail if it doesn't work - we're only trying to be nice, don't fall over on it
-void ageDNSPacket(char* packet, size_t length, uint32_t seconds)
+void ageDNSPacket(char* packet, size_t length, uint32_t seconds, const dnsheader_aligned& aligned_dh)
 {
-  if(length < sizeof(dnsheader))
+  if (length < sizeof(dnsheader)) {
     return;
-  try
-  {
-    const dnsheader* dh = reinterpret_cast<const dnsheader*>(packet);
-    const uint64_t dqcount = ntohs(dh->qdcount);
-    const uint64_t numrecords = ntohs(dh->ancount) + ntohs(dh->nscount) + ntohs(dh->arcount);
+  }
+  try {
+    const dnsheader* dhp = aligned_dh.get();
+    const uint64_t dqcount = ntohs(dhp->qdcount);
+    const uint64_t numrecords = ntohs(dhp->ancount) + ntohs(dhp->nscount) + ntohs(dhp->arcount);
     DNSPacketMangler dpm(packet, length);
 
-    uint64_t n;
-    for(n=0; n < dqcount; ++n) {
+    for (uint64_t rec = 0; rec < dqcount; ++rec) {
       dpm.skipDomainName();
       /* type and class */
       dpm.skipBytes(4);
     }
-   // cerr<<"Skipped "<<n<<" questions, now parsing "<<numrecords<<" records"<<endl;
-    for(n=0; n < numrecords; ++n) {
+
+    for(uint64_t rec = 0; rec < numrecords; ++rec) {
       dpm.skipDomainName();
 
       uint16_t dnstype = dpm.get16BitInt();
       /* class */
       dpm.skipBytes(2);
 
-      if(dnstype == QType::OPT) // not aging that one with a stick
-        break;
-
-      dpm.decreaseAndSkip32BitInt(seconds);
+      if (dnstype != QType::OPT) { // not aging that one with a stick
+        dpm.decreaseAndSkip32BitInt(seconds);
+      } else {
+        dpm.skipBytes(4);
+      }
       dpm.skipRData();
     }
   }
-  catch(...)
-  {
-    return;
+  catch(...) {
   }
 }
 
-void ageDNSPacket(std::string& packet, uint32_t seconds)
+void ageDNSPacket(std::string& packet, uint32_t seconds, const dnsheader_aligned& aligned_dh)
 {
-  ageDNSPacket((char*)packet.c_str(), packet.length(), seconds);
+  ageDNSPacket(packet.data(), packet.length(), seconds, aligned_dh);
 }
 
 uint32_t getDNSPacketMinTTL(const char* packet, size_t length, bool* seenAuthSOA)
@@ -926,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);
@@ -973,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);
@@ -1005,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);
@@ -1095,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);
@@ -1129,3 +1188,53 @@ bool getEDNSUDPPayloadSizeAndZ(const char* packet, size_t length, uint16_t* payl
 
   return false;
 }
+
+bool visitDNSPacket(const std::string_view& packet, const std::function<bool(uint8_t, uint16_t, uint16_t, uint32_t, uint16_t, const char*)>& visitor)
+{
+  if (packet.size() < sizeof(dnsheader)) {
+    return false;
+  }
+
+  try
+  {
+    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) {
+      (void) reader.getName();
+      /* type and class */
+      reader.skip(4);
+    }
+
+    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);
+      uint16_t dnstype = reader.get16BitInt();
+      uint16_t dnsclass = reader.get16BitInt();
+
+      if (dnstype == QType::OPT) {
+        // not getting near that one with a stick
+        break;
+      }
+
+      uint32_t dnsttl = reader.get32BitInt();
+      uint16_t contentLength = reader.get16BitInt();
+      uint16_t pos = reader.getPosition();
+
+      bool done = visitor(section, dnsclass, dnstype, dnsttl, contentLength, &packet.at(pos));
+      if (done) {
+        return true;
+      }
+
+      reader.skip(contentLength);
+    }
+  }
+  catch (...) {
+    return false;
+  }
+
+  return true;
+}
index 980e327e441ef4eb7dc4fceacd6ad6ec7f0463e1..20ce6396beb87d5dccc6c4750649f56ec904a390 100644 (file)
 #include <sstream>
 #include <stdexcept>
 #include <iostream>
+#include <unordered_set>
+#include <utility>
 #include <vector>
-#include <errno.h>
+#include <cerrno>
 // #include <netinet/in.h>
 #include "misc.hh"
 
@@ -65,7 +67,7 @@ class MOADNSParser;
 class PacketReader
 {
 public:
-  PacketReader(const pdns_string_view& content, uint16_t initialPos=sizeof(dnsheader))
+  PacketReader(const std::string_view& content, uint16_t initialPos=sizeof(dnsheader))
     : d_pos(initialPos), d_startrecordpos(initialPos), d_content(content)
   {
     if(content.size() > std::numeric_limits<uint16_t>::max())
@@ -133,10 +135,9 @@ public:
     val=get8BitInt();
   }
 
-
-  void xfrName(DNSName &name, bool compress=false, bool noDot=false)
+  void xfrName(DNSName& name, bool /* compress */ = false, bool /* noDot */ = false)
   {
-    name=getName();
+    name = getName();
   }
 
   void xfrText(string &text, bool multi=false, bool lenField=true)
@@ -183,7 +184,7 @@ private:
   uint16_t d_startrecordpos; // needed for getBlob later on
   uint16_t d_recordlen;      // ditto
   uint16_t not_used; // Aligns the whole class on 8-byte boundaries
-  const pdns_string_view d_content;
+  const std::string_view d_content;
 };
 
 struct DNSRecord;
@@ -191,15 +192,16 @@ 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 void toPacket(DNSPacketWriter& pw)=0;
-  virtual string serialize(const DNSName& qname, bool canonic=false, bool lowerCase=false) // it would rock if this were const, but it is too hard
+  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
   {
     vector<uint8_t> packet;
     DNSPacketWriter pw(packet, g_rootdnsname, 1);
@@ -222,6 +224,7 @@ public:
     return typeid(*this)==typeid(rhs) && this->getZoneRepresentation() == rhs.getZoneRepresentation();
   }
 
+  // parse the content in wire format, possibly including compressed pointers pointing to the owner name
   static shared_ptr<DNSRecordContent> deserialize(const DNSName& qname, uint16_t qtype, const string& serialized);
 
   void doRecordCheck(const struct DNSRecord&){}
@@ -265,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())
@@ -274,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:
@@ -289,16 +297,61 @@ protected:
 
 struct DNSRecord
 {
-  DNSRecord() : d_type(0), d_class(QClass::IN), d_ttl(0), d_clen(0), d_place(DNSResourceRecord::ANSWER)
+  DNSRecord() :
+    d_class(QClass::IN)
   {}
   explicit DNSRecord(const DNSResourceRecord& rr);
+  DNSRecord(const std::string& name,
+            std::shared_ptr<DNSRecordContent> content,
+            const uint16_t type,
+            const uint16_t qclass = QClass::IN,
+            const uint32_t ttl = 86400,
+            const uint16_t clen = 0,
+            const DNSResourceRecord::Place place = DNSResourceRecord::ANSWER) :
+    d_name(DNSName(name)),
+    d_content(std::move(content)),
+    d_type(type),
+    d_class(qclass),
+    d_ttl(ttl),
+    d_clen(clen),
+    d_place(place) {}
+
   DNSName d_name;
-  std::shared_ptr<DNSRecordContent> d_content;
-  uint16_t d_type;
-  uint16_t d_class;
-  uint32_t d_ttl;
-  uint16_t d_clen;
-  DNSResourceRecord::Place d_place;
+private:
+  std::shared_ptr<const DNSRecordContent> d_content;
+public:
+  uint16_t d_type{};
+  uint16_t d_class{};
+  uint32_t d_ttl{};
+  uint16_t d_clen{};
+  DNSResourceRecord::Place d_place{DNSResourceRecord::ANSWER};
+
+  [[nodiscard]] std::string print(const std::string& indent = "") const
+  {
+    std::stringstream s;
+    s << indent << "Content = " << d_content->getZoneRepresentation() << std::endl;
+    s << indent << "Type = " << d_type << std::endl;
+    s << indent << "Class = " << d_class << std::endl;
+    s << indent << "TTL = " << d_ttl << std::endl;
+    s << indent << "clen = " << d_clen << std::endl;
+    s << indent << "Place = " << std::to_string(d_place) << std::endl;
+    return s.str();
+  }
+
+  void setContent(const std::shared_ptr<const DNSRecordContent>& content)
+  {
+    d_content = content;
+  }
+
+  void setContent(std::shared_ptr<const DNSRecordContent>&& content)
+  {
+    d_content = std::move(content);
+  }
+
+  [[nodiscard]] const std::shared_ptr<const DNSRecordContent>& getContent() const
+  {
+    return d_content;
+  }
 
   bool operator<(const DNSRecord& rhs) const
   {
@@ -336,18 +389,27 @@ struct DNSRecord
 
     string lzrp, rzrp;
     if(a.d_content)
-      lzrp=toLower(a.d_content->getZoneRepresentation());
+      lzrp = a.d_content->getZoneRepresentation();
     if(b.d_content)
-      rzrp=toLower(b.d_content->getZoneRepresentation());
-
-    return lzrp < rzrp;
+      rzrp = b.d_content->getZoneRepresentation();
+
+    switch (a.d_type) {
+    case QType::TXT:
+    case QType::SPF:
+#if !defined(RECURSOR)
+    case QType::LUA:
+#endif
+      return lzrp < rzrp;
+    default:
+      return toLower(lzrp) < toLower(rzrp);
+    }
   }
 
-
   bool operator==(const DNSRecord& rhs) const
   {
-    if(d_type != rhs.d_type || d_class != rhs.d_class || d_name != rhs.d_name)
+    if (d_type != rhs.d_type || d_class != rhs.d_class || d_name != rhs.d_name) {
       return false;
+    }
 
     return *d_content == *rhs.d_content;
   }
@@ -376,7 +438,7 @@ public:
   UnknownRecordContent(const string& zone);
 
   string getZoneRepresentation(bool noDot) const override;
-  void toPacket(DNSPacketWriter& pw) override;
+  void toPacket(DNSPacketWriter& pw) const override;
   uint16_t getType() const override
   {
     return d_dr.d_type;
@@ -405,7 +467,7 @@ public:
   //! Parse from a pointer and length
   MOADNSParser(bool query, const char *packet, unsigned int len) : d_tsigPos(0)
   {
-    init(query, pdns_string_view(packet, len));
+    init(query, std::string_view(packet, len));
   }
 
   DNSName d_qname;
@@ -426,26 +488,29 @@ public:
   bool hasEDNS() const;
 
 private:
-  void init(bool query, const pdns_string_view& packet);
+  void init(bool query, const std::string_view& packet);
   uint16_t d_tsigPos;
 };
 
 string simpleCompress(const string& label, const string& root="");
-void ageDNSPacket(char* packet, size_t length, uint32_t seconds);
-void ageDNSPacket(std::string& packet, uint32_t seconds);
+void ageDNSPacket(char* packet, size_t length, uint32_t seconds, const dnsheader_aligned&);
+void ageDNSPacket(std::string& packet, uint32_t seconds, const dnsheader_aligned&);
 void editDNSPacketTTL(char* packet, size_t length, const std::function<uint32_t(uint8_t, uint16_t, uint16_t, uint32_t)>& visitor);
-void clearDNSPacketRecordTypes(vector<uint8_t>& packet, const std::set<QType>& qtypes);
-void clearDNSPacketRecordTypes(PacketBuffer& packet, const std::set<QType>& qtypes);
-void clearDNSPacketRecordTypes(char* packet, size_t& length, const std::set<QType>& qtypes);
+void clearDNSPacketRecordTypes(vector<uint8_t>& packet, const std::unordered_set<QType>& qtypes);
+void clearDNSPacketRecordTypes(PacketBuffer& packet, const std::unordered_set<QType>& qtypes);
+void clearDNSPacketRecordTypes(char* packet, size_t& length, const std::unordered_set<QType>& qtypes);
 uint32_t getDNSPacketMinTTL(const char* packet, size_t length, bool* seenAuthSOA=nullptr);
 uint32_t getDNSPacketLength(const char* packet, size_t length);
 uint16_t getRecordsOfTypeCount(const char* packet, size_t length, uint8_t section, uint16_t type);
 bool getEDNSUDPPayloadSizeAndZ(const char* packet, size_t length, uint16_t* payloadSize, uint16_t* z);
+/* call the visitor for every records in the answer, authority and additional sections, passing the section, class, type, ttl, rdatalength and rdata
+   to the visitor. Stops whenever the visitor returns false or at the end of the packet */
+bool visitDNSPacket(const std::string_view& packet, const std::function<bool(uint8_t, uint16_t, uint16_t, uint32_t, uint16_t, const char*)>& visitor);
 
 template<typename T>
-std::shared_ptr<T> getRR(const DNSRecord& dr)
+std::shared_ptr<const T> getRR(const DNSRecord& dr)
 {
-  return std::dynamic_pointer_cast<T>(dr.d_content);
+  return std::dynamic_pointer_cast<const T>(dr.getContent());
 }
 
 /** Simple DNSPacketMangler. Ritual is: get a pointer into the packet and moveOffset() to beyond your needs
index 179a8c9aabb7250d65824e6217a8ab83a8f8f228..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(const 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_content=answer.first.d_content;
-                i->second.complete->addRecord(std::move(dzr));
+                dzr.dr.d_ttl = answer.first.d_ttl;
+                dzr.dr.d_place = answer.first.d_place;
+                dzr.dr.setContent(answer.first.getContent());
+                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 4f581cbc0340def73b3d4cc6058132dad4545bf5..6b0079247ebc6682d6e5f42babaf6319ae12a7b6 100644 (file)
@@ -55,7 +55,7 @@ string DNSResourceRecord::getZoneRepresentation(bool noDot) const {
     case QType::SRV:
     case QType::MX:
       stringtok(parts, content);
-      if (!parts.size())
+      if (parts.empty())
         return "";
       last = *parts.rbegin();
       ret << content;
@@ -91,17 +91,17 @@ bool DNSResourceRecord::operator==(const DNSResourceRecord& rhs)
 
 boilerplate_conv(A, conv.xfrIP(d_ip));
 
-ARecordContent::ARecordContent(uint32_t ip) 
+ARecordContent::ARecordContent(uint32_t ip)
 {
   d_ip = ip;
 }
 
-ARecordContent::ARecordContent(const ComboAddress& ca) 
+ARecordContent::ARecordContent(const ComboAddress& ca)
 {
   d_ip = ca.sin4.sin_addr.s_addr;
 }
 
-AAAARecordContent::AAAARecordContent(const ComboAddress& ca) 
+AAAARecordContent::AAAARecordContent(const ComboAddress& ca)
 {
   d_ip6.assign((const char*)ca.sin6.sin6_addr.s6_addr, 16);
 }
@@ -130,7 +130,7 @@ ComboAddress AAAARecordContent::getCA(int port) const
 
 
 void ARecordContent::doRecordCheck(const DNSRecord& dr)
-{  
+{
   if(dr.d_clen!=4)
     throw MOADNSException("Wrong size for A record ("+std::to_string(dr.d_clen)+")");
 }
@@ -157,7 +157,7 @@ boilerplate_conv(SPF, conv.xfrText(d_text, true));
 boilerplate_conv(HINFO, conv.xfrText(d_cpu);   conv.xfrText(d_host));
 
 boilerplate_conv(RP,
-                 conv.xfrName(d_mbox);   
+                 conv.xfrName(d_mbox);
                  conv.xfrName(d_info)
                  );
 
@@ -181,13 +181,13 @@ string LUARecordContent::getCode() const
 }
 #endif
 
-void OPTRecordContent::getData(vector<pair<uint16_t, string> >& options)
+void OPTRecordContent::getData(vector<pair<uint16_t, string> >& options) const
 {
   string::size_type pos=0;
   uint16_t code, len;
   while(d_data.size() >= 4 + pos) {
-    code = 256 * (unsigned char)d_data[pos] + (unsigned char)d_data[pos+1];
-    len = 256 * (unsigned char)d_data[pos+2] + (unsigned char)d_data[pos+3];
+    code = 256 * (unsigned char)d_data.at(pos) + (unsigned char)d_data.at(pos+1);
+    len = 256 * (unsigned char)d_data.at(pos+2) + (unsigned char)d_data.at(pos+3);
     pos+=4;
 
     if(pos + len > d_data.size())
@@ -209,7 +209,7 @@ boilerplate_conv(TSIG,
                  conv.xfr16BitInt(d_origID);
                  conv.xfr16BitInt(d_eRcode);
                  size=d_otherData.size();
-                 conv.xfr16BitInt(size); 
+                 conv.xfr16BitInt(size);
                  if (size>0) conv.xfrBlobNoSpaces(d_otherData, size);
                  );
 
@@ -231,7 +231,7 @@ boilerplate_conv(IPSECKEY,
    conv.xfr8BitInt(d_preference);
    conv.xfr8BitInt(d_gatewaytype);
    conv.xfr8BitInt(d_algorithm);
+
    // now we need to determine values
    switch(d_gatewaytype) {
    case 0: // NO KEY
@@ -243,7 +243,7 @@ boilerplate_conv(IPSECKEY,
      conv.xfrIP6(d_ip6);
      break;
    case 3: // DNS label
-     conv.xfrName(d_gateway, false); 
+     conv.xfrName(d_gateway, false);
      break;
    default:
      throw MOADNSException("Parsing record content: invalid gateway type");
@@ -259,7 +259,7 @@ boilerplate_conv(IPSECKEY,
    default:
      throw MOADNSException("Parsing record content: invalid algorithm type");
    }
-) 
+)
 
 boilerplate_conv(DHCID,
                  conv.xfrBlob(d_content);
@@ -279,16 +279,16 @@ boilerplate_conv(NAPTR,
                  )
 
 
-SRVRecordContent::SRVRecordContent(uint16_t preference, uint16_t weight, uint16_t port, DNSName  target) 
+SRVRecordContent::SRVRecordContent(uint16_t preference, uint16_t weight, uint16_t port, DNSName  target)
 : d_weight(weight), d_port(port), d_target(std::move(target)), d_preference(preference)
 {}
 
 boilerplate_conv(SRV,
                  conv.xfr16BitInt(d_preference);   conv.xfr16BitInt(d_weight);   conv.xfr16BitInt(d_port);
-                 conv.xfrName(d_target); 
+                 conv.xfrName(d_target);
                  )
 
-SOARecordContent::SOARecordContent(DNSName  mname, DNSName  rname, const struct soatimes& st) 
+SOARecordContent::SOARecordContent(DNSName  mname, DNSName  rname, const struct soatimes& st)
 : d_mname(std::move(mname)), d_rname(std::move(rname)), d_st(st)
 {
 }
@@ -304,9 +304,9 @@ boilerplate_conv(SOA,
                  );
 #undef KEY
 boilerplate_conv(KEY,
-                 conv.xfr16BitInt(d_flags); 
-                 conv.xfr8BitInt(d_protocol); 
-                 conv.xfr8BitInt(d_algorithm); 
+                 conv.xfr16BitInt(d_flags);
+                 conv.xfr8BitInt(d_protocol);
+                 conv.xfr8BitInt(d_algorithm);
                  conv.xfrBlob(d_certificate);
                  );
 
@@ -318,21 +318,21 @@ boilerplate_conv(ZONEMD,
                  );
 
 boilerplate_conv(CERT,
-                 conv.xfr16BitInt(d_type); 
+                 conv.xfr16BitInt(d_type);
                  if (d_type == 0) throw MOADNSException("CERT type 0 is reserved");
 
-                 conv.xfr16BitInt(d_tag); 
-                 conv.xfr8BitInt(d_algorithm); 
+                 conv.xfr16BitInt(d_tag);
+                 conv.xfr8BitInt(d_algorithm);
                  conv.xfrBlob(d_certificate);
                  )
 
 boilerplate_conv(TLSA,
-                 conv.xfr8BitInt(d_certusage); 
-                 conv.xfr8BitInt(d_selector); 
-                 conv.xfr8BitInt(d_matchtype); 
+                 conv.xfr8BitInt(d_certusage);
+                 conv.xfr8BitInt(d_selector);
+                 conv.xfr8BitInt(d_matchtype);
                  conv.xfrHexBlob(d_cert, true);
-                 )                 
-                 
+                 )
+
 boilerplate_conv(OPENPGPKEY,
                  conv.xfrBlob(d_keyring);
                  )
@@ -360,74 +360,74 @@ boilerplate_conv(SMIMEA,
                  conv.xfrHexBlob(d_cert, true);
                  )
 
-DSRecordContent::DSRecordContent() {}
+DSRecordContent::DSRecordContent() = default;
 boilerplate_conv(DS,
-                 conv.xfr16BitInt(d_tag); 
-                 conv.xfr8BitInt(d_algorithm); 
-                 conv.xfr8BitInt(d_digesttype); 
+                 conv.xfr16BitInt(d_tag);
+                 conv.xfr8BitInt(d_algorithm);
+                 conv.xfr8BitInt(d_digesttype);
                  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); 
-                 conv.xfr8BitInt(d_digesttype); 
+                 conv.xfr16BitInt(d_tag);
+                 conv.xfr8BitInt(d_algorithm);
+                 conv.xfr8BitInt(d_digesttype);
                  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); 
-                 conv.xfr8BitInt(d_digesttype); 
+                 conv.xfr16BitInt(d_tag);
+                 conv.xfr8BitInt(d_algorithm);
+                 conv.xfr8BitInt(d_digesttype);
                  conv.xfrHexBlob(d_digest, true); // keep reading across spaces
                  )
 
 
 boilerplate_conv(SSHFP,
-                 conv.xfr8BitInt(d_algorithm); 
-                 conv.xfr8BitInt(d_fptype); 
+                 conv.xfr8BitInt(d_algorithm);
+                 conv.xfr8BitInt(d_fptype);
                  conv.xfrHexBlob(d_fingerprint, true);
                  )
 
 boilerplate_conv(RRSIG,
-                 conv.xfrType(d_type); 
-                   conv.xfr8BitInt(d_algorithm); 
-                   conv.xfr8BitInt(d_labels); 
-                   conv.xfr32BitInt(d_originalttl); 
-                   conv.xfrTime(d_sigexpire); 
-                   conv.xfrTime(d_siginception); 
-                 conv.xfr16BitInt(d_tag); 
+                 conv.xfrType(d_type);
+                   conv.xfr8BitInt(d_algorithm);
+                   conv.xfr8BitInt(d_labels);
+                   conv.xfr32BitInt(d_originalttl);
+                   conv.xfrTime(d_sigexpire);
+                   conv.xfrTime(d_siginception);
+                 conv.xfr16BitInt(d_tag);
                  conv.xfrName(d_signer);
                  conv.xfrBlob(d_signature);
                  )
-                 
-RRSIGRecordContent::RRSIGRecordContent() {}
+
+RRSIGRecordContent::RRSIGRecordContent() = default;
 
 boilerplate_conv(DNSKEY,
-                 conv.xfr16BitInt(d_flags); 
-                 conv.xfr8BitInt(d_protocol); 
-                 conv.xfr8BitInt(d_algorithm); 
+                 conv.xfr16BitInt(d_flags);
+                 conv.xfr8BitInt(d_protocol);
+                 conv.xfr8BitInt(d_algorithm);
                  conv.xfrBlob(d_key);
                  )
-DNSKEYRecordContent::DNSKEYRecordContent() {}
+DNSKEYRecordContent::DNSKEYRecordContent() = default;
 
 boilerplate_conv(CDNSKEY,
-                 conv.xfr16BitInt(d_flags); 
-                 conv.xfr8BitInt(d_protocol); 
-                 conv.xfr8BitInt(d_algorithm); 
+                 conv.xfr16BitInt(d_flags);
+                 conv.xfr8BitInt(d_protocol);
+                 conv.xfr8BitInt(d_algorithm);
                  conv.xfrBlob(d_key);
                  )
-CDNSKEYRecordContent::CDNSKEYRecordContent() {}
+CDNSKEYRecordContent::CDNSKEYRecordContent() = default;
 
 boilerplate_conv(RKEY,
-                 conv.xfr16BitInt(d_flags); 
-                 conv.xfr8BitInt(d_protocol); 
-                 conv.xfr8BitInt(d_algorithm); 
+                 conv.xfr16BitInt(d_flags);
+                 conv.xfr8BitInt(d_protocol);
+                 conv.xfr8BitInt(d_algorithm);
                  conv.xfrBlob(d_key);
                  )
-RKEYRecordContent::RKEYRecordContent() {}
+RKEYRecordContent::RKEYRecordContent() = default;
 
 boilerplate_conv(NID,
                  conv.xfr16BitInt(d_preference);
@@ -463,24 +463,25 @@ std::shared_ptr<DNSRecordContent> EUI48RecordContent::make(const string& zone)
 {
     // try to parse
     auto ret=std::make_shared<EUI48RecordContent>();
-    // format is 6 hex bytes and dashes    
-    if (sscanf(zone.c_str(), "%2hhx-%2hhx-%2hhx-%2hhx-%2hhx-%2hhx", 
-           ret->d_eui48, ret->d_eui48+1, ret->d_eui48+2, 
+    // format is 6 hex bytes and dashes
+    if (sscanf(zone.c_str(), "%2hhx-%2hhx-%2hhx-%2hhx-%2hhx-%2hhx",
+           ret->d_eui48, ret->d_eui48+1, ret->d_eui48+2,
            ret->d_eui48+3, ret->d_eui48+4, ret->d_eui48+5) != 6) {
        throw MOADNSException("Asked to encode '"+zone+"' as an EUI48 address, but does not parse");
     }
     return ret;
 }
-void EUI48RecordContent::toPacket(DNSPacketWriter& pw)
+void EUI48RecordContent::toPacket(DNSPacketWriter& pw) const
 {
     string blob(d_eui48, d_eui48+6);
-    pw.xfrBlob(blob); 
+    pw.xfrBlob(blob);
 }
-string EUI48RecordContent::getZoneRepresentation(bool noDot) const
+
+string EUI48RecordContent::getZoneRepresentation(bool /* noDot */) const
 {
-    char tmp[18]; 
+    char tmp[18];
     snprintf(tmp,sizeof(tmp),"%02x-%02x-%02x-%02x-%02x-%02x",
-           d_eui48[0], d_eui48[1], d_eui48[2], 
+           d_eui48[0], d_eui48[1], d_eui48[2],
            d_eui48[3], d_eui48[4], d_eui48[5]);
     return tmp;
 }
@@ -507,7 +508,7 @@ std::shared_ptr<DNSRecordContent> EUI64RecordContent::make(const string& zone)
     // try to parse
     auto ret=std::make_shared<EUI64RecordContent>();
     // format is 8 hex bytes and dashes
-    if (sscanf(zone.c_str(), "%2hhx-%2hhx-%2hhx-%2hhx-%2hhx-%2hhx-%2hhx-%2hhx", 
+    if (sscanf(zone.c_str(), "%2hhx-%2hhx-%2hhx-%2hhx-%2hhx-%2hhx-%2hhx-%2hhx",
            ret->d_eui64, ret->d_eui64+1, ret->d_eui64+2,
            ret->d_eui64+3, ret->d_eui64+4, ret->d_eui64+5,
            ret->d_eui64+6, ret->d_eui64+7) != 8) {
@@ -515,14 +516,15 @@ std::shared_ptr<DNSRecordContent> EUI64RecordContent::make(const string& zone)
     }
     return ret;
 }
-void EUI64RecordContent::toPacket(DNSPacketWriter& pw)
+void EUI64RecordContent::toPacket(DNSPacketWriter& pw) const
 {
     string blob(d_eui64, d_eui64+8);
     pw.xfrBlob(blob);
 }
-string EUI64RecordContent::getZoneRepresentation(bool noDot) const
+
+string EUI64RecordContent::getZoneRepresentation(bool /* noDot */) const
 {
-    char tmp[24]; 
+    char tmp[24];
     snprintf(tmp,sizeof(tmp),"%02x-%02x-%02x-%02x-%02x-%02x-%02x-%02x",
            d_eui64[0], d_eui64[1], d_eui64[2],
            d_eui64[3], d_eui64[4], d_eui64[5],
@@ -685,7 +687,7 @@ std::shared_ptr<DNSRecordContent> APLRecordContent::make(const string& zone) {
 
 
 // DNSRecord to Packet conversion
-void APLRecordContent::toPacket(DNSPacketWriter& pw) {
+void APLRecordContent::toPacket(DNSPacketWriter& pw) const {
   for (auto & ard : aplrdata) {
     pw.xfr16BitInt(ard.d_family);
     pw.xfr8BitInt(ard.d_prefix);
@@ -703,7 +705,7 @@ void APLRecordContent::toPacket(DNSPacketWriter& pw) {
 }
 
 // Decode record into string
-string APLRecordContent::getZoneRepresentation(bool noDot) const {
+string APLRecordContent::getZoneRepresentation(bool /* noDot */) const {
   string s_n, s_family, output;
   ComboAddress ca;
   Netmask nm;
@@ -758,14 +760,16 @@ void SVCBBaseRecordContent::setHints(const SvcParam::SvcParamKey &key, const std
   if (p == d_params.end()) {
     return;
   }
+
   std::vector<ComboAddress> h;
   h.reserve(h.size() + addresses.size());
   h.insert(h.end(), addresses.begin(), addresses.end());
+
   try {
     auto newParam = SvcParam(key, std::move(h));
     d_params.erase(p);
     d_params.insert(newParam);
-  } catch(...) {
+  } catch (...) {
     // XXX maybe we should SERVFAIL instead?
     return;
   }
@@ -780,7 +784,7 @@ void SVCBBaseRecordContent::removeParam(const SvcParam::SvcParamKey &key) {
 }
 
 bool SVCBBaseRecordContent::hasParams() const {
-  return d_params.size() > 0;
+  return !d_params.empty();
 }
 
 bool SVCBBaseRecordContent::hasParam(const SvcParam::SvcParamKey &key) const {
@@ -803,6 +807,16 @@ set<SvcParam>::const_iterator SVCBBaseRecordContent::getParamIt(const SvcParam::
   return p;
 }
 
+std::shared_ptr<SVCBBaseRecordContent> SVCBRecordContent::clone() const
+{
+  return std::shared_ptr<SVCBBaseRecordContent>(std::make_shared<SVCBRecordContent>(*this));
+}
+
+std::shared_ptr<SVCBBaseRecordContent> HTTPSRecordContent::clone() const
+{
+  return std::shared_ptr<SVCBBaseRecordContent>(std::make_shared<HTTPSRecordContent>(*this));
+}
+
 /* SVCB end */
 
 boilerplate_conv(TKEY,
@@ -837,7 +851,7 @@ static uint16_t makeTag(const std::string& data)
 
   unsigned long ac;     /* assumed to be 32 bits or larger */
   unsigned int i;                /* loop index */
-  
+
   for ( ac = 0, i = 0; i < keysize; ++i )
     ac += (i & 1) ? key[i] : key[i] << 8;
   ac += (ac >> 16) & 0xFFFF;
@@ -845,12 +859,6 @@ static uint16_t makeTag(const std::string& data)
 }
 
 uint16_t DNSKEYRecordContent::getTag() const
-{
-  DNSKEYRecordContent tmp(*this);
-  return makeTag(tmp.serialize(DNSName()));  // this can't be const for some reason
-}
-
-uint16_t DNSKEYRecordContent::getTag() 
 {
   return makeTag(this->serialize(DNSName()));
 }
@@ -886,25 +894,6 @@ bool getEDNSOpts(const MOADNSParser& mdp, EDNSOpts* eo)
   return false;
 }
 
-DNSRecord makeOpt(const uint16_t udpsize, const uint16_t extRCode, const uint16_t extFlags)
-{
-  EDNS0Record stuff;
-  stuff.extRCode=0;
-  stuff.version=0;
-  stuff.extFlags=htons(extFlags);
-  DNSRecord dr;
-  static_assert(sizeof(EDNS0Record) == sizeof(dr.d_ttl), "sizeof(EDNS0Record) must match sizeof(DNSRecord.d_ttl)");
-  memcpy(&dr.d_ttl, &stuff, sizeof(stuff));
-  dr.d_ttl=ntohl(dr.d_ttl);
-  dr.d_name=g_rootdnsname;
-  dr.d_type = QType::OPT;
-  dr.d_class=udpsize;
-  dr.d_place=DNSResourceRecord::ADDITIONAL;
-  dr.d_content = std::make_shared<OPTRecordContent>();
-  // if we ever do options, I think we stuff them into OPTRecordContent::data
-  return dr;
-}
-
 void reportBasicTypes()
 {
   ARecordContent::report();
@@ -988,11 +977,13 @@ void reportAllTypes()
 
 ComboAddress getAddr(const DNSRecord& dr, uint16_t defport)
 {
-  if(auto addr=getRR<ARecordContent>(dr)) {
-    return addr->getCA(defport);
+  if (auto a = getRR<ARecordContent>(dr)) {
+    return a->getCA(defport);
+  }
+  else if (auto aaaa = getRR<AAAARecordContent>(dr)) {
+    return aaaa->getCA(defport);
   }
-  else
-    return getRR<AAAARecordContent>(dr)->getCA(defport);
+  throw std::invalid_argument("not an A or AAAA record");
 }
 
 /**
index 3dc3efae42e926d967f459df63811d5ec3553877..f674dd4da9696fb6b206a7f4b6fc0e8e3995df79 100644 (file)
@@ -26,6 +26,7 @@
 
 #include "dnsparser.hh"
 #include "dnswriter.hh"
+#include "lock.hh"
 #include "rcpgenerator.hh"
 #include <set>
 #include <bitset>
@@ -40,8 +41,9 @@
   static std::shared_ptr<DNSRecordContent> make(const DNSRecord &dr, PacketReader& pr);          \
   static std::shared_ptr<DNSRecordContent> make(const string& zonedata);                         \
   string getZoneRepresentation(bool noDot=false) const override;                                 \
-  void toPacket(DNSPacketWriter& pw) override;                                                   \
-  uint16_t getType() const override { return QType::RNAME; }                                   \
+  void toPacket(DNSPacketWriter& pw) const override;                                             \
+  uint16_t getType() const override { return QType::RNAME; }                                     \
+  template<class Convertor> void xfrPacket(Convertor& conv, bool noDot=false) const;             \
   template<class Convertor> void xfrPacket(Convertor& conv, bool noDot=false);
 
 class NAPTRRecordContent : public DNSRecordContent
@@ -174,7 +176,7 @@ class TSIGRecordContent : public DNSRecordContent
 {
 public:
   includeboilerplate(TSIG)
-  TSIGRecordContent() {}
+  TSIGRecordContent() = default;
 
   uint16_t d_origID{0};
   uint16_t d_fudge{0};
@@ -272,6 +274,11 @@ class ALIASRecordContent : public DNSRecordContent
 public:
   includeboilerplate(ALIAS)
 
+  [[nodiscard]] const DNSName& getContent() const
+  {
+    return d_content;
+  }
+private:
   DNSName d_content;
 };
 #endif
@@ -327,9 +334,9 @@ private:
 class OPTRecordContent : public DNSRecordContent
 {
 public:
-  OPTRecordContent(){}
+  OPTRecordContent() = default;
   includeboilerplate(OPT)
-  void getData(vector<pair<uint16_t, string> > &opts);
+  void getData(vector<pair<uint16_t, string> > &opts) const;
 private:
   string d_data;
 };
@@ -360,7 +367,6 @@ public:
   DNSKEYRecordContent();
   includeboilerplate(DNSKEY)
   uint16_t getTag() const;
-  uint16_t getTag();
 
   uint16_t d_flags{0};
   uint8_t d_protocol{0};
@@ -368,7 +374,7 @@ public:
   string d_key;
   bool operator<(const DNSKEYRecordContent& rhs) const
   {
-    return std::tie(d_flags, d_protocol, d_algorithm, d_key) < 
+    return std::tie(d_flags, d_protocol, d_algorithm, d_key) <
       std::tie(rhs.d_flags, rhs.d_protocol, rhs.d_algorithm, rhs.d_key);
   }
 };
@@ -527,32 +533,35 @@ class SVCBBaseRecordContent : public DNSRecordContent
     bool hasParam(const SvcParam::SvcParamKey &key) const;
     // Get the parameter with |key|, will throw out_of_range if param isn't there
     SvcParam getParam(const SvcParam::SvcParamKey &key) const;
+    virtual std::shared_ptr<SVCBBaseRecordContent> clone() const = 0;
 
   protected:
-    uint16_t d_priority;
+    std::set<SvcParam> d_params;
     DNSName d_target;
-    set<SvcParam> d_params;
+    uint16_t d_priority;
 
     // Get the iterator to parameter with |key|, return value can be d_params::end
-    set<SvcParam>::const_iterator getParamIt(const SvcParam::SvcParamKey &key) const;
+  std::set<SvcParam>::const_iterator getParamIt(const SvcParam::SvcParamKey &key) const;
 };
 
 class SVCBRecordContent : public SVCBBaseRecordContent
 {
 public:
   includeboilerplate(SVCB)
+  std::shared_ptr<SVCBBaseRecordContent> clone() const override;
 };
 
 class HTTPSRecordContent : public SVCBBaseRecordContent
 {
 public:
   includeboilerplate(HTTPS)
+  std::shared_ptr<SVCBBaseRecordContent> clone() const override;
 };
 
 class RRSIGRecordContent : public DNSRecordContent
 {
 public:
-  RRSIGRecordContent(); 
+  RRSIGRecordContent();
   includeboilerplate(RRSIG)
 
   uint16_t d_type{0};
@@ -564,7 +573,7 @@ public:
 };
 
 //namespace {
-  struct soatimes 
+  struct soatimes
   {
     uint32_t serial;
     uint32_t refresh;
@@ -629,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
@@ -664,7 +674,7 @@ public:
   }
 
   void fromPacket(PacketReader& pr);
-  void toPacket(DNSPacketWriter& pw);
+  void toPacket(DNSPacketWriter& pw) const;
   std::string getZoneRepresentation() const;
 
   static constexpr size_t const nbTypes = 65536;
@@ -691,15 +701,14 @@ 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);
   static std::shared_ptr<DNSRecordContent> make(const string& content);
   string getZoneRepresentation(bool noDot=false) const override;
-  void toPacket(DNSPacketWriter& pw) override;
+  void toPacket(DNSPacketWriter& pw) const override;
   uint16_t getType() const override
   {
     return QType::NSEC;
@@ -729,15 +738,14 @@ 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);
   static std::shared_ptr<DNSRecordContent> make(const string& content);
   string getZoneRepresentation(bool noDot=false) const override;
-  void toPacket(DNSPacketWriter& pw) override;
+  void toPacket(DNSPacketWriter& pw) const override;
 
   uint8_t d_algorithm{0}, d_flags{0};
   uint16_t d_iterations{0};
@@ -776,15 +784,14 @@ 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);
   static std::shared_ptr<DNSRecordContent> make(const string& content);
   string getZoneRepresentation(bool noDot=false) const override;
-  void toPacket(DNSPacketWriter& pw) override;
+  void toPacket(DNSPacketWriter& pw) const override;
 
   uint16_t getType() const override
   {
@@ -805,15 +812,14 @@ 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);
   static std::shared_ptr<DNSRecordContent> make(const string& content);
   string getZoneRepresentation(bool noDot=false) const override;
-  void toPacket(DNSPacketWriter& pw) override;
+  void toPacket(DNSPacketWriter& pw) const override;
 
   uint16_t getType() const override
   {
@@ -830,15 +836,14 @@ 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);
   static std::shared_ptr<DNSRecordContent> make(const string& content);
   string getZoneRepresentation(bool noDot=false) const override;
-  void toPacket(DNSPacketWriter& pw) override;
+  void toPacket(DNSPacketWriter& pw) const override;
 
   uint8_t d_version{0}, d_size{0}, d_horizpre{0}, d_vertpre{0};
   uint32_t d_latitude{0}, d_longitude{0}, d_altitude{0};
@@ -891,30 +896,30 @@ private:
   DNSName d_fqdn;
 };
 
-class EUI48RecordContent : public DNSRecordContent 
+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;
-  void toPacket(DNSPacketWriter& pw) override;
+  void toPacket(DNSPacketWriter& pw) const override;
   uint16_t getType() const override { return QType::EUI48; }
 private:
  // storage for the bytes
- uint8_t d_eui48[6]; 
+ uint8_t d_eui48[6];
 };
 
 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;
-  void toPacket(DNSPacketWriter& pw) override;
+  void toPacket(DNSPacketWriter& pw) const override;
   uint16_t getType() const override { return QType::EUI64; }
 private:
  // storage for the bytes
@@ -936,7 +941,7 @@ typedef struct s_APLRDataElement {
 class APLRecordContent : public DNSRecordContent
 {
 public:
-  APLRecordContent() {};
+  APLRecordContent() = default;
   includeboilerplate(APL)
 private:
   std::vector<APLRDataElement> aplrdata;
@@ -998,7 +1003,7 @@ std::shared_ptr<RNAME##RecordContent::DNSRecordContent> RNAME##RecordContent::ma
   return std::make_shared<RNAME##RecordContent>(zonedata);                                         \
 }                                                                                                  \
                                                                                                    \
-void RNAME##RecordContent::toPacket(DNSPacketWriter& pw)                                           \
+void RNAME##RecordContent::toPacket(DNSPacketWriter& pw) const                                     \
 {                                                                                                  \
   this->xfrPacket(pw);                                                                             \
 }                                                                                                  \
@@ -1031,17 +1036,23 @@ string RNAME##RecordContent::getZoneRepresentation(bool noDot) const
   RecordTextWriter rtw(ret, noDot);                                                                       \
   const_cast<RNAME##RecordContent*>(this)->xfrPacket(rtw);                                         \
   return ret;                                                                                      \
-}                                                                                                  
-                                                                                           
-
-#define boilerplate_conv(RNAME, CONV)                             \
-boilerplate(RNAME)                                                \
-template<class Convertor>                                         \
-void RNAME##RecordContent::xfrPacket(Convertor& conv, bool noDot) \
-{                                                                 \
-  CONV;                                                           \
+}
+
+
+#define boilerplate_conv(RNAME, CONV)                                   \
+boilerplate(RNAME)                                                      \
+template<class Convertor>                                               \
+void RNAME##RecordContent::xfrPacket(Convertor& conv, bool /* noDot */) \
+{                                                                       \
+  CONV;                                                                 \
+  if (conv.eof() == false) throw MOADNSException("When parsing " #RNAME " trailing data was not parsed: '" + conv.getRemaining() + "'"); \
+}                                                                       \
+template<class Convertor>                                               \
+void RNAME##RecordContent::xfrPacket(Convertor& conv, bool /* noDot */) const \
+{                                                                       \
+  CONV;                                                                 \
   if (conv.eof() == false) throw MOADNSException("When parsing " #RNAME " trailing data was not parsed: '" + conv.getRemaining() + "'"); \
-}                                                                 \
+}                                                                       \
 
 struct EDNSOpts
 {
@@ -1055,7 +1066,6 @@ struct EDNSOpts
 
 class MOADNSParser;
 bool getEDNSOpts(const MOADNSParser& mdp, EDNSOpts* eo);
-DNSRecord makeOpt(const uint16_t udpsize, const uint16_t extRCode, const uint16_t extFlags);
 void reportBasicTypes();
 void reportOtherTypes();
 void reportAllTypes();
index 0d4b8d10014f7bb6bb05ac8c33002057d289e5e4..36f0db835b4f25df50c4ca13992cdd2c9509ffef 100644 (file)
@@ -39,17 +39,17 @@ There is one central object, which has (when complete)
    Original answer
    Socket answer
 
-What to do with timeouts. We keep around at most 65536 outstanding answers. 
+What to do with timeouts. We keep around at most 65536 outstanding answers.
 */
 
-/* 
+/*
    mental_clock=0;
    for(;;) {
 
    do {
       read a packet
       send a packet
-    } while(time_of_last_packet_sent < mental_clock) 
+    } while(time_of_last_packet_sent < mental_clock)
     mental_clock=time_of_last_packet_sent;
 
     wait for a response packet for 0.1 seconds
@@ -87,7 +87,7 @@ using namespace ::boost::multi_index;
 
 StatBag S;
 bool g_quiet=true;
-int g_timeoutMsec=0; 
+int g_timeoutMsec=0;
 
 namespace po = boost::program_options;
 
@@ -106,9 +106,9 @@ static const struct timeval operator*(float fact, const struct timeval& rhs)
 
     ret.tv_usec=(unsigned int)(1000000*d);
     normalizeTV(ret);
-    
+
     cout<<"out complex: "<<ret.tv_sec<<" + "<<ret.tv_usec<<"\n";
-    
+
     return ret;
   }
 
@@ -160,7 +160,7 @@ public:
 
 private:
   std::deque<uint16_t> d_available;
-  
+
 } s_idmanager;
 
 struct AssignedIDTag{};
@@ -175,19 +175,19 @@ 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;
 };
 
 typedef multi_index_container<
-  QuestionData, 
+  QuestionData,
   indexed_by<
              ordered_unique<tag<QuestionTag>, BOOST_MULTI_INDEX_MEMBER(QuestionData, QuestionIdentifier, d_qi) > ,
              ordered_unique<tag<AssignedIDTag>,  BOOST_MULTI_INDEX_MEMBER(QuestionData, int, d_assignedID) >
             >
 > qids_t;
-                                         
+
 qids_t qids;
 bool g_throttled;
 
@@ -209,7 +209,7 @@ static void WeOrigSlowQueriesDelta(int& weOutstanding, int& origOutstanding, int
   for(qids_t::iterator i=qids.begin(); i!=qids.end(); ++i) {
     double dt=DiffTime(i->d_resentTime, now);
     if(dt < 2.0) {
-      if(i->d_newRcode == -1) 
+      if(i->d_newRcode == -1)
         weOutstanding++;
       if(i->d_origRcode == -1)
         origOutstanding++;
@@ -273,7 +273,7 @@ vector<uint32_t> flightTimes;
 static void accountFlightTime(qids_t::const_iterator iter)
 {
   if(flightTimes.empty())
-    flightTimes.resize(2050); 
+    flightTimes.resize(2050);
 
   struct timeval now;
   gettimeofday(&now, 0);
@@ -296,19 +296,23 @@ static uint64_t countLessThan(unsigned int msec)
 static void emitFlightTimes()
 {
   uint64_t totals = countLessThan(flightTimes.size());
+  if (totals == 0) {
+    // Avoid division by zero below
+    totals = 1;
+  }
   unsigned int limits[]={1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100, 200, 500, 1000, (unsigned int) flightTimes.size()};
   uint64_t sofar=0;
   cout.setf(std::ios::fixed);
   cout.precision(2);
   for(unsigned int i =0 ; i < sizeof(limits)/sizeof(limits[0]); ++i) {
     if(limits[i]!=flightTimes.size())
-      cout<<"Within "<<limits[i]<<" msec: ";
-    else 
-      cout<<"Beyond "<<limits[i]-2<<" msec: ";
+      cout<<"Within "<<limits[i]<<" ms: ";
+    else
+      cout<<"Beyond "<<limits[i]-2<<" ms: ";
     uint64_t here = countLessThan(limits[i]);
     cout<<100.0*here/totals<<"% ("<<100.0*(here-sofar)/totals<<"%)"<<endl;
     sofar=here;
-    
+
   }
 }
 
@@ -320,9 +324,9 @@ static void measureResultAndClean(qids_t::const_iterator iter)
   set<DNSRecord> canonicOrig, canonicNew;
   compactAnswerSet(qd.d_origAnswers, canonicOrig);
   compactAnswerSet(qd.d_newAnswers, canonicNew);
-        
+
   if(!g_quiet) {
-    cout<<qd.d_qi<<", orig rcode: "<<qd.d_origRcode<<", ours: "<<qd.d_newRcode;  
+    cout<<qd.d_qi<<", orig rcode: "<<qd.d_origRcode<<", ours: "<<qd.d_newRcode;
     cout<<", "<<canonicOrig.size()<< " vs " << canonicNew.size()<<", perfect: ";
   }
 
@@ -334,7 +338,7 @@ static void measureResultAndClean(qids_t::const_iterator iter)
   else {
     if(!g_quiet)
       cout<<"no\n";
-    
+
     if(qd.d_norecursionavailable)
       if(!g_quiet)
         cout<<"\t* original nameserver did not provide recursion for this question *"<<endl;
@@ -362,16 +366,16 @@ static void measureResultAndClean(qids_t::const_iterator iter)
     if(!g_quiet) {
       cout<<"orig: rcode="<<qd.d_origRcode<<"\n";
       for(set<DNSRecord>::const_iterator i=canonicOrig.begin(); i!=canonicOrig.end(); ++i)
-        cout<<"\t"<<i->d_name<<"\t"<<DNSRecordContent::NumberToType(i->d_type)<<"\t'"  << (i->d_content ? i->d_content->getZoneRepresentation() : "") <<"'\n";
+        cout<<"\t"<<i->d_name<<"\t"<<DNSRecordContent::NumberToType(i->d_type)<<"\t'"  << (i->getContent() ? i->getContent()->getZoneRepresentation() : "") <<"'\n";
       cout<<"new: rcode="<<qd.d_newRcode<<"\n";
       for(set<DNSRecord>::const_iterator i=canonicNew.begin(); i!=canonicNew.end(); ++i)
-        cout<<"\t"<<i->d_name<<"\t"<<DNSRecordContent::NumberToType(i->d_type)<<"\t'"  << (i->d_content ? i->d_content->getZoneRepresentation() : "") <<"'\n";
+        cout<<"\t"<<i->d_name<<"\t"<<DNSRecordContent::NumberToType(i->d_type)<<"\t'"  << (i->getContent() ? i->getContent()->getZoneRepresentation() : "") <<"'\n";
       cout<<"\n";
       cout<<"-\n";
 
     }
   }
-  
+
   int releaseID=qd.d_assignedID;
   qids.erase(iter); // qd invalid now
   s_idmanager.releaseID(releaseID);
@@ -383,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, remote)) {
+  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;
@@ -403,13 +408,13 @@ try
       qids_by_id_index_t& idindex=qids.get<AssignedIDTag>();
       qids_by_id_index_t::const_iterator found=idindex.find(ntohs(mdp.d_header.id));
       if(found == idindex.end()) {
-        if(!g_quiet)      
+        if(!g_quiet)
           cout<<"Received an answer ("<<mdp.d_qname<<") from reference nameserver with id "<<mdp.d_header.id<<" which we can't match to a question!"<<endl;
         s_weunmatched++;
         continue;
       }
 
-      QuestionData qd=*found;    // we have to make a copy because we reinsert below      
+      QuestionData qd=*found;    // we have to make a copy because we reinsert below
       qd.d_newAnswers=mdp.d_answers;
       qd.d_newRcode=mdp.d_header.rcode;
       idindex.replace(found, qd);
@@ -426,7 +431,7 @@ try
     {
       s_wednserrors++;
     }
-    catch(std::exception& e) 
+    catch(std::exception& e)
     {
       s_wednserrors++;
     }
@@ -475,7 +480,7 @@ static void printStats(uint64_t origWaitingFor=0, uint64_t weWaitingFor=0)
   cerr<<(datafmt % "Refer." % s_questions % weWaitingFor    % s_wenever    % s_weanswers   % 0 % s_wetimedout    % 0 % 0);
 
   cerr<<weWaitingFor<<" queries that could still come in on time, "<<qids.size()<<" outstanding"<<endl;
-  
+
   cerr<<"we late: "<<s_wetimedout<<", orig late: "<< s_origtimedout<<", "<<s_questions<<" questions sent, "<<s_origanswers
       <<" original answers, "<<s_perfect<<" perfect, "<<s_mostly<<" mostly correct"<<", "<<s_webetter<<" we better, "<<s_origbetter<<" orig better ("<<s_origbetterset.size()<<" diff)"<<endl;
   cerr<<"we never: "<<s_wenever<<", orig never: "<<s_orignever<<endl;
@@ -497,7 +502,7 @@ static void houseKeeping()
 
   int weWaitingFor, origWaitingFor, weSlow, origSlow;
   WeOrigSlowQueriesDelta(weWaitingFor, origWaitingFor, weSlow, origSlow);
-    
+
   if(!g_throttled) {
     if( weWaitingFor > 1000) {
       cerr<<"Too many questions ("<<weWaitingFor<<") outstanding, throttling"<<endl;
@@ -533,7 +538,7 @@ static void generateOptRR(const std::string& optRData, string& res)
   edns0.extRCode = 0;
   edns0.version = 0;
   edns0.extFlags = 0;
-  
+
   dh.d_type = htons(QType::OPT);
   dh.d_class = htons(1280);
   memcpy(&dh.d_ttl, &edns0, sizeof edns0);
@@ -572,10 +577,26 @@ static void addECSOption(char* packet, const size_t packetSize, uint16_t* len, c
   }
 }
 
+static bool checkIPTransparentUsability()
+{
+#ifdef IP_TRANSPARENT
+  try {
+    auto s = Socket(SSocket(AF_INET, SOCK_DGRAM, 0));
+    SSetsockopt(s.getHandle(), IPPROTO_IP , IP_TRANSPARENT, 1);
+    return true;
+  }
+  catch (const std::exception& e) {
+    cerr << "Error while checking whether IP_TRANSPARENT (required for '--source-from-pcap') is working properly: " << e.what() << endl;
+  }
+#endif /* IP_TRANSPARENT */
+  return false;
+}
+
+
 static bool g_rdSelector;
 static uint16_t g_pcapDnsPort;
 
-static bool sendPacketFromPR(PcapPacketReader& pr, const ComboAddress& remote, int stamp)
+static bool sendPacketFromPR(PcapPacketReader& pr, const ComboAddress& remote, int stamp, [[maybe_unused]] bool usePCAPSourceIP)
 {
   bool sent=false;
   if (pr.d_len <= sizeof(dnsheader)) {
@@ -591,7 +612,7 @@ static bool sendPacketFromPR(PcapPacketReader& pr, const ComboAddress& remote, i
 
   QuestionData qd;
   try {
-    // yes, we send out ALWAYS. Even if we don't do anything with it later, 
+    // yes, we send out ALWAYS. Even if we don't do anything with it later,
     if(!dh->qr) { // this is to stress out the reference server with all the pain
       s_questions++;
       qd.d_assignedID = s_idmanager.getID();
@@ -609,8 +630,19 @@ static bool sendPacketFromPR(PcapPacketReader& pr, const ComboAddress& remote, i
         addECSOption((char*)pr.d_payload, 1500, &dlen, pr.getSource(), stamp);
         pr.d_len=dlen;
       }
-
-      s_socket->sendTo((const char*)pr.d_payload, dlen, remote);
+#ifdef IP_TRANSPARENT
+      if (usePCAPSourceIP) {
+        auto s = Socket(SSocket(AF_INET, SOCK_DGRAM, 0));
+        SSetsockopt(s.getHandle(), IPPROTO_IP , IP_TRANSPARENT, 1);
+        SBind(s.getHandle(), pr.getSource());
+        sendto(s.getHandle(), reinterpret_cast<const char*>(pr.d_payload), dlen, 0, reinterpret_cast<const struct sockaddr*>(&remote), remote.getSocklen());
+      }
+      else {
+#endif /* IP_TRANSPARENT */
+        s_socket->sendTo((const char*)pr.d_payload, dlen, remote);
+#ifdef IP_TRANSPARENT
+      }
+#endif /* IP_TRANSPARENT */
       sent=true;
       dh->id=tmp;
     }
@@ -633,12 +665,12 @@ static bool sendPacketFromPR(PcapPacketReader& pr, const ComboAddress& remote, i
     }
     else {
       s_origanswers++;
-      qids_t::const_iterator iter=qids.find(qi);      
+      qids_t::const_iterator iter=qids.find(qi);
       if(iter != qids.end()) {
         QuestionData eqd=*iter;
         eqd.d_origAnswers=mdp.d_answers;
         eqd.d_origRcode=mdp.d_header.rcode;
-        
+
         if(!dh->ra) {
           s_norecursionavailable++;
           eqd.d_norecursionavailable=true;
@@ -648,7 +680,7 @@ static bool sendPacketFromPR(PcapPacketReader& pr, const ComboAddress& remote, i
         if(eqd.d_newRcode!=-1) {
           measureResultAndClean(iter);
         }
-        
+
         return sent;
       }
       else {
@@ -658,20 +690,20 @@ static bool sendPacketFromPR(PcapPacketReader& pr, const ComboAddress& remote, i
       }
     }
   }
-  catch(const MOADNSException &mde)
-  {
-    if(!g_quiet)
+  catch (const MOADNSException& mde) {
+    if (!g_quiet) {
       cerr<<"Error parsing packet: "<<mde.what()<<endl;
+    }
     s_idmanager.releaseID(qd.d_assignedID);  // not added to qids for cleanup
     s_origdnserrors++;
   }
-  catch(std::exception &e)
-  {
-    if(!g_quiet)
+  catch (std::exception& e) {
+    if (!g_quiet) {
       cerr<<"Error parsing packet: "<<e.what()<<endl;
+    }
 
     s_idmanager.releaseID(qd.d_assignedID);  // not added to qids for cleanup
-    s_origdnserrors++;    
+    s_origdnserrors++;
   }
 
   return sent;
@@ -698,6 +730,7 @@ try
     ("ecs-stamp", "Add original IP address to ECS in replay")
     ("ecs-mask", po::value<uint16_t>(), "Replace first octet of src IP address with this value in ECS")
     ("source-ip", po::value<string>()->default_value(""), "IP to send the replayed packet from")
+    ("source-from-pcap", po::value<bool>()->default_value(false), "Send the replayed packets from the source IP present in the PCAP (requires IP_TRANSPARENT support)")
     ("source-port", po::value<uint16_t>()->default_value(0), "Port to send the replayed packet from");
 
   po::options_description alloptions;
@@ -746,12 +779,13 @@ try
   g_timeoutMsec=g_vm["timeout-msec"].as<uint32_t>();
 
   PcapPacketReader pr(g_vm["pcap-source"].as<string>());
-  s_socket= make_unique<Socket>(AF_INET, SOCK_DGRAM);
+  s_socket = make_unique<Socket>(AF_INET, SOCK_DGRAM);
 
   s_socket->setNonBlocking();
 
-  if(g_vm.count("source-ip") && !g_vm["source-ip"].as<string>().empty())
+  if(g_vm.count("source-ip") && !g_vm["source-ip"].as<string>().empty()) {
     s_socket->bind(ComboAddress(g_vm["source-ip"].as<string>(), g_vm["source-port"].as<uint16_t>()));
+  }
 
   try {
     setSocketReceiveBuffer(s_socket->getHandle(), 2000000);
@@ -766,7 +800,7 @@ try
     cerr<<e.what()<<endl;
   }
 
-  ComboAddress remote(g_vm["target-ip"].as<string>(), 
+  ComboAddress remote(g_vm["target-ip"].as<string>(),
                     g_vm["target-port"].as<uint16_t>());
 
  int stamp = -1;
@@ -775,6 +809,12 @@ try
 
   cerr<<"Replaying packets to: '"<<g_vm["target-ip"].as<string>()<<"', port "<<g_vm["target-port"].as<uint16_t>()<<endl;
 
+  bool usePCAPSourceIP = g_vm["source-from-pcap"].as<bool>();
+  if (usePCAPSourceIP && !checkIPTransparentUsability()) {
+    cerr << "--source-from-pcap requested but IP_TRANSPARENT support is unavailable or not working properly" << endl;
+    exit(EXIT_FAILURE);
+  }
+
   unsigned int once=0;
   struct timeval mental_time;
   mental_time.tv_sec=0; mental_time.tv_usec=0;
@@ -788,12 +828,12 @@ try
       cerr<<"Interrupted from terminal"<<endl;
       break;
     }
-    if(!((once++)%100)) 
+    if(!((once++)%100))
       houseKeeping();
-    
+
     struct timeval packet_ts;
-    packet_ts.tv_sec = 0; 
-    packet_ts.tv_usec = 0; 
+    packet_ts.tv_sec = 0;
+    packet_ts.tv_usec = 0;
 
     while(packet_ts < mental_time) {
       if(!first && !pr.getUDPPacket()) // otherwise we miss the first packet
@@ -803,10 +843,11 @@ try
       packet_ts.tv_sec = pr.d_pheader.ts.tv_sec;
       packet_ts.tv_usec = pr.d_pheader.ts.tv_usec;
 
-      if(sendPacketFromPR(pr, remote, stamp))
+      if (sendPacketFromPR(pr, remote, stamp, usePCAPSourceIP)) {
         count++;
-    } 
-    if(packetLimit && count >= packetLimit) 
+      }
+    }
+    if(packetLimit && count >= packetLimit)
       break;
 
     mental_time=packet_ts;
@@ -829,4 +870,3 @@ catch(std::exception& e)
 {
   cerr<<"Fatal: "<<e.what()<<endl;
 }
-
index 4b16569946d29b2b3c15775c1d407c3cb257e73e..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
 
@@ -49,7 +49,7 @@ namespace po = boost::program_options;
 po::variables_map g_vm;
 
 ArgvMap& arg()
-{      
+{
   static ArgvMap theArg;
   return theArg;
 }
@@ -106,7 +106,7 @@ struct LiveCounts
   }
 };
 
-static void visitor(const StatNode* node, const StatNode::Stat& selfstat, const StatNode::Stat& childstat)
+static void visitor(const StatNode* node, const StatNode::Stat& /* selfstat */, const StatNode::Stat& childstat)
 {
   // 20% servfails, >100 children, on average less than 2 copies of a query
   // >100 different subqueries
@@ -139,32 +139,33 @@ 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")
     ("verbose,v", "be verbose");
-    
+
   hidden.add_options()
     ("files", po::value<vector<string> >(), "files");
 
-  alloptions.add(desc).add(hidden); 
+  alloptions.add(desc).add(hidden);
 
   po::positional_options_description p;
   p.add("files", -1);
 
   po::store(po::command_line_parser(argc, argv).options(alloptions).positional(p).run(), g_vm);
   po::notify(g_vm);
+
   vector<string> files;
-  if(g_vm.count("files")) 
-    files = g_vm["files"].as<vector<string> >(); 
+  if(g_vm.count("files"))
+    files = g_vm["files"].as<vector<string> >();
 
   if(g_vm.count("version")) {
     cerr<<"dnsscope "<<VERSION<<endl;
@@ -199,24 +200,35 @@ 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;
-  
+
   time_t lowestTime=0, highestTime=0;
   time_t lastsec=0;
   LiveCounts lastcounts;
   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;
           }
-          
-         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++;
-            }
+        }
+        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(pr.d_ip->ip_v == 4) 
-           ++ipv4DNSPackets;
-         else
-           ++ipv6DNSPackets;
-        
-         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);
+        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;
             }
-            lastsec = pr.d_pheader.ts.tv_sec;
-            lastcounts = lc;
+          }
+          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(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, 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(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]++;
+
+            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;
+          }
+        }
       }
-      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";
   }
 
   /*
@@ -394,7 +419,7 @@ try
     cout<<a.first<<": qcount="<<a.second.d_qcount<<", answercount="<<a.second.d_answercount<<endl;
   }
   */
-  
+
   cout<<"Timespan: "<<(highestTime-lowestTime)/3600.0<<" hours"<<endl;
 
   cout<<nonDNSIP<<" non-DNS UDP, "<<dnserrors<<" dns decoding errors, "<<parsefail<<" packets failed to parse"<<endl;
@@ -437,7 +462,7 @@ try
     totpairs+=i->second;
     tottime+=i->first*i->second;
   }
-  
+
   typedef map<uint32_t, bool> done_t;
   done_t done;
   for(auto a : {50, 100, 200, 300, 400, 800, 1000, 2000, 4000, 8000, 32000, 64000, 256000, 1024000, 2048000})
@@ -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);
@@ -466,7 +491,7 @@ try
   }
 #endif
 
-  
+
   sum=0;
   double lastperc=0, perc=0;
   uint64_t lastsum=0;
@@ -478,10 +503,10 @@ try
 
         perc=sum*100.0/totpairs;
         if(j->first < 1024)
-          cout<< perc <<"% of questions answered within " << j->first << " usec (";
+          cout<< perc <<"% of questions answered within " << j->first << " us (";
         else
-          cout<< perc <<"% of questions answered within " << j->first/1000.0 << " msec (";
-        
+          cout<< perc <<"% of questions answered within " << j->first/1000.0 << " ms (";
+
         cout<<perc-lastperc<<"%)\n";
         lastperc=sum*100.0/totpairs;
         lastsum=sum;
@@ -494,27 +519,27 @@ try
     if(!j->second) {
       perc=sum*100.0/totpairs;
       if(j->first < 1024)
-        cout<< perc <<"% of questions answered within " << j->first << " usec (";
+        cout<< perc <<"% of questions answered within " << j->first << " us (";
       else
-        cout<< perc <<"% of questions answered within " << j->first/1000.0 << " msec (";
-      
+        cout<< perc <<"% of questions answered within " << j->first/1000.0 << " ms (";
+
       cout<<perc-lastperc<<"%)\n";
       lastperc=sum*100.0/totpairs;
       lastsum=sum;
       break;
     }
   }
-  
+
   cout<< (totpairs-lastsum)<<" responses ("<<((totpairs-lastsum)*100.0/answers) <<"%) older than "<< (done.rbegin()->first/1000000.0) <<" seconds"<<endl;
   if(totpairs)
-    cout<<"Average non-late response time: "<<tottime/totpairs<<" usec"<<endl;
+    cout<<"Average non-late response time: "<<tottime/totpairs<<" us"<<endl;
 
   if(!g_vm["load-stats"].as<string>().empty()) {
     ofstream load(g_vm["load-stats"].as<string>().c_str());
-    if(!load) 
+    if(!load)
       throw runtime_error("Error writing load statistics to "+g_vm["load-stats"].as<string>());
     for(pcounts_t::value_type& val :  pcounts) {
-      load<<val.first<<'\t'<<val.second.questions<<'\t'<<val.second.answers<<'\t'<<val.second.outstanding<<'\n';  
+      load<<val.first<<'\t'<<val.second.questions<<'\t'<<val.second.answers<<'\t'<<val.second.outstanding<<'\n';
     }
   }
 
@@ -528,7 +553,7 @@ try
   vector<ComboAddress> diff;
   set_difference(requestors.begin(), requestors.end(), recipients.begin(), recipients.end(), back_inserter(diff), ComboAddress::addressOnlyLessThan());
   cout<<"Saw "<<diff.size()<<" unique remotes asking questions, but not getting RA answers"<<endl;
-  
+
   ofstream ignored("ignored");
   for(const ComboAddress& rem :  diff) {
     ignored<<rem.toString()<<'\n';
index 92f80da46977c8fdf7ebe8d7be9c9f0c90039f6e..9024b04502637ef0dbe9af619a332b836ffa1b7f 100644 (file)
@@ -22,6 +22,7 @@
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
+#include <functional>
 #include "dnsparser.hh"
 #include "sstuff.hh"
 #include "misc.hh"
@@ -44,6 +45,7 @@
 #ifdef HAVE_P11KIT1
 #include "pkcs11signers.hh"
 #endif
+#include "gss_context.hh"
 #include "misc.hh"
 
 using namespace boost::assign;
@@ -62,11 +64,11 @@ std::unique_ptr<DNSCryptoKeyEngine> DNSCryptoKeyEngine::makeFromISCFile(DNSKEYRe
   fp.reset();
 
   auto dke = makeFromISCString(drc, isc);
-  vector<string> checkKeyErrors;
+  auto checkKeyErrors = std::vector<std::string>{};
 
-  if(!dke->checkKey(&checkKeyErrors)) {
+  if(!dke->checkKey(checkKeyErrors)) {
     string reason;
-    if(checkKeyErrors.size()) {
+    if(!checkKeyErrors.empty()) {
       reason = " ("+boost::algorithm::join(checkKeyErrors, ", ")+")";
     }
     throw runtime_error("Invalid DNS Private Key in file '"+string(fname)+"'"+reason);
@@ -168,10 +170,17 @@ std::unique_ptr<DNSCryptoKeyEngine> DNSCryptoKeyEngine::makeFromISCString(DNSKEY
   return dpk;
 }
 
-std::unique_ptr<DNSCryptoKeyEngine> DNSCryptoKeyEngine::makeFromPEMFile(DNSKEYRecordContent& drc, const std::string& filename, std::FILE& fp, const uint8_t algorithm)
+std::unique_ptr<DNSCryptoKeyEngine> DNSCryptoKeyEngine::makeFromPEMFile(DNSKEYRecordContent& drc, const uint8_t algorithm, std::FILE& inputFile, const std::string& filename)
 {
   auto maker = DNSCryptoKeyEngine::make(algorithm);
-  maker->createFromPEMFile(drc, filename, fp);
+  maker->createFromPEMFile(drc, inputFile, filename);
+  return maker;
+}
+
+std::unique_ptr<DNSCryptoKeyEngine> DNSCryptoKeyEngine::makeFromPEMString(DNSKEYRecordContent& drc, uint8_t algorithm, const std::string& contents)
+{
+  auto maker = DNSCryptoKeyEngine::make(algorithm);
+  maker->createFromPEMString(drc, contents);
   return maker;
 }
 
@@ -179,14 +188,18 @@ std::string DNSCryptoKeyEngine::convertToISC() const
 {
   storvector_t storvector = this->convertToISCVector();
   ostringstream ret;
-  ret<<"Private-key-format: v1.2\n";
-  for(const storvector_t::value_type& value :  storvector) {
+  ret << "Private-key-format: v1.2\n";
+  for (const storvector_t::value_type& value : storvector) {
+    // clang-format off
     if(value.first != "Algorithm" && value.first != "PIN" &&
        value.first != "Slot" && value.first != "Engine" &&
-       value.first != "Label" && value.first != "PubLabel")
-      ret<<value.first<<": "<<Base64Encode(value.second)<<"\n";
-    else
-      ret<<value.first<<": "<<value.second<<"\n";
+       value.first != "Label" && value.first != "PubLabel") {
+      ret << value.first << ": " << Base64Encode(value.second) << "\n";
+    }
+    else {
+      ret << value.first << ": " << value.second << "\n";
+    }
+    // clang-format on
   }
   return ret.str();
 }
@@ -194,13 +207,13 @@ std::string DNSCryptoKeyEngine::convertToISC() const
 std::unique_ptr<DNSCryptoKeyEngine> DNSCryptoKeyEngine::make(unsigned int algo)
 {
   const makers_t& makers = getMakers();
-  makers_t::const_iterator iter = makers.find(algo);
+
+  auto iter = makers.find(algo);
   if (iter != makers.cend()) {
     return (iter->second)(algo);
   }
-  else {
-    throw runtime_error("Request to create key object for unknown algorithm number " + std::to_string(algo));
-  }
+
+  throw runtime_error("Request to create key object for unknown algorithm number " + std::to_string(algo));
 }
 
 /**
@@ -218,13 +231,38 @@ 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);
-  if(getMakers().count(algo) && fallback) {
+  if (getMakers().count(algo) != 0 && fallback) {
     return;
   }
-  getMakers()[algo]=maker;
+  getMakers()[algo] = maker;
 }
 
 bool DNSCryptoKeyEngine::testAll()
@@ -278,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);
@@ -287,7 +431,7 @@ void DNSCryptoKeyEngine::testMakers(unsigned int algo, maker_t* creator, maker_t
   cout<<"Testing algorithm "<<algo<<"("<<DNSSECKeeper::algorithm2name(algo)<<"): '"<<dckeCreate->getName()<<"' ->'"<<dckeSign->getName()<<"' -> '"<<dckeVerify->getName()<<"' ";
   unsigned int bits;
   if(algo <= 10)
-    bits=1024;
+    bits=2048;
   else if(algo == DNSSECKeeper::ECCGOST || algo == DNSSECKeeper::ECDSA256 || algo == DNSSECKeeper::ED25519)
     bits = 256;
   else if(algo == DNSSECKeeper::ECDSA384)
@@ -303,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");
@@ -362,7 +476,7 @@ void DNSCryptoKeyEngine::testMakers(unsigned int algo, maker_t* creator, maker_t
 
   if(verified) {
     udiffVerify = dt.udiff() / 100;
-    cout<<"Signature & verify ok, create "<<udiffCreate<<"usec, signature "<<udiffSign<<"usec, verify "<<udiffVerify<<"usec"<<endl;
+    cout<<"Signature & verify ok, create "<<udiffCreate<<"us, signature "<<udiffSign<<"us, verify "<<udiffVerify<<"us"<<endl;
   }
   else {
     throw runtime_error("Verification of creator "+dckeCreate->getName()+" with signer "+dckeSign->getName()+" and verifier "+dckeVerify->getName()+" failed");
@@ -401,7 +515,7 @@ string getMessageForRRSET(const DNSName& qname, const RRSIGRecordContent& rrc, c
   // zonemd: digest = hash( RR(1) | RR(2) | RR(3) | ... ), so skip RRSIG_RDATA
 
   if (includeRRSIG_RDATA) {
-    toHash.append(const_cast<RRSIGRecordContent&>(rrc).serialize(g_rootdnsname, true, true));
+    toHash.append(rrc.serialize(g_rootdnsname, true, true));
     toHash.resize(toHash.size() - rrc.d_signature.length()); // chop off the end, don't sign the signature!
   }
   string nameToHash(qname.toDNSStringLC());
@@ -412,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
@@ -422,7 +537,7 @@ string getMessageForRRSET(const DNSName& qname, const RRSIGRecordContent& rrc, c
     }
   }
 
-  for(const shared_ptr<DNSRecordContent>& add : signRecords) {
+  for (const shared_ptr<const DNSRecordContent>& add : signRecords) {
     toHash.append(nameToHash);
     uint16_t tmp=htons(rrc.d_type);
     toHash.append((char*)&tmp, 2);
@@ -440,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();
 }
 
@@ -479,7 +609,7 @@ DSRecordContent makeDSFromDNSKey(const DNSName& qname, const DNSKEYRecordContent
 {
   string toHash;
   toHash.assign(qname.toDNSStringLC());
-  toHash.append(const_cast<DNSKEYRecordContent&>(drc).serialize(DNSName(), true, true));
+  toHash.append(drc.serialize(DNSName(), true, true));
 
   DSRecordContent dsrc;
   try {
@@ -488,7 +618,7 @@ DSRecordContent makeDSFromDNSKey(const DNSName& qname, const DNSKEYRecordContent
     dsrc.d_digest = dpk->hash(toHash);
   }
   catch(const std::exception& e) {
-    throw std::runtime_error("Asked to create (C)DS record of unknown digest type " + std::to_string(digest));
+    throw std::runtime_error("Asked to create (C)DS record of unknown digest type " + std::to_string(digest) + ": " + e.what());
   }
 
   dsrc.d_algorithm = drc.d_algorithm;
@@ -514,6 +644,7 @@ static DNSKEYRecordContent makeDNSKEYFromDNSCryptoKeyEngine(const std::shared_pt
 
 uint32_t getStartOfWeek()
 {
+  // coverity[store_truncates_time_t]
   uint32_t now = time(nullptr);
   now -= (now % (7*86400));
   return now;
@@ -589,9 +720,14 @@ void decrementHash(std::string& raw) // I wonder if this is correct, cmouse? ;-)
   }
 }
 
-DNSKEYRecordContent DNSSECPrivateKey::getDNSKEY() const
+const DNSKEYRecordContent& DNSSECPrivateKey::getDNSKEY() const
 {
-  return makeDNSKEYFromDNSCryptoKeyEngine(getKey(), d_algorithm, d_flags);
+  return d_dnskey;
+}
+
+void DNSSECPrivateKey::computeDNSKEY()
+{
+  d_dnskey = makeDNSKEYFromDNSCryptoKeyEngine(getKey(), d_algorithm, d_flags);
 }
 
 static string calculateHMAC(const std::string& key, const std::string& text, TSIGHashEnum hasher) {
@@ -693,7 +829,9 @@ void addTSIG(DNSPacketWriter& pw, TSIGRecordContent& trc, const DNSName& tsigkey
   string toSign = makeTSIGPayload(tsigprevious, reinterpret_cast<const char*>(pw.getContent().data()), pw.getContent().size(), tsigkeyname, trc, timersonly);
 
   if (algo == TSIG_GSS) {
-    throw PDNSException(string("Unsupported TSIG GSS algorithm ") + trc.d_algoName.toLogString());
+    if (!gss_add_signature(tsigkeyname, toSign, trc.d_mac)) {
+      throw PDNSException(string("Could not add TSIG signature with algorithm 'gss-tsig' and key name '")+tsigkeyname.toLogString()+string("'"));
+    }
   } else {
     trc.d_mac = calculateHMAC(tsigsecret, toSign, algo);
     //  trc.d_mac[0]++; // sabotage
@@ -728,7 +866,10 @@ bool validateTSIG(const std::string& packet, size_t sigPos, const TSIGTriplet& t
   tsigMsg = makeTSIGMessageFromTSIGPacket(packet, sigPos, tt.name, trc, previousMAC, timersOnly, dnsHeaderOffset);
 
   if (algo == TSIG_GSS) {
-    throw std::runtime_error("Unsupported TSIG GSS algorithm " + trc.d_algoName.toLogString());
+    GssContext gssctx(tt.name);
+    if (!gss_verify_signature(tt.name, tsigMsg, theirMAC)) {
+      throw std::runtime_error("Signature with TSIG key '"+tt.name.toLogString()+"' failed to validate");
+    }
   } else {
     string ourMac = calculateHMAC(tt.secret, tsigMsg, algo);
 
index 4b9e5f56c709020f0419beb83625e178c3cfbd89..617a9c7df03fc97be6e785d6299a8ce71cf18ed1 100644 (file)
@@ -24,6 +24,7 @@
 
 #include <string>
 #include <vector>
+#include <optional>
 #include <map>
 #include "misc.hh"
 
@@ -35,85 +36,154 @@ class DNSCryptoKeyEngine
 {
   public:
     explicit DNSCryptoKeyEngine(unsigned int algorithm) : d_algorithm(algorithm) {}
-    virtual ~DNSCryptoKeyEngine() {};
-    virtual string getName() const = 0;
+    virtual ~DNSCryptoKeyEngine() = default;
+    [[nodiscard]] virtual string getName() const = 0;
 
-    typedef std::map<std::string, std::string> stormap_t;
-    typedef std::vector<std::pair<std::string, std::string > > storvector_t;
+    using stormap_t = std::map<std::string, std::string>;
+    using storvector_t = std::vector<std::pair<std::string, std::string>>;
     virtual void create(unsigned int bits)=0;
-    virtual void createFromPEMFile(DNSKEYRecordContent& drc, const std::string& filename, std::FILE& fp)
+
+    virtual void createFromPEMFile(DNSKEYRecordContent& /* drc */, std::FILE& /* inputFile */, const std::optional<std::reference_wrapper<const std::string>> filename = std::nullopt)
+    {
+      if (filename.has_value()) {
+        throw std::runtime_error("Can't create key from PEM file `" + filename->get() + "`");
+      }
+
+      throw std::runtime_error("Can't create key from PEM contents");
+    }
+
+    /**
+     * \brief Creates a key engine from a PEM string.
+     *
+     * Receives PEM contents and creates a key engine.
+     *
+     * \param[in] drc Key record contents to be populated.
+     *
+     * \param[in] contents The PEM string contents.
+     *
+     * \return A key engine populated with the contents of the PEM string.
+     */
+    void createFromPEMString(DNSKEYRecordContent& drc, const std::string& contents)
     {
-      throw std::runtime_error("Can't create key from PEM file");
+      // NOLINTNEXTLINE(*-cast): POSIX APIs.
+      unique_ptr<std::FILE, decltype(&std::fclose)> inputFile{fmemopen(const_cast<char*>(contents.data()), contents.length(), "r"), &std::fclose};
+      createFromPEMFile(drc, *inputFile);
     }
-    virtual storvector_t convertToISCVector() const =0;
-    std::string convertToISC() const ;
-    virtual void convertToPEM(std::FILE& fp) const
+
+    [[nodiscard]] virtual storvector_t convertToISCVector() const =0;
+    [[nodiscard]] std::string convertToISC() const ;
+
+    virtual void convertToPEMFile(std::FILE& /* outputFile */) const
     {
       throw std::runtime_error(getName() + ": Conversion to PEM not supported");
     };
-    virtual std::string sign(const std::string& msg) const =0;
-    virtual std::string hash(const std::string& msg) const
+
+    /**
+     * \brief Converts the key into a PEM string.
+     *
+     * \return A string containing the key's PEM contents.
+     */
+    [[nodiscard]] auto convertToPEMString() const -> std::string
+    {
+      const size_t buflen = 4096;
+
+      std::string output{};
+      output.resize(buflen);
+      unique_ptr<std::FILE, decltype(&std::fclose)> outputFile{fmemopen(output.data(), output.length() - 1, "w"), &std::fclose};
+      convertToPEMFile(*outputFile);
+      std::fflush(outputFile.get());
+      output.resize(std::ftell(outputFile.get()));
+
+      return output;
+    };
+
+    [[nodiscard]] virtual std::string sign(const std::string& msg) const =0;
+
+    [[nodiscard]] virtual std::string hash(const std::string& msg) const
     {
        throw std::runtime_error("hash() function not implemented");
        return msg;
     }
-    virtual bool verify(const std::string& msg, const std::string& signature) const =0;
 
-    virtual std::string getPubKeyHash()const =0;
-    virtual std::string getPublicKeyString()const =0;
-    virtual int getBits() const =0;
-    virtual unsigned int getAlgorithm() const
+    [[nodiscard]] virtual bool verify(const std::string& msg, const std::string& signature) const =0;
+
+    [[nodiscard]] virtual std::string getPublicKeyString()const =0;
+    [[nodiscard]] virtual int getBits() const =0;
+    [[nodiscard]] virtual unsigned int getAlgorithm() const
     {
       return d_algorithm;
     }
 
     virtual void fromISCMap(DNSKEYRecordContent& drc, stormap_t& stormap) = 0;
     virtual void fromPublicKeyString(const std::string& content) = 0;
-    virtual bool checkKey(vector<string>* errorMessages = nullptr) const
+
+    [[nodiscard]] virtual bool checkKey(std::optional<std::reference_wrapper<std::vector<std::string>>> /* errorMessages */ = std::nullopt) const
     {
       return true;
     }
+
     static std::unique_ptr<DNSCryptoKeyEngine> makeFromISCFile(DNSKEYRecordContent& drc, const char* fname);
 
     /**
      * \brief Creates a key engine from a PEM file.
      *
-     * Receives an open file handle with PEM contents and creates a key
-     * engine corresponding to the algorithm requested.
+     * Receives an open file handle with PEM contents and creates a key engine
+     * corresponding to the algorithm requested.
      *
      * \param[in] drc Key record contents to be populated.
      *
-     * \param[in] filename Only used for providing filename information
-     * in error messages.
+     * \param[in] algorithm Which algorithm to use. See
+     * https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
+     *
+     * \param[in] fp An open file handle to a file containing PEM contents.
      *
-     * \param[in] fp An open file handle to a file containing PEM
-     * contents.
+     * \param[in] filename Only used for providing filename information in error messages.
+     *
+     * \return A key engine corresponding to the requested algorithm and populated with
+     * the contents of the PEM file.
+     */
+    static std::unique_ptr<DNSCryptoKeyEngine> makeFromPEMFile(DNSKEYRecordContent& drc, uint8_t algorithm, std::FILE& inputFile, const std::string& filename);
+
+    /**
+     * \brief Creates a key engine from a PEM string.
+     *
+     * Receives PEM contents and creates a key engine corresponding to the algorithm
+     * requested.
+     *
+     * \param[in] drc Key record contents to be populated.
      *
      * \param[in] algorithm Which algorithm to use. See
      * https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
      *
-     * \return A key engine corresponding to the requested algorithm and
-     * populated with the contents of the PEM file.
+     * \param[in] contents The PEM contents.
+     *
+     * \return A key engine corresponding to the requested algorithm and populated with
+     * the contents of the PEM string.
      */
-    static std::unique_ptr<DNSCryptoKeyEngine> makeFromPEMFile(DNSKEYRecordContent& drc, const std::string& filename, std::FILE& fp, uint8_t algorithm);
+    static std::unique_ptr<DNSCryptoKeyEngine> makeFromPEMString(DNSKEYRecordContent& drc, uint8_t algorithm, const std::string& contents);
 
     static std::unique_ptr<DNSCryptoKeyEngine> makeFromISCString(DNSKEYRecordContent& drc, const std::string& content);
     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);
 
-    typedef std::unique_ptr<DNSCryptoKeyEngine> maker_t(unsigned int algorithm);
+    using maker_t = std::unique_ptr<DNSCryptoKeyEngine> (unsigned int);
 
     static void report(unsigned int algorithm, maker_t* maker, bool fallback=false);
     static void testMakers(unsigned int algorithm, maker_t* creator, maker_t* signer, maker_t* verifier);
     static vector<pair<uint8_t, string>> listAllAlgosWithBackend();
     static bool testAll();
     static bool testOne(int algo);
-  private:
+    static bool verifyOne(unsigned int algo);
+    static bool testVerify(unsigned int algo, maker_t* verifier);
+    static string listSupportedAlgoNames();
 
-    typedef std::map<unsigned int, maker_t*> makers_t;
-    typedef std::map<unsigned int, vector<maker_t*> > allmakers_t;
+  private:
+    using makers_t = std::map<unsigned int, maker_t *>;
+    using allmakers_t = std::map<unsigned int, vector<maker_t *>>;
     static makers_t& getMakers()
     {
       static makers_t s_makers;
@@ -124,6 +194,9 @@ 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;
 };
@@ -140,25 +213,43 @@ struct DNSSECPrivateKey
     return d_key;
   }
 
-  void setKey(std::shared_ptr<DNSCryptoKeyEngine>& key)
+  // be aware that calling setKey() will also set the algorithm
+  void setKey(std::shared_ptr<DNSCryptoKeyEngine>& key, uint16_t flags, std::optional<uint8_t> algorithm = std::nullopt)
   {
     d_key = key;
-    d_algorithm = d_key->getAlgorithm();
+    d_flags = flags;
+    d_algorithm = algorithm ? *algorithm : d_key->getAlgorithm();
+    computeDNSKEY();
   }
 
-  void setKey(std::unique_ptr<DNSCryptoKeyEngine>&& key)
+  // be aware that calling setKey() will also set the algorithm
+  void setKey(std::unique_ptr<DNSCryptoKeyEngine>&& key, uint16_t flags, std::optional<uint8_t> algorithm = std::nullopt)
   {
     d_key = std::move(key);
-    d_algorithm = d_key->getAlgorithm();
+    d_flags = flags;
+    d_algorithm = algorithm ? *algorithm : d_key->getAlgorithm();
+    computeDNSKEY();
   }
 
-  DNSKEYRecordContent getDNSKEY() const;
+  const DNSKEYRecordContent& getDNSKEY() const;
 
-  uint16_t d_flags;
-  uint8_t d_algorithm;
+  uint16_t getFlags() const
+  {
+    return d_flags;
+  }
+
+  uint8_t getAlgorithm() const
+  {
+    return d_algorithm;
+  }
 
 private:
+  void computeDNSKEY();
+
+  DNSKEYRecordContent d_dnskey;
   std::shared_ptr<DNSCryptoKeyEngine> d_key;
+  uint16_t d_flags{0};
+  uint8_t d_algorithm{0};
 };
 
 
@@ -179,12 +270,12 @@ struct CanonicalCompare
 };
 
 struct sharedDNSSECRecordCompare {
-    bool operator() (const shared_ptr<DNSRecordContent>& a, const shared_ptr<DNSRecordContent>& b) const {
+    bool operator() (const shared_ptr<const DNSRecordContent>& a, const shared_ptr<const DNSRecordContent>& b) const {
       return a->serialize(g_rootdnsname, true, true) < b->serialize(g_rootdnsname, true, true);
     }
 };
 
-typedef std::set<std::shared_ptr<DNSRecordContent>, sharedDNSSECRecordCompare> sortedRecords_t;
+typedef std::set<std::shared_ptr<const DNSRecordContent>, sharedDNSSECRecordCompare> sortedRecords_t;
 
 string getMessageForRRSET(const DNSName& qname, const RRSIGRecordContent& rrc, const sortedRecords_t& signRecords, bool processRRSIGLabels = false, bool includeRRSIG_RDATA = true);
 
index 4dfe4c1b1db80af73ddeae70f59e76b35fb5ce69..50e81bbb1522bdd781218c77fcdf4276469d31d5 100644 (file)
@@ -171,13 +171,13 @@ private:
 public:
   DNSSECKeeper() : d_keymetadb( new UeberBackend("key-only")), d_ourDB(true)
   {
-    
+
   }
-  
+
   DNSSECKeeper(UeberBackend* db) : d_keymetadb(db), d_ourDB(false)
   {
   }
-  
+
   ~DNSSECKeeper()
   {
     if(d_ourDB)
@@ -202,9 +202,9 @@ public:
   bool deactivateKey(const DNSName& zname, unsigned int id);
   bool publishKey(const DNSName& zname, unsigned int id);
   bool unpublishKey(const DNSName& zname, unsigned int id);
-  bool checkKeys(const DNSName& zname, vector<string>* errorMessages = nullptr);
+  bool checkKeys(const DNSName& zname, std::optional<std::reference_wrapper<std::vector<std::string>>> errorMessages);
 
-  bool getNSEC3PARAM(const DNSName& zname, NSEC3PARAMRecordContent* n3p=0, bool* narrow=0, bool useCache=true);
+  bool getNSEC3PARAM(const DNSName& zname, NSEC3PARAMRecordContent* n3p=nullptr, bool* narrow=nullptr, bool useCache=true);
   bool checkNSEC3PARAM(const NSEC3PARAMRecordContent& ns3p, string& msg);
   bool setNSEC3PARAM(const DNSName& zname, const NSEC3PARAMRecordContent& n3p, const bool& narrow=false);
   bool unsetNSEC3PARAM(const DNSName& zname);
@@ -220,22 +220,22 @@ 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)
   {
     (*d_keymetadb->backends.begin())->startTransaction(zone, zone_id);
   }
-  
+
   void commitTransaction()
   {
     (*d_keymetadb->backends.begin())->commitTransaction();
   }
-  
+
   void getFromMetaOrDefault(const DNSName& zname, const std::string& key, std::string& value, const std::string& defaultvalue);
   bool getFromMeta(const DNSName& zname, const std::string& key, std::string& value);
   void getSoaEdit(const DNSName& zname, std::string& value, bool useCache=true);
-  bool unSecureZone(const DNSName& zone, std::string& error, std::string& info);
+  bool unSecureZone(const DNSName& zone, std::string& error);
   bool rectifyZone(const DNSName& zone, std::string& error, std::string& info, bool doTransaction);
 
   static void setMaxEntries(size_t maxEntries);
@@ -250,33 +250,33 @@ private:
   struct KeyCacheEntry
   {
     typedef vector<DNSSECKeeper::keymeta_t> keys_t;
-  
-    uint32_t getTTD() const
+
+    uint32_t isStale(time_t now) const
     {
-      return d_ttd;
+      return d_ttd < now;
     }
-  
+
     DNSName d_domain;
     mutable keys_t d_keys;
     unsigned int d_ttd;
   };
-  
+
   struct METACacheEntry
   {
-    time_t getTTD() const
+    time_t isStale(time_t now) const
     {
-      return d_ttd;
+      return d_ttd < now;
     }
 
     DNSName d_domain;
     mutable METAValues d_value;
     time_t d_ttd;
   };
-  
+
   struct KeyCacheTag{};
   struct CompositeTag{};
   struct SequencedTag{};
-  
+
   typedef multi_index_container<
     KeyCacheEntry,
     indexed_by<
index cdf265e074ac4f58071e3babee36e9e41f4b3e58..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;
 
 const static std::set<uint16_t> g_KSKSignedQTypes {QType::DNSKEY, QType::CDS, QType::CDNSKEY};
 AtomicCounter* g_signatureCount;
 
-static std::string getLookupKey(const std::string& msg)
+static std::string getLookupKeyFromMessage(const std::string& msg)
 {
   try {
-    return pdns_md5sum(msg);
+    return pdns::md5(msg);
   }
   catch(const std::runtime_error& e) {
-    return pdns_sha1sum(msg);
+    return pdns::sha1(msg);
+  }
+}
+
+static std::string getLookupKeyFromPublicKey(const std::string& pubKey)
+{
+  /* arbitrarily cut off at 64 bytes, the main idea is to save space
+     for very large keys like RSA ones (1024+ bits so 128+ bytes) by storing a 20 bytes hash
+     instead */
+  if (pubKey.size() <= 64) {
+    return pubKey;
   }
+  return pdns::sha1sum(pubKey);
 }
 
 static void fillOutRRSIG(DNSSECPrivateKey& dpk, const DNSName& signQName, RRSIGRecordContent& rrc, const sortedRecords_t& toSign)
@@ -60,12 +73,11 @@ static void fillOutRRSIG(DNSSECPrivateKey& dpk, const DNSName& signQName, RRSIGR
   rrc.d_tag = drc.getTag();
   rrc.d_algorithm = drc.d_algorithm;
 
-  string msg=getMessageForRRSET(signQName, rrc, toSign); // this is what we will hash & sign
-  pair<string, string> lookup(rc->getPubKeyHash(), getLookupKey(msg));  // this hash is a memory saving exercise
+  string msg = getMessageForRRSET(signQName, rrc, toSign); // this is what we will hash & sign
+  pair<string, string> lookup(getLookupKeyFromPublicKey(drc.d_key), getLookupKeyFromMessage(msg));  // this hash is a memory saving exercise
 
-  bool doCache=true;
-  if(doCache)
-  {
+  bool doCache = true;
+  if (doCache) {
     auto signatures = g_signatures.read_lock();
     signaturecache_t::const_iterator iter = signatures->find(lookup);
     if (iter != signatures->end()) {
@@ -78,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);
 
@@ -104,7 +116,7 @@ static int getRRSIGsForRRSET(DNSSECKeeper& dk, const DNSName& signer, const DNSN
   rrc.d_type=signQType;
 
   rrc.d_labels=signQName.countLabels()-signQName.isWildcard();
-  rrc.d_originalttl=signTTL; 
+  rrc.d_originalttl=signTTL;
   rrc.d_siginception=startOfWeek - 7*86400; // XXX should come from zone metadata
   rrc.d_sigexpire=startOfWeek + 14*86400;
   rrc.d_signer = signer;
@@ -147,8 +159,8 @@ static void addSignature(DNSSECKeeper& dk, UeberBackend& db, const DNSName& sign
     if(getRRSIGsForRRSET(dk, signer, wildcardname.countLabels() ? wildcardname : signQName, signQType, signTTL, toSign, rrcs) < 0)  {
       // cerr<<"Error signing a record!"<<endl;
       return;
-    } 
-  
+    }
+
     DNSZoneRecord rr;
     rr.dr.d_name=signQName;
     rr.dr.d_type=QType::RRSIG;
@@ -159,14 +171,14 @@ static void addSignature(DNSSECKeeper& dk, UeberBackend& db, const DNSName& sign
     rr.auth=false;
     rr.dr.d_place = signPlace;
     for(RRSIGRecordContent& rrc :  rrcs) {
-      rr.dr.d_content = std::make_shared<RRSIGRecordContent>(rrc);
+      rr.dr.setContent(std::make_shared<RRSIGRecordContent>(rrc));
       outsigned.push_back(rr);
     }
   }
   toSign.clear();
 }
 
-uint64_t signatureCacheSize(const std::string& str)
+uint64_t signatureCacheSize(const std::string& /* str */)
 {
   return g_signatures.read_lock()->size();
 }
@@ -187,19 +199,19 @@ static bool getBestAuthFromSet(const set<DNSName>& authSet, const DNSName& name,
     }
   }
   while(sname.chopOff());
-  
+
   return false;
 }
 
 void addRRSigs(DNSSECKeeper& dk, UeberBackend& db, const set<DNSName>& authSet, vector<DNSZoneRecord>& rrs)
 {
   stable_sort(rrs.begin(), rrs.end(), rrsigncomp);
-  
-  DNSName signQName, wildcardQName;
+
+  DNSName authQName, signQName, wildcardQName;
   uint16_t signQType=0;
   uint32_t signTTL=0;
   uint32_t origTTL=0;
-  
+
   DNSResourceRecord::Place signPlace=DNSResourceRecord::ANSWER;
   sortedRecords_t toSign;
 
@@ -209,11 +221,17 @@ void addRRSigs(DNSSECKeeper& dk, UeberBackend& db, const set<DNSName>& authSet,
   DNSName signer;
   for(auto pos = rrs.cbegin(); pos != rrs.cend(); ++pos) {
     if(pos != rrs.cbegin() && (signQType != pos->dr.d_type  || signQName != pos->dr.d_name)) {
-      if(getBestAuthFromSet(authSet, signQName, signer))
+      if (getBestAuthFromSet(authSet, authQName, signer))
         addSignature(dk, db, signer, signQName, wildcardQName, signQType, signTTL, signPlace, toSign, signedRecords, origTTL);
     }
     signedRecords.push_back(*pos);
-    signQName= pos->dr.d_name.makeLowerCase();
+    signQName = pos->dr.d_name.makeLowerCase();
+    if (pos->dr.d_type == QType::NSEC) {
+      authQName = signQName.getCommonLabels(getRR<NSECRecordContent>(pos->dr)->d_next);
+    }
+    else {
+      authQName = signQName;
+    }
     if(!pos->wildcardname.empty())
       wildcardQName = pos->wildcardname.makeLowerCase();
     else
@@ -226,10 +244,10 @@ void addRRSigs(DNSSECKeeper& dk, UeberBackend& db, const set<DNSName>& authSet,
     origTTL = pos->dr.d_ttl;
     signPlace = pos->dr.d_place;
     if(pos->auth || pos->dr.d_type == QType::DS) {
-      toSign.insert(pos->dr.d_content); // so ponder.. should this be a deep copy perhaps?
+      toSign.insert(pos->dr.getContent()); // so ponder.. should this be a deep copy perhaps?
     }
   }
-  if(getBestAuthFromSet(authSet, signQName, signer))
+  if (getBestAuthFromSet(authSet, authQName, signer))
     addSignature(dk, db, signer, signQName, wildcardQName, signQType, signTTL, signPlace, toSign, signedRecords, origTTL);
   rrs.swap(signedRecords);
 }
index d0fd632eb255c91a3047193821cae101de842cda..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};
 
@@ -31,10 +71,11 @@ DnstapMessage::DnstapMessage(std::string& buffer, DnstapMessage::MessageType typ
   pbf.add_bytes(DnstapBaseFields::version, PACKAGE_STRING);
   pbf.add_enum(DnstapBaseFields::type, DnstapMessageTypes::message);
 
-  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet);
   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) {
@@ -46,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));
@@ -56,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 +119,14 @@ DnstapMessage::DnstapMessage(std::string& buffer, DnstapMessage::MessageType typ
     pbf_message.add_fixed32(DnstapMessageFields::response_time_nsec, responseTime->tv_nsec);
   }
 
-  if (!dh->qr) {
-    pbf_message.add_bytes(DnstapMessageFields::query_message, packet, len);
-  } else {
-    pbf_message.add_bytes(DnstapMessageFields::response_message, packet, len);
+  if (packet != nullptr && len >= sizeof(dnsheader)) {
+    const dnsheader_aligned dnsheader(packet);
+    if (!dnsheader->qr) {
+      pbf_message.add_bytes(DnstapMessageFields::query_message, packet, len);
+    }
+    else {
+      pbf_message.add_bytes(DnstapMessageFields::response_message, packet, len);
+    }
   }
 
   if (auth) {
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 51f1e8b3cefd2f2854dbe059b01f582f898f4659..32df39a8530bd73845e334c8f5f6dc40a395f74d 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,11 +250,11 @@ 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;
-    cout<<"Timeout: "<< g_timeoutMsec<<"msec"<<endl;
+    cout<<"Timeout: "<< g_timeoutMsec<<" ms"<<endl;
     cout << "Using TCP_NODELAY: "<<g_tcpNoDelay<<endl;
   }
 
@@ -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;
 
@@ -305,8 +312,8 @@ try
   }
 
   cout<<"Average qps: "<<mean(qps)<<", median qps: "<<median(qps)<<endl;
-  cout<<"Average UDP latency: "<<mean(udpspeeds)<<"usec, median: "<<median(udpspeeds)<<"usec"<<endl;
-  cout<<"Average TCP latency: "<<mean(tcpspeeds)<<"usec, median: "<<median(tcpspeeds)<<"usec"<<endl;
+  cout<<"Average UDP latency: "<<mean(udpspeeds)<<" us, median: "<<median(udpspeeds)<<" us"<<endl;
+  cout<<"Average TCP latency: "<<mean(tcpspeeds)<<" us, median: "<<median(tcpspeeds)<<" us"<<endl;
 
   cout<<"OK: "<<g_OK<<", network errors: "<<g_networkErrors<<", other errors: "<<g_otherErrors<<endl;
   cout<<"Timeouts: "<<g_timeOuts<<endl;
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 0fd81b4d3d0b776953f3645683ef27a6534ebfff..cc6f32516f9f9afff23cef8c229d56c4473ab191 100644 (file)
 #include <limits.h>
 
 /* d_content:                                      <---- d_stuff ---->
-                                      v d_truncatemarker  
+                                      v d_truncatemarker
    dnsheader | qname | qtype | qclass | {recordname| dnsrecordheader | record }
-                                        ^ d_rollbackmarker           ^ d_sor 
-    
+                                        ^ d_rollbackmarker           ^ d_sor
+
 
 */
 
@@ -131,13 +131,13 @@ template <typename Container> void GenericDNSPacketWriter<Container>::addOpt(con
 
 template <typename Container> void GenericDNSPacketWriter<Container>::xfr48BitInt(uint64_t val)
 {
-  unsigned char bytes[6];
+  std::array<unsigned char, 6> bytes;
   uint16_t theLeft = htons((val >> 32)&0xffffU);
   uint32_t theRight = htonl(val & 0xffffffffU);
-  memcpy(bytes, (void*)&theLeft, sizeof(theLeft));
-  memcpy(bytes+2, (void*)&theRight, sizeof(theRight));
+  memcpy(&bytes[0], (void*)&theLeft, sizeof(theLeft));
+  memcpy(&bytes[2], (void*)&theRight, sizeof(theRight));
 
-  d_content.insert(d_content.end(), bytes, bytes + sizeof(bytes));
+  d_content.insert(d_content.end(), bytes.begin(), bytes.end());
 }
 
 template <typename Container> void GenericDNSPacketWriter<Container>::xfrNodeOrLocatorID(const NodeOrLocatorID& val)
@@ -213,7 +213,7 @@ template <typename Container> uint16_t GenericDNSPacketWriter<Container>::lookup
 
   /* name might be a.root-servers.net, we need to be able to benefit from finding:
      b.root-servers.net, or even:
-     b\xc0\x0c 
+     b\xc0\x0c
   */
   unsigned int bestpos=0;
   *matchLen=0;
@@ -236,10 +236,10 @@ template <typename Container> uint16_t GenericDNSPacketWriter<Container>::lookup
       cout<<"Domain "<<name<<" too large to compress"<<endl;
     return 0;
   }
-  
+
   if(l_verbose) {
     cout<<"Input vector for lookup "<<name<<": ";
-    for(const auto n : nvect) 
+    for(const auto n : nvect)
       cout << n<<" ";
     cout<<endl;
     cout<<makeHexDump(string(raw.c_str(), raw.c_str()+raw.size()))<<endl;
@@ -285,7 +285,7 @@ template <typename Container> uint16_t GenericDNSPacketWriter<Container>::lookup
     }
     if(l_verbose) {
       cout<<"Packet vector: "<<endl;
-      for(const auto n : pvect) 
+      for(const auto n : pvect)
         cout << n<<" ";
       cout<<endl;
     }
@@ -334,7 +334,7 @@ template <typename Container> void GenericDNSPacketWriter<Container>::xfrName(co
   uint16_t li=0;
   uint16_t matchlen=0;
   if(d_compress && compress && (li=lookupName(name, &matchlen)) && li < maxCompressionOffset) {
-    const auto& dns=name.getStorage(); 
+    const auto& dns=name.getStorage();
     if(l_verbose)
       cout<<"Found a substring of "<<matchlen<<" bytes from the back, offset: "<<li<<", dnslen: "<<dns.size()<<endl;
     // found a substring, if www.powerdns.com matched powerdns.com, we get back matchlen = 13
@@ -392,7 +392,7 @@ template <typename Container> void GenericDNSPacketWriter<Container>::xfrBlobNoS
   xfrBlob(blob);
 }
 
-template <typename Container> void GenericDNSPacketWriter<Container>::xfrHexBlob(const string& blob, bool keepReading)
+template <typename Container> void GenericDNSPacketWriter<Container>::xfrHexBlob(const string& blob, bool /* keepReading */)
 {
   xfrBlob(blob);
 }
@@ -432,13 +432,13 @@ template <typename Container> void GenericDNSPacketWriter<Container>::xfrSvcPara
       break;
     case SvcParam::ipv4hint:
       xfr16BitInt(param.getIPHints().size() * 4); // size
-      for (auto a: param.getIPHints()) {
+      for (const auto& a: param.getIPHints()) {
         xfrCAWithoutPort(param.getKey(), a);
       }
       break;
     case SvcParam::ipv6hint:
       xfr16BitInt(param.getIPHints().size() * 16); // size
-      for (auto a: param.getIPHints()) {
+      for (const auto& a: param.getIPHints()) {
         xfrCAWithoutPort(param.getKey(), a);
       }
       break;
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 56152b6e18154b2bf2ac01f4d5f795881aad9a0f..58a26f16918f6e33da6ecd2ed728800c25e324c3 100644 (file)
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #pragma once
-#include "iputils.hh"
-#include "libssl.hh"
-#include "noinitvector.hh"
-#include "stat_t.hh"
 
-struct DOHServerConfig;
+#include "config.h"
 
-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};
-
-  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()
-  {
-  }
+#ifdef HAVE_DNS_OVER_HTTPS
+#ifdef HAVE_LIBH2OEVLOOP
 
-  void reloadCertificates()
-  {
-  }
+#include <ctime>
+#include <memory>
+#include <string>
 
-  void rotateTicketsKey(time_t now)
-  {
-  }
+struct CrossProtocolQuery;
+struct DNSQuestion;
 
-  void loadTicketsKeys(const std::string& keyFile)
-  {
-  }
+std::unique_ptr<CrossProtocolQuery> getDoHCrossProtocolQueryFromDQ(DNSQuestion& dq, bool isResponse);
 
-  void handleTicketsKeyRotation()
-  {
-  }
+#include "dnsdist-doh-common.hh"
 
-  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* ptr)
-  {
-  }
-};
-
-#else /* HAVE_DNS_OVER_HTTPS */
-#include <unordered_map>
-
-#include "dnsdist-idstate.hh"
-
-struct st_h2o_req_t;
-struct DownstreamState;
-
-struct DOHUnit
+struct H2ODOHFrontend : public DOHFrontend
 {
-  DOHUnit()
-  {
-    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:
 
-  IDState ids;
-  std::string sni;
-  std::string path;
-  std::string scheme;
-  std::string host;
-  std::string contentType;
-  std::unordered_map<std::string, std::string> headers;
-  PacketBuffer query;
-  PacketBuffer response;
-  std::shared_ptr<DownstreamState> downstream{nullptr};
-  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, IDState&& state);
+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 3c5237a5bd20396ad5354aeb4f683b4f6fd49c3e..25c49521b7e3fe84d9041bce32ccfa58ebeebdfb 100644 (file)
  * 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 "config.h"
 #if !defined(RECURSOR)
 #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;
+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(int level, 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...);
 
-  if(g_syslog)
-    syslog(level, "%s", str.str().c_str());
+  auto output = str.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';
-    }
-    std::cout<<buffer;
+  if (!skipSyslog && dnsdist::logging::LoggingConfiguration::getSyslog()) {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg): syslog is what it is
+    syslog(level, "%s", output.c_str());
   }
-#endif
-
-  std::cout<<str.str()<<std::endl;
-}
 
+  if (dnsdist::logging::LoggingConfiguration::getLogTimestamps()) {
+    dnsdist::logging::logTime(stream);
+  }
 
-#define vinfolog if(g_verbose)infolog
-
-template<typename... Args>
-void infolog(const char* s, Args... args)
-{
-  genlog(LOG_INFO, s, args...);
-}
-
-template<typename... Args>
-void warnlog(const char* s, Args... args)
-{
-  genlog(LOG_WARNING, s, args...);
+  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 errlog(const char* s, Args... args)
+template <typename... Args>
+void verboselog(const char* formatStr, const Args&... args)
 {
-  genlog(LOG_ERR, s, args...);
+#ifdef DNSDIST
+  if (auto& stream = dnsdist::logging::LoggingConfiguration::getVerboseStream()) {
+    genlog(*stream, LOG_DEBUG, true, formatStr, args...);
+  }
+  else {
+#endif /* DNSDIST */
+    genlog(std::cout, LOG_DEBUG, false, formatStr, args...);
+#ifdef DNSDIST
+  }
+#endif /* DNSDIST */
 }
 
+#define vinfolog \
+  if (g_verbose) \
+  verboselog
 
-#else // RECURSOR
-
-#define g_verbose 0
-
-inline void dolog(Logger::Urgency u, const char* s)
+template <typename... Args>
+void infolog(const char* formatStr, const Args&... args)
 {
-  g_log << u << s << std::endl;
+  genlog(std::cout, LOG_INFO, false, formatStr, args...);
 }
 
-inline void dolog(const char* s)
+template <typename... Args>
+void warnlog(const char* formatStr, const Args&... args)
 {
-  g_log << s << std::endl;
+  genlog(std::cout, LOG_WARNING, false, formatStr, args...);
 }
 
-template<typename T, typename... Args>
-void dolog(Logger::Urgency u, const char* s, T value, Args... args)
+template <typename... Args>
+void errlog(const char* formatStr, const 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++;
-  }
+  genlog(std::cout, LOG_ERR, false, formatStr, args...);
 }
 
-#define vinfolog if(g_verbose)infolog
+#else // RECURSOR
+#define g_verbose 0
+#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 31869cb1f8059495206bb4bd0459e9a49d121b3f..4add3bfbdfaa93e975928fe383429f1051bf8411 100644 (file)
 #include "logger.hh"
 #include "dns.hh"
 #include "arguments.hh"
-#include <signal.h>
+#include <csignal>
 #include "misc.hh"
 #include "communicator.hh"
 #include "dnsseckeeper.hh"
 #include "nameserver.hh"
 #include "responsestats.hh"
 #include "ueberbackend.hh"
-#include "common_startup.hh"
+#include "auth-main.hh"
 
 extern ResponseStats g_rs;
 
@@ -50,7 +50,7 @@ bool DLQuitPlease()
   return s_pleasequit;
 }
 
-string DLQuitHandler(const vector<string>&parts, Utility::pid_t ppid)
+string DLQuitHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
 {
   string ret="No return value";
   if(parts[0]=="QUIT") {
@@ -66,7 +66,7 @@ static void dokill(int)
   exit(0);
 }
 
-string DLCurrentConfigHandler(const vector<string>&parts, Utility::pid_t ppid)
+string DLCurrentConfigHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
 {
   if(parts.size() > 1) {
     if(parts.size() == 2 && parts[1] == "diff") {
@@ -77,19 +77,20 @@ string DLCurrentConfigHandler(const vector<string>&parts, Utility::pid_t ppid)
   return ::arg().configstring(true, true);
 }
 
-string DLRQuitHandler(const vector<string>&parts, Utility::pid_t ppid)
+string DLRQuitHandler(const vector<string>& /* parts */, Utility::pid_t /* ppid */)
 {
   signal(SIGALRM, dokill);
   alarm(1);
   return "Exiting";
 }
 
-string DLPingHandler(const vector<string>&parts, Utility::pid_t ppid)
+string DLPingHandler(const vector<string>& /* parts */, Utility::pid_t /* ppid */)
 {
   return "PONG";
 }
 
-string DLShowHandler(const vector<string>&parts, Utility::pid_t ppid) {
+string DLShowHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
+{
   try {
     extern StatBag S;
     string ret("Wrong number of parameters");
@@ -114,21 +115,21 @@ void setStatus(const string &str)
   d_status=str;
 }
 
-string DLStatusHandler(const vector<string>&parts, Utility::pid_t ppid)
+string DLStatusHandler(const vector<string>& /* parts */, Utility::pid_t ppid)
 {
   ostringstream os;
   os<<ppid<<": "<<d_status;
   return os.str();
 }
 
-string DLUptimeHandler(const vector<string>&parts, Utility::pid_t ppid)
+string DLUptimeHandler(const vector<string>& /* parts */, Utility::pid_t /* ppid */)
 {
   ostringstream os;
-  os<<humanDuration(time(nullptr)-s_starttime);
+  os<<humanDuration(time(nullptr)-g_starttime);
   return os.str();
 }
 
-string DLPurgeHandler(const vector<string>&parts, Utility::pid_t ppid)
+string DLPurgeHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
 {
   ostringstream os;
   int ret=0;
@@ -153,7 +154,7 @@ string DLPurgeHandler(const vector<string>&parts, Utility::pid_t ppid)
   return os.str();
 }
 
-string DLCCHandler(const vector<string>&parts, Utility::pid_t ppid)
+string DLCCHandler(const vector<string>& /* parts */, Utility::pid_t /* ppid */)
 {
   extern AuthPacketCache PC;
   extern AuthQueryCache QC;
@@ -162,7 +163,7 @@ string DLCCHandler(const vector<string>&parts, Utility::pid_t ppid)
   ostringstream os;
   bool first=true;
   for(map<char,uint64_t>::const_iterator i=counts.begin();i!=counts.end();++i) {
-    if(!first) 
+    if(!first)
       os<<", ";
     first=false;
 
@@ -170,22 +171,24 @@ string DLCCHandler(const vector<string>&parts, Utility::pid_t ppid)
       os<<"negative queries: ";
     else if(i->first=='Q')
       os<<"queries: ";
-    else 
+    else
       os<<"unknown: ";
 
     os<<i->second;
   }
+  if(!first)
+    os<<", ";
   os<<"packets: "<<packetEntries;
 
   return os.str();
 }
 
-string DLQTypesHandler(const vector<string>&parts, Utility::pid_t ppid)
+string DLQTypesHandler(const vector<string>& /* parts */, Utility::pid_t /* ppid */)
 {
   return g_rs.getQTypeReport();
 }
 
-string DLRSizesHandler(const vector<string>&parts, Utility::pid_t ppid)
+string DLRSizesHandler(const vector<string>& /* parts */, Utility::pid_t /* ppid */)
 {
   typedef map<uint16_t, uint64_t> respsizes_t;
   respsizes_t respsizes = g_rs.getSizeResponseCounts();
@@ -197,7 +200,7 @@ string DLRSizesHandler(const vector<string>&parts, Utility::pid_t ppid)
   return os.str();
 }
 
-string DLRemotesHandler(const vector<string>&parts, Utility::pid_t ppid)
+string DLRemotesHandler(const vector<string>& /* parts */, Utility::pid_t /* ppid */)
 {
   extern StatBag S;
   typedef vector<pair<string, unsigned int> > totals_t;
@@ -210,7 +213,7 @@ string DLRemotesHandler(const vector<string>&parts, Utility::pid_t ppid)
   return ret;
 }
 
-string DLSettingsHandler(const vector<string>&parts, Utility::pid_t ppid)
+string DLSettingsHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
 {
   static const char *whitelist[]={"query-logging",nullptr};
   const char **p;
@@ -218,7 +221,7 @@ string DLSettingsHandler(const vector<string>&parts, Utility::pid_t ppid)
   if(parts.size()!=3) {
     return "Syntax: set variable value";
   }
-  
+
   for(p=whitelist;*p;p++)
     if(*p==parts[1])
       break;
@@ -232,12 +235,12 @@ string DLSettingsHandler(const vector<string>&parts, Utility::pid_t ppid)
 
 }
 
-string DLVersionHandler(const vector<string>&parts, Utility::pid_t ppid)
+string DLVersionHandler(const vector<string>& /* parts */, Utility::pid_t /* ppid */)
 {
   return VERSION;
 }
 
-string DLNotifyRetrieveHandler(const vector<string>&parts, Utility::pid_t ppid)
+string DLNotifyRetrieveHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
 {
   extern CommunicatorClass Communicator;
   ostringstream os;
@@ -251,15 +254,15 @@ string DLNotifyRetrieveHandler(const vector<string>&parts, Utility::pid_t ppid)
     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;
@@ -268,22 +271,22 @@ string DLNotifyRetrieveHandler(const vector<string>&parts, Utility::pid_t ppid)
     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.kind != DomainInfo::Slave || di.masters.empty()))
-    return "Zone '" + domain.toString() + "' is not a secondary zone (or has no primary defined)";
+  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)
+string DLNotifyHostHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
 {
   extern CommunicatorClass Communicator;
   ostringstream os;
@@ -311,7 +314,7 @@ string DLNotifyHostHandler(const vector<string>&parts, Utility::pid_t ppid)
   return "Added to queue";
 }
 
-string DLNotifyHandler(const vector<string>&parts, Utility::pid_t ppid)
+string DLNotifyHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
 {
   extern CommunicatorClass Communicator;
   UeberBackend B;
@@ -328,7 +331,7 @@ string DLNotifyHandler(const vector<string>&parts, Utility::pid_t ppid)
     int total = 0;
     int notified = 0;
     for (const auto& di : domains) {
-      if (di.kind == DomainInfo::Master || di.kind == DomainInfo::Slave) { // Primary and secondary if secondary-do-renotify is enabled
+      if (di.kind != DomainInfo::Native) { // Primary and secondary if secondary-do-renotify is enabled
         total++;
         if(Communicator.notifyDomain(di.zone, &B))
           notified++;
@@ -336,8 +339,8 @@ string DLNotifyHandler(const vector<string>&parts, Utility::pid_t ppid)
     }
 
     if (total != notified)
-      return itoa(notified)+" out of "+itoa(total)+" zones added to queue - see log";
-    return "Added "+itoa(total)+" MASTER/SLAVE zones to queue";
+      return std::to_string(notified)+" out of "+std::to_string(total)+" zones added to queue - see log";
+    return "Added " + std::to_string(total) + " MASTER/SLAVE/PRODUCER/CONSUMER zones to queue";
   } else {
     DNSName domain;
     try {
@@ -351,7 +354,7 @@ string DLNotifyHandler(const vector<string>&parts, Utility::pid_t ppid)
   }
 }
 
-string DLRediscoverHandler(const vector<string>&parts, Utility::pid_t ppid)
+string DLRediscoverHandler(const vector<string>& /* parts */, Utility::pid_t /* ppid */)
 {
   UeberBackend B;
   try {
@@ -366,7 +369,7 @@ string DLRediscoverHandler(const vector<string>&parts, Utility::pid_t ppid)
 
 }
 
-string DLReloadHandler(const vector<string>&parts, Utility::pid_t ppid)
+string DLReloadHandler(const vector<string>& /* parts */, Utility::pid_t /* ppid */)
 {
   UeberBackend B;
   B.reload();
@@ -374,36 +377,31 @@ string DLReloadHandler(const vector<string>&parts, Utility::pid_t ppid)
   return "Ok";
 }
 
-
-string DLListZones(const vector<string>&parts, Utility::pid_t ppid)
+string DLListZones(const vector<string>& parts, Utility::pid_t /* ppid */)
 {
   UeberBackend B;
   g_log<<Logger::Notice<<"Received request to list zones."<<endl;
   vector<DomainInfo> domains;
   B.getAllDomains(&domains, false, false);
   ostringstream ret;
-  int kindFilter = -1;
+  DomainInfo::DomainKind kind;
   if (parts.size() > 1) {
-    if (toUpper(parts[1]) == "PRIMARY" || toUpper(parts[1]) == "MASTER")
-      kindFilter = 0;
-    else if (toUpper(parts[1]) == "SECONDARY" || toUpper(parts[1]) == "SLAVE")
-      kindFilter = 1;
-    else if (toUpper(parts[1]) == "NATIVE")
-      kindFilter = 2;
+    kind = DomainInfo::stringToKind(parts.at(1));
+  }
+  else {
+    kind = DomainInfo::All;
   }
 
   int count = 0;
 
   for (const auto& di: domains) {
-    if (di.kind == kindFilter || kindFilter == -1) {
+    if (di.kind == kind || kind == DomainInfo::All) {
       ret<<di.zone.toString()<<endl;
       count++;
     }
   }
-  if (kindFilter != -1)
-    ret<<parts[1]<<" zonecount:"<<count;
-  else
-    ret<<"All zonecount:"<<count;
+
+  ret << DomainInfo::getKindString(kind) << " zonecount: " << count;
 
   return ret.str();
 }
@@ -412,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";
@@ -429,7 +427,8 @@ string DLTokenLogin(const vector<string>&parts, Utility::pid_t ppid)
 #endif
 }
 
-string DLSuckRequests(const vector<string> &parts, Utility::pid_t ppid) {
+string DLSuckRequests(const vector<string>& /* parts */, Utility::pid_t /* ppid */)
+{
   string ret;
   for (auto const &d: Communicator.getSuckRequests()) {
     ret += d.first.toString() + " " + d.second.toString() + "\n";
index 63d06bd36815a25e35c47b58c5c36ad7eff3e4d8..d2e7b06096299e8224520ac4bd847ed3a00f47d1 100644 (file)
@@ -37,7 +37,7 @@
 #include <iostream>
 #include <sstream>
 #include <sys/types.h>
-#include <signal.h>
+#include <csignal>
 
 #include <sys/stat.h>
 #include <fcntl.h>
@@ -214,14 +214,12 @@ string DynListener::getLine()
   vector<char> mesg;
   mesg.resize(1024000);
 
-  ssize_t len;
-
   ComboAddress remote;
   socklen_t remlen=remote.getSocklen();
 
   if(d_nonlocal) {
     for(;;) {
-      d_client=accept(d_s,(sockaddr*)&remote,&remlen);
+      d_client = accept(d_s, reinterpret_cast<sockaddr *>(&remote), &remlen);
       if(d_client<0) {
         if(errno!=EINTR)
           g_log<<Logger::Error<<"Unable to accept controlsocket connection ("<<d_s<<"): "<<stringerror()<<endl;
@@ -237,12 +235,12 @@ string DynListener::getLine()
 
       std::shared_ptr<FILE> fp=std::shared_ptr<FILE>(fdopen(dup(d_client), "r"), fclose);
       if(d_tcp) {
-        if(!fgets(&mesg[0], mesg.size(), fp.get())) {
+        if (fgets(mesg.data(), static_cast<int>(mesg.size()), fp.get()) == nullptr) {
           g_log<<Logger::Error<<"Unable to receive password from controlsocket ("<<d_client<<"): "<<stringerror()<<endl;
           close(d_client);
           continue;
         }
-        string password(&mesg[0]);
+        string password(mesg.data());
         boost::trim(password);
         if(password.empty() || password!=arg()["tcp-control-secret"]) {
           g_log<<Logger::Error<<"Wrong password on TCP control socket"<<endl;
@@ -253,14 +251,15 @@ string DynListener::getLine()
         }
       }
       errno=0;
-      if(!fgets(&mesg[0], mesg.size(), fp.get())) {
-        if(errno)
+      if (fgets(mesg.data(), static_cast<int>(mesg.size()), fp.get()) == nullptr) {
+        if (errno) {
           g_log<<Logger::Error<<"Unable to receive line from controlsocket ("<<d_client<<"): "<<stringerror()<<endl;
+        }
         close(d_client);
         continue;
       }
       
-      if(strlen(&mesg[0]) == mesg.size()) {
+      if (strlen(mesg.data()) == mesg.size()) {
         g_log<<Logger::Error<<"Line on controlsocket ("<<d_client<<") was too long"<<endl;
         close(d_client);
         continue;
@@ -269,21 +268,29 @@ string DynListener::getLine()
     }
   }
   else {
-    if(isatty(0))
-      if(write(1, "% ", 2) !=2)
-        throw PDNSException("Writing to console: "+stringerror());
-    if((len=read(0, &mesg[0], mesg.size())) < 0) 
-      throw PDNSException("Reading from the control pipe: "+stringerror());
-    else if(len==0)
+    if (isatty(0) != 0) {
+      if (write(1, "% ", 2) != 2) {
+        throw PDNSException("Writing to console: " + stringerror());
+      }
+    }
+
+    ssize_t len = read(0, mesg.data(), mesg.size());
+    if (len < 0) {
+      throw PDNSException("Reading from the control pipe: " + stringerror());
+    }
+
+    if (len == 0) {
       throw PDNSException("Guardian exited - going down as well");
+    }
 
-    if(static_cast<size_t>(len) == mesg.size())
+    if (static_cast<size_t>(len) == mesg.size()) {
       throw PDNSException("Line on control console was too long");
+    }
 
-    mesg[len]=0;
+    mesg[len] = 0;
   }
 
-  return &mesg[0];
+  return mesg.data();
 }
 
 void DynListener::sendlines(const string &l)
@@ -315,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)
@@ -330,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 cbe45c47b53ab1f03d6a69a7dcc5216597bd78b2..2c3f354e88aadbcbdf858dd8bed54d453db1a2aa 100644 (file)
@@ -23,7 +23,7 @@
 #include <string>
 #include <vector>
 #include <sys/types.h>
-#include <errno.h>
+#include <cerrno>
 #include <iostream>
 #include <sstream>
 #include "iputils.hh"
index 7799c300fdd5801595e253584043d124199a5d55..64619d473b6dda9406f31ea5a41978a63246ea1c 100644 (file)
@@ -54,7 +54,7 @@ StatBag S;
 
 int main(int argc, char **argv)
 {
-  string s_programname="pdns";
+  string programname="pdns";
 
   ::arg().set("config-dir","Location of configuration directory (pdns.conf)")=SYSCONFDIR;
   ::arg().set("socket-dir",string("Where the controlsocket will live, ")+LOCALSTATEDIR+"/pdns when unset and not chrooted" )="";
@@ -83,9 +83,9 @@ int main(int argc, char **argv)
   }
 
   if(::arg()["config-name"]!="")
-    s_programname+="-"+::arg()["config-name"];
+    programname+="-"+::arg()["config-name"];
 
-  string configname=::arg()["config-dir"]+"/"+s_programname+".conf";
+  string configname=::arg()["config-dir"]+"/"+programname+".conf";
   cleanSlashes(configname);
 
   if(!::arg().mustDo("no-config")) {
@@ -103,14 +103,14 @@ int main(int argc, char **argv)
     socketname = ::arg()["chroot"] + ::arg()["socket-dir"];
   }
 
-  socketname += "/" + s_programname + ".controlsocket";
+  socketname += "/" + programname + ".controlsocket";
   cleanSlashes(socketname);
 
   try {
     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 100fd3636b675080a8c147975e91e5f2d82fa13b..778c45f563bfed1e2841af321640fff80cdf7e3f 100644 (file)
@@ -27,7 +27,7 @@
 #include <sys/un.h>
 #include <unistd.h>
 #include <libgen.h>
-#include <errno.h>
+#include <cerrno>
 #include "iputils.hh"
 #include "pdnsexception.hh"
 
@@ -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 5992b13f86dbad7219781c2c55ecb6fa1bd1b8f7..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) {
@@ -87,6 +87,7 @@ bool EDNSCookiesOpt::isValid(const string& secret, const ComboAddress& source) c
   uint32_t ts;
   memcpy(&ts, &server[4], sizeof(ts));
   ts = ntohl(ts);
+  // coverity[store_truncates_time_t]
   uint32_t now = static_cast<uint32_t>(time(nullptr));
   // RFC 9018 section 4.3:
   //    The DNS server
@@ -121,6 +122,7 @@ bool EDNSCookiesOpt::shouldRefresh() const
   uint32_t ts;
   memcpy(&ts, &server[4], sizeof(ts));
   ts = ntohl(ts);
+  // coverity[store_truncates_time_t]
   uint32_t now = static_cast<uint32_t>(time(nullptr));
   // RFC 9018 section 4.3:
   //    The DNS server
@@ -137,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");
@@ -154,6 +156,7 @@ bool EDNSCookiesOpt::makeServerCookie(const string& secret, const ComboAddress&
   server.reserve(16);
   server = "\x01"; // Version
   server.resize(4, '\0'); // 3 reserved bytes
+  // coverity[store_truncates_time_t]
   uint32_t now = htonl(static_cast<uint32_t>(time(nullptr)));
   server += string(reinterpret_cast<const char*>(&now), sizeof(now));
   server.resize(8);
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 a26eb230c1f39e641153d8337f52de3aecbc6984..fe6e4bf1dcd76eff1254d27babb48521e15797bd 100644 (file)
@@ -44,7 +44,7 @@ struct EDNSOptionView
 static constexpr size_t EDNSOptionCodeSize = 2;
 static constexpr size_t EDNSOptionLengthSize = 2;
 
-typedef std::map<uint16_t, EDNSOptionView> EDNSOptionViewMap;
+using EDNSOptionViewMap = std::map<uint16_t, EDNSOptionView>;
 
 /* extract all EDNS0 options from a pointer on the beginning rdLen of the OPT RR */
 int getEDNSOptions(const char* optRR, size_t len, EDNSOptionViewMap& options);
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 85158d246f6879a4c2e9d3f3057c418e7090ed89..4ccb0a016c3d0f04e55d03228fb333f88f210400 100644 (file)
@@ -36,8 +36,8 @@
 class EpollFDMultiplexer : public FDMultiplexer
 {
 public:
-  EpollFDMultiplexer();
-  ~EpollFDMultiplexer()
+  EpollFDMultiplexer(unsigned int maxEventsHint);
+  ~EpollFDMultiplexer() override
   {
     if (d_epollfd >= 0) {
       close(d_epollfd);
@@ -59,12 +59,11 @@ public:
 private:
   int d_epollfd;
   std::vector<epoll_event> d_eevents;
-  static int s_maxevents; // not a hard maximum
 };
 
-static FDMultiplexer* makeEpoll()
+static FDMultiplexer* makeEpoll(unsigned int maxEventsHint)
 {
-  return new EpollFDMultiplexer();
+  return new EpollFDMultiplexer(maxEventsHint);
 }
 
 static struct EpollRegisterOurselves
@@ -75,12 +74,10 @@ static struct EpollRegisterOurselves
   }
 } doItEpoll;
 
-int EpollFDMultiplexer::s_maxevents = 1024;
-
-EpollFDMultiplexer::EpollFDMultiplexer() :
-  d_eevents(s_maxevents)
+EpollFDMultiplexer::EpollFDMultiplexer(unsigned int maxEventsHint) :
+  d_eevents(maxEventsHint)
 {
-  d_epollfd = epoll_create(s_maxevents); // not hard max
+  d_epollfd = epoll_create(static_cast<int>(maxEventsHint)); // not hard max, just a hint that is actually ignored since Linux 2.6.8
   if (d_epollfd < 0) {
     throw FDMultiplexerException("Setting up epoll: " + stringerror());
   }
@@ -156,7 +153,7 @@ void EpollFDMultiplexer::alterFD(int fd, FDMultiplexer::EventKind, FDMultiplexer
 
 void EpollFDMultiplexer::getAvailableFDs(std::vector<int>& fds, int timeout)
 {
-  int ret = epoll_wait(d_epollfd, d_eevents.data(), s_maxevents, timeout);
+  int ret = epoll_wait(d_epollfd, d_eevents.data(), d_eevents.size(), timeout);
 
   if (ret < 0 && errno != EINTR) {
     throw FDMultiplexerException("epoll returned error: " + stringerror());
@@ -173,7 +170,7 @@ int EpollFDMultiplexer::run(struct timeval* now, int timeout)
     throw FDMultiplexerException("FDMultiplexer::run() is not reentrant!\n");
   }
 
-  int ret = epoll_wait(d_epollfd, d_eevents.data(), s_maxevents, timeout);
+  int ret = epoll_wait(d_epollfd, d_eevents.data(), d_eevents.size(), timeout);
   gettimeofday(now, nullptr); // MANDATORY
 
   if (ret < 0 && errno != EINTR) {
diff --git a/pdns/filterpo.cc b/pdns/filterpo.cc
deleted file mode 100644 (file)
index de437e5..0000000
+++ /dev/null
@@ -1,804 +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 <iostream>
-#include <boost/format.hpp>
-
-#include "filterpo.hh"
-#include "namespaces.hh"
-#include "dnsrecords.hh"
-
-// Names below are RPZ Actions and end with a dot (except "Local Data")
-static const std::string rpzDropName("rpz-drop."),
-  rpzTruncateName("rpz-tcp-only."),
-  rpzNoActionName("rpz-passthru."),
-  rpzCustomName("Local Data");
-
-// Names below are (part) of RPZ Trigger names and do NOT end with a dot
-static const std::string rpzClientIPName("rpz-client-ip"),
-  rpzIPName("rpz-ip"),
-  rpzNSDnameName("rpz-nsdname"),
-  rpzNSIPName("rpz-nsip");
-
-DNSFilterEngine::DNSFilterEngine()
-{
-}
-
-bool DNSFilterEngine::Zone::findExactQNamePolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const
-{
-  return findExactNamedPolicy(d_qpolName, qname, pol);
-}
-
-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);
-    return true;
-  }
-  return false;
-}
-
-bool DNSFilterEngine::Zone::findNSIPPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const
-{
-  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();
-    return true;
-  }
-  return false;
-}
-
-bool DNSFilterEngine::Zone::findResponsePolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const
-{
-  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();
-    return true;
-  }
-  return false;
-}
-
-bool DNSFilterEngine::Zone::findClientPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const
-{
-  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();
-    return true;
-  }
-  return false;
-}
-
-bool DNSFilterEngine::Zone::findNamedPolicy(const std::unordered_map<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol)
-{
-  if (polmap.empty()) {
-    return false;
-  }
-
-  /* for www.powerdns.com, we need to check:
-     www.powerdns.com.
-       *.powerdns.com.
-                *.com.
-                    *.
-   */
-
-  std::unordered_map<DNSName, DNSFilterEngine::Policy>::const_iterator iter;
-  iter = polmap.find(qname);
-
-  if(iter != polmap.end()) {
-    pol=iter->second;
-    return true;
-  }
-
-  DNSName s(qname);
-  while(s.chopOff()){
-    iter = polmap.find(g_wildcarddnsname+s);
-    if(iter != polmap.end()) {
-      pol=iter->second;
-      pol.d_trigger = iter->first;
-      pol.d_hit = qname.toStringNoDot();
-      return true;
-    }
-  }
-  return false;
-}
-
-bool DNSFilterEngine::Zone::findExactNamedPolicy(const std::unordered_map<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol)
-{
-  if (polmap.empty()) {
-    return false;
-  }
-
-  const auto& it = polmap.find(qname);
-  if (it != polmap.end()) {
-    pol = it->second;
-    pol.d_trigger = qname;
-    pol.d_hit = qname.toStringNoDot();
-    return true;
-  }
-
-  return false;
-}
-
-bool DNSFilterEngine::getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& pol) const
-{
-  // cout<<"Got question for nameserver name "<<qname<<endl;
-  std::vector<bool> zoneEnabled(d_zones.size());
-  size_t count = 0;
-  bool allEmpty = true;
-  for (const auto& z : d_zones) {
-    bool enabled = true;
-    const auto& zoneName = z->getName();
-    if (z->getPriority() >= pol.getPriority()) {
-      enabled = false;
-    }
-    else if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
-      enabled = false;
-    }
-    else {
-      if (z->hasNSPolicies()) {
-        allEmpty = false;
-      }
-      else {
-        enabled = false;
-      }
-    }
-
-    zoneEnabled[count] = enabled;
-    ++count;
-  }
-
-  if (allEmpty) {
-    return false;
-  }
-
-  /* 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);
-  }
-
-  count = 0;
-  for(const auto& z : d_zones) {
-    if (!zoneEnabled[count]) {
-      ++count;
-      continue;
-    }
-    if (z->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)) {
-        // 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();
-        return true;
-      }
-    }
-    ++count;
-  }
-
-  return false;
-}
-
-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()) {
-      break;
-    }
-    const auto& zoneName = z->getName();
-    if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
-      continue;
-    }
-
-    if(z->findNSIPPolicy(address, pol)) {
-      //      cerr<<"Had a hit on the nameserver ("<<address.toString()<<") used to process the query"<<endl;
-      return true;
-    }
-  }
-  return false;
-}
-
-bool DNSFilterEngine::getClientPolicy(const ComboAddress& ca, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& pol) const
-{
-  // cout<<"Got question from "<<ca.toString()<<endl;
-  for (const auto& z : d_zones) {
-    if (z->getPriority() >= pol.getPriority()) {
-      break;
-    }
-    const auto& zoneName = z->getName();
-    if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
-      continue;
-    }
-
-    if (z->findClientPolicy(ca, pol)) {
-      // cerr<<"Had a hit on the IP address ("<<ca.toString()<<") of the client"<<endl;
-      return true;
-    }
-  }
-  return false;
-}
-
-bool DNSFilterEngine::getQueryPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& pol) const
-{
-  //cerr<<"Got question for "<<qname<<' '<< pol.getPriority()<< endl;
-  std::vector<bool> zoneEnabled(d_zones.size());
-  size_t count = 0;
-  bool allEmpty = true;
-  for (const auto& z : d_zones) {
-    bool enabled = true;
-    if (z->getPriority() >= pol.getPriority()) {
-      enabled = false;
-    } else {
-      const auto& zoneName = z->getName();
-      if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
-        enabled = false;
-      }
-      else {
-        if (z->hasQNamePolicies()) {
-          allEmpty = false;
-        }
-        else {
-          enabled = false;
-        }
-      }
-    }
-
-    zoneEnabled[count] = enabled;
-    ++count;
-  }
-
-  if (allEmpty) {
-    return false;
-  }
-
-  /* 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);
-  }
-
-  count = 0;
-  for (const auto& z : d_zones) {
-    if (!zoneEnabled[count]) {
-      ++count;
-      continue;
-    }
-
-    if (z->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)) {
-        // 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();
-        return true;
-      }
-    }
-
-    ++count;
-  }
-
-  return false;
-}
-
-bool DNSFilterEngine::getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& pol) const
-{
-  for (const auto& record : records) {
-    if (getPostPolicy(record, discardedPolicies, pol)) {
-      return true;
-    }
-  }
-
-  return false;
-}
-
-bool DNSFilterEngine::getPostPolicy(const DNSRecord& record, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& pol) const
-{
-  ComboAddress ca;
-  if (record.d_place != DNSResourceRecord::ANSWER) {
-    return false;
-  }
-
-  if (record.d_type == QType::A) {
-    if (auto rec = getRR<ARecordContent>(record)) {
-      ca = rec->getCA();
-    }
-  }
-  else if(record.d_type == QType::AAAA) {
-    if (auto rec = getRR<AAAARecordContent>(record)) {
-      ca = rec->getCA();
-    }
-  }
-  else {
-    return false;
-  }
-
-  for (const auto& z : d_zones) {
-    if (z->getPriority() >= pol.getPriority()) {
-      break;
-    }
-    const auto& zoneName = z->getName();
-    if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
-      return false;
-    }
-
-    if (z->findResponsePolicy(ca, pol)) {
-      return true;
-    }
-  }
-
-  return false;
-}
-
-void DNSFilterEngine::assureZones(size_t zone)
-{
-  if(d_zones.size() <= zone)
-    d_zones.resize(zone+1);
-}
-
-void DNSFilterEngine::Zone::addNameTrigger(std::unordered_map<DNSName,Policy>& map, const DNSName& n, Policy&& pol, bool ignoreDuplicate, PolicyType ptype)
-{
-  auto it = map.find(n);
-
-  if (it != map.end()) {
-    auto& existingPol = it->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 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());
-    }
-
-    existingPol.d_custom.reserve(existingPol.d_custom.size() + pol.d_custom.size());
-
-    std::move(pol.d_custom.begin(), pol.d_custom.end(), std::back_inserter(existingPol.d_custom));
-  }
-  else {
-    auto& qpol = map.insert({n, std::move(pol)}).first->second;
-    qpol.d_zoneData = d_zoneData;
-    qpol.d_type = ptype;
-  }
-}
-
-void DNSFilterEngine::Zone::addNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& nm, Policy&& pol, bool ignoreDuplicate, PolicyType ptype)
-{
-  bool exists = nmt.has_key(nm);
-
-  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);
-
-    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 (existingPol.d_kind != PolicyKind::Custom && ignoreDuplicate) {
-      throw std::runtime_error("Adding a " + getTypeToString(ptype) + "-based filter policy of kind " + getKindToString(existingPol.d_kind) + " but there was already an existing policy for the following netmask: " + nm.toString());
-    }
-
-    existingPol.d_custom.reserve(existingPol.d_custom.size() + pol.d_custom.size());
-
-    std::move(pol.d_custom.begin(), pol.d_custom.end(), std::back_inserter(existingPol.d_custom));
-  }
-  else {
-    pol.d_zoneData = d_zoneData;
-    pol.d_type = ptype;
-    nmt.insert(nm).second = std::move(pol);
-  }
-}
-
-bool DNSFilterEngine::Zone::rmNameTrigger(std::unordered_map<DNSName,Policy>& map, const DNSName& n, const Policy& pol)
-{
-  auto found = map.find(n);
-  if (found == map.end()) {
-    return false;
-  }
-
-  auto& existing = found->second;
-  if (existing.d_kind != DNSFilterEngine::PolicyKind::Custom) {
-    map.erase(found);
-    return true;
-  }
-
-  /* for custom types, we might have more than one type,
-     and then we need to remove only the right ones. */
-  bool result = false;
-  for (auto& toRemove : pol.d_custom) {
-    for (auto it = existing.d_custom.begin(); it != existing.d_custom.end(); ++it) {
-      if (**it == *toRemove) {
-        existing.d_custom.erase(it);
-        result = true;
-        break;
-      }
-    }
-  }
-
-  // No records left for this trigger?
-  if (existing.d_custom.size() == 0) {
-    map.erase(found);
-    return true;
-  }
-
-  return result;
-}
-
-bool DNSFilterEngine::Zone::rmNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& nm, const Policy& pol)
-{
-  bool found = nmt.has_key(nm);
-  if (!found) {
-    return false;
-  }
-
-  // XXX NetMaskTree's node_type has a non-const second, but lookup() returns a const node_type *, so we cannot modify second
-  // Should look into making lookup) return a non-const node_type *...
-  auto& existing = const_cast<Policy&>(nmt.lookup(nm)->second);
-  if (existing.d_kind != DNSFilterEngine::PolicyKind::Custom) {
-    nmt.erase(nm);
-    return true;
-  }
-
-  /* for custom types, we might have more than one type,
-     and then we need to remove only the right ones. */
-
-  bool result = false;
-  for (auto& toRemove : pol.d_custom) {
-    for (auto it = existing.d_custom.begin(); it != existing.d_custom.end(); ++it) {
-      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);
-    return true;
-  }
-
-  return result;
-}
-
-void DNSFilterEngine::Zone::addClientTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate)
-{
-  addNetmaskTrigger(d_qpolAddr, nm, std::move(pol), ignoreDuplicate, PolicyType::ClientIP);
-}
-
-void DNSFilterEngine::Zone::addResponseTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate)
-{
-  addNetmaskTrigger(d_postpolAddr, nm, std::move(pol), ignoreDuplicate, PolicyType::ResponseIP);
-}
-
-void DNSFilterEngine::Zone::addQNameTrigger(const DNSName& n, Policy&& pol, bool ignoreDuplicate)
-{
-  addNameTrigger(d_qpolName, n, std::move(pol), ignoreDuplicate, PolicyType::QName);
-}
-
-void DNSFilterEngine::Zone::addNSTrigger(const DNSName& n, Policy&& pol, bool ignoreDuplicate)
-{
-  addNameTrigger(d_propolName, n, std::move(pol), ignoreDuplicate, PolicyType::NSDName);
-}
-
-void DNSFilterEngine::Zone::addNSIPTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate)
-{
-  addNetmaskTrigger(d_propolNSAddr, nm, std::move(pol), ignoreDuplicate, PolicyType::NSIP);
-}
-
-bool DNSFilterEngine::Zone::rmClientTrigger(const Netmask& nm, const Policy& pol)
-{
-  return rmNetmaskTrigger(d_qpolAddr, nm, pol);
-}
-
-bool DNSFilterEngine::Zone::rmResponseTrigger(const Netmask& nm, const Policy& pol)
-{
-  return rmNetmaskTrigger(d_postpolAddr, nm, pol);
-}
-
-bool DNSFilterEngine::Zone::rmQNameTrigger(const DNSName& n, const Policy& pol)
-{
-  return rmNameTrigger(d_qpolName, n, pol);
-}
-
-bool DNSFilterEngine::Zone::rmNSTrigger(const DNSName& n, const Policy& pol)
-{
-  return rmNameTrigger(d_propolName, n, pol);
-}
-
-bool DNSFilterEngine::Zone::rmNSIPTrigger(const Netmask& nm, const Policy& pol)
-{
-  return rmNetmaskTrigger(d_propolNSAddr, nm, pol);
-}
-
-std::string DNSFilterEngine::Policy::getLogString() const {
-  return ": RPZ Hit; PolicyName=" + getName() + "; Trigger=" + d_trigger.toLogString() + "; Hit=" + d_hit + "; Type=" + getTypeToString(d_type) + "; Kind=" + getKindToString(d_kind);
-}
-
-DNSRecord DNSFilterEngine::Policy::getRecordFromCustom(const DNSName& qname, const std::shared_ptr<DNSRecordContent>& custom) const
-{
-  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.d_content = custom;
-
-  if (dr.d_type == QType::CNAME) {
-    const auto content = std::dynamic_pointer_cast<CNAMERecordContent>(custom);
-    if (content) {
-      DNSName target = content->getTarget();
-      if (target.isWildcard()) {
-        target.chopOff();
-        dr.d_content = std::make_shared<CNAMERecordContent>(qname + target);
-      }
-    }
-  }
-
-  return dr;
-}
-
-std::vector<DNSRecord> DNSFilterEngine::Policy::getCustomRecords(const DNSName& qname, uint16_t qtype) const
-{
-  if (d_kind != PolicyKind::Custom) {
-    throw std::runtime_error("Asking for a custom record from a filtering policy of a non-custom type");
-  }
-
-  std::vector<DNSRecord> result;
-
-  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.d_content = custom;
-
-    if (dr.d_type == QType::CNAME) {
-      const auto content = std::dynamic_pointer_cast<CNAMERecordContent>(custom);
-      if (content) {
-        DNSName target = content->getTarget();
-        if (target.isWildcard()) {
-          target.chopOff();
-          dr.d_content = std::make_shared<CNAMERecordContent>(qname + target);
-        }
-      }
-    }
-
-    result.emplace_back(getRecordFromCustom(qname, custom));
-  }
-
-  return result;
-}
-
-std::string DNSFilterEngine::getKindToString(DNSFilterEngine::PolicyKind kind)
-{
-  //static const std::string rpzPrefix("rpz-");
-
-  switch(kind) {
-  case DNSFilterEngine::PolicyKind::NoAction:
-    return rpzNoActionName;
-  case DNSFilterEngine::PolicyKind::Drop:
-    return rpzDropName;
-  case DNSFilterEngine::PolicyKind::NXDOMAIN:
-    return g_rootdnsname.toString();
-  case PolicyKind::NODATA:
-    return g_wildcarddnsname.toString();
-  case DNSFilterEngine::PolicyKind::Truncate:
-    return rpzTruncateName;
-  case DNSFilterEngine::PolicyKind::Custom:
-    return rpzCustomName;
-  default:
-    throw std::runtime_error("Unexpected DNSFilterEngine::Policy kind");
-  }
-}
-
-std::string DNSFilterEngine::getTypeToString(DNSFilterEngine::PolicyType type)
-{
-  switch(type) {
-  case DNSFilterEngine::PolicyType::None:
-    return "none";
-  case DNSFilterEngine::PolicyType::QName:
-    return "QName";
-  case DNSFilterEngine::PolicyType::ClientIP:
-    return "Client IP";
-  case DNSFilterEngine::PolicyType::ResponseIP:
-    return "Response IP";
-  case DNSFilterEngine::PolicyType::NSDName:
-    return "Name Server Name";
-  case DNSFilterEngine::PolicyType::NSIP:
-    return "Name Server IP";
-  default:
-    throw std::runtime_error("Unexpected DNSFilterEngine::Policy type");
-  }
-}
-
-std::vector<DNSRecord> DNSFilterEngine::Policy::getRecords(const DNSName& qname) const
-{
-  std::vector<DNSRecord> result;
-
-  if (d_kind == PolicyKind::Custom) {
-    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.d_content = DNSRecordContent::mastermake(QType::CNAME, QClass::IN, getKindToString(d_kind));
-    result.push_back(std::move(dr));
-  }
-
-  return result;
-}
-
-void DNSFilterEngine::Zone::dumpNamedPolicy(FILE* fp, 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.d_content->getZoneRepresentation().c_str());
-  }
-}
-
-DNSName DNSFilterEngine::Zone::maskToRPZ(const Netmask& nm)
-{
-  int bits = nm.getBits();
-  DNSName res(std::to_string(bits));
-  const auto& addr = nm.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]));
-  }
-  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;
-
-    // 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:
-    //
-    //    If there exists more than one sequence of zero-valued fields of
-    //    identical length, then only the last such sequence is compressed.
-    //    Note that [RFC5952] specifies compressing the first such sequence,
-    //    but our notation here reverses the order of fields, and so must also
-    //    reverse the selection of which zero sequence to compress.
-    //
-    // 'cur.len > best.len' from the original code is replaced by 'cur.len >= best.len', so the last-longest wins.
-
-    struct { 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) {
-        if (cur.base == -1) {  // start of a run of zeroes
-          cur = { i, 1 };
-        } else {
-          cur.len++;           // continuation of a run of zeroes
-        }
-      } else {                 // not a zero
-        if (cur.base != -1) {  // end of a run of zeroes
-          if (best.base == -1 || cur.len >= best.len) { // first run of zeroes, or a better one than we found before
-            best = cur;
-          }
-          cur.base = -1;
-        }
-      }
-    }
-
-    if (cur.base != -1) {      // address ended with a zero
-      if (best.base == -1 || cur.len >= best.len) {     // first run of zeroes, or a better one than we found before
-        best = cur;
-      }
-    }
-
-    if (best.base != -1 && best.len < 2) {              // if our best run is only one zero long, we do not replace it
-      best.base = -1;
-    }
-    for (int i=0; i < (int)elems.size(); i++) {
-      if (i == best.base) {
-        temp = DNSName("zz") + temp;
-        i = i + best.len - 1;
-      } else {
-        temp = DNSName((boost::format("%x") % elems.at(i)).str()) + temp;
-      }
-    }
-    res += temp;
-  }
-
-  return res;
-}
-
-
-void DNSFilterEngine::Zone::dumpAddrPolicy(FILE* fp, const Netmask& nm, const DNSName& name, const Policy& pol)
-{
-  DNSName full = maskToRPZ(nm);
-  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.d_content->getZoneRepresentation().c_str());
-  }
-}
-
-void DNSFilterEngine::Zone::dump(FILE* fp) 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());
-
-  for (const auto& pair : d_qpolName) {
-    dumpNamedPolicy(fp, pair.first + d_domain, pair.second);
-  }
-
-  for (const auto& pair : d_propolName) {
-    dumpNamedPolicy(fp, pair.first + DNSName(rpzNSDnameName) + d_domain, pair.second);
-  }
-
-  for (const auto& pair : d_qpolAddr) {
-    dumpAddrPolicy(fp, pair.first, DNSName(rpzClientIPName) + d_domain, pair.second);
-  }
-
-  for (const auto& pair : d_propolNSAddr) {
-    dumpAddrPolicy(fp, pair.first, DNSName(rpzNSIPName) + d_domain, pair.second);
-  }
-
-  for (const auto& pair : d_postpolAddr) {
-    dumpAddrPolicy(fp, pair.first, DNSName(rpzIPName) + d_domain, pair.second);
-  }
-}
-
-void mergePolicyTags(std::unordered_set<std::string>& tags, const std::unordered_set<std::string>& newTags)
-{
-  for (const auto& tag : newTags) {
-    tags.insert(tag);
-  }
-}
diff --git a/pdns/filterpo.hh b/pdns/filterpo.hh
deleted file mode 100644 (file)
index 971aabd..0000000
+++ /dev/null
@@ -1,422 +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 "iputils.hh"
-#include "dns.hh"
-#include "dnsname.hh"
-#include "dnsparser.hh"
-#include <map>
-#include <unordered_map>
-#include <limits>
-
-/* 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.
-
-
-   We know the following actions:
-
-   No action - just pass it on
-   Drop - drop a query, no response
-   NXDOMAIN - fake up an NXDOMAIN for the query
-   NODATA - just return no data for this qtype
-   Truncate - set TC bit
-   Modified - "we fake an answer for you"
-
-   These actions can be caused by the following triggers:
-
-   qname - the query name
-   client-ip - the IP address of the requestor
-   response-ip - an IP address in the response
-   ns-name - the name of a server used in the delegation
-   ns-ip - the IP address of a server used in the delegation
-
-   This means we get several hook points:
-   1) when the query comes in: qname & client-ip
-   2) during processing: ns-name & ns-ip
-   3) after processing: response-ip
-
-   Triggers meanwhile can apply to:
-   Verbatim domain names
-   Wildcard versions (*.domain.com does NOT match domain.com)
-   Netmasks (IPv4 and IPv6)
-   Finally, triggers are grouped in different zones. The "first" zone that has a match
-   is consulted. Then within that zone, rules again have precedences.
-*/
-
-
-class DNSFilterEngine
-{
-public:
-  enum class PolicyKind : uint8_t { NoAction, Drop, NXDOMAIN, NODATA, Truncate, Custom};
-  enum class PolicyType : uint8_t { None, QName, ClientIP, ResponseIP, NSDName, NSIP };
-  typedef uint16_t Priority;
-  static const Priority maximumPriority = std::numeric_limits<Priority>::max();
-  
-  static std::string getKindToString(PolicyKind kind);
-  static std::string getTypeToString(PolicyType type);
-
-  struct PolicyZoneData
-  {
-    /* shared by all the policies from a single zone */
-    std::unordered_set<std::string> d_tags;
-    std::string d_name;
-    std::string d_extendedErrorExtra;
-    boost::optional<uint16_t> d_extendedErrorCode{boost::none};
-    Priority d_priority{maximumPriority};
-    bool d_policyOverridesGettag{true};
-  };
-
-  struct Policy
-  {
-    Policy(): d_ttl(0), d_kind(PolicyKind::NoAction), d_type(PolicyType::None)
-    {
-    }
-
-    Policy(PolicyKind kind, PolicyType type, int32_t ttl=0, std::shared_ptr<PolicyZoneData> data=nullptr, const std::vector<std::shared_ptr<DNSRecordContent>>& custom={}): d_custom(custom), d_zoneData(data), d_ttl(ttl), d_kind(kind), d_type(type)
-    {
-    }
-
-    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
-    {
-      static const std::string notSet;
-      if (d_zoneData) {
-        return d_zoneData->d_name;
-      }
-      return notSet;
-    }
-
-    void setName(const std::string& name)
-    {
-      /* until now the PolicyZoneData was shared,
-         we now need to copy it, then write to it */
-      std::shared_ptr<PolicyZoneData> newZoneData;
-      if (d_zoneData) {
-        newZoneData = std::make_shared<PolicyZoneData>(*d_zoneData);
-      }
-      else {
-        newZoneData = std::make_shared<PolicyZoneData>();
-      }
-      newZoneData->d_name = name;
-      d_zoneData = newZoneData;
-    }
-
-    const std::unordered_set<std::string>& getTags() const
-    {
-      static const std::unordered_set<std::string> notSet;
-      if (d_zoneData) {
-        return d_zoneData->d_tags;
-      }
-      return notSet;
-    }
-
-    Priority getPriority() const
-    {
-      static Priority notSet = maximumPriority;
-      if (d_zoneData) {
-        return d_zoneData->d_priority;
-      }
-      return notSet;
-    }
-
-    bool policyOverridesGettag() const {
-      if (d_zoneData) {
-        return d_zoneData->d_policyOverridesGettag;
-      }
-      return true;
-    }
-
-    bool wasHit() const
-    {
-      return (d_type != DNSFilterEngine::PolicyType::None && d_kind != DNSFilterEngine::PolicyKind::NoAction);
-    }
-
-    std::string getLogString() const;
-    std::vector<DNSRecord> getCustomRecords(const DNSName& qname, uint16_t qtype) const;
-    std::vector<DNSRecord> getRecords(const DNSName& qname) const;
-
-    std::vector<std::shared_ptr<DNSRecordContent>> d_custom;
-    std::shared_ptr<PolicyZoneData> d_zoneData{nullptr};
-    DNSName d_trigger;
-    string d_hit;
-    /* Yup, we are currently using the same TTL for every record for a given name */
-    int32_t d_ttl;
-    PolicyKind d_kind;
-    PolicyType d_type;
-
-  private:
-    DNSRecord getRecordFromCustom(const DNSName& qname, const std::shared_ptr<DNSRecordContent>& custom) const;
-  };
-
-  class Zone {
-  public:
-    Zone(): d_zoneData(std::make_shared<PolicyZoneData>())
-    {
-    }
-
-    void clear()
-    {
-      d_qpolAddr.clear();
-      d_postpolAddr.clear();
-      d_propolName.clear();
-      d_propolNSAddr.clear();
-      d_qpolName.clear();
-    }
-    void reserve(size_t entriesCount)
-    {
-      d_qpolName.reserve(entriesCount);
-    }
-    void setName(const std::string& name)
-    {
-      d_zoneData->d_name = name;
-    }
-    void setDomain(const DNSName& domain)
-    {
-      d_domain = domain;
-    }
-    void setSerial(uint32_t serial)
-    {
-      d_serial = serial;
-    }
-    void setRefresh(uint32_t refresh)
-    {
-      d_refresh = refresh;
-    }
-    void setTags(std::unordered_set<std::string>&& tags)
-    {
-      d_zoneData->d_tags = std::move(tags);
-    }
-    void setPolicyOverridesGettag(bool flag)
-    {
-      d_zoneData->d_policyOverridesGettag = flag;
-    }
-    void setExtendedErrorCode(uint16_t code)
-    {
-      d_zoneData->d_extendedErrorCode = code;
-    }
-    void setExtendedErrorExtra(const std::string& extra)
-    {
-      d_zoneData->d_extendedErrorExtra = extra;
-    }
-
-    const std::string& getName() const
-    {
-      return d_zoneData->d_name;
-    }
-
-    DNSName getDomain() const
-    {
-      return d_domain;
-    }
-
-    uint32_t getRefresh() const
-    {
-      return d_refresh;
-    }
-
-    uint32_t getSerial() const
-    {
-      return d_serial;
-    }
-
-    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 addClientTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate = false);
-    void addQNameTrigger(const DNSName& nm, Policy&& pol, bool ignoreDuplicate = false);
-    void addNSTrigger(const DNSName& dn, Policy&& pol, bool ignoreDuplicate = false);
-    void addNSIPTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate = false);
-    void addResponseTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate = false);
-
-    bool rmClientTrigger(const Netmask& nm, const Policy& pol);
-    bool rmQNameTrigger(const DNSName& nm, const Policy& pol);
-    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 findExactQNamePolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const;
-    bool findExactNSPolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const;
-    bool findNSIPPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const;
-    bool findResponsePolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const;
-    bool findClientPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const;
-
-    bool hasClientPolicies() const
-    {
-      return !d_qpolAddr.empty();
-    }
-    bool hasQNamePolicies() const
-    {
-      return !d_qpolName.empty();
-    }
-    bool hasNSPolicies() const
-    {
-      return !d_propolName.empty();
-    }
-    bool hasNSIPPolicies() const
-    {
-      return !d_propolNSAddr.empty();
-    }
-    bool hasResponsePolicies() const
-    {
-      return !d_postpolAddr.empty();
-    }
-    Priority getPriority() const {
-      return d_zoneData->d_priority;
-    }
-    void setPriority(Priority p) {
-      d_zoneData->d_priority = p;
-    }
-    
-    static DNSName maskToRPZ(const Netmask& nm);
-
-  private:
-    void addNameTrigger(std::unordered_map<DNSName,Policy>& map, const DNSName& n, Policy&& pol, bool ignoreDuplicate, PolicyType ptype);
-    void addNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& nm, Policy&& pol, bool ignoreDuplicate, PolicyType ptype);
-    bool rmNameTrigger(std::unordered_map<DNSName,Policy>& map, const DNSName& n, const Policy& pol);
-    bool rmNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& nm, const Policy& pol);
-
-  private:
-    static bool findExactNamedPolicy(const std::unordered_map<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol);
-    static bool findNamedPolicy(const std::unordered_map<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol);
-    static void dumpNamedPolicy(FILE* fp, const DNSName& name, const Policy& pol);
-    static void dumpAddrPolicy(FILE* fp, const Netmask& nm, const DNSName& name, const Policy& pol);
-
-    std::unordered_map<DNSName, Policy> d_qpolName;   // QNAME trigger (RPZ)
-    NetmaskTree<Policy> d_qpolAddr;         // Source address
-    std::unordered_map<DNSName, Policy> d_propolName; // NSDNAME (RPZ)
-    NetmaskTree<Policy> d_propolNSAddr;     // NSIP (RPZ)
-    NetmaskTree<Policy> d_postpolAddr;      // IP trigger (RPZ)
-    DNSName d_domain;
-    std::shared_ptr<PolicyZoneData> d_zoneData{nullptr};
-    uint32_t d_serial{0};
-    uint32_t d_refresh{0};
-  };
-
-  DNSFilterEngine();
-  void clear()
-  {
-    for(auto& z : d_zones) {
-      z->clear();
-    }
-  }
-  void clearZones()
-  {
-    d_zones.clear();
-  }
-  const std::shared_ptr<Zone> getZone(size_t zoneIdx) const
-  {
-    std::shared_ptr<Zone> result{nullptr};
-    if (zoneIdx < d_zones.size()) {
-      result = d_zones[zoneIdx];
-    }
-    return result;
-  }
-  const std::shared_ptr<Zone> getZone(const std::string& name) const
-  {
-    for (const auto& zone : d_zones) {
-      const auto& zName = zone->getName();
-      if (zName == name) {
-        return zone;
-      }
-    }
-    return nullptr;
-  }
-  size_t addZone(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)
-  {
-    if (newZone) {
-      assureZones(zoneIdx);
-      newZone->setPriority(zoneIdx);
-      d_zones[zoneIdx] = newZone;
-    }
-  }
-
-  bool getQueryPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
-  bool getClientPolicy(const ComboAddress& ca, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
-  bool getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
-  bool getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
-  bool getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
-  bool getPostPolicy(const DNSRecord& record, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
-
-  // A few convenience methods for the unit test code
-  Policy getQueryPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies, Priority p) const {
-    Policy policy;
-    policy.d_zoneData = std::make_shared<PolicyZoneData>();
-    policy.d_zoneData->d_priority = p;
-    getQueryPolicy(qname, discardedPolicies, policy);
-    return policy;
-  }
-
-  Policy getClientPolicy(const ComboAddress& ca, const std::unordered_map<std::string,bool>& discardedPolicies, Priority p) const {
-    Policy policy;
-    policy.d_zoneData = std::make_shared<PolicyZoneData>();
-    policy.d_zoneData->d_priority = p;
-    getClientPolicy(ca, discardedPolicies, policy);
-    return policy;
-  }
-
-  Policy getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies, Priority p) const {
-    Policy policy;
-    policy.d_zoneData = std::make_shared<PolicyZoneData>();
-    policy.d_zoneData->d_priority = p;
-    getProcessingPolicy(qname, discardedPolicies, policy);
-    return policy;
-  }
-
-  Policy getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string,bool>& discardedPolicies, Priority p) const {
-    Policy policy;
-    policy.d_zoneData = std::make_shared<PolicyZoneData>();
-    policy.d_zoneData->d_priority = p;
-    getProcessingPolicy(address, discardedPolicies, policy);
-    return policy;
-  }
-
-  Policy getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string,bool>& discardedPolicies, Priority p) const {
-    Policy policy;
-    policy.d_zoneData = std::make_shared<PolicyZoneData>();
-    policy.d_zoneData->d_priority = p;
-    getPostPolicy(records, discardedPolicies, policy);
-    return policy;
-  }
-
-  size_t size() const {
-    return d_zones.size();
-  }
-private:
-  void assureZones(size_t zone);
-  vector<std::shared_ptr<Zone>> d_zones;
-};
-
-void mergePolicyTags(std::unordered_set<std::string>& tags, const std::unordered_set<std::string>& newTags);
index 6e608cbeea4921b0f35252d6f5e1b53852f2adf2..0de4632036ff60ad5a8f3c81b9d857db5350f085 100644 (file)
@@ -6,6 +6,7 @@
 
 #ifdef RECURSOR
 #include "logger.hh"
+#include "logging.hh"
 #else
 #include "dolog.hh"
 #endif
@@ -157,19 +158,16 @@ FrameStreamLogger::~FrameStreamLogger()
   this->cleanup();
 }
 
-void FrameStreamLogger::queueData(const std::string& data)
+RemoteLoggerInterface::Result FrameStreamLogger::queueData(const std::string& data)
 {
   if (!d_ioqueue || !d_iothr) {
-    return;
+    ++d_permanentFailures;
+    return Result::OtherError;
   }
   uint8_t *frame = (uint8_t*)malloc(data.length());
   if (!frame) {
-#ifdef RECURSOR
-    g_log<<Logger::Warning<<"FrameStreamLogger: cannot allocate memory for stream."<<std::endl;
-#else
-    warnlog("FrameStreamLogger: cannot allocate memory for stream.");
-#endif
-    return;
+    ++d_queueFullDrops; // XXX separate count?
+    return Result::TooLarge;
   }
   memcpy(frame, data.c_str(), data.length());
 
@@ -179,23 +177,16 @@ void FrameStreamLogger::queueData(const std::string& data)
   if (res == fstrm_res_success) {
     // Frame successfully queued.
     ++d_framesSent;
+    return Result::Queued;
   } else if (res == fstrm_res_again) {
     free(frame);
-#ifdef RECURSOR
-    g_log<<Logger::Debug<<"FrameStreamLogger: queue full, dropping."<<std::endl;
-#else
-    vinfolog("FrameStreamLogger: queue full, dropping.");
-#endif
     ++d_queueFullDrops;
+    return Result::PipeFull;
  } else {
     // Permanent failure.
     free(frame);
-#ifdef RECURSOR
-    g_log<<Logger::Warning<<"FrameStreamLogger: submitting to queue failed."<<std::endl;
-#else
-    warnlog("FrameStreamLogger: submitting to queue failed.");
-#endif
     ++d_permanentFailures;
+    return Result::OtherError;
   }
 }
 
index a59e194b9deb6d25f0fd5502f2a06bce72ad9a77..3eafb3d997f8a9a6617cc694cb3535d310308339 100644 (file)
@@ -38,12 +38,32 @@ class FrameStreamLogger : public RemoteLoggerInterface, boost::noncopyable
 public:
   FrameStreamLogger(int family, const std::string& address, bool connect, const std::unordered_map<string,unsigned>& options = std::unordered_map<string,unsigned>());
   ~FrameStreamLogger();
-  void queueData(const std::string& data) override;
-  std::string toString() const override
+  [[nodiscard]] RemoteLoggerInterface::Result queueData(const std::string& data) override;
+
+  [[nodiscard]] std::string address() const override
+  {
+    return d_address;
+  }
+
+  [[nodiscard]] std::string name() const override
+  {
+    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)";
   }
 
+  [[nodiscard]] RemoteLoggerInterface::Stats getStats() override
+  {
+    return Stats{.d_queued = d_framesSent,
+                 .d_pipeFull = d_queueFullDrops,
+                 .d_tooLarge = 0,
+                 .d_otherError = d_permanentFailures
+    };
+  }
+
 private:
 
   const int d_family;
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;
diff --git a/pdns/fuzz_yahttp.cc b/pdns/fuzz_yahttp.cc
new file mode 100644 (file)
index 0000000..88ce42b
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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 <yahttp/yahttp.hpp>
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+  try {
+    YaHTTP::AsyncRequestLoader yarl;
+    YaHTTP::Request req;
+
+    yarl.initialize(&req);
+    bool finished = yarl.feed(std::string(reinterpret_cast<const char*>(data), size));
+    if (finished) {
+      yarl.finalize();
+    }
+  }
+  catch (const YaHTTP::ParseError& e) {
+  }
+  catch (const std::exception& e) {
+  }
+  catch (...) {
+  }
+
+  return 0;
+}
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;
 
diff --git a/pdns/gss_context.cc b/pdns/gss_context.cc
new file mode 100644 (file)
index 0000000..1297356
--- /dev/null
@@ -0,0 +1,584 @@
+/*
+ * 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 "gss_context.hh"
+#include "logger.hh"
+
+#ifndef ENABLE_GSS_TSIG
+
+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) {}
+GssContext::GssContext(const DNSName& /* label */) :
+  d_error(GSS_CONTEXT_UNSUPPORTED), d_type(GSS_CONTEXT_NONE) {}
+void GssContext::setLocalPrincipal(const std::string& /* name */) {}
+bool GssContext::getLocalPrincipal(std::string& /* name */) { return false; }
+void GssContext::setPeerPrincipal(const std::string& /* name */) {}
+bool GssContext::getPeerPrincipal(std::string& /* name */) { return false; }
+void GssContext::generateLabel(const std::string& /* suffix */) {}
+void GssContext::setLabel(const DNSName& /* label */) {}
+bool GssContext::init(const std::string& /* input */, std::string& /* output */) { return false; }
+bool GssContext::accept(const std::string& /* input */, std::string& /* output */) { return false; }
+bool GssContext::destroy() { return false; }
+bool GssContext::expired() { return false; }
+bool GssContext::valid() { return false; }
+bool GssContext::sign(const std::string& /* input */, std::string& /* output */) { return false; }
+bool GssContext::verify(const std::string& /* input */, const std::string& /* signature */) { return false; }
+GssContextError GssContext::getError() { return GSS_CONTEXT_UNSUPPORTED; }
+
+#else
+
+#include <unordered_map>
+
+#include "lock.hh"
+
+#define TSIG_GSS_EXPIRE_INTERVAL 60
+
+class GssCredential : boost::noncopyable
+{
+public:
+  GssCredential(const std::string& name, const gss_cred_usage_t usage) :
+    d_nameS(name), d_usage(usage)
+  {
+    gss_buffer_desc buffer;
+
+    if (!name.empty()) {
+      buffer.length = name.size();
+      buffer.value = const_cast<void*>(static_cast<const void*>(name.c_str()));
+      OM_uint32 min;
+      auto maj = gss_import_name(&min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &d_name);
+      if (maj != GSS_S_COMPLETE) {
+        d_name = GSS_C_NO_NAME;
+        d_valid = false;
+        return;
+      }
+    }
+
+    renew();
+  };
+
+  ~GssCredential()
+  {
+    OM_uint32 tmp_min __attribute__((unused));
+    if (d_cred != GSS_C_NO_CREDENTIAL) {
+      (void)gss_release_cred(&tmp_min, &d_cred);
+    }
+    if (d_name != GSS_C_NO_NAME) {
+      (void)gss_release_name(&tmp_min, &d_name);
+    }
+  };
+
+  bool expired() const
+  {
+    if (d_expires == -1) {
+      return false;
+    }
+    return time(nullptr) > d_expires;
+  }
+
+  bool renew()
+  {
+    OM_uint32 time_rec, tmp_maj, tmp_min __attribute__((unused));
+    tmp_maj = gss_acquire_cred(&tmp_min, d_name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, d_usage, &d_cred, nullptr, &time_rec);
+
+    if (tmp_maj != GSS_S_COMPLETE) {
+      d_valid = false;
+      (void)gss_release_name(&tmp_min, &d_name);
+      d_name = GSS_C_NO_NAME;
+      return false;
+    }
+
+    d_valid = true;
+
+    // We do not want forever, but a good time
+    if (time_rec == GSS_C_INDEFINITE) {
+      time_rec = 24 * 60 * 60;
+    }
+    d_expires = time(nullptr) + time_rec;
+
+    return true;
+  }
+
+  bool valid()
+  {
+    return d_valid && !expired();
+  }
+
+  std::string d_nameS;
+  gss_cred_usage_t d_usage;
+  gss_name_t d_name{GSS_C_NO_NAME};
+  gss_cred_id_t d_cred{GSS_C_NO_CREDENTIAL};
+  time_t d_expires{time(nullptr) + 60}; // partly initialized will be cleaned up
+  bool d_valid{false};
+}; // GssCredential
+
+static LockGuarded<std::unordered_map<std::string, std::shared_ptr<GssCredential>>> s_gss_accept_creds;
+static LockGuarded<std::unordered_map<std::string, std::shared_ptr<GssCredential>>> s_gss_init_creds;
+
+class GssSecContext : boost::noncopyable
+{
+public:
+  GssSecContext(std::shared_ptr<GssCredential> cred)
+  {
+    if (!cred->valid()) {
+      throw PDNSException("Invalid credential " + cred->d_nameS);
+    }
+    d_cred = std::move(cred);
+  }
+
+  ~GssSecContext()
+  {
+    OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
+    if (d_ctx != GSS_C_NO_CONTEXT) {
+      tmp_maj = gss_delete_sec_context(&tmp_min, &d_ctx, GSS_C_NO_BUFFER);
+    }
+    if (d_peer_name != GSS_C_NO_NAME) {
+      tmp_maj = gss_release_name(&tmp_min, &(d_peer_name));
+    }
+  }
+
+  std::shared_ptr<GssCredential> d_cred;
+  GssContextType d_type{GSS_CONTEXT_NONE};
+  gss_ctx_id_t d_ctx{GSS_C_NO_CONTEXT};
+  gss_name_t d_peer_name{GSS_C_NO_NAME};
+  time_t d_expires{time(nullptr) + 60}; // partly initialized wil be cleaned up
+
+  enum
+  {
+    GssStateInitial,
+    GssStateNegotiate,
+    GssStateComplete,
+    GssStateError
+  } d_state{GssStateInitial};
+}; // GssSecContext
+
+static LockGuarded<std::unordered_map<DNSName, std::shared_ptr<GssSecContext>>> s_gss_sec_context;
+
+template <typename T>
+static void doExpire(T& m, time_t now)
+{
+  auto lock = m.lock();
+  for (auto i = lock->begin(); i != lock->end();) {
+    if (now > i->second->d_expires) {
+      i = lock->erase(i);
+    }
+    else {
+      ++i;
+    }
+  }
+}
+
+static void expire()
+{
+  static time_t s_last_expired;
+  time_t now = time(nullptr);
+  if (now - s_last_expired < TSIG_GSS_EXPIRE_INTERVAL) {
+    return;
+  }
+  s_last_expired = now;
+  doExpire(s_gss_init_creds, now);
+  doExpire(s_gss_accept_creds, now);
+  doExpire(s_gss_sec_context, now);
+}
+
+bool GssContext::supported() { return true; }
+
+void GssContext::initialize()
+{
+  d_peerPrincipal = "";
+  d_localPrincipal = "";
+  d_error = GSS_CONTEXT_NO_ERROR;
+  d_type = GSS_CONTEXT_NONE;
+}
+
+GssContext::GssContext()
+{
+  initialize();
+  generateLabel("pdns.tsig.");
+}
+
+GssContext::GssContext(const DNSName& label)
+{
+  initialize();
+  setLabel(label);
+}
+
+void GssContext::generateLabel(const std::string& suffix)
+{
+  std::ostringstream oss;
+  oss << std::hex << time(nullptr) << "." << suffix;
+  setLabel(DNSName(oss.str()));
+}
+
+void GssContext::setLabel(const DNSName& label)
+{
+  d_label = label;
+  auto lock = s_gss_sec_context.lock();
+  auto it = lock->find(d_label);
+  if (it != lock->end()) {
+    d_secctx = it->second;
+    d_type = d_secctx->d_type;
+  }
+}
+
+bool GssContext::expired()
+{
+  return (!d_secctx || (d_secctx->d_expires > -1 && d_secctx->d_expires < time(nullptr)));
+}
+
+bool GssContext::valid()
+{
+  return (d_secctx && !expired() && d_secctx->d_state == GssSecContext::GssStateComplete);
+}
+
+bool GssContext::init(const std::string& input, std::string& output)
+{
+  expire();
+
+  OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
+  OM_uint32 maj, min;
+  gss_buffer_desc recv_tok, send_tok, buffer;
+  OM_uint32 flags;
+  OM_uint32 expires;
+
+  if (d_label.empty()) {
+    d_error = GSS_CONTEXT_INVALID;
+    return false;
+  }
+
+  d_type = GSS_CONTEXT_INIT;
+  std::shared_ptr<GssCredential> cred;
+  {
+    auto lock = s_gss_init_creds.lock();
+    auto it = lock->find(d_localPrincipal);
+    if (it == lock->end()) {
+      it = lock->emplace(d_localPrincipal, std::make_shared<GssCredential>(d_localPrincipal, GSS_C_INITIATE)).first;
+    }
+    cred = it->second;
+  }
+
+  // see if we can find a context in non-completed state
+  if (d_secctx) {
+    if (d_secctx->d_state != GssSecContext::GssStateNegotiate) {
+      d_error = GSS_CONTEXT_INVALID;
+      return false;
+    }
+  }
+  else {
+    // make context
+    auto lock = s_gss_sec_context.lock();
+    d_secctx = std::make_shared<GssSecContext>(cred);
+    d_secctx->d_state = GssSecContext::GssStateNegotiate;
+    d_secctx->d_type = d_type;
+    (*lock)[d_label] = d_secctx;
+  }
+
+  recv_tok.length = input.size();
+  recv_tok.value = const_cast<void*>(static_cast<const void*>(input.c_str()));
+
+  if (!d_peerPrincipal.empty()) {
+    buffer.value = const_cast<void*>(static_cast<const void*>(d_peerPrincipal.c_str()));
+    buffer.length = d_peerPrincipal.size();
+    maj = gss_import_name(&min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &(d_secctx->d_peer_name));
+    if (maj != GSS_S_COMPLETE) {
+      processError("gss_import_name", maj, min);
+      return false;
+    }
+  }
+
+  maj = gss_init_sec_context(&min, cred->d_cred, &d_secctx->d_ctx, d_secctx->d_peer_name, GSS_C_NO_OID, GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG, GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, &recv_tok, nullptr, &send_tok, &flags, &expires);
+
+  if (send_tok.length > 0) {
+    output.assign(static_cast<char*>(send_tok.value), send_tok.length);
+    tmp_maj = gss_release_buffer(&tmp_min, &send_tok);
+  }
+
+  if (maj == GSS_S_COMPLETE) {
+    // We do not want forever
+    if (expires == GSS_C_INDEFINITE) {
+      expires = 60;
+    }
+    d_secctx->d_expires = time(nullptr) + expires;
+    d_secctx->d_state = GssSecContext::GssStateComplete;
+    return true;
+  }
+  else if (maj != GSS_S_CONTINUE_NEEDED) {
+    processError("gss_init_sec_context", maj, min);
+  }
+
+  return (maj == GSS_S_CONTINUE_NEEDED);
+}
+
+bool GssContext::accept(const std::string& input, std::string& output)
+{
+  expire();
+
+  OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
+  OM_uint32 maj, min;
+  gss_buffer_desc recv_tok, send_tok;
+  OM_uint32 flags;
+  OM_uint32 expires;
+
+  if (d_label.empty()) {
+    d_error = GSS_CONTEXT_INVALID;
+    return false;
+  }
+
+  d_type = GSS_CONTEXT_ACCEPT;
+  std::shared_ptr<GssCredential> cred;
+  {
+    auto lock = s_gss_accept_creds.lock();
+    auto it = lock->find(d_localPrincipal);
+    if (it == lock->end()) {
+      it = lock->emplace(d_localPrincipal, std::make_shared<GssCredential>(d_localPrincipal, GSS_C_ACCEPT)).first;
+    }
+    cred = it->second;
+  }
+
+  // see if we can find a context in non-completed state
+  if (d_secctx) {
+    if (d_secctx->d_state != GssSecContext::GssStateNegotiate) {
+      d_error = GSS_CONTEXT_INVALID;
+      return false;
+    }
+  }
+  else {
+    // make context
+    auto lock = s_gss_sec_context.lock();
+    d_secctx = std::make_shared<GssSecContext>(cred);
+    d_secctx->d_state = GssSecContext::GssStateNegotiate;
+    d_secctx->d_type = d_type;
+    (*lock)[d_label] = d_secctx;
+  }
+
+  recv_tok.length = input.size();
+  recv_tok.value = const_cast<void*>(static_cast<const void*>(input.c_str()));
+
+  maj = gss_accept_sec_context(&min, &d_secctx->d_ctx, cred->d_cred, &recv_tok, GSS_C_NO_CHANNEL_BINDINGS, &d_secctx->d_peer_name, nullptr, &send_tok, &flags, &expires, nullptr);
+
+  if (send_tok.length > 0) {
+    output.assign(static_cast<char*>(send_tok.value), send_tok.length);
+    tmp_maj = gss_release_buffer(&tmp_min, &send_tok);
+  }
+
+  if (maj == GSS_S_COMPLETE) {
+    // We do not want forever
+    if (expires == GSS_C_INDEFINITE) {
+      expires = 60;
+    }
+    d_secctx->d_expires = time(nullptr) + expires;
+    d_secctx->d_state = GssSecContext::GssStateComplete;
+    return true;
+  }
+  else if (maj != GSS_S_CONTINUE_NEEDED) {
+    processError("gss_accept_sec_context", maj, min);
+  }
+  return (maj == GSS_S_CONTINUE_NEEDED);
+};
+
+bool GssContext::sign(const std::string& input, std::string& output)
+{
+  OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
+  OM_uint32 maj, min;
+
+  gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER;
+  gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER;
+
+  recv_tok.length = input.size();
+  recv_tok.value = const_cast<void*>(static_cast<const void*>(input.c_str()));
+
+  maj = gss_get_mic(&min, d_secctx->d_ctx, GSS_C_QOP_DEFAULT, &recv_tok, &send_tok);
+
+  if (send_tok.length > 0) {
+    output.assign(static_cast<char*>(send_tok.value), send_tok.length);
+    tmp_maj = gss_release_buffer(&tmp_min, &send_tok);
+  }
+
+  if (maj != GSS_S_COMPLETE) {
+    processError("gss_get_mic", maj, min);
+  }
+
+  return (maj == GSS_S_COMPLETE);
+}
+
+bool GssContext::verify(const std::string& input, const std::string& signature)
+{
+  OM_uint32 maj, min;
+
+  gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER;
+  gss_buffer_desc sign_tok = GSS_C_EMPTY_BUFFER;
+
+  recv_tok.length = input.size();
+  recv_tok.value = const_cast<void*>(static_cast<const void*>(input.c_str()));
+  sign_tok.length = signature.size();
+  sign_tok.value = const_cast<void*>(static_cast<const void*>(signature.c_str()));
+
+  maj = gss_verify_mic(&min, d_secctx->d_ctx, &recv_tok, &sign_tok, nullptr);
+
+  if (maj != GSS_S_COMPLETE) {
+    processError("gss_get_mic", maj, min);
+  }
+
+  return (maj == GSS_S_COMPLETE);
+}
+
+bool GssContext::destroy()
+{
+  if (d_label.empty()) {
+    return false;
+  }
+  auto lock = s_gss_sec_context.lock();
+  return lock->erase(d_label) == 1;
+}
+
+void GssContext::setLocalPrincipal(const std::string& name)
+{
+  d_localPrincipal = name;
+}
+
+bool GssContext::getLocalPrincipal(std::string& name)
+{
+  name = d_localPrincipal;
+  return name.size() > 0;
+}
+
+void GssContext::setPeerPrincipal(const std::string& name)
+{
+  d_peerPrincipal = name;
+}
+
+bool GssContext::getPeerPrincipal(std::string& name)
+{
+  gss_buffer_desc value;
+  OM_uint32 maj, min;
+
+  if (d_secctx->d_peer_name != GSS_C_NO_NAME) {
+    maj = gss_display_name(&min, d_secctx->d_peer_name, &value, nullptr);
+    if (maj == GSS_S_COMPLETE && value.length > 0) {
+      name.assign(static_cast<char*>(value.value), value.length);
+      maj = gss_release_buffer(&min, &value);
+      return true;
+    }
+    else {
+      return false;
+    }
+  }
+  else {
+    return false;
+  }
+}
+
+std::tuple<size_t, size_t, size_t> GssContext::getCounts()
+{
+  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)
+{
+  OM_uint32 tmp_min;
+  gss_buffer_desc msg;
+  OM_uint32 msg_ctx;
+
+  msg_ctx = 0;
+  while (1) {
+    ostringstream oss;
+    if (gss_display_status(&tmp_min, maj, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &msg) == GSS_S_COMPLETE) {
+      oss << method << ": " << msg.value;
+    }
+    else {
+      oss << method << ": ?";
+    }
+    if (msg.length != 0) {
+      gss_release_buffer(&tmp_min, &msg);
+    }
+    d_gss_errors.push_back(oss.str());
+    if (!msg_ctx)
+      break;
+  }
+  msg_ctx = 0;
+  while (1) {
+    ostringstream oss;
+    if (gss_display_status(&tmp_min, min, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &msg) == GSS_S_COMPLETE) {
+      oss << method << ": " << msg.value;
+    }
+    else {
+      oss << method << ": ?";
+    }
+    if (msg.length != 0) {
+      gss_release_buffer(&tmp_min, &msg);
+    }
+    d_gss_errors.push_back(oss.str());
+    if (!msg_ctx)
+      break;
+  }
+}
+
+#endif
+
+bool gss_add_signature(const DNSName& context, const std::string& message, std::string& mac)
+{
+  string tmp_mac;
+  GssContext gssctx(context);
+  if (!gssctx.valid()) {
+    g_log << Logger::Error << "GSS context '" << context << "' is not valid" << endl;
+    for (const string& error : gssctx.getErrorStrings()) {
+      g_log << Logger::Error << "GSS error: " << error << endl;
+      ;
+    }
+    return false;
+  }
+
+  if (!gssctx.sign(message, tmp_mac)) {
+    g_log << Logger::Error << "Could not sign message using GSS context '" << context << "'" << endl;
+    for (const string& error : gssctx.getErrorStrings()) {
+      g_log << Logger::Error << "GSS error: " << error << endl;
+      ;
+    }
+    return false;
+  }
+  mac = std::move(tmp_mac);
+  return true;
+}
+
+bool gss_verify_signature(const DNSName& context, const std::string& message, const std::string& mac)
+{
+  GssContext gssctx(context);
+  if (!gssctx.valid()) {
+    g_log << Logger::Error << "GSS context '" << context << "' is not valid" << endl;
+    for (const string& error : gssctx.getErrorStrings()) {
+      g_log << Logger::Error << "GSS error: " << error << endl;
+      ;
+    }
+    return false;
+  }
+
+  if (!gssctx.verify(message, mac)) {
+    g_log << Logger::Error << "Could not verify message using GSS context '" << context << "'" << endl;
+    for (const string& error : gssctx.getErrorStrings()) {
+      g_log << Logger::Error << "GSS error: " << error << endl;
+      ;
+    }
+    return false;
+  }
+  return true;
+}
diff --git a/pdns/gss_context.hh b/pdns/gss_context.hh
new file mode 100644 (file)
index 0000000..ba2e545
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "namespaces.hh"
+#include "pdnsexception.hh"
+#include "dns.hh"
+
+#ifdef ENABLE_GSS_TSIG
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_krb5.h>
+extern bool g_doGssTSIG;
+#endif
+
+//! Generic errors
+enum GssContextError
+{
+  GSS_CONTEXT_NO_ERROR,
+  GSS_CONTEXT_UNSUPPORTED,
+  GSS_CONTEXT_NOT_FOUND,
+  GSS_CONTEXT_NOT_INITIALIZED,
+  GSS_CONTEXT_INVALID,
+  GSS_CONTEXT_EXPIRED,
+  GSS_CONTEXT_ALREADY_INITIALIZED
+};
+
+//! GSS context types
+enum GssContextType
+{
+  GSS_CONTEXT_NONE,
+  GSS_CONTEXT_INIT,
+  GSS_CONTEXT_ACCEPT
+};
+
+class GssSecContext;
+
+/*! Class for representing GSS names, such as host/host.domain.com@REALM.
+ */
+class GssName
+{
+public:
+  //! Initialize to empty name
+  GssName()
+  {
+    setName("");
+  };
+
+  //! Initialize using specific name
+  GssName(const std::string& name)
+  {
+    setName(name);
+  };
+
+#ifdef ENABLE_GSS_TSIG
+  //! Parse name into native representation
+  bool setName(const std::string& name)
+  {
+    gss_buffer_desc buffer;
+    d_name = GSS_C_NO_NAME;
+
+    if (!name.empty()) {
+      buffer.length = name.size();
+      buffer.value = (void*)name.c_str();
+      d_maj = gss_import_name(&d_min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &d_name);
+      return d_maj == GSS_S_COMPLETE;
+    }
+
+    return true;
+  }
+#else
+  bool setName(const std::string& /* name */)
+  {
+    return false;
+  }
+#endif
+
+  ~GssName()
+  {
+#ifdef ENABLE_GSS_TSIG
+    if (d_name != GSS_C_NO_NAME)
+      gss_release_name(&d_min, &d_name);
+#endif
+  };
+
+#ifdef ENABLE_GSS_TSIG
+  //! Compare two Gss Names, if no gss support is compiled in, returns false always
+  //! This is not necessarily same as string comparison between two non-parsed names
+  bool operator==(const GssName& rhs)
+  {
+    OM_uint32 maj, min;
+    int result;
+    maj = gss_compare_name(&min, d_name, rhs.d_name, &result);
+    return (maj == GSS_S_COMPLETE && result != 0);
+  }
+#else
+  bool operator==(const GssName& /* rhs */)
+  {
+    return false;
+  }
+#endif
+
+#ifdef ENABLE_GSS_TSIG
+  //! Compare two Gss Names, if no gss support is compiled in, returns false always
+  //! This is not necessarily same as string comparison between two non-parsed names
+  bool match(const std::string& name)
+  {
+    OM_uint32 maj, min;
+    int result;
+    gss_name_t comp;
+    gss_buffer_desc buffer;
+    buffer.length = name.size();
+    buffer.value = (void*)name.c_str();
+    maj = gss_import_name(&min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &comp);
+    if (maj != GSS_S_COMPLETE)
+      throw PDNSException("Could not import " + name + ": " + std::to_string(maj) + string(",") + std::to_string(min));
+    // do comparison
+    maj = gss_compare_name(&min, d_name, comp, &result);
+    gss_release_name(&min, &comp);
+    return (maj == GSS_S_COMPLETE && result != 0);
+  }
+#else
+  bool match(const std::string& /* name */)
+  {
+    return false;
+  }
+#endif
+
+  //! Check if GSS name was parsed successfully.
+  bool valid()
+  {
+#ifdef ENABLE_GSS_TSIG
+    return d_maj == GSS_S_COMPLETE;
+#else
+    return false;
+#endif
+  }
+
+private:
+#ifdef ENABLE_GSS_TSIG
+  OM_uint32 d_maj, d_min;
+  gss_name_t d_name;
+#endif
+}; // GssName
+
+class GssContext
+{
+public:
+  static std::tuple<size_t, size_t, size_t> getCounts();
+  static bool supported(); //<! Returns true if GSS is supported in the first place
+  GssContext(); //<! Construct new GSS context with random name
+  GssContext(const DNSName& label); //<! Create or open existing named context
+
+  void setLocalPrincipal(const std::string& name); //<! Set our gss name
+  bool getLocalPrincipal(std::string& name); //<! Get our name
+  void setPeerPrincipal(const std::string& name); //<! Set remote name (do not use after negotiation)
+  bool getPeerPrincipal(std::string& name); //<! Return remote name, returns actual name after negotiation
+
+  void generateLabel(const std::string& suffix); //<! Generate random context name using suffix (such as mydomain.com)
+  void setLabel(const DNSName& label); //<! Set context name to this label
+  const DNSName& getLabel() { return d_label; } //<! Return context name
+
+  bool init(const std::string& input, std::string& output); //<! Perform GSS Initiate Security Context handshake
+  bool accept(const std::string& input, std::string& output); //<! Perform GSS Accept Security Context handshake
+  bool destroy(); //<! Release the cached context
+  bool expired(); //<! Check if context is expired
+  bool valid(); //<! Check if context is valid
+
+  bool sign(const std::string& input, std::string& output); //<! Sign something using gss
+  bool verify(const std::string& input, const std::string& signature); //<! Validate gss signature with something
+
+  GssContextError getError(); //<! Get error
+  const std::vector<std::string> getErrorStrings() { return d_gss_errors; } //<! Get native error texts
+private:
+  void release(); //<! Release context
+  void initialize(); //<! Initialize context
+#ifdef ENABLE_GSS_TSIG
+  void processError(const string& method, OM_uint32 maj, OM_uint32 min); //<! Process and fill error text vector
+#endif
+  DNSName d_label; //<! Context name
+  std::string d_peerPrincipal; //<! Remote name
+  std::string d_localPrincipal; //<! Our name
+  GssContextError d_error; //<! Context error
+  GssContextType d_type; //<! Context type
+  std::vector<std::string> d_gss_errors; //<! Native error string(s)
+  std::shared_ptr<GssSecContext> d_secctx; //<! Attached security context
+}; // GssContext
+
+bool gss_add_signature(const DNSName& context, const std::string& message, std::string& mac); //<! Create signature
+bool gss_verify_signature(const DNSName& context, const std::string& message, const std::string& mac); //<! Validate signature
index 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 7cfbcac343e27905ea03b6f9f69822c977903efc..4b9263bc8248e34bdf38a1bd95b3a098af5e36ce 100644 (file)
@@ -21,6 +21,7 @@
  */
 #pragma once
 
+#include <cassert>
 #include <algorithm>
 #include <limits>
 #include <stdexcept>
@@ -35,15 +36,26 @@ namespace pdns
 // By convention, we are using microsecond units
 struct Bucket
 {
+  Bucket(std::string name, uint64_t boundary, uint64_t val) :
+    d_name(std::move(name)), d_boundary(boundary), d_count(val) {}
   const std::string d_name;
-  const uint64_t d_boundary{0};
+  const uint64_t d_boundary;
   mutable uint64_t d_count{0};
+
+  Bucket(const Bucket&) = default;
+  Bucket& operator=(const Bucket& rhs)
+  {
+    assert(d_name == rhs.d_name);
+    assert(d_boundary == rhs.d_boundary);
+    d_count = rhs.d_count;
+    return *this;
+  }
 };
 
 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) :
@@ -147,9 +159,22 @@ public:
     d_sum += d;
   }
 
+  BaseHistogram& operator+=(const BaseHistogram& rhs)
+  {
+    assert(d_name == rhs.d_name);
+    assert(d_buckets.size() == rhs.d_buckets.size());
+    for (size_t bucket = 0; bucket < d_buckets.size(); ++bucket) {
+      assert(d_buckets[bucket].d_name == rhs.d_buckets[bucket].d_name);
+      assert(d_buckets[bucket].d_boundary == rhs.d_buckets[bucket].d_boundary);
+      d_buckets[bucket].d_count += rhs.d_buckets[bucket].d_count;
+    }
+    d_sum += rhs.d_sum;
+    return *this;
+  }
+
 private:
   std::vector<B> d_buckets;
-  const std::string d_name;
+  std::string d_name;
   mutable SumType d_sum{0};
 
   std::vector<uint64_t> to125(uint64_t start, int num)
index 90a2bcc655d2d35c1985f11d98ee68f9be56a928..1d64cc902dd759d6a4f136daba8bb9d5676b68f4 100644 (file)
@@ -1,5 +1,6 @@
 #include "ipcipher.hh"
 #include "ext/ipcrypt/ipcrypt.h"
+#include <cassert>
 #include <openssl/aes.h>
 #include <openssl/evp.h>
 
@@ -11,92 +12,176 @@ int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen,
 */
 std::string makeIPCipherKey(const std::string& password)
 {
-  static const char salt[]="ipcipheripcipher";
+  static const char salt[] = "ipcipheripcipher";
   unsigned char out[16];
 
-  PKCS5_PBKDF2_HMAC_SHA1(password.c_str(), password.size(), (const unsigned char*)salt, sizeof(salt)-1, 50000, sizeof(out), out);
+  PKCS5_PBKDF2_HMAC_SHA1(password.c_str(), password.size(), (const unsigned char*)salt, sizeof(salt) - 1, 50000, sizeof(out), out);
 
   return std::string((const char*)out, (const char*)out + sizeof(out));
 }
 
-static ComboAddress encryptCA4(const ComboAddress& ca, const std::string &key)
+static ComboAddress encryptCA4(const ComboAddress& ca, const std::stringkey)
 {
-  if(key.size() != 16)
+  if (key.size() != 16) {
     throw std::runtime_error("Need 128 bits of key for ipcrypt");
+  }
 
-  ComboAddress ret=ca;
+  ComboAddress ret = ca;
 
   // always returns 0, has no failure mode
-  ipcrypt_encrypt(      (unsigned char*)&ret.sin4.sin_addr.s_addr,
-                 (const unsigned char*)  &ca.sin4.sin_addr.s_addr,
-                 (const unsigned char*)key.c_str());
+  ipcrypt_encrypt((unsigned char*)&ret.sin4.sin_addr.s_addr,
+                  (const unsigned char*)&ca.sin4.sin_addr.s_addr,
+                  (const unsigned char*)key.c_str());
+
   return ret;
 }
 
-static ComboAddress decryptCA4(const ComboAddress& ca, const std::string &key)
+static ComboAddress decryptCA4(const ComboAddress& ca, const std::stringkey)
 {
-  if(key.size() != 16)
+  if (key.size() != 16) {
     throw std::runtime_error("Need 128 bits of key for ipcrypt");
+  }
 
-  ComboAddress ret=ca;
+  ComboAddress ret = ca;
 
   // always returns 0, has no failure mode
-  ipcrypt_decrypt(      (unsigned char*)&ret.sin4.sin_addr.s_addr,
-                 (const unsigned char*)  &ca.sin4.sin_addr.s_addr,
-                 (const unsigned char*)key.c_str());
+  ipcrypt_decrypt((unsigned char*)&ret.sin4.sin_addr.s_addr,
+                  (const unsigned char*)&ca.sin4.sin_addr.s_addr,
+                  (const unsigned char*)key.c_str());
+
   return ret;
 }
 
-
-static ComboAddress encryptCA6(const ComboAddress& ca, const std::string &key)
+static ComboAddress encryptCA6(const ComboAddress& address, const std::string& key)
 {
-  if(key.size() != 16)
+  if (key.size() != 16) {
     throw std::runtime_error("Need 128 bits of key for ipcrypt");
-
-  ComboAddress ret=ca;
-
+  }
+
+  ComboAddress ret = address;
+
+#if OPENSSL_VERSION_MAJOR >= 3
+  auto ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(EVP_CIPHER_CTX_new(), &EVP_CIPHER_CTX_free);
+  if (ctx == nullptr) {
+    throw pdns::OpenSSL::error("encryptCA6: Could not initialize cipher context");
+  }
+
+  auto aes128cbc = std::unique_ptr<EVP_CIPHER, decltype(&EVP_CIPHER_free)>(EVP_CIPHER_fetch(nullptr, "AES-128-CBC", nullptr), &EVP_CIPHER_free);
+
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  if (EVP_EncryptInit(ctx.get(), aes128cbc.get(), reinterpret_cast<const unsigned char*>(key.c_str()), nullptr) == 0) {
+    throw pdns::OpenSSL::error("encryptCA6: Could not initialize encryption algorithm");
+  }
+
+  // Disable padding
+  const auto inSize = sizeof(address.sin6.sin6_addr.s6_addr);
+  static_assert(inSize == 16, "We disable padding and so we must assume a data size of 16 bytes");
+  const auto blockSize = EVP_CIPHER_get_block_size(aes128cbc.get());
+  assert(blockSize == 16);
+  EVP_CIPHER_CTX_set_padding(ctx.get(), 0);
+
+  int updateLen = 0;
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  const auto* input = reinterpret_cast<const unsigned char*>(&address.sin6.sin6_addr.s6_addr);
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  auto* output = reinterpret_cast<unsigned char*>(&ret.sin6.sin6_addr.s6_addr);
+  if (EVP_EncryptUpdate(ctx.get(), output, &updateLen, input, static_cast<int>(inSize)) == 0) {
+    throw pdns::OpenSSL::error("encryptCA6: Could not encrypt address");
+  }
+
+  int finalLen = 0;
+  if (EVP_EncryptFinal_ex(ctx.get(), output + updateLen, &finalLen) == 0) {
+    throw pdns::OpenSSL::error("encryptCA6: Could not finalize address encryption");
+  }
+
+  assert(updateLen + finalLen == inSize);
+#else
   AES_KEY wctx;
   AES_set_encrypt_key((const unsigned char*)key.c_str(), 128, &wctx);
-  AES_encrypt((const unsigned char*)&ca.sin6.sin6_addr.s6_addr,
+  AES_encrypt((const unsigned char*)&address.sin6.sin6_addr.s6_addr,
               (unsigned char*)&ret.sin6.sin6_addr.s6_addr, &wctx);
+#endif
 
   return ret;
 }
 
-static ComboAddress decryptCA6(const ComboAddress& ca, const std::string &key)
+static ComboAddress decryptCA6(const ComboAddress& address, const std::string& key)
 {
-  if(key.size() != 16)
+  if (key.size() != 16) {
     throw std::runtime_error("Need 128 bits of key for ipcrypt");
-
-  ComboAddress ret=ca;
+  }
+
+  ComboAddress ret = address;
+
+#if OPENSSL_VERSION_MAJOR >= 3
+  auto ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(EVP_CIPHER_CTX_new(), &EVP_CIPHER_CTX_free);
+  if (ctx == nullptr) {
+    throw pdns::OpenSSL::error("decryptCA6: Could not initialize cipher context");
+  }
+
+  auto aes128cbc = std::unique_ptr<EVP_CIPHER, decltype(&EVP_CIPHER_free)>(EVP_CIPHER_fetch(nullptr, "AES-128-CBC", nullptr), &EVP_CIPHER_free);
+
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  if (EVP_DecryptInit(ctx.get(), aes128cbc.get(), reinterpret_cast<const unsigned char*>(key.c_str()), nullptr) == 0) {
+    throw pdns::OpenSSL::error("decryptCA6: Could not initialize decryption algorithm");
+  }
+
+  // Disable padding
+  const auto inSize = sizeof(address.sin6.sin6_addr.s6_addr);
+  static_assert(inSize == 16, "We disable padding and so we must assume a data size of 16 bytes");
+  const auto blockSize = EVP_CIPHER_get_block_size(aes128cbc.get());
+  assert(blockSize == 16);
+  EVP_CIPHER_CTX_set_padding(ctx.get(), 0);
+
+  int updateLen = 0;
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  const auto* input = reinterpret_cast<const unsigned char*>(&address.sin6.sin6_addr.s6_addr);
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  auto* output = reinterpret_cast<unsigned char*>(&ret.sin6.sin6_addr.s6_addr);
+  if (EVP_DecryptUpdate(ctx.get(), output, &updateLen, input, static_cast<int>(inSize)) == 0) {
+    throw pdns::OpenSSL::error("decryptCA6: Could not decrypt address");
+  }
+
+  int finalLen = 0;
+  if (EVP_DecryptFinal_ex(ctx.get(), output + updateLen, &finalLen) == 0) {
+    throw pdns::OpenSSL::error("decryptCA6: Could not finalize address decryption");
+  }
+
+  assert(updateLen + finalLen == inSize);
+#else
   AES_KEY wctx;
   AES_set_decrypt_key((const unsigned char*)key.c_str(), 128, &wctx);
-  AES_decrypt((const unsigned char*)&ca.sin6.sin6_addr.s6_addr,
+  AES_decrypt((const unsigned char*)&address.sin6.sin6_addr.s6_addr,
               (unsigned char*)&ret.sin6.sin6_addr.s6_addr, &wctx);
+#endif
 
   return ret;
 }
 
-
 ComboAddress encryptCA(const ComboAddress& ca, const std::string& key)
 {
-  if(ca.sin4.sin_family == AF_INET)
+  if (ca.sin4.sin_family == AF_INET) {
     return encryptCA4(ca, key);
-  else if(ca.sin4.sin_family == AF_INET6)
+  }
+
+  if (ca.sin4.sin_family == AF_INET6) {
     return encryptCA6(ca, key);
-  else
-    throw std::runtime_error("ipcrypt can't encrypt non-IP addresses");
+  }
+
+  throw std::runtime_error("ipcrypt can't encrypt non-IP addresses");
 }
 
 ComboAddress decryptCA(const ComboAddress& ca, const std::string& key)
 {
-  if(ca.sin4.sin_family == AF_INET)
+  if (ca.sin4.sin_family == AF_INET) {
     return decryptCA4(ca, key);
-  else if(ca.sin4.sin_family == AF_INET6)
+  }
+
+  if (ca.sin4.sin_family == AF_INET6) {
     return decryptCA6(ca, key);
-  else
-    throw std::runtime_error("ipcrypt can't decrypt non-IP addresses");
+  }
 
+  throw std::runtime_error("ipcrypt can't decrypt non-IP addresses");
 }
 
 #endif /* HAVE_IPCIPHER */
index 64037de21265ce1104bcc6a67eb2d991aab96f1d..ce944e6622305cd68f27b3e8e370dc3162655413 100644 (file)
@@ -7,7 +7,7 @@
 // see https://powerdns.org/ipcipher
 
 #ifdef HAVE_IPCIPHER
-ComboAddress encryptCA(const ComboAddress& ca, const std::string& key);
-ComboAddress decryptCA(const ComboAddress& ca, const std::string& key);
+ComboAddress encryptCA(const ComboAddress& address, const std::string& key);
+ComboAddress decryptCA(const ComboAddress& address, const std::string& key);
 std::string makeIPCipherKey(const std::string& password);
 #endif /* HAVE_IPCIPHER */
index 42522bdebaa719de11757fb301c116efd5d88ea6..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)
@@ -147,7 +148,7 @@ int SSetsockopt(int sockfd, int level, int opname, int value)
   return ret;
 }
 
-void setSocketIgnorePMTU(int sockfd, int family)
+void setSocketIgnorePMTU([[maybe_unused]] int sockfd, [[maybe_unused]] int family)
 {
   if (family == AF_INET) {
 #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
@@ -191,6 +192,27 @@ void setSocketIgnorePMTU(int sockfd, int family)
   }
 }
 
+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)
 {
@@ -295,30 +317,6 @@ int sendOnNBSocket(int fd, const struct msghdr *msgh)
   return sendErr;
 }
 
-ssize_t sendfromto(int sock, const void* data, size_t len, int flags, const ComboAddress& from, const ComboAddress& to)
-{
-  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);
-}
-
 // be careful: when using this for receive purposes, make sure addr->sin4.sin_family is set appropriately so getSocklen works!
 // be careful: when using this function for *send* purposes, be sure to set cbufsize to 0!
 // be careful: if you don't call addCMsgSrcAddr after fillMSGHdr, make sure to set msg_control to NULL
@@ -540,11 +538,13 @@ void setSocketBuffer(int fd, int optname, uint32_t size)
   uint32_t psize = 0;
   socklen_t len = sizeof(psize);
 
-  if (!getsockopt(fd, SOL_SOCKET, optname, &psize, &len) && psize > size) {
-    throw std::runtime_error("Not decreasing socket buffer size from " + std::to_string(psize) + " to " + std::to_string(size));
+  if (getsockopt(fd, SOL_SOCKET, optname, &psize, &len) != 0) {
+    throw std::runtime_error("Unable to retrieve socket buffer size:" + stringerror());
   }
-
-  if (setsockopt(fd, SOL_SOCKET, optname, &size, sizeof(size)) < 0) {
+  if (psize >= size) {
+    return;
+  }
+  if (setsockopt(fd, SOL_SOCKET, optname, &size, sizeof(size)) != 0) {
     throw std::runtime_error("Unable to raise socket buffer size to " + std::to_string(size) + ": " + stringerror());
   }
 }
@@ -559,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;
@@ -580,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;
   }
@@ -608,6 +642,80 @@ 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
+
+#ifdef HAVE_GETIFADDRS
+static uint8_t convertNetmaskToBits(const uint8_t* mask, socklen_t len)
+{
+  if (mask == nullptr || len > 16) {
+    throw std::runtime_error("Invalid parameters passed to convertNetmaskToBits");
+  }
+
+  uint8_t result = 0;
+  // for all bytes in the address (4 for IPv4, 16 for IPv6)
+  for (size_t idx = 0; idx < len; idx++) {
+    uint8_t byte = *(mask + idx);
+    // count the number of bits set
+    while (byte > 0) {
+      result += (byte & 1);
+      byte >>= 1;
+    }
+  }
+  return result;
+}
+#endif /* HAVE_GETIFADDRS */
+
+#ifdef HAVE_GETIFADDRS
+std::vector<Netmask> getListOfRangesOfNetworkInterface(const std::string& itf)
+{
+  std::vector<Netmask> result;
+  struct ifaddrs *ifaddr = nullptr;
+  if (getifaddrs(&ifaddr) == -1) {
+    return result;
+  }
+
+  for (struct ifaddrs *ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
+    if (ifa->ifa_name == nullptr || strcmp(ifa->ifa_name, itf.c_str()) != 0) {
+      continue;
+    }
+    if (ifa->ifa_addr == nullptr || (ifa->ifa_addr->sa_family != AF_INET && ifa->ifa_addr->sa_family != AF_INET6)) {
+      continue;
+    }
+    ComboAddress addr;
+    try {
+      addr.setSockaddr(ifa->ifa_addr, ifa->ifa_addr->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6));
+    }
+    catch (...) {
+      continue;
+    }
+
+    if (ifa->ifa_addr->sa_family == AF_INET) {
+      auto netmask = reinterpret_cast<const struct sockaddr_in*>(ifa->ifa_netmask);
+      uint8_t maskBits = convertNetmaskToBits(reinterpret_cast<const uint8_t*>(&netmask->sin_addr.s_addr), sizeof(netmask->sin_addr.s_addr));
+      result.emplace_back(addr, maskBits);
+    }
+    else if (ifa->ifa_addr->sa_family == AF_INET6) {
+      auto netmask = reinterpret_cast<const struct sockaddr_in6*>(ifa->ifa_netmask);
+      uint8_t maskBits = convertNetmaskToBits(reinterpret_cast<const uint8_t*>(&netmask->sin6_addr.s6_addr), sizeof(netmask->sin6_addr.s6_addr));
+      result.emplace_back(addr, maskBits);
+    }
+  }
+
+  freeifaddrs(ifaddr);
+  return result;
+}
+#else
+std::vector<Netmask> getListOfRangesOfNetworkInterface(const std::string& /* itf */)
+{
+  std::vector<Netmask> result;
+  return result;
+}
+#endif                          // HAVE_GETIFADDRS
index 225532cb7c8ef1ceb978970ba80373d7c45018e7..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!
@@ -283,6 +302,32 @@ union ComboAddress {
       return "invalid "+stringerror();
   }
 
+  [[nodiscard]] string toStringReversed() const
+  {
+    if (isIPv4()) {
+      const auto ip = ntohl(sin4.sin_addr.s_addr);
+      auto a = (ip >> 0) & 0xFF;
+      auto b = (ip >> 8) & 0xFF;
+      auto c = (ip >> 16) & 0xFF;
+      auto d = (ip >> 24) & 0xFF;
+      return std::to_string(a) + "." + std::to_string(b) + "." + std::to_string(c) + "." + std::to_string(d);
+    }
+    else {
+      const auto* addr = &sin6.sin6_addr;
+      std::stringstream res{};
+      res << std::hex;
+      for (int i = 15; i >= 0; i--) {
+        auto byte = addr->s6_addr[i];
+        res << ((byte >> 0) & 0xF) << ".";
+        res << ((byte >> 4) & 0xF);
+        if (i != 0) {
+          res << ".";
+        }
+      }
+      return res.str();
+    }
+  }
+
   string toStringWithPort() const
   {
     if(sin4.sin_family==AF_INET)
@@ -306,6 +351,11 @@ union ComboAddress {
     return toStringWithPortExcept(53);
   }
 
+  [[nodiscard]] string toStructuredLogString() const
+  {
+    return toStringWithPort();
+  }
+
   string toByteString() const
   {
     if (isIPv4()) {
@@ -316,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);
@@ -457,12 +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(bits);
+  }
+  Netmask(const sockaddr_in6* network, uint8_t bits = 0xff): d_network(network)
+  {
+    d_network.sin4.sin_port = 0;
+    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);
@@ -657,12 +720,28 @@ public:
     return d_network.getBit(bit);
   }
 
+  struct Hash {
+    size_t operator()(const Netmask& nm) const
+    {
+      return burtle(&nm.d_bits, 1, ComboAddress::addressOnlyHash()(nm.d_network));
+    }
+  };
+
 private:
   ComboAddress d_network;
   uint32_t d_mask;
   uint8_t d_bits;
 };
 
+namespace std {
+  template<>
+  struct hash<Netmask> {
+    auto operator()(const Netmask& nm) const {
+      return Netmask::Hash{}(nm);
+    }
+  };
+}
+
 /** Binary tree map implementation with <Netmask,T> pair.
  *
  * This is an binary tree implementation for storing attributes for IPv4 and IPv6 prefixes.
@@ -886,15 +965,21 @@ private:
 
   void copyTree(const NetmaskTree& rhs)
   {
-    TreeNode *node;
-
-    node = rhs.d_root.get();
-    if (node != nullptr)
-      node = node->traverse_l();
-    while (node != nullptr) {
-      if (node->assigned)
-        insert(node->node.first).second = node->node.second;
-      node = node->traverse_lnr();
+    try {
+      TreeNode *node = rhs.d_root.get();
+      if (node != nullptr)
+        node = node->traverse_l();
+      while (node != nullptr) {
+        if (node->assigned)
+          insert(node->node.first).second = node->node.second;
+        node = node->traverse_lnr();
+      }
+    }
+    catch (const NetmaskException&) {
+      abort();
+    }
+    catch (const std::logic_error&) {
+      abort();
     }
   }
 
@@ -1124,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;
@@ -1199,7 +1284,7 @@ public:
   }
 
   //<! checks whether the container is empty.
-  bool empty() const {
+  [[nodiscard]] bool empty() const {
     return (d_size == 0);
   }
 
@@ -1225,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);
@@ -1233,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())
@@ -1295,8 +1381,7 @@ private:
 class NetmaskGroup
 {
 public:
-  NetmaskGroup() noexcept {
-  }
+  NetmaskGroup() noexcept = default;
 
   //! If this IP address is matched by any of the classes within
 
@@ -1358,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())
@@ -1392,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)
@@ -1444,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;
@@ -1626,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)
@@ -1639,7 +1736,6 @@ bool HarvestDestinationAddress(const struct msghdr* msgh, ComboAddress* destinat
 bool HarvestTimestamp(struct msghdr* msgh, struct timeval* tv);
 void fillMSGHdr(struct msghdr* msgh, struct iovec* iov, cmsgbuf_aligned* cbuf, size_t cbufsize, char* data, size_t datalen, ComboAddress* addr);
 int sendOnNBSocket(int fd, const struct msghdr *msgh);
-ssize_t sendfromto(int sock, const void* data, size_t len, int flags, const ComboAddress& from, const ComboAddress& to);
 size_t sendMsgWithOptions(int fd, const char* buffer, size_t len, const ComboAddress* dest, const ComboAddress* local, unsigned int localItf, int flags);
 
 /* requires a non-blocking, connected TCP socket */
@@ -1650,9 +1746,12 @@ ComboAddress parseIPAndPort(const std::string& input, uint16_t port);
 
 std::set<std::string> getListOfNetworkInterfaces();
 std::vector<ComboAddress> getListOfAddressesOfNetworkInterface(const std::string& itf);
+std::vector<Netmask> getListOfRangesOfNetworkInterface(const std::string& itf);
 
 /* These functions throw if the value was already set to a higher value,
    or on error */
 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 d299568488d0afaa1e000b41fc4d462253af3015..ac041cb0090566925727072a9d1ecba659ff43a9 100644 (file)
@@ -27,7 +27,7 @@
 #include "tsigverifier.hh"
 
 vector<pair<vector<DNSRecord>, vector<DNSRecord> > > processIXFRRecords(const ComboAddress& primary, const DNSName& zone,
-                                                                        const vector<DNSRecord>& records, const std::shared_ptr<SOARecordContent>& primarySOA)
+                                                                        const vector<DNSRecord>& records, const std::shared_ptr<const SOARecordContent>& primarySOA)
 {
   vector<pair<vector<DNSRecord>, vector<DNSRecord> > >  ret;
 
@@ -123,9 +123,14 @@ 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!
-vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAddress& primary, const DNSName& zone, const DNSRecord& oursr, 
-                                                                   const TSIGTriplet& tt, const ComboAddress* laddr, size_t maxReceivedBytes)
+ // 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)
 {
+  // Auth documents xfrTimeout to be a max idle time (sets totalTimeout=false)
+  // Rec documents it to be a total XFR time (sets totalTimeout=true)
+  //
   vector<pair<vector<DNSRecord>, vector<DNSRecord> > >  ret;
   vector<uint8_t> packet;
   DNSPacketWriter pw(packet, zone, QType::IXFR);
@@ -133,7 +138,7 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAd
   pw.getHeader()->rd=0;
   pw.getHeader()->id=dns_random_uint16();
   pw.startRecord(zone, QType::SOA, 0, QClass::IN, DNSResourceRecord::AUTHORITY);
-  oursr.d_content->toPacket(pw);
+  oursr.getContent()->toPacket(pw);
 
   pw.commit();
   TSIGRecordContent trc;
@@ -157,12 +162,31 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAd
   msg.append((const char*)&packet[0], packet.size());
 
   Socket s(primary.sin4.sin_family, SOCK_STREAM);
-  //  cout<<"going to connect"<<endl;
-  if(laddr)
+  if (laddr != nullptr) {
     s.bind(*laddr);
-  s.connect(primary);
-  //  cout<<"Connected"<<endl;
-  s.writen(msg);
+  }
+  s.setNonBlocking();
+
+  const time_t xfrStart = time(nullptr);
+
+  // Helper function: if we have a total timeout, check it and set elapsed to the total time taken sofar,
+  // otherwise set elapsed to 0, making the total time limit ineffective
+  const auto timeoutChecker = [=] () -> time_t {
+    time_t elapsed = 0;
+    if (totalTimeout) {
+      elapsed = time(nullptr) - xfrStart;
+      if (elapsed >= xfrTimeout) {
+        throw std::runtime_error("Reached the maximum elapsed time in an IXFR delta for zone '" + zone.toLogString() + "' from primary " + primary.toStringWithPort());
+      }
+    }
+    return elapsed;
+  };
+
+  s.connect(primary, xfrTimeout);
+
+  time_t elapsed = timeoutChecker();
+  // coverity[store_truncates_time_t]
+  s.writenWithTimeout(msg.data(), msg.size(), xfrTimeout - elapsed);
 
   // CURRENT PRIMARY SOA
   // REPEAT:
@@ -170,45 +194,69 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAd
   //   RECORDS TO REMOVE
   //   SOA WHERE THIS DELTA GOES
   //   RECORDS TO ADD
-  // CURRENT PRIMARY SOA 
-  std::shared_ptr<SOARecordContent> primarySOA = nullptr;
+  // CURRENT PRIMARY SOA
+  std::shared_ptr<const SOARecordContent> primarySOA = nullptr;
   vector<DNSRecord> records;
   size_t receivedBytes = 0;
-  int8_t ixfrInProgress = -2;
   std::string reply;
 
-  for(;;) {
-    // IXFR end
-    if (ixfrInProgress >= 0)
+  enum transferStyle { Unknown, AXFR, IXFR } style = Unknown;
+  const unsigned int expectedSOAForAXFR = 2;
+  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;
+    }
+    if (style == IXFR && primarySOACount == expectedSOAForIXFR) {
+      state = "IXFRdone";
+      break;
+    }
 
-    if(s.read((char*)&len, sizeof(len)) != sizeof(len))
+    elapsed = timeoutChecker();
+    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);
-    //    cout<<"Got chunk of "<<len<<" bytes"<<endl;
-    if(!len)
+    len = ntohs(len);
+    if (len == 0) {
+      state = "zeroLen";
       break;
+    }
+    // Currently no more break statements after this
 
-    if (maxReceivedBytes > 0 && (maxReceivedBytes - receivedBytes) < (size_t) len)
+    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());
+    }
 
     reply.resize(len);
-    readn2(s.getHandle(), &reply.at(0), len);
+
+    elapsed = timeoutChecker();
+    const struct timeval remainingTime = { .tv_sec = xfrTimeout - elapsed, .tv_usec = 0 };
+    const struct timeval idleTime = remainingTime;
+    readn2WithTimeout(s.getHandle(), reply.data(), len, idleTime, remainingTime, false);
     receivedBytes += len;
 
     MOADNSParser mdp(false, reply);
-    if(mdp.d_header.rcode) 
+    if (mdp.d_header.rcode) {
       throw std::runtime_error("Got an error trying to IXFR zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort()+"': "+RCode::to_s(mdp.d_header.rcode));
+    }
 
-    //    cout<<"Got a response, rcode: "<<mdp.d_header.rcode<<", got "<<mdp.d_answers.size()<<" answers"<<endl;
-
-    if(!tt.algo.empty()) { // TSIG verify message
+    if (!tt.algo.empty()) { // TSIG verify message
       tsigVerifier.check(reply, mdp);
     }
 
-    for(auto& r: mdp.d_answers) {
-      //      cout<<r.first.d_name<< " " <<r.first.d_content->getZoneRepresentation()<<endl;
+    for (auto& r: mdp.d_answers) {
       if(!primarySOA) {
         // we have not seen the first SOA record yet
         if (r.first.d_type != QType::SOA) {
@@ -220,21 +268,36 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAd
           throw std::runtime_error("Error getting the content of the first SOA record of the IXFR answer for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort()+"'");
         }
 
-        if(sr->d_st.serial == std::dynamic_pointer_cast<SOARecordContent>(oursr.d_content)->d_st.serial) {
+        if(sr->d_st.serial == getRR<SOARecordContent>(oursr)->d_st.serial) {
           // 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);
         if (!sr) {
           throw std::runtime_error("Error getting the content of SOA record of IXFR answer for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort()+"'");
         }
 
-        // we hit the last SOA record
-        // IXFR is considered to be done if we hit the last SOA record twice
+        // we hit a marker SOA record
         if (primarySOA->d_st.serial == sr->d_st.serial) {
-          ixfrInProgress++;
+          ++primarySOACount;
+        }
+      }
+      // When we see the 2nd record, we can decide what the style is
+      if (records.size() == 1 && style == Unknown) {
+        if (r.first.d_type != QType::SOA) {
+          // Non-empty AXFR style has a non-SOA record following the first SOA
+          style = AXFR;
+        }
+        else if (primarySOACount == expectedSOAForAXFR) {
+          // Empty zone AXFR style: start SOA is immediately followed by end marker SOA
+          style = AXFR;
+        }
+        else {
+          // IXFR has a 2nd SOA (with different serial) following the first
+          style = IXFR;
         }
       }
 
@@ -245,7 +308,7 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAd
         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);
@@ -253,7 +316,21 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAd
     }
   }
 
-  //  cout<<"Got "<<records.size()<<" records"<<endl;
+  switch (style) {
+  case IXFR:
+    if (primarySOACount != expectedSOAForIXFR) {
+      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 (primarySOACount=" + std::to_string(primarySOACount) + ")  for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort() + " state=" + state);
+    }
+    break;
+  case Unknown:
+    throw std::runtime_error("Incomplete XFR (primarySOACount=" + std::to_string(primarySOACount) + ") for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort() + " state=" + state);
+    break;
+  }
 
   return processIXFRRecords(primary, zone, records, primarySOA);
 }
index 341fb82f7b7ee3212832a3791061e15ba8fb375a..bdbabd3d8db42540f788b0fe07a1095362b179af 100644 (file)
 #include "dnsparser.hh"
 #include "dnsrecords.hh"
 
-vector<pair<vector<DNSRecord>, vector<DNSRecord> > >   getIXFRDeltas(const ComboAddress& primary, const DNSName& zone, 
-                                                                     const DNSRecord& sr, const TSIGTriplet& tt=TSIGTriplet(),
-                                                                     const ComboAddress* laddr=0, size_t maxReceivedBytes=0);
+vector<pair<vector<DNSRecord>, vector<DNSRecord>>>   getIXFRDeltas(const ComboAddress& primary, const DNSName& zone, 
+                                                                   const DNSRecord& sr,
+                                                                   uint16_t xfrTimeout = 0, bool totalTime = false,
+                                                                   const TSIGTriplet& tt=TSIGTriplet(),
+                                                                   const ComboAddress* laddr=0, size_t maxReceivedBytes=0);
 
-vector<pair<vector<DNSRecord>, vector<DNSRecord> > > processIXFRRecords(const ComboAddress& primary, const DNSName& zone,
-                                                                        const vector<DNSRecord>& records, const std::shared_ptr<SOARecordContent>& primarySOA);
+vector<pair<vector<DNSRecord>, vector<DNSRecord>>> processIXFRRecords(const ComboAddress& primary, const DNSName& zone,
+                                                                      const vector<DNSRecord>& records, const std::shared_ptr<const SOARecordContent>& primarySOA);
index 3aa9720568417bf8be91b3412974508d57d4d96b..a1592d6f339d332889152c9e3b2245a534528786 100644 (file)
  */
 
 #include "ixfrdist-stats.hh"
+#include "misc.hh"
 
 std::string ixfrdistStats::getStats() {
   std::stringstream stats;
   const std::string prefix = "ixfrdist_";
 
-  stats<<"# HELP "<<prefix<<"uptime_seconds The uptime of the process"<<std::endl;
-  stats<<"# TYPE "<<prefix<<"uptime_seconds gauge"<<std::endl;
+  stats<<"# HELP "<<prefix<<"uptime_seconds The uptime of the process (in seconds)"<<std::endl;
+  stats<<"# TYPE "<<prefix<<"uptime_seconds counter"<<std::endl;
   stats<<prefix<<"uptime_seconds "<<time(nullptr) - progStats.startTime<<std::endl;
 
+  stats<<"# HELP "<<prefix<<"sys_msec Number of msec spent in system time"<<std::endl;
+  stats<<"# TYPE "<<prefix<<"sys_msec counter"<<std::endl;
+  stats<<prefix<<"sys_msec "<<getCPUTimeSystem("")<<std::endl;
+
+  stats<<"# HELP "<<prefix<<"user_msec Number of msec spent in user time"<<std::endl;
+  stats<<"# TYPE "<<prefix<<"user_msec counter"<<std::endl;
+  stats<<prefix<<"user_msec "<<getCPUTimeUser("")<<std::endl;
+
+  stats<<"# HELP "<<prefix<<"fd_usage Number of open file descriptors"<<std::endl;
+  stats<<"# TYPE "<<prefix<<"fd_usage gauge"<<std::endl;
+  stats<<prefix<<"fd_usage "<<getOpenFileDescriptors("")<<std::endl;
+
+  stats<<"# HELP "<<prefix<<"real_memory_usage Actual unique use of memory in bytes (approx)"<<std::endl;
+  stats<<"# TYPE "<<prefix<<"real_memory_usage gauge"<<std::endl;
+  stats<<prefix<<"real_memory_usage "<<getRealMemoryUsage("")<<std::endl;
+
   stats<<"# HELP "<<prefix<<"domains The amount of configured domains"<<std::endl;
   stats<<"# TYPE "<<prefix<<"domains gauge"<<std::endl;
   stats<<prefix<<"domains "<<domainStats.size()<<std::endl;
 
-  uint64_t numSOAChecks{0}, numSOAChecksFailed{0}, numSOAinQueries{0}, numIXFRinQueries{0}, numAXFRinQueries{0}, numAXFRFailures{0}, numIXFRFailures{0};
-  bool helpAdded{false};
+  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 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 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;
+    stats<<"# HELP "<<prefix<<"axfr_inqueries_total Number of times an AXFR query was received"<<std::endl;
+    stats<<"# TYPE "<<prefix<<"axfr_inqueries_total counter"<<std::endl;
+    stats<<"# HELP "<<prefix<<"axfr_failures_total Number of times an AXFR query was not properly answered"<<std::endl;
+    stats<<"# TYPE "<<prefix<<"axfr_failures_total counter"<<std::endl;
+    stats<<"# HELP "<<prefix<<"ixfr_inqueries_total Number of times an IXFR query was received"<<std::endl;
+    stats<<"# TYPE "<<prefix<<"ixfr_inqueries_total counter"<<std::endl;
+    stats<<"# HELP "<<prefix<<"ixfr_failures_total Number of times an IXFR query was not properly answered"<<std::endl;
+    stats<<"# TYPE "<<prefix<<"ixfr_failures_total counter"<<std::endl;
+  }
+
   for (auto const &d : domainStats) {
-    if (!helpAdded) {
-      stats<<"# HELP "<<prefix<<"soa_serial The SOA serial number of a domain"<<std::endl;
-      stats<<"# TYPE "<<prefix<<"soa_serial gauge"<<std::endl;
-    }
     if(d.second.haveZone)
-      stats<<prefix<<"soa_serial{domain="<<d.first<<"} "<<d.second.currentSOA<<std::endl;
+      stats<<prefix<<"soa_serial{domain=\""<<d.first<<"\"} "<<d.second.currentSOA<<std::endl;
     else
-      stats<<prefix<<"soa_serial{domain="<<d.first<<"} NaN"<<std::endl;
+      stats<<prefix<<"soa_serial{domain=\""<<d.first<<"\"} NaN"<<std::endl;
 
-    if (!helpAdded) {
-      stats<<"# HELP "<<prefix<<"soa_checks Number of times a SOA check at the master was attempted"<<std::endl;
-      stats<<"# TYPE "<<prefix<<"soa_checks counter"<<std::endl;
-    }
-    stats<<prefix<<"soa_checks{domain="<<d.first<<"} "<<d.second.numSOAChecks<<std::endl;
-    numSOAChecks += d.second.numSOAChecks;
-
-    if (!helpAdded) {
-      stats<<"# HELP "<<prefix<<"soa_checks_failed Number of times a SOA check at the master failed"<<std::endl;
-      stats<<"# TYPE "<<prefix<<"soa_checks_failed counter"<<std::endl;
-    }
-    stats<<prefix<<"soa_checks_failed{domain="<<d.first<<"} "<<d.second.numSOAChecksFailed<<std::endl;
-    numSOAChecksFailed += d.second.numSOAChecksFailed;
-
-    if (!helpAdded) {
-      stats<<"# HELP "<<prefix<<"soa_inqueries Number of times a SOA query was received"<<std::endl;
-      stats<<"# TYPE "<<prefix<<"soa_inqueries counter"<<std::endl;
-    }
-    stats<<prefix<<"soa_inqueries{domain="<<d.first<<"} "<<d.second.numSOAinQueries<<std::endl;
-    numSOAinQueries += d.second.numSOAinQueries;
-
-    if (!helpAdded) {
-      stats<<"# HELP "<<prefix<<"axfr_inqueries Number of times an AXFR query was received"<<std::endl;
-      stats<<"# TYPE "<<prefix<<"axfr_inqueries counter"<<std::endl;
-    }
-    stats<<prefix<<"axfr_inqueries{domain="<<d.first<<"} "<<d.second.numAXFRinQueries<<std::endl;
-    numAXFRinQueries += d.second.numAXFRinQueries;
+    stats<<prefix<<"soa_checks_total{domain=\""<<d.first<<"\"} "<<d.second.numSOAChecks<<std::endl;
+    stats<<prefix<<"soa_checks_failed_total{domain=\""<<d.first<<"\"} "<<d.second.numSOAChecksFailed<<std::endl;
+    stats<<prefix<<"soa_inqueries_total{domain=\""<<d.first<<"\"} "<<d.second.numSOAinQueries<<std::endl;
+    stats<<prefix<<"axfr_inqueries_total{domain=\""<<d.first<<"\"} "<<d.second.numAXFRinQueries<<std::endl;
+    stats<<prefix<<"axfr_failures_total{domain=\""<<d.first<<"\"} "<<d.second.numAXFRFailures<<std::endl;
+    stats<<prefix<<"ixfr_inqueries_total{domain=\""<<d.first<<"\"} "<<d.second.numIXFRinQueries<<std::endl;
+    stats<<prefix<<"ixfr_failures_total{domain=\""<<d.first<<"\"} "<<d.second.numIXFRFailures<<std::endl;
+  }
 
-    if (!helpAdded) {
-      stats<<"# HELP "<<prefix<<"axfr_failures Number of times an AXFR query was not properly answered"<<std::endl;
-      stats<<"# TYPE "<<prefix<<"axfr_failures counter"<<std::endl;
-    }
-    stats<<prefix<<"axfr_failures{domain="<<d.first<<"} "<<d.second.numAXFRFailures<<std::endl;
-    numAXFRFailures += d.second.numAXFRFailures;
+  if (!notimpStats.empty()) {
+    stats<<"# HELP "<<prefix<<"notimp An unimplemented opcode"<<std::endl;
+    stats<<"# TYPE "<<prefix<<"notimp counter"<<std::endl;
+  }
 
-    if (!helpAdded) {
-      stats<<"# HELP "<<prefix<<"ixfr_inqueries Number of times an IXFR query was received"<<std::endl;
-      stats<<"# TYPE "<<prefix<<"ixfr_inqueries counter"<<std::endl;
-    }
-    stats<<prefix<<"ixfr_inqueries{domain="<<d.first<<"} "<<d.second.numIXFRinQueries<<std::endl;
-    numIXFRinQueries += d.second.numIXFRinQueries;
+  for (std::size_t i = 0; i < notimpStats.size() ; i++) {
+    auto val = notimpStats.at(i).load();
 
-    if (!helpAdded) {
-      stats<<"# HELP "<<prefix<<"ixfr_failures Number of times an IXFR query was not properly answered"<<std::endl;
-      stats<<"# TYPE "<<prefix<<"ixfr_failures counter"<<std::endl;
+    if (val > 0) {
+      stats<<prefix<<"notimp{opcode=\""<<Opcode::to_s(i)<<"\"} "<<val<<std::endl;
     }
-    stats<<prefix<<"ixfr_failures{domain="<<d.first<<"} "<<d.second.numIXFRFailures<<std::endl;
-    numIXFRFailures += d.second.numIXFRFailures;
-    helpAdded = true;
   }
 
-  stats<<prefix<<"soa_checks "<<numSOAChecks<<std::endl;
-  stats<<prefix<<"soa_checks_failed "<<numSOAChecksFailed<<std::endl;
-  stats<<prefix<<"soa_inqueries "<<numSOAinQueries<<std::endl;
-  stats<<prefix<<"axfr_inqueries "<<numAXFRinQueries<<std::endl;
-  stats<<prefix<<"ixfr_inqueries "<<numIXFRinQueries<<std::endl;
-  stats<<prefix<<"axfr_failures "<<numAXFRFailures<<std::endl;
-  stats<<prefix<<"ixfr_failures "<<numIXFRFailures<<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;
+
   return stats.str();
 }
index addfe060030e2b48e97280a478a98ec25a2bf9da..69f48f9458d88e1f846641ae54b06259939a82e1 100644 (file)
@@ -24,6 +24,7 @@
 #include <map>
 #include <string>
 
+#include "dns.hh"
 #include "dnsname.hh"
 #include "pdnsexception.hh"
 
@@ -64,28 +65,41 @@ class ixfrdistStats {
     void registerDomain(const DNSName& d) {
       domainStats[d].haveZone = false;
     }
+
+    void incrementUnknownDomainInQueries(const DNSName& /* d */)
+    { // the name is ignored. It would be great to report it, but we don't want to blow up Prometheus
+      progStats.unknownDomainInQueries += 1;
+    }
+
+    void incrementNotImplemented(uint8_t opcode)
+    {
+      notimpStats.at(opcode) ++;
+    }
+
   private:
     class perDomainStat {
       public:
         bool                  haveZone;
-        std::atomic<uint32_t> currentSOA; // NOTE: this will wrongly be zero for unavailable zones
+        std::atomic<uint32_t> currentSOA{0}; // NOTE: this will wrongly be zero for unavailable zones
 
-        std::atomic<uint32_t> numSOAChecks;
-        std::atomic<uint32_t> numSOAChecksFailed;
+        std::atomic<uint32_t> numSOAChecks{0};
+        std::atomic<uint32_t> numSOAChecksFailed{0};
 
-        std::atomic<uint64_t> numSOAinQueries;
-        std::atomic<uint64_t> numAXFRinQueries;
-        std::atomic<uint64_t> numIXFRinQueries;
+        std::atomic<uint64_t> numSOAinQueries{0};
+        std::atomic<uint64_t> numAXFRinQueries{0};
+        std::atomic<uint64_t> numIXFRinQueries{0};
 
-        std::atomic<uint64_t> numAXFRFailures;
-        std::atomic<uint64_t> numIXFRFailures;
+        std::atomic<uint64_t> numAXFRFailures{0};
+        std::atomic<uint64_t> numIXFRFailures{0};
     };
     class programStats {
       public:
         time_t startTime;
+        std::atomic<uint32_t> unknownDomainInQueries{0};
     };
 
     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 aea08ee066fbab3867b39866e814d314587aa5cc..47203b7cc84b26c6ea18f09f28c2f2b47c268bbd 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()
 {
@@ -124,8 +142,8 @@ struct convert<Netmask> {
 } // namespace YAML
 
 struct ixfrdiff_t {
-  shared_ptr<SOARecordContent> oldSOA;
-  shared_ptr<SOARecordContent> newSOA;
+  shared_ptr<const SOARecordContent> oldSOA;
+  shared_ptr<const SOARecordContent> newSOA;
   vector<DNSRecord> removals;
   vector<DNSRecord> additions;
   uint32_t oldSOATTL;
@@ -133,7 +151,7 @@ struct ixfrdiff_t {
 };
 
 struct ixfrinfo_t {
-  shared_ptr<SOARecordContent> soa; // The SOA of the latest AXFR
+  shared_ptr<const SOARecordContent> soa; // The SOA of the latest AXFR
   records_t latestAXFR;             // The most recent AXFR
   vector<std::shared_ptr<ixfrdiff_t>> ixfrDiffs;
   uint32_t soaTTL;
@@ -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;
@@ -227,7 +258,7 @@ static void cleanUpDomain(const DNSName& domain, const uint16_t& keep, const str
   }
 }
 
-static void getSOAFromRecords(const records_t& records, shared_ptr<SOARecordContent>& soa, uint32_t& soaTTL) {
+static void getSOAFromRecords(const records_t& records, shared_ptr<const SOARecordContent>& soa, uint32_t& soaTTL) {
   for (const auto& dnsrecord : records) {
     if (dnsrecord.d_type == QType::SOA) {
       soa = getRR<SOARecordContent>(dnsrecord);
@@ -241,7 +272,7 @@ static void getSOAFromRecords(const records_t& records, shared_ptr<SOARecordCont
   throw PDNSException("No SOA in supplied records");
 }
 
-static void makeIXFRDiff(const records_t& from, const records_t& to, std::shared_ptr<ixfrdiff_t>& diff, const shared_ptr<SOARecordContent>& fromSOA = nullptr, uint32_t fromSOATTL=0, const shared_ptr<SOARecordContent>& toSOA = nullptr, uint32_t toSOATTL = 0) {
+static void makeIXFRDiff(const records_t& from, const records_t& to, std::shared_ptr<ixfrdiff_t>& diff, const shared_ptr<const SOARecordContent>& fromSOA = nullptr, uint32_t fromSOATTL=0, const shared_ptr<const SOARecordContent>& toSOA = nullptr, uint32_t toSOATTL = 0) {
   set_difference(from.cbegin(), from.cend(), to.cbegin(), to.cend(), back_inserter(diff->removals), from.value_comp());
   set_difference(to.cbegin(), to.cend(), from.cbegin(), from.cend(), back_inserter(diff->additions), from.value_comp());
   diff->oldSOA = fromSOA;
@@ -270,7 +301,93 @@ 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, 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
+      sendNotification(remote.sin4.sin_family == AF_INET ? sock4 : sock6, domain, remote, notificationId);
+    } 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);
+
+  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));
+  }
+  closesocket(sock4);
+  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;
 
@@ -282,7 +399,7 @@ static void updateThread(const string& workdir, const uint16_t& keep, const uint
     try {
       g_log<<Logger::Info<<"Trying to initially load domain "<<domain<<" from disk"<<endl;
       auto serial = getSerialFromDir(dir);
-      shared_ptr<SOARecordContent> soa;
+      shared_ptr<const SOARecordContent> soa;
       uint32_t soaTTL;
       {
         string fname = workdir + "/" + domain.toString() + "/" + std::to_string(serial);
@@ -330,32 +447,42 @@ static void updateThread(const string& workdir, const uint16_t& keep, const uint
       }
 
       DNSName domain = domainConfig.first;
-      shared_ptr<SOARecordContent> current_soa;
+      shared_ptr<const SOARecordContent> current_soa;
       const auto& zoneInfo = getCurrentZoneInfo(domain);
       if (zoneInfo != nullptr) {
         current_soa = zoneInfo->soa;
       }
 
       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;
-      shared_ptr<SOARecordContent> sr;
+      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,21 +490,21 @@ 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
-      shared_ptr<SOARecordContent> soa;
+      shared_ptr<const SOARecordContent> soa;
       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 +574,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 +592,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;
+  }
 
-  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})");
+  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;
+
+  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 +688,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 +723,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 +742,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,7 +751,22 @@ static bool makeRefusedPacket(const MOADNSParser& mdp, vector<uint8_t>& packet)
   return true;
 }
 
-static vector<uint8_t> getSOAPacket(const MOADNSParser& mdp, const shared_ptr<SOARecordContent>& soa, uint32_t soaTTL) {
+/*
+ * 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);
   pw.getHeader()->id = mdp.d_header.id;
@@ -573,7 +794,7 @@ static bool sendPacketOverTCP(int fd, const std::vector<uint8_t>& packet)
 static bool addRecordToWriter(DNSPacketWriter& pw, const DNSName& zoneName, const DNSRecord& record, bool compress)
 {
   pw.startRecord(record.d_name + zoneName, record.d_type, record.d_ttl, QClass::IN, DNSResourceRecord::ANSWER, compress);
-  record.d_content->toPacket(pw);
+  record.getContent()->toPacket(pw);
   if (pw.size() > 16384) {
     pw.rollback();
     return false;
@@ -640,7 +861,7 @@ static bool handleAXFR(int fd, const MOADNSParser& mdp) {
     return false;
   }
 
-  shared_ptr<SOARecordContent> soa = zoneInfo->soa;
+  shared_ptr<const SOARecordContent> soa = zoneInfo->soa;
   uint32_t soaTTL = zoneInfo->soaTTL;
   const records_t& records = zoneInfo->latestAXFR;
 
@@ -665,7 +886,7 @@ static bool handleAXFR(int fd, const MOADNSParser& mdp) {
 /* Produces an IXFR if one can be made according to the rules in RFC 1995 and
  * creates a SOA or AXFR packet when required by the RFC.
  */
-static bool handleIXFR(int fd, const ComboAddress& destination, const MOADNSParser& mdp, const shared_ptr<SOARecordContent>& clientSOA) {
+static bool handleIXFR(int fd, const MOADNSParser& mdp, const shared_ptr<const SOARecordContent>& clientSOA) {
   vector<std::shared_ptr<ixfrdiff_t>> toSend;
 
   /* we get a shared pointer of the zone info that we can't modify, ever.
@@ -717,24 +938,34 @@ static bool handleIXFR(int fd, const ComboAddress& destination, const MOADNSPars
     return handleAXFR(fd, mdp);
   }
 
-  std::vector<std::vector<uint8_t>> packets;
-  for (const auto& diff : toSend) {
-    /* An IXFR packet's ANSWER section looks as follows:
-     * SOA new_serial
-     * SOA old_serial
-     * ... removed records ...
-     * SOA new_serial
-     * ... added records ...
-     * SOA new_serial
-     */
 
+  /* An IXFR packet's ANSWER section looks as follows:
+    * SOA latest_serial C
+
+    First set of changes:
+    * SOA requested_serial A
+    * ... removed records ...
+    * SOA intermediate_serial B
+    * ... added records ...
+
+    Next set of changes:
+    * SOA intermediate_serial B
+    * ... removed records ...
+    * SOA latest_serial C
+    * ... added records ...
+
+    * SOA latest_serial C
+    */
+
+  const auto latestSOAPacket = getSOAPacket(mdp, zoneInfo->soa, zoneInfo->soaTTL);
+  if (!sendPacketOverTCP(fd, latestSOAPacket)) {
+    return false;
+  }
+
+  for (const auto& diff : toSend) {
     const auto newSOAPacket = getSOAPacket(mdp, diff->newSOA, diff->newSOATTL);
     const auto oldSOAPacket = getSOAPacket(mdp, diff->oldSOA, diff->oldSOATTL);
 
-    if (!sendPacketOverTCP(fd, newSOAPacket)) {
-      return false;
-    }
-
     if (!sendPacketOverTCP(fd, oldSOAPacket)) {
       return false;
     }
@@ -750,20 +981,26 @@ static bool handleIXFR(int fd, const ComboAddress& destination, const MOADNSPars
     if (!sendRecordsOverTCP(fd, mdp, diff->additions)) {
       return false;
     }
+  }
 
-    if (!sendPacketOverTCP(fd, newSOAPacket)) {
-      return false;
-    }
+  if (!sendPacketOverTCP(fd, latestSOAPacket)) {
+    return false;
   }
 
   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;
@@ -786,14 +1023,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
@@ -807,8 +1059,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) {
@@ -817,6 +1075,10 @@ static void handleUDPRequest(int fd, boost::any&) {
   }
   return;
 }
+catch(std::exception& e) {
+  return;
+}
+
 
 static void handleTCPRequest(int fd, boost::any&) {
   ComboAddress saddr;
@@ -836,7 +1098,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;
@@ -888,13 +1152,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 (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;
       }
 
-      if (mdp.d_qtype == QType::SOA) {
-        vector<uint8_t> packet;
+      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);
@@ -914,7 +1202,7 @@ static void tcpWorker(int tid) {
          *  query, but with the query type being IXFR and the authority section
          *  containing the SOA record of client's version of the zone.
          */
-        shared_ptr<SOARecordContent> clientSOA;
+        shared_ptr<const SOARecordContent> clientSOA;
         for (auto &answer : mdp.d_answers) {
           // from dnsparser.hh:
           // typedef vector<pair<DNSRecord, uint16_t > > answers_t;
@@ -932,7 +1220,7 @@ static void tcpWorker(int tid) {
           continue;
         }
 
-        if (!handleIXFR(cfd, saddr, mdp, clientSOA)) {
+        if (!handleIXFR(cfd, mdp, clientSOA)) {
           close(cfd);
           continue;
         }
@@ -1091,15 +1379,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;
@@ -1152,13 +1451,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()
@@ -1174,16 +1491,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;
@@ -1200,195 +1526,331 @@ 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);
+          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);
+        }
+        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;
+        }
+      }
+    }
 
-  set<int> allSockets;
-  for (const auto& addr : config["listen"].as<vector<ComboAddress>>()) {
-    for (const auto& stype : {SOCK_DGRAM, SOCK_STREAM}) {
+    if (config["gid"].IsDefined()) {
+      auto gid = config["gid"].as<string>();
       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
-        }
-        fdm->addReadFD(s, stype == SOCK_DGRAM ? handleUDPRequest : handleTCPRequest);
-        allSockets.insert(s);
-      } catch(runtime_error &e) {
-        g_log<<Logger::Error<<e.what()<<endl;
+        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 c2efb8ca857e7f5cce55e5365a677e434d0731fa..a1b07220a3f1070cdda0f724f877401db9f37e07 100644 (file)
@@ -4,7 +4,7 @@ Documentation=man:ixfrdist(1)
 Documentation=man:ixfrdist.yml(5)
 Documentation=https://doc.powerdns.com
 Wants=network-online.target
-After=network-online.target
+After=network-online.target time-sync.target
 
 [Service]
 Type=simple
@@ -34,6 +34,11 @@ RestrictRealtime=true
 RestrictSUIDSGID=true
 SystemCallArchitectures=native
 SystemCallFilter=~ @clock @debug @module @mount @raw-io @reboot @swap @cpu-emulation @obsolete
+ProtectProc=invisible
+PrivateIPC=true
+RemoveIPC=true
+DevicePolicy=closed
+MemoryDenyWriteExecute=true
 
 [Install]
 WantedBy=multi-user.target
index 58b5463562c228f1e7cdde174109ffefd51b8b2c..5aa5912ed2d88d4a7dba87c985c7362d9167c8e4 100644 (file)
 
 #include <cinttypes>
 #include <dirent.h>
-#include <errno.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<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;
 }
 
@@ -97,7 +104,7 @@ uint32_t getSerialFromRecords(const records_t& records, DNSRecord& soaret)
   auto found = records.equal_range(std::tie(g_rootdnsname, t));
 
   for(auto iter = found.first; iter != found.second; ++iter) {
-    auto soa = std::dynamic_pointer_cast<SOARecordContent>(iter->d_content);
+    auto soa = getRR<SOARecordContent>(*iter);
     if (soa) {
       soaret = *iter;
       return soa->d_st.serial;
@@ -113,7 +120,7 @@ static void writeRecords(FILE* fp, const records_t& records)
             r.d_name.isRoot() ? "@" :  r.d_name.toStringNoDot().c_str(),
             r.d_ttl,
             DNSRecordContent::NumberToType(r.d_type).c_str(),
-            r.d_content->getZoneRepresentation().c_str()) < 0) {
+            r.getContent()->getZoneRepresentation().c_str()) < 0) {
       throw runtime_error(stringerror());
     }
   }
@@ -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 = std::unique_ptr<FILE, int(*)(FILE*)>(fopen((fname+".partial").c_str(), "w"), fclose);
+  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);
@@ -187,7 +197,7 @@ void loadZoneFromDisk(records_t& records, const string& fname, const DNSName& zo
  * Load the zone `zone` from `fname` and put the first found SOA into `soa`
  * Does NOT check for nullptr
  */
-void loadSOAFromDisk(const DNSName& zone, const string& fname, shared_ptr<SOARecordContent>& soa, uint32_t& soaTTL)
+void loadSOAFromDisk(const DNSName& zone, const string& fname, shared_ptr<const SOARecordContent>& soa, uint32_t& soaTTL)
 {
   ZoneParserTNG zpt(fname, zone);
   zpt.disableGenerate();
index bce8c6b48fd900d7599ab91253aed18246713ab2..10285029c0d3383a0dd1726625cc1c01bd5d58c3 100644 (file)
@@ -26,6 +26,7 @@
 #include <boost/multi_index_container.hpp>
 #include <boost/multi_index/key_extractors.hpp>
 #include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/mem_fun.hpp>
 
 #include "dnsparser.hh"
 #include "dnsrecords.hh"
@@ -34,7 +35,7 @@ using namespace boost::multi_index;
 
 struct CIContentCompareStruct
 {
-  bool operator()(const shared_ptr<DNSRecordContent>&a, const shared_ptr<DNSRecordContent>& b) const
+  bool operator()(const shared_ptr<const DNSRecordContent>&a, const shared_ptr<const DNSRecordContent>& b) const
   {
     return toLower(a->getZoneRepresentation()) < toLower(b->getZoneRepresentation());
   }
@@ -49,15 +50,15 @@ typedef multi_index_container <
                       member<DNSRecord, DNSName, &DNSRecord::d_name>,
                       member<DNSRecord, uint16_t, &DNSRecord::d_type>,
                       member<DNSRecord, uint16_t, &DNSRecord::d_class>,
-                      member<DNSRecord, shared_ptr<DNSRecordContent>, &DNSRecord::d_content> >,
+                      BOOST_MULTI_INDEX_CONST_MEM_FUN(DNSRecord, const shared_ptr<const DNSRecordContent>&, getContent) >,
         composite_key_compare<CanonDNSNameCompare, std::less<uint16_t>, std::less<uint16_t>, CIContentCompareStruct >
       > /* ordered_non_uniquw */
     > /* indexed_by */
 > /* multi_index_container */ records_t;
 
-uint32_t getSerialFromMaster(const ComboAddress& master, const DNSName& zone, shared_ptr<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);
 void loadZoneFromDisk(records_t& records, const string& fname, const DNSName& zone);
-void loadSOAFromDisk(const DNSName& zone, const string& fname, shared_ptr<SOARecordContent>& soa, uint32_t& soaTTL);
+void loadSOAFromDisk(const DNSName& zone, const string& fname, shared_ptr<const SOARecordContent>& soa, uint32_t& soaTTL);
index e72ce55df2b48ff8e3fb1232949028318384c4e9..a8e0c3cba1b321eddbd933de840128c6178711ec 100644 (file)
@@ -32,6 +32,7 @@
 #include "dnssecinfra.hh"
 
 #include "dns_random.hh"
+#include "gss_context.hh"
 #include <boost/multi_index_container.hpp>
 #include "axfr-retriever.hh"
 #include <fstream>
@@ -86,12 +87,12 @@ int main(int argc, char** argv) {
 
       set_difference(before.cbegin(), before.cend(), after.cbegin(), after.cend(), back_inserter(diff), before.value_comp());
       for(const auto& d : diff) {
-        cout<<'-'<< (d.d_name+zone) <<" IN "<<DNSRecordContent::NumberToType(d.d_type)<<" "<<d.d_content->getZoneRepresentation()<<endl;
+        cout<<'-'<< (d.d_name+zone) <<" IN "<<DNSRecordContent::NumberToType(d.d_type)<<" "<<d.getContent()->getZoneRepresentation()<<endl;
       }
       diff.clear();
       set_difference(after.cbegin(), after.cend(), before.cbegin(), before.cend(), back_inserter(diff), before.value_comp());
       for(const auto& d : diff) {
-        cout<<'+'<< (d.d_name+zone) <<" IN "<<DNSRecordContent::NumberToType(d.d_type)<<" "<<d.d_content->getZoneRepresentation()<<endl;
+        cout<<'+'<< (d.d_name+zone) <<" IN "<<DNSRecordContent::NumberToType(d.d_type)<<" "<<d.getContent()->getZoneRepresentation()<<endl;
       }
       exit(1);
     }
@@ -100,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.
@@ -108,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;
 
@@ -140,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;
@@ -176,16 +177,17 @@ int main(int argc, char** argv) {
 
       cout<<"Checking for update, our serial number is "<<ourSerial<<".. ";
       cout.flush();
-      shared_ptr<SOARecordContent> sr;
-      uint32_t serial = getSerialFromMaster(master, zone, sr, tt);
+      shared_ptr<const SOARecordContent> sr;
+      uint32_t serial = getSerialFromPrimary(primary, zone, sr, tt);
       if(ourSerial == serial) {
-        cout<<"still up to date, their serial is "<<serial<<", sleeping "<<sr->d_st.refresh<<" seconds"<<endl;
-        sleep(sr->d_st.refresh);
+        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, tt);
+      auto deltas = getIXFRDeltas(primary, zone, ourSoa, 20, false, tt);
       cout<<"Got "<<deltas.size()<<" deltas, applying.."<<endl;
 
       for(const auto& delta : deltas) {
@@ -197,7 +199,7 @@ int main(int argc, char** argv) {
         uint32_t newserial=0;
         for(const auto& rr : add) {
           if(rr.d_type == QType::SOA) {
-            newserial=std::dynamic_pointer_cast<SOARecordContent>(rr.d_content)->d_st.serial;
+            newserial=getRR<SOARecordContent>(rr)->d_st.serial;
           }
         }
 
@@ -212,8 +214,8 @@ int main(int argc, char** argv) {
         bool stop=false;
 
         for(const auto& rr : remove) {
-          report<<'-'<< (rr.d_name+zone) <<" IN "<<DNSRecordContent::NumberToType(rr.d_type)<<" "<<rr.d_content->getZoneRepresentation()<<endl;
-          auto range = records.equal_range(std::tie(rr.d_name, rr.d_type, rr.d_class, rr.d_content));
+          report<<'-'<< (rr.d_name+zone) <<" IN "<<DNSRecordContent::NumberToType(rr.d_type)<<" "<<rr.getContent()->getZoneRepresentation()<<endl;
+          auto range = records.equal_range(std::tie(rr.d_name, rr.d_type, rr.d_class, rr.getContent()));
           if(range.first == range.second) {
             cout<<endl<<" !! Could not find record "<<rr.d_name<<" to remove!!"<<endl;
             //   stop=true;
@@ -223,7 +225,7 @@ int main(int argc, char** argv) {
         }
 
         for(const auto& rr : add) {
-          report<<'+'<< (rr.d_name+zone) <<" IN "<<DNSRecordContent::NumberToType(rr.d_type)<<" "<<rr.d_content->getZoneRepresentation()<<endl;
+          report<<'+'<< (rr.d_name+zone) <<" IN "<<DNSRecordContent::NumberToType(rr.d_type)<<" "<<rr.getContent()->getZoneRepresentation()<<endl;
           records.insert(rr);
         }
         if(stop) {
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);
diff --git a/pdns/keyroller/Pipfile b/pdns/keyroller/Pipfile
new file mode 100644 (file)
index 0000000..75bc29a
--- /dev/null
@@ -0,0 +1,16 @@
+[[source]]
+url = "https://pypi.python.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+PyYAML = "*"
+pytimeparse = "*"
+requests = "*"
+json-tricks = "*"
+nose = "*"
+
+[dev-packages]
+
+[requires]
+python_version = "3.9"
diff --git a/pdns/keyroller/Pipfile.lock b/pdns/keyroller/Pipfile.lock
new file mode 100644 (file)
index 0000000..095a721
--- /dev/null
@@ -0,0 +1,134 @@
+{
+    "_meta": {
+        "hash": {
+            "sha256": "4e2f92539310263746373e8e7767e75dd601122d6497f7dbc650e81fea92f719"
+        },
+        "pipfile-spec": 6,
+        "requires": {
+            "python_version": "3.9"
+        },
+        "sources": [
+            {
+                "name": "pypi",
+                "url": "https://pypi.python.org/simple",
+                "verify_ssl": true
+            }
+        ]
+    },
+    "default": {
+        "certifi": {
+            "hashes": [
+                "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082",
+                "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"
+            ],
+            "index": "pypi",
+            "markers": "python_version >= '3.6'",
+            "version": "==2023.7.22"
+        },
+        "charset-normalizer": {
+            "hashes": [
+                "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597",
+                "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"
+            ],
+            "markers": "python_version >= '3'",
+            "version": "==2.0.12"
+        },
+        "idna": {
+            "hashes": [
+                "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
+                "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"
+            ],
+            "markers": "python_version >= '3'",
+            "version": "==3.4"
+        },
+        "json-tricks": {
+            "hashes": [
+                "sha256:3432a602773b36ff0fe5b94a74f5de8612c843a256724e15c32f9f669844b6ef",
+                "sha256:bdf7d8677bccea722984be7f68946a981e4f50c21901e292d71b9c0c60a4ace3"
+            ],
+            "index": "pypi",
+            "version": "==3.15.5"
+        },
+        "nose": {
+            "hashes": [
+                "sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac",
+                "sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a",
+                "sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98"
+            ],
+            "index": "pypi",
+            "version": "==1.3.7"
+        },
+        "pytimeparse": {
+            "hashes": [
+                "sha256:04b7be6cc8bd9f5647a6325444926c3ac34ee6bc7e69da4367ba282f076036bd",
+                "sha256:e86136477be924d7e670646a98561957e8ca7308d44841e21f5ddea757556a0a"
+            ],
+            "index": "pypi",
+            "version": "==1.1.8"
+        },
+        "pyyaml": {
+            "hashes": [
+                "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf",
+                "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
+                "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
+                "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
+                "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b",
+                "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4",
+                "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07",
+                "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba",
+                "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9",
+                "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
+                "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
+                "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
+                "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782",
+                "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
+                "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
+                "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
+                "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
+                "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
+                "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1",
+                "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
+                "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
+                "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
+                "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
+                "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
+                "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
+                "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d",
+                "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
+                "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
+                "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7",
+                "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
+                "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
+                "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
+                "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358",
+                "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
+                "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
+                "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
+                "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
+                "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f",
+                "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
+                "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
+            ],
+            "index": "pypi",
+            "version": "==6.0"
+        },
+        "requests": {
+            "hashes": [
+                "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61",
+                "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"
+            ],
+            "index": "pypi",
+            "version": "==2.27.1"
+        },
+        "urllib3": {
+            "hashes": [
+                "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.18"
+        }
+    },
+    "develop": {}
+}
diff --git a/pdns/keyroller/README.md b/pdns/keyroller/README.md
new file mode 100644 (file)
index 0000000..2c08edb
--- /dev/null
@@ -0,0 +1,158 @@
+# PDNS Keyroll Daemon
+
+## Configuration
+
+See `pdns-keyroller.conf.example`
+
+## pdns-keyroller
+
+Main util that should be run periodically (crontab job for instance). Will list all configured automatic rolls
+and proceed to scheduled operations (start a new roll, advanced roll steps).
+
+## pdns-keyroller-ctl
+
+You can configure a zone for automatic keyroll using `pdns-keyroller-ctl`
+
+    # use the domain defaults defined in the configuration file
+    $ pdns-keyroller-ctl configs roll example.com
+
+    # Specify ZSK and KSK rollover frequency
+    $ pdns-keyroller-ctl configs roll example.com \
+        --zsk-frequency 6w --ksk-frequency never
+
+    # Overwrite an existing configuration
+    $ pdns-keyroller-ctl configs roll example.com --force \
+        --zsk-frequency 6w --ksk-frequency never
+
+    # Look at an existing configuration
+    $ pdns-keyroller-ctl configs show example.com
+
+
+You can now list the configured zones and see last roll informations using
+
+    # use the domain defaults defined in the configuration file
+    $ pdns-keyroller-ctl configs
+    INFO:pdns-keyroller:example.com. is not rolling. Last KSK roll was
+    never and the last ZSK roll was never
+
+Some steps require manual actions such as KSK roll and publishing new DS to the parent. You can list such zones
+
+    $ pdns-keyroller-ctl roll waiting
+
+And advance to the next step when you have published the new DS, waiting `TTL` seconds
+
+    $ pdns-keyroller-ctl roll step <ZONE> <TTL>
+
+Removed :
+- NSEC3 param roll
+- keystyle roll
+
+## Dev environment
+
+Setting up the environment
+
+    $ virtualenv .venv
+    $ source .venv/bin/activate
+    $ pip install -r requirements.txt
+
+
+## Packaging
+
+For now, only `centos-7` `<target>` is supported
+
+    $ git submodule update --init --recursive
+    $ bash builder/build.sh <target>
+
+## Meta content
+
+Zone configuration and roll status is persisted through the domain metadata system provided by the authoritative server. The format used for the content is JSON.
+
+### Zone configuration
+
+Zone configuration is attached as a meta data with the key `X-PDNSKEYROLLER-CONFIG`
+
+``` json
+    {
+       "key_style" : "split",
+       "ksk_algo" : 13,
+       "ksk_frequency" : "6w",
+       "ksk_keysize" : 3069,
+       "ksk_method" : "prepublish",
+       "version" : 1,
+       "zsk_algo" : 13,
+       "zsk_frequency" : 0,
+       "zsk_keysize" : 3069,
+       "zsk_method" : "prepublish"
+    }
+```
+
+* `version` : this json format version identifier
+* `key_style` : `single` or `split` depending on the number of keys
+* `xsk_algo` : algorithm to roll as name or number, see bellow
+* `xsk_frequency` : the rate at which to roll the keys
+* `xsk_keysize` : keysize in bits
+* `xsk_method` : strategy for the rollover (for now, only `prepublish` is supported)
+
+Frequency is parsed as time expressions like the following :
+
+* `6 weeks`, `6w`
+* `120 days`, `120d`
+* `1w 3d 2h 32m`
+
+Supported algorithms are :
+
+*  1: `RSAMD5`
+*  2: `DH`
+*  3: `DSA`
+*  5: `RSASHA1`
+*  6: `DSA-NSEC3-SHA1`
+*  7: `RSASHA1-NSEC3-SHA1`
+*  8: `RSASHA256`
+* 10: `RSASHA512`
+* 12: `ECC-GOST`
+* 13: `ECDSAP256`
+* 14: `ECDSAP384`
+* 15: `ED25519`
+* 16: `ED448`
+
+### Roll status
+
+Roll status is attached as a meta data with the key `X-PDNSKEYROLLER-STATE`
+
+``` json
+{
+   "current_roll" : {
+      "__instance_type__" : [
+         "pdnskeyroller.prepublishkeyroll",
+         "PrePublishKeyRoll"
+      ],
+      "attributes" : {
+         "algo" : "ECDSAP256",
+         "complete" : false,
+         "current_step" : 1,
+         "current_step_datetime" : 1650635957.26533,
+         "keytype" : "ksk",
+         "new_keyid" : 6,
+         "old_keyids" : [
+            4
+         ],
+         "rolltype" : "prepublish",
+         "step_datetimes" : [
+            1650632357.26229
+         ]
+      }
+   },
+   "last_ksk_roll_datetime" : 0,
+   "last_zsk_roll_datetime" : 0,
+   "version" : 1
+}
+```
+
+* `current_roll` contains informations about the actual roll
+* `current_roll.complete` tells if the roll is finished
+* `current_roll.current_step` is the step number
+* `current_roll.current_step_datetime` tells when the step has to be performed
+* `current_roll.new_keyid` contains the identifier of the new generated key when `old_keyids` contains the keys that are being replaced
+* `current_roll.step_datetimes` contains timestamp at which the steps have been performed
+* `last_xsk_roll_datetime` contains the timestamp of the last keyroll
+* `version` contains a document format identifier
diff --git a/pdns/keyroller/pdns-keyroller-ctl.py b/pdns/keyroller/pdns-keyroller-ctl.py
new file mode 100755 (executable)
index 0000000..1e43cc8
--- /dev/null
@@ -0,0 +1,215 @@
+#!/usr/bin/env python3
+import argparse
+import logging
+import sys
+from pdnskeyroller import domainstate, domainconfig, keyrollerdomain
+from pdnskeyroller.config import KeyrollerConfig
+from pdnskeyroller.prepublishkeyroll import PrePublishKeyRoll
+from pdnsapi.api import PDNSApi
+from datetime import datetime, timedelta
+import random
+
+logger = logging.getLogger('pdns-keyroller')
+
+def display_keyrollerdomain_infos(zone, api):
+    zoneconf = keyrollerdomain.KeyrollerDomain(zone, api)
+    if zoneconf.state :
+        if zoneconf.state.is_rolling:
+            timeleft = zoneconf.state.current_roll.current_step_datetime - datetime.now()
+            logger.info(
+                '{} is rolling its {} using the {} method. It is in the step {}, which was made {}. Next step scheduled {}'.format(
+                    zone, zoneconf.state.current_roll.keytype.upper(),
+                    zoneconf.state.current_roll.rolltype, zoneconf.state.current_roll.current_step_name,
+                    zoneconf.state.current_roll.step_datetimes[-1],
+                    "in {}".format(timeleft) if timeleft > timedelta(0) else "ASAP"
+                )
+            )
+        else:
+            logger.info('{} is not rolling. Last KSK roll was {} and the last ZSK roll was {}'.format(
+                zone, zoneconf.state.last_ksk_roll_str, zoneconf.state.last_zsk_roll_str))
+    else :
+        logger.info('{} is not rolling'.format(zone))
+
+if __name__ == '__main__':
+    argp = argparse.ArgumentParser(
+        prog='pdns-keyroller-ctl', formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+        description='PowerDNS DNSSEC key-roller')
+    argp.add_argument('--config', '-c', metavar='PATH', type=str, default='/etc/powerdns/pdns-keyroller.conf',
+                      help='Load this configuration file')
+    argp.add_argument('--baseurl', '-b', required=False, metavar='BASEURL', help='The base-URL for the authoritative webserver'
+                      'Overrides the one set in the config-file')
+    argp.add_argument('--apikey', '-k', required=False, metavar='API-KEY', help='The key needed to access the API')
+    argp.add_argument('--verbose', '-v', action='count', help='Be more verbose')
+    argp.set_defaults(command='none')
+
+    sub_parsers = argp.add_subparsers()
+
+    configs_parser = sub_parsers.add_parser('configs', help='Lists configured domains')
+    configs_parser.set_defaults(command='configs', action='list')
+
+    configs_subparsers = configs_parser.add_subparsers()
+
+    configs_show_parser = configs_subparsers.add_parser('show', help='Show the roll configuration of the current domain')
+    configs_show_parser.set_defaults(action='show')
+    configs_show_parser.add_argument('domain', metavar='DOMAIN')
+
+    configs_roll_parser = configs_subparsers.add_parser('roll', help='Setup the domain for autoroll')
+    configs_roll_parser.set_defaults(action='roll')
+    configs_roll_parser.add_argument('domain', metavar='DOMAIN')
+
+    configs_roll_parser.add_argument('--force', '-f', required=False, default=False, action="store_true", help='Force creation even if a configuration already exists')
+    configs_roll_parser.add_argument('--ksk-frequency', required=False)
+    configs_roll_parser.add_argument('--ksk-algo', required=False)
+    configs_roll_parser.add_argument('--zsk-algo', required=False)
+    configs_roll_parser.add_argument('--zsk-frequency', required=False)
+
+    configs_list_parser = configs_subparsers.add_parser('list', help='List all configured domains')
+    configs_list_parser.set_defaults(action='list')
+
+
+
+    # roll
+    roll_parser = sub_parsers.add_parser('roll', help='Manipulate current rolls')
+    roll_parser.set_defaults(command='roll', action='waiting')
+
+    roll_subparsers = roll_parser.add_subparsers()
+
+    roll_waiting_parser = roll_subparsers.add_parser('waiting', help='List waiting zones (KSK rolls waiting for DS change)')
+    roll_waiting_parser.set_defaults(action='waiting')
+
+    roll_step_parser = roll_subparsers.add_parser('step', help='Step waiting roll')
+    roll_step_parser.set_defaults(action='step')
+
+    roll_step_parser.add_argument('domain', metavar='DOMAIN')
+    roll_step_parser.add_argument('ttl', metavar='TTL')
+
+    arguments = argp.parse_args()
+
+    if arguments.verbose:
+        if arguments.verbose == 1:
+            logging.basicConfig(level=logging.INFO)
+        else:
+            logging.basicConfig(level=logging.DEBUG)
+
+    config = KeyrollerConfig(arguments.config)
+    api_config = config.api()
+    try:
+        if arguments.baseurl:
+            api_config['baseurl'] = arguments.baseurl
+        if arguments.apikey:
+            api_config['apikey'] = arguments.apikey
+        api = PDNSApi(**api_config)
+    except ConnectionError as e:
+        logger.error("Unable to connect to PowerDNS: {}".format(e))
+        sys.exit(1)
+
+    if arguments.command == 'none':
+        argp.print_help()
+        sys.exit(1)
+
+    if arguments.command == 'configs':
+        if arguments.action == 'list':
+            for zone in api.get_zones():
+                try:
+                    display_keyrollerdomain_infos(zone.id, api)
+                except FileNotFoundError:
+                    logger.debug("No config found for domain {}".format(zone.id))
+                    continue
+                except Exception as e:
+                    logger.error("Unable to get config for domain {}: {}".format(zone.id, e))
+        if arguments.action == 'show':
+            try:
+                domaincfg = domainconfig.from_api(arguments.domain, api)
+                logger.info(
+                    '{} has the following roll configuration: KSK {}, ZSK {}'.format(
+                        arguments.domain,
+                        domaincfg.ksk_frequency,
+                        domaincfg.zsk_frequency,
+                    )
+                )
+                display_keyrollerdomain_infos(arguments.domain, api)
+            except FileNotFoundError:
+                logger.error("{} is not under automatic keyroll".format(arguments.domain))
+            except ConnectionError:
+                logger.error(
+                    'No such domain {}'.format(
+                        arguments.domain
+                    )
+                )
+            except Exception as e:
+                logger.error("Unable to get config for domain {}: {}".format(zone.id, e))
+
+
+        if arguments.action == 'roll':
+            docreate = False
+            try:
+                domaincfg = domainconfig.from_api(arguments.domain, api)
+                if not arguments.force:
+                    logger.error(
+                        '{} already has an autoroll setup'.format(
+                            arguments.domain
+                        )
+                    )
+                else:
+                    docreate = True
+            except FileNotFoundError:
+                docreate = True
+            except ConnectionError:
+                logger.error(
+                    'No such domain {}'.format(
+                        arguments.domain
+                    )
+                )
+
+            if docreate:
+                domaincfg = domainconfig.DomainConfig(**config.defaults())
+                try:
+                    if arguments.ksk_frequency:
+                        domaincfg.ksk_frequency = arguments.ksk_frequency
+                    if arguments.ksk_algo:
+                        domaincfg.ksk_algo = arguments.ksk_algo
+                    if arguments.zsk_frequency:
+                        domaincfg.zsk_frequency = arguments.zsk_frequency
+                    if arguments.zsk_algo:
+                        domaincfg.zsk_algo = arguments.zsk_algo
+                    domainconfig.to_api(arguments.domain, api, domaincfg)
+                    logger.info(
+                        'Successfully created configuration for {}: KSK {}, ZSK {}'.format(
+                            arguments.domain,
+                            domaincfg.ksk_frequency,
+                            domaincfg.zsk_frequency,
+                        )
+                    )
+                except SyntaxError as e:
+                    logger.error(
+                        'Unable to setup given frequency {}: {}'.format(
+                            arguments.domain, e
+                        )
+                    )
+    if arguments.command == 'roll':
+        if arguments.action == 'waiting':
+            for zone in api.get_zones():
+                try:
+                    zoneconf = keyrollerdomain.KeyrollerDomain(zone.id, api)
+                    if zoneconf.state and zoneconf.state.is_rolling and zoneconf.state.current_roll.is_waiting_ds():
+                        logger.info('{} is waiting for DS replacement'.format(zone.id))
+                except FileNotFoundError:
+                    continue
+        elif arguments.action == 'step':
+            try:
+                zoneconf = keyrollerdomain.KeyrollerDomain(arguments.domain, api)
+                if zoneconf.state and zoneconf.state.current_roll.is_waiting_ds():
+                    zoneconf.step(force=True, customttl=int(arguments.ttl))
+                    logger.info(
+                        'Successfuly steped {}, now waiting {} before deleting the keys'.format(
+                            arguments.domain,
+                            arguments.ttl,
+                        )
+                    )
+
+            except FileNotFoundError:
+                logger.error(
+                    'No such zone to step {}'.format(
+                        arguments.domain
+                    )
+                )
diff --git a/pdns/keyroller/pdns-keyroller.conf.example b/pdns/keyroller/pdns-keyroller.conf.example
new file mode 100644 (file)
index 0000000..117925c
--- /dev/null
@@ -0,0 +1,28 @@
+keyroller:
+  loglevel: 'info'
+
+# for more informations on the PowerDNS Authoritative Server HTTP API
+# @see https://doc.powerdns.com/authoritative/http-api/index.html
+API:
+  baseurl: 'http://localhost:8081'
+  apikey: 'secret'
+  server: 'localhost'
+  timeout: 2
+
+# Default configuration for zone automatic keyroll defines the frequency of both ZSK and KSK rolls
+#
+# Supported algos are listed here:
+# https://doc.powerdns.com/authoritative/manpages/pdnsutil.1.html#dnssec-related-commands
+#
+# For now only pre-publish keyroll for splitted keys is supported
+# see https://datatracker.ietf.org/doc/html/rfc6781.html
+#
+# time expressions are parsed like described https://pypi.org/project/pytimeparse/
+
+domain_defaults:
+  ksk_frequency: never
+  ksk_algo: ecdsap256
+  ksk_method: prepublish
+  zsk_frequency: 6w
+  zsk_algo: ecdsap256
+  zsk_method: prepublish
diff --git a/pdns/keyroller/pdns-keyroller.py b/pdns/keyroller/pdns-keyroller.py
new file mode 100755 (executable)
index 0000000..8554433
--- /dev/null
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+import argparse
+import logging
+import pdnskeyroller.daemon
+import sys
+import traceback
+
+logger = logging.getLogger('pdns-keyroller')
+
+if __name__ == '__main__':
+    argp = argparse.ArgumentParser(
+        prog='pdns-keyroller', formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+        description='PowerDNS DNSSEC key-roller daemon')
+    argp.add_argument('--verbose', '-v', action='count', help='Be more verbose')
+    argp.add_argument('--config', '-c', metavar='PATH', type=str, default='/etc/powerdns/pdns-keyroller.conf',
+                      help='Load this configuration file')
+
+    arguments = argp.parse_args()
+
+    if arguments.verbose:
+        if arguments.verbose == 1:
+            logging.basicConfig(level=logging.INFO)
+        else:
+            logging.basicConfig(level=logging.DEBUG)
+
+    d = None
+    try:
+        d = pdnskeyroller.daemon.Daemon(arguments.config)
+    except ConnectionError as e:
+        logger.fatal('Unable to start: {}'.format(e))
+        sys.exit(1)
+
+    try:
+        d.run()
+    except Exception as e:
+        print(traceback.extract_tb(e))
+        logger.error("Unable to run: {}".format(e))
diff --git a/pdns/keyroller/pdnsapi/__init__.py b/pdns/keyroller/pdnsapi/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/pdns/keyroller/pdnsapi/api.py b/pdns/keyroller/pdnsapi/api.py
new file mode 100644 (file)
index 0000000..57401ee
--- /dev/null
@@ -0,0 +1,445 @@
+import re
+import logging
+import urllib.parse
+import requests
+
+import pdnsapi.cryptokey
+from pdnsapi.cryptokey import CryptoKey
+from pdnsapi.zone import Zone
+from pdnsapi.metadata import ZoneMetadata
+
+logger = logging.getLogger(__name__)
+
+
+# FIXME: clients should not be doing this escaping. We need to switch this to the appropriate zone ID lookup API.
+def _sanitize_dnsname(name):
+    """
+    Appends a dot to `name` if needed
+
+    :param name: A DNS Name
+    :return: A DNS Name that has a trailing dot
+    :rtype: str
+    """
+    if name == '.' or name == '=2E':
+        # lol
+        return '=2E'
+    if name[-1] != '.':
+        return name + '.'
+    return name
+
+
+class PDNSApi:
+    """
+    A wrapper-class that connects to the PowerDNS REST API to perform data manipulations
+
+    TODO: We should probably try to do some caching
+    """
+
+    def __init__(self, apikey, version=1, baseurl='http://localhost:8081', server='localhost', timeout=2):
+        """
+        :param apikey: The API Key needed to access the API (`api-key` setting)
+        :param version: The version of the API used, only 1 is supported at the moment
+        :param baseurl: The URL where the lives, without the `/api....`
+        :param server: The name of the server, 'localhost' by default. Use this when connecting to the API through e.g.
+                       pdnscontrol or zone-control
+        :param timeout: The timeout in seconds for a request
+        :raises: ConnectionError when the API is not reachable
+        """
+        api_suffix = {
+            0: '',
+            1: '/api/v1',
+        }[int(version)]
+        url = baseurl + '{}/servers/{}'.format(api_suffix, server)
+        # Strip double (or more) slashes
+        self.url = urllib.parse.urljoin(url, re.sub(r'/{2,}', '/', urllib.parse.urlparse(url).path))
+        if apikey is None:
+            raise Exception('apikey may not be None!')
+        self.apikey = apikey
+        self.timeout = timeout
+
+        # needed for __repr__
+        self._version = version
+        self._baseurl = baseurl
+        self._server = server
+
+        # Test the API, raises in _do_request
+        self._do_request('', 'GET')
+
+    def __repr__(self):
+        return '{}.PDNSApi(apikey="{}", version={}, baseurl="{}", server="{}", timeout={})'.format(
+            __name__,
+            self.apikey,
+            self._version,
+            self._baseurl,
+            self._server,
+            self.timeout
+        )
+
+    def _do_request(self, uri, method, data=None):
+        """
+        Does the actual API call.
+
+        :param uri: Sub-path for the request, e.g. '/zones'
+        :param method: HTTP method to use
+        :param data: dict or list of data to send along with the request
+        :return: a tuple containing the HTTP status code and the JSON response in Python format (i.e. list/dict)
+        :rtype: tuple(int, str)
+        """
+        headers = {
+            'Accept': 'application/json',
+            'X-API-Key': self.apikey,
+        }
+
+        full_url = self.url + uri
+
+        if data is not None:
+            if not (isinstance(data, dict) or isinstance(data, list)):
+                raise ValueError('data was passed as a {}, needs to be dict or list!'.format(type(data)))
+            if method.upper() != 'GET':
+                headers.update({'Content-Type': 'application/json'})
+
+        logger.debug('Attempting {} request to {} with data: {}'.format(method, full_url, data))
+
+        ret = None
+        try:
+            res = requests.request(method, full_url, headers=headers, json=data)
+            try:
+                ret = res.json()
+            except ValueError:
+                # We don't care that the response was empty
+                pass
+            res.raise_for_status()
+            logger.debug("Success! Got a {} response with data: {}".format(res.status_code, ret))
+            return res.status_code, ret
+        except requests.ConnectionError as e:
+            logger.debug("Got a Connection error: {}".format(str(e)))
+            raise ConnectionError("Unable to connect to {}: {}".format(full_url, e))
+        except requests.HTTPError as e:
+            logger.debug("Got an HTTP {} Error: {}".format(e.response.status_code, ret))
+            raise ConnectionError("HTTP error code {} received for {}: {}".format(
+                e.response.status_code, e.request.url, ret.get('error', ret)))
+        except Exception as e:
+            msg = "Error doing {} request to {}: {}".format(method, full_url, e)
+            logger.debug(msg)
+            raise ConnectionError(msg)
+
+    def get_cryptokeys(self, zone):
+        """
+        Get all CryptoKeys for `zone`
+
+        :param str zone: The zone to get the keys for
+        :return: All the cryptokeys for the zone
+        :rtype: list(CryptoKey)
+        """
+        code, resp = self._do_request('/zones/{}/cryptokeys'.format(_sanitize_dnsname(zone)),
+                                      'GET')
+
+        if code == 200:
+            cryptokeys = []
+            for k in resp:
+                k.pop('type')
+                cryptokeys.append(CryptoKey(**k))
+            return cryptokeys
+
+        raise Exception('Unexpected response: {}: {}'.format(code, resp))
+
+    def get_cryptokey(self, zone, cryptokey):
+        """
+        Gets a single CryptoKey
+
+        :param zone: The zone name
+        :param cryptokey: The id of the key or a :class:`CryptoKey <pdnsapi.cryptokey.CryptoKey>`, when the latter is provided, only the ``id`` field
+                           is read
+        :return: a :class:`pdnsapi.cryptokey.CryptoKey`
+        """
+        keyid = -1
+        if isinstance(cryptokey, CryptoKey):
+            keyid = cryptokey.id
+        if isinstance(cryptokey, str) or isinstance(cryptokey, int):
+            keyid = cryptokey
+        if keyid == -1:
+            raise Exception("cryptokey is not a CryptoKey, nor a str or int")
+
+        code, resp = self._do_request('/zones/{}/cryptokeys/{}'.format(_sanitize_dnsname(zone), keyid),
+                                      'GET')
+
+        if code == 200:
+            resp.pop('type')
+            return CryptoKey(**resp)
+
+        raise Exception('Unexpected response: {}: {}'.format(code, resp))
+
+    def set_cryptokey_active(self, zone, cryptokey, active=True):
+        """
+        Sets the `active` field of a CryptoKey
+
+        :param zone: The name of the zone
+        :param cryptokey: The :class:`pdnsapi.cryptokey.CryptoKey` or a string of the `id` field
+                          Note: the `active`-field of this object is ignored!
+        :param active: A boolean for the `active` field
+        :return: the new :class:`pdnsapi.cryptokey.Cryptokey`
+        :raises: Exception on failure
+        """
+        keyid = -1
+        if isinstance(cryptokey, CryptoKey):
+            keyid = cryptokey.id
+        if isinstance(cryptokey, str) or isinstance(cryptokey, int):
+            keyid = int(cryptokey)
+        if keyid == -1:
+            raise Exception("cryptokey is not a CryptoKey, nor a str or int")
+
+        code, resp = self._do_request('/zones/{}/cryptokeys/{}'.format(_sanitize_dnsname(zone), keyid),
+                                      'PUT',
+                                      {'active': active})
+        if code == 422:
+            raise Exception('Failed to set cryptokey {} in zone {} to {}: {}'.format(
+                keyid, zone, 'active' if active else 'inactive', resp))
+        if code == 204:
+            return self.get_cryptokey(zone, cryptokey)
+
+        raise Exception('Unexpected response: {}: {}'.format(code, resp))
+
+    def set_cryptokey_published(self, zone, cryptokey, published=True):
+        """
+        Sets the `published` field of a CryptoKey
+
+        :param zone: The name of the zone
+        :param cryptokey: The :class:`pdnsapi.cryptokey.CryptoKey` or a string of the `id` field
+        :param published: A boolean for the `published` field
+        :return: the new :class:`pdnsapi.cryptokey.Cryptokey`
+        :raises: Exception on failure
+        """
+        keyid = -1
+        if isinstance(cryptokey, CryptoKey):
+            keyid = cryptokey.id
+        if isinstance(cryptokey, str) or isinstance(cryptokey, int):
+            keyid = int(cryptokey)
+        if keyid == -1:
+            raise Exception("cryptokey is not a CryptoKey, nor a str or int")
+
+        code, resp = self._do_request('/zones/{}/cryptokeys/{}'.format(_sanitize_dnsname(zone), keyid),
+                                      'PUT',
+                                      {'published': published,
+                                       'active': True})
+        if code == 422:
+            raise Exception('Failed to set cryptokey {} in zone {} to {}: {}'.format(
+                keyid, zone, 'published' if published else 'unpublished', resp))
+        if code == 204:
+            return self.get_cryptokey(zone, cryptokey)
+
+        raise Exception('Unexpected response: {}: {}'.format(code, resp))
+
+    def publish_cryptokey(self, zone, cryptokey):
+
+        return  self.set_cryptokey_published(zone, cryptokey, published=True)
+
+    def unpublish_cryptokey(self, zone, cryptokey):
+
+        return  self.set_cryptokey_published(zone, cryptokey, published=False)
+
+    def delete_cryptokey(self, zone, cryptokey):
+        """
+        Removes a cryptokey
+
+        :param zone: The name of the zone
+        :param cryptokey: The :class:`pdnsapi.zone.CryptoKey` or a string of the `id` field
+                          Note: the `active`-field of this object is ignored!
+        :return: On success
+        :raises: Exception on failure
+        """
+        keyid = -1
+        if isinstance(cryptokey, CryptoKey):
+            keyid = cryptokey.id
+        if isinstance(cryptokey, str) or isinstance(cryptokey, int):
+            keyid = cryptokey
+        if keyid == -1:
+            raise Exception("cryptokey is not a CryptoKey, nor a str or int")
+        code, resp = self._do_request('/zones/{}/cryptokeys/{}'.format(_sanitize_dnsname(zone), keyid),
+                                      'DELETE')
+        if code == 422:
+            raise Exception('Failed to remove cryptokey {} in zone {}: {}'.format(
+                keyid, zone, resp))
+        if code == 204:
+            return
+
+        raise Exception('Unexpected response: {}: {}'.format(code, resp))
+
+    def add_cryptokey(self, zone, keytype='zsk', active=False, content=None, algo=None, bits=None, published=True):
+        """
+        Adds a CryptoKey to zone. If content is None, a new key is generated by the server, using algorithm from `algo`
+        and a size of `bits` (if applicable). If `content` and `algo` are both None, the server default is used (in
+        4.0.X, this is algorithm 13, ECDSAP256SHA256)
+
+        :param zone: The zone for which to create the key
+        :param keytype: Either 'ksk' or 'zsk'
+        :param active: Bool whether or not the new key should be active
+        :param content: An ISC encoded private key.
+        :param algo: An integer or lowercase DNSSEC algorithm name
+        :param bits: The size of the key
+        :return: The created CryptoKey on success
+        :raises: an Exception on failure
+        """
+
+        data = {'active': active,
+                'keytype': keytype,
+                'published': published}
+
+        if content is not None:
+            data.update({'content': content})
+
+        if algo is not None:
+            algo = pdnsapi.cryptokey.shorthand_to_algo.get(algo, algo)
+            data.update({'algorithm': algo})
+
+        if bits is not None:
+            data.update({'bits': bits})
+
+        code, resp = self._do_request('/zones/{}/cryptokeys'.format(_sanitize_dnsname(zone)),
+                                      'POST',
+                                      data)
+
+        if code == 422:
+            raise Exception('Unable to create CryptoKey in zone {}: {}'.format(zone, resp))
+        if code == 201:
+            resp.pop('type')
+            return CryptoKey(**resp)
+
+        raise Exception('Unexpected response: {}: {}'.format(code, resp))
+
+    def get_zones(self):
+        """
+        Get all zones
+
+        :return: All zones ons the server
+        :rtype: list(:class:`pdnsapi.zone.Zone`)
+        """
+        code, resp = self._do_request('/zones',
+                                      'GET')
+        if code == 200:
+            return [Zone(**zone) for zone in resp]
+
+        raise Exception('Unexpected response: {}: {}'.format(code, resp))
+
+    def get_zone(self, zone):
+        """
+        Gets the full zone contents
+
+        :param str zone: The zone we want the full contents for
+        :return: a :class:`pdnsapi.zone.Zone`
+        """
+        code, resp = self._do_request('/zones/{}'.format(_sanitize_dnsname(zone)),
+                                      'GET')
+
+        if code == 200:
+            return Zone(**resp)
+
+        raise Exception('Unexpected response: {}: {}'.format(code, resp))
+
+    def bump_soa(self, zone, serial=None):
+        """
+        Bump zone SOA serial number
+
+        :param str zone: The zone we want to bump
+        :param str serial: The new serial otherwise will update to existing serial+1
+        :return: a :class:`pdnsapi.zone.Zone`
+        """
+
+        soa = None
+        content = self.get_zone(zone)
+        for rrset in content.rrsets:
+            if rrset.rtype == "SOA" :
+                soa = rrset
+                break
+
+        if soa is None:
+            raise Exception('No such SOA record')
+
+        
+        newcontent = soa.records[0].content.split(" ")
+        if serial != None:
+            newcontent[2] = serial
+        else:
+            newcontent[2] = str(int(newcontent[2]) + 1)
+        code, resp = self._do_request('/zones/{}'.format(_sanitize_dnsname(zone)),
+                                      'PATCH',
+                                      {
+                                          "rrsets": [{
+                                              "name": soa.name,
+                                              "type": soa.rtype,
+                                              "ttl": soa.ttl,
+                                              "changetype": "REPLACE",
+                                              "records": [
+                                                  {
+                                                      "content": " ".join(newcontent),
+                                                      "disabled": soa.records[0].disabled
+                                                  }
+                                              ]
+                                          }]
+                                      })
+
+        if code == 204:
+            return self.get_zone(zone)
+
+        raise Exception('Unexpected response: {}: {}'.format(code, resp))
+
+    def set_zone_param(self, zone, param, value):
+        """
+
+        :param zone:
+        :param param:
+        :param value:
+        :return:
+        """
+        zonename = _sanitize_dnsname(zone)
+        code, resp = self._do_request('/zones/{}'.format(zonename),
+                                      'PUT', {param: value})
+
+        if code == 204:
+            return self.get_zone(zonename)
+
+        raise Exception('Unexpected response: {}: {}'.format(code, resp))
+
+    def get_zone_metadata(self, zone, kind=''):
+        """
+        Gets zone metadata
+
+        :param zone: The zone for which to retrieve the meta data
+        :param kind: The zone metadata kind to retrieve. If this is an empty string, all zone metadata is retrieved
+        :return: A list of :class:`pdnsapi.metadata.ZoneMetadata` objects
+        """
+        code, resp = self._do_request('/zones/{}/metadata{}'.format(_sanitize_dnsname(zone), '/' + kind if len(kind) else ''),
+                                      'GET')
+
+        if code == 200:
+            if kind == '':
+                return [ZoneMetadata(r['kind'], r['metadata']) for r in resp]
+            else:
+                return ZoneMetadata(resp['kind'], resp['metadata'])
+
+        raise Exception('Unexpected response: {}: {}'.format(code, resp))
+
+    def set_zone_metadata(self, zone, kind, metadata):
+        if not isinstance(metadata, list):
+            metadata = [metadata]
+        obj = {'metadata': metadata}
+        code, resp = self._do_request('/zones/{}/metadata/{}'.format(_sanitize_dnsname(zone), kind),
+                                      'PUT',
+                                      obj)
+
+        if code == 422:
+            raise Exception('Failed to set metadata {} in zone {} to {}: {}'.format(kind, zone, metadata, resp))
+        if code == 200:
+            return ZoneMetadata(resp['kind'], resp['metadata'])
+
+        raise Exception('Unexpected response: {}: {}'.format(code, resp))
+
+    def delete_zone_metadata(self, zone, kind):
+        code, resp = self._do_request('/zones/{}/metadata/{}'.format(_sanitize_dnsname(zone), kind),
+                                      'DELETE')
+
+        if code == 422:
+            raise Exception('Failed to remove metadata {} in zone {}: {}'.format(kind, zone, resp))
+        if code == 200:
+            return
+
+        raise Exception('Unexpected response: {}: {}'.format(code, resp))
diff --git a/pdns/keyroller/pdnsapi/cryptokey.py b/pdns/keyroller/pdnsapi/cryptokey.py
new file mode 100644 (file)
index 0000000..5634d81
--- /dev/null
@@ -0,0 +1,94 @@
+algo_to_shorthand = {
+    1: "RSAMD5",
+    2: "DH",
+    3: "DSA",
+    5: "RSASHA1",
+    6: "DSA-NSEC3-SHA1",
+    7: "RSASHA1-NSEC3-SHA1",
+    8: "RSASHA256",
+    10: "RSASHA512",
+    12: "ECC-GOST",
+    13: "ECDSAP256",
+    14: "ECDSAP384",
+    15: "ED25519",
+    16: "ED448",
+}
+
+shorthand_to_algo = {v: k for k, v in algo_to_shorthand.items()}
+
+algo_to_bits = {
+    13: 256,
+    14: 512,
+    15: 32,
+    16: 57.
+}
+
+
+class CryptoKey:
+    """
+    Represents a CryptoKey from the API
+    """
+    _algo = None
+
+    def __init__(self, id, active, keytype, flags=None, algo=None, dnskey=None, ds=None, privatekey=None, **kwargs):
+        """
+        Construct a new CryptoKey
+
+        :param int id: The id number of the key
+        :param bool active: Whether or not this key is active
+        :param string keytype: The type of key, KSK, ZSK or CSK
+        :param int flags: The flags of this key
+        :param algo: The algorithm of the key. Can be an integer or a string mnemonic
+        :param string dnskey: The DNSKEY zonefile content
+        :param list(string) ds: The DS records for this key
+        :param string privatekey: The private key content
+        :param dict kwargs: for compatibility with (future) API responses, ignored
+        """
+        self.id = id
+        self.active = active
+        self.keytype = keytype
+        self.flags = flags
+        self.dnskey = dnskey
+        self.ds = ds
+        self.privatekey = privatekey
+        self.algo = algo or dnskey.split(' ')[2]
+
+    def __repr__(self):
+        return 'CryptoKey({id}, {active}, {keytype}, {flags}, {algo}, {dnskey}, {ds}, "{privatekey})'.format(
+            id=self.id, active=self.active, keytype=self.keytype, flags=self.flags, algo=self.algo, dnskey=self.dnskey,
+            ds=self.ds, privatekey=self.privatekey)
+
+    def __str__(self):
+        return str({
+            'id': self.id,
+            'active': self.active,
+            'keytype': self.keytype,
+            'flags': self.flags,
+            'dnskey': self.dnskey,
+            'ds': self.ds,
+            'privatekey': self.privatekey,
+            'algo': self.algo,
+        })
+
+    @property
+    def algo(self):
+        """
+        Returns the algorithm of this key
+
+        :return: Either the mnemonic or the algorithm number is the mnemonic is unknown
+        """
+        return algo_to_shorthand.get(self._algo, self._algo)
+
+    @algo.setter
+    def algo(self, val):
+        if isinstance(val, int):
+            self._algo = val
+            return
+        if isinstance(val, str):
+            try:
+                self._algo = int(val)
+            except ValueError:
+                self.algo = shorthand_to_algo.get(val, val)
+            return
+        raise ValueError("Value is not a str or int, but a {}".format(type(val)))
+
diff --git a/pdns/keyroller/pdnsapi/metadata.py b/pdns/keyroller/pdnsapi/metadata.py
new file mode 100644 (file)
index 0000000..28b32ed
--- /dev/null
@@ -0,0 +1,18 @@
+class ZoneMetadata:
+    def __init__(self, kind, metadata, type="Metadata"):
+        self.kind = kind
+        if not isinstance(metadata, list):
+            raise Exception('metadata must be a list, not a {}'.format(type(metadata)))
+        self.metadata = metadata
+
+    def empty(self):
+        return not self.metadata
+
+    def __repr__(self):
+        return 'ZoneMetadata({}, {})'.format(self.kind, self.metadata)
+
+    def __str__(self):
+        return str({
+            'kind': self.kind,
+            'metadata': self.metadata
+        })
diff --git a/pdns/keyroller/pdnsapi/zone.py b/pdns/keyroller/pdnsapi/zone.py
new file mode 100644 (file)
index 0000000..d7eef2e
--- /dev/null
@@ -0,0 +1,160 @@
+class RRSet:
+    def __init__(self, name, type, ttl, records, comments=[]):
+        """
+        Represents and RRSet from the API, see https://doc.powerdns.com/md/httpapi/api_spec/#zone95collection
+
+        :param str name: Name of the rrset
+        :param str type: Type of the rrset
+        :param int ttl: Time to Live of the rrset
+        :param list records: a list of :class:`Record`
+        :param list comments: a list of :class:`Comment`
+        """
+        self._records = []
+        self._comments = []
+        self.name = name
+        self.rtype = type
+        self.ttl = ttl
+        self.records = records
+        self.comments = comments
+
+    def __repr__(self):
+        return 'RRSet("{}", "{}", {}, {}, {})'.format(self.name, self.rtype, self.ttl, self.records, self.comments)
+
+    def __str__(self):
+        ret = '\n'.join(['; {}'.format(c) for c in self.comments])
+        ret += '\n'.join(['{}{}\tIN\t{}\t{}'.format(';' if rec.disabled else '', self.name, self.rtype, rec.content)
+                         for rec in self.records])
+        return ret
+
+    @property
+    def records(self):
+        return self._records
+
+    @records.setter
+    def records(self, val):
+        if not isinstance(val, list):
+            raise Exception('TODO')
+        if all(isinstance(v, dict) for v in val):
+            self._records = []
+            for v in val:
+                self._records.append(Record(**v))
+            return
+        if not all(isinstance(v, Record) for v in val):
+            raise Exception('Not all records are of type Record')
+        self._records = val
+
+    @property
+    def comments(self):
+        return self._comments
+
+    @comments.setter
+    def comments(self, val):
+        if not isinstance(val, list):
+            raise Exception('TODO')
+        if all(isinstance(v, dict) for v in val):
+            self._comments = []
+            for v in val:
+                self._comments.append(Comment(**v))
+            return
+        if not all(isinstance(v, Comment) for v in val):
+            raise Exception('Not all comments are of type Comment')
+        self._comments = val
+
+
+class Record:
+    def __init__(self, content, disabled):
+        """
+        Represents a Record from the API. Note that is does not contian the rrname nor ttl (these are held by the
+        encompassing :class:`RRSet` object).
+
+        :param str content: The content of the record in zonefile-format
+        :param bool disabled: True if this record is disabled
+        """
+        self.content = content
+        self.disabled = bool(disabled)
+
+    def __repr__(self):
+        return 'Record("{}", "{}")'.format(self.content, self.disabled)
+
+
+class Comment:
+    def __init__(self, content, modified_at, account):
+        """
+        Constructor, see https://doc.powerdns.com/md/httpapi/api_spec/#zone95collection
+
+        :param str content: The content of the comment
+        :param int modified_at: A timestamp when the comment was changed
+        :param account: The account that made this comment
+        """
+        self.content = content
+        # TODO make modified_at a datetime.datetime
+        self.modified_at = modified_at
+        self.account = account
+
+    def __repr__(self):
+        return 'Comment("{}", "{}", "{})'.format(self.content, self.modified_at, self.account)
+
+    def __str__(self):
+        return '{} by {} on {}'.format(self.content, self.account, self.modified_at)
+
+
+class Zone:
+    """
+    This represents a Zone-object
+    """
+    _keys = ["id", "name", "url", "kind", "serial", "notified_serial", "masters", "dnssec", "nsec3param",
+             "nsec3narrow", "presigned", "soa_edit", "soa_edit_api", "account", "nameservers", "servers",
+             "recursion_desired", "rrsets", "last_check"]
+    _rrsets = []
+    _kind = ''
+
+    def __init__(self, **kwargs):
+        """
+        Constructor
+        :param kwargs: Any of the elements named in https://doc.powerdns.com/md/httpapi/api_spec/#zone95collection
+        """
+        for k, v in kwargs.items():
+            if k in Zone._keys:
+                setattr(self, k, v)
+
+    def __str__(self):
+        ret = "{}".format('\n'.join(['; {} = {}'.format(
+            k, str(getattr(self, k))) for k in Zone._keys if getattr(self, k, None) and k != 'rrsets']))
+        ret += "\n{}".format('\n'.join([str(v) for v in self.rrsets]))
+        return ret
+
+    def __repr__(self):
+        return 'Zone({})'.format(
+            ', '.join(['{}="{}"'.format(k, getattr(self, k)) for k in Zone._keys if getattr(self, k, None)]))
+
+    @property
+    def kind(self):
+        return self._kind
+
+    @kind.setter
+    def kind(self, val):
+        if val not in ['Native', 'Master', 'Slave']:
+            raise Exception("TODO")
+        self._kind = val
+
+    @property
+    def rrsets(self):
+        return self._rrsets
+
+    @rrsets.setter
+    def rrsets(self, val):
+        """
+        Sets the :class:`RRSet`s for this Zone
+        :param val: a list of :class:`RRSet`s or :class:`dict`s. The latter is converted to RRsets
+        :return:
+        """
+        if not isinstance(val, list):
+            raise Exception('Please pass a list of RRSets')
+        if all(isinstance(v, dict) for v in val):
+            self._rrsets = []
+            for v in val:
+                self._rrsets.append(RRSet(**v))
+            return
+        if not all(isinstance(v, RRSet) for v in val):
+            raise Exception('Not all rrsets are actually RRSets')
+        self._rrsets = val
diff --git a/pdns/keyroller/pdnskeyroller/__init__.py b/pdns/keyroller/pdnskeyroller/__init__.py
new file mode 100644 (file)
index 0000000..115fdb9
--- /dev/null
@@ -0,0 +1,4 @@
+__author__ = 'PowerDNS.COM BV'
+
+PDNSKEYROLLER_CONFIG_metadata_kind = 'X-PDNSKEYROLLER-CONFIG'
+PDNSKEYROLLER_STATE_metadata_kind = 'X-PDNSKEYROLLER-STATE'
diff --git a/pdns/keyroller/pdnskeyroller/config.py b/pdns/keyroller/pdnskeyroller/config.py
new file mode 100644 (file)
index 0000000..9cbb281
--- /dev/null
@@ -0,0 +1,68 @@
+import yaml
+import datetime
+import logging
+
+from pdnsapi.api import PDNSApi
+import pdnskeyroller.keyrollerdomain
+
+logger = logging.getLogger(__name__)
+
+
+class KeyrollerConfig:
+    def __init__(self, configfile):
+        self._configfile = configfile
+        self._config = self._load_config()
+
+    def _load_config(self):
+        # These are all the Defaults
+        tmp_conf = {
+            'keyroller': {
+                'loglevel': 'info',
+            },
+            'API': {
+                'version': 1,
+                'baseurl': 'http://localhost:8081',
+                'server': 'localhost',
+                'apikey': '',
+                'timeout': '2',
+            },
+            'domain_defaults': {
+                'ksk_frequency': 0,
+                'ksk_algo': 13,
+                'ksk_method': 'prepublish',
+                'zsk_frequency': '6w',
+                'zsk_algo': 13,
+                'zsk_method': 'prepublish',
+                'key_style': 'single',
+                'ksk_keysize': 3069,
+                'zsk_keysize': 3069,
+            },
+        }
+
+        logger.debug("Loading configuration from {}".format(self._configfile))
+        try:
+            with open(self._configfile, 'r') as f:
+                a = yaml.safe_load(f)
+                if a:
+                    for k, v in tmp_conf.items():
+                        if isinstance(v, dict) and isinstance(a.get(k), dict):
+                            tmp_conf[k].update(a.get(k))
+                        if isinstance(v, list) and isinstance(a.get(k), list):
+                            tmp_conf[k] = a.get(k)
+
+            loglevel = getattr(logging, tmp_conf['keyroller']['loglevel'].upper())
+            if not isinstance(loglevel, int):
+                loglevel = logging.INFO
+            logger.info("Setting loglevel to {}".format(loglevel))
+            logging.basicConfig(level=loglevel)
+
+        except FileNotFoundError as e:
+            logger.error('Unable to load configuration file: {}'.format(e))
+
+        return tmp_conf
+
+    def api(self):
+        return self._config['API']
+
+    def defaults(self):
+        return self._config['domain_defaults']
diff --git a/pdns/keyroller/pdnskeyroller/daemon.py b/pdns/keyroller/pdnskeyroller/daemon.py
new file mode 100644 (file)
index 0000000..b63b662
--- /dev/null
@@ -0,0 +1,119 @@
+import yaml
+import datetime
+import logging
+
+from pdnsapi.api import PDNSApi
+from pdnskeyroller import domainstate
+import pdnskeyroller.keyrollerdomain
+from pdnskeyroller.prepublishkeyroll import PrePublishKeyRoll
+
+logger = logging.getLogger(__name__)
+
+
+class Daemon:
+    def __init__(self, configfile):
+        self._configfile = configfile
+        self._config = self._load_config()
+
+        # Initialize all domains
+        self._domains = {}
+        api = PDNSApi(**self._config['API'])
+        for zone in api.get_zones():
+            try:
+                zoneconf = pdnskeyroller.keyrollerdomain.KeyrollerDomain(zone.id, api)
+                self._domains[zone.id] = zoneconf
+            except FileNotFoundError:
+                logger.debug("No config found for zone {}".format(zone.id))
+                continue
+            except Exception as e:
+                logger.error("Unable to load informations for zone {}".format(zone.id))
+                continue
+
+    def _load_config(self):
+        # These are all the Defaults
+        tmp_conf = {
+            'keyroller': {
+                'loglevel': 'info',
+            },
+            'API': {
+                'version': 1,
+                'baseurl': 'http://localhost:8081',
+                'server': 'localhost',
+                'apikey': '',
+                'timeout': '2',
+            },
+        }
+
+        logger.debug("Loading configuration from {}".format(self._configfile))
+        try:
+            with open(self._configfile, 'r') as f:
+                a = yaml.safe_load(f)
+                if a:
+                    for k, v in tmp_conf.items():
+                        if isinstance(v, dict) and isinstance(a.get(k), dict):
+                            tmp_conf[k].update(a.get(k))
+                        if isinstance(v, list) and isinstance(a.get(k), list):
+                            tmp_conf[k] = a.get(k)
+
+            loglevel = getattr(logging, tmp_conf['keyroller']['loglevel'].upper())
+            if not isinstance(loglevel, int):
+                loglevel = logging.INFO
+            logger.info("Setting loglevel to {}".format(loglevel))
+            logging.basicConfig(level=loglevel)
+
+        except FileNotFoundError as e:
+            logger.error('Unable to load configuration file: {}'.format(e))
+
+        return tmp_conf
+
+    def _get_actionable_domains(self):
+        now = datetime.datetime.now()
+        return [zone for zone, domainconf in self._domains.items() if
+                domainconf.next_action_datetime and domainconf.next_action_datetime <= now]
+
+    def update_config(self):
+        """
+        Should be called when we want to update the config of a running instance (not implemented)
+
+        :return:
+        """
+        pass
+
+    def run(self):
+        actionable_domains = self._get_actionable_domains()
+        now = datetime.datetime.now()
+        logger.debug("Found {} domain(s) ({} actionable)".format(len(self._domains), len(actionable_domains)))
+
+
+        if len(actionable_domains) > 0:
+            for domain in actionable_domains:
+                keyrollerdomain = self._domains[domain]
+                if keyrollerdomain.state.is_rolling:
+                    try:
+                        logger.info("Moving to step {} for {} roll".format(keyrollerdomain.current_step_name, keyrollerdomain.zone))
+                        keyrollerdomain.step()
+                    except Exception as e:
+                        logger.error("Unable to advance keyroll: {}".format(e))
+                else:
+                    next_ksk_roll = keyrollerdomain.next_ksk_roll()
+                    next_zsk_roll = keyrollerdomain.next_zsk_roll()
+                    if next_zsk_roll is not None and next_zsk_roll <= now:
+                        try:
+                            logger.info("Starting {} {} keyroll for {} ({} algo)".format("pre-publish", "ZSK", keyrollerdomain.zone, keyrollerdomain.config.zsk_algo))
+                            roll = PrePublishKeyRoll()
+                            roll.initiate(keyrollerdomain.zone, keyrollerdomain.api, 'zsk', keyrollerdomain.config.zsk_algo)
+                            keyrollerdomain.state.current_roll = roll
+                            domainstate.to_api(keyrollerdomain.zone, keyrollerdomain.api, keyrollerdomain.state)
+                        except Exception as e:
+                            logger.error("Unable to start keyroll: {}".format(e))
+                    elif next_ksk_roll is not None and next_ksk_roll <= now:
+                        try:
+                            logger.info("Starting {} {} keyroll for {} ({} algo)".format("pre-publish", "KSK", keyrollerdomain.zone, keyrollerdomain.config.zsk_algo))
+                            roll = PrePublishKeyRoll()
+                            roll.initiate(keyrollerdomain.zone, keyrollerdomain.api, 'ksk', keyrollerdomain.config.ksk_algo)
+                            keyrollerdomain.state.current_roll = roll
+                            domainstate.to_api(keyrollerdomain.zone, keyrollerdomain.api, keyrollerdomain.state)
+                        except Exception as e:
+                            logger.error("Unable to start keyroll: {}".format(e))
+        else:
+            logger.info("No action taken")
diff --git a/pdns/keyroller/pdnskeyroller/domainconfig.py b/pdns/keyroller/pdnskeyroller/domainconfig.py
new file mode 100644 (file)
index 0000000..c809fef
--- /dev/null
@@ -0,0 +1,198 @@
+import pdnsapi.api
+from pdnskeyroller import PDNSKEYROLLER_CONFIG_metadata_kind
+from pytimeparse.timeparse import timeparse
+import pdnsapi.metadata
+import json_tricks.nonp as json_tricks
+from pdnskeyroller.util import (parse_algo)
+
+DOMAINCONFIG_VERSION = 1
+
+def from_api(zone, api):
+    """
+    Retrieves a keyroller configuration for zone ``zone`` from the api ``api``
+
+    :param string zone: The zone to retrieve the Config for
+    :param pdnsapi.api.PDNSApi api: The API to use
+    :return: The configuration
+    :rtype: :class:`DomainConfig`
+    :raises: FileNotFoundError if ``zone`` does not have a roller config
+    """
+    if not isinstance(api, pdnsapi.api.PDNSApi):
+        raise Exception('api is not a PDNSApi')
+
+    metadata = api.get_zone_metadata(zone, PDNSKEYROLLER_CONFIG_metadata_kind)
+
+    if metadata.empty():
+        raise FileNotFoundError
+
+    if len(metadata.metadata) > 1:
+        raise Exception("More than one {} Domain Metadata found for {}!".format(PDNSKEYROLLER_CONFIG_metadata_kind,
+                                                                                zone))
+    try:
+        state = json_tricks.loads(metadata.metadata[0])
+    except Exception as e:
+        raise ValueError(e)
+
+    return DomainConfig(**state)
+
+def to_api(zone, api, config):
+    """
+
+    :param zone:
+    :param api:
+    :param config:
+    :return:
+    """
+    if not isinstance(api, pdnsapi.api.PDNSApi):
+        raise Exception('api must be a PDNSApi instance, not a {}'.format(type(api)))
+    if not isinstance(config, DomainConfig):
+        raise Exception('config must be a DomainConfig instance, not a {}'.format(type(config)))
+
+    api.set_zone_metadata(zone, PDNSKEYROLLER_CONFIG_metadata_kind, str(config))
+
+
+class DomainConfig:
+    __version = DOMAINCONFIG_VERSION
+    __ksk_frequency = 0
+    __ksk_algo = 13
+    __ksk_keysize = 3096
+    __ksk_method = "prepublish"
+    __zsk_frequency = "6w"
+    __zsk_algo = 13
+    __zsk_keysize = 3096
+    __zsk_method = "prepublish"
+    __key_style = "split"
+
+    def __init__(self, version=DOMAINCONFIG_VERSION, ksk_frequency=0, ksk_algo=13, ksk_keysize=3096, ksk_method="prepublish",
+                 zsk_frequency="6w", zsk_algo=13, zsk_keysize=3096, zsk_method="prepublish", key_style="split", **kwargs):
+
+        self.version = version
+
+        self.ksk_frequency = ksk_frequency
+        self.ksk_algo = ksk_algo
+        self.ksk_keysize = ksk_keysize
+        self.ksk_method = ksk_method
+
+        self.zsk_frequency = zsk_frequency
+        self.zsk_algo = zsk_algo
+        self.zsk_keysize = zsk_keysize
+        self.zsk_method = zsk_method
+
+        self.key_style = key_style
+        if kwargs:
+            logger.warning('Unknown keys passed: {}'.format(', '.join(
+                [k for k, v in kwargs.items()])))
+
+    @property
+    def ksk_frequency(self):
+
+        return self.__ksk_frequency
+
+    @ksk_frequency.setter
+    def ksk_frequency(self, value):
+        if value != "never" and value != 0:
+            if timeparse(value) is None:
+                raise SyntaxError('Can not parse value "%s" to as timedelta' % value)
+            self.__ksk_frequency = value
+        else:
+            self.__ksk_frequency = 0
+
+    @property
+    def ksk_algo(self):
+        return self.__ksk_algo
+
+    @ksk_algo.setter
+    def ksk_algo(self, value):
+        self.__ksk_algo = parse_algo(value)
+
+    @property
+    def ksk_keysize(self):
+        return self.__ksk_keysize
+
+    @ksk_keysize.setter
+    def ksk_keysize(self, value):
+        self.__ksk_keysize = value
+
+    @property
+    def ksk_method(self):
+        return self.__ksk_method
+
+    @ksk_method.setter
+    def ksk_method(self, value):
+        self.__ksk_method = value
+
+    @property
+    def zsk_frequency(self):
+        return self.__zsk_frequency
+
+    @zsk_frequency.setter
+    def zsk_frequency(self, value):
+        if value != "never" and value != 0:
+            if timeparse(value) is None:
+                raise SyntaxError('Can not parse value "%s" to as timedelta' % value)
+            self.__zsk_frequency = value
+        else:
+            self.__zsk_frequency = 0
+
+    @property
+    def zsk_algo(self):
+        return self.__zsk_algo
+
+    @zsk_algo.setter
+    def zsk_algo(self, value):
+        self.__zsk_algo = parse_algo(value)
+
+    @property
+    def zsk_keysize(self):
+        return self.__zsk_keysize
+
+    @zsk_keysize.setter
+    def zsk_keysize(self, value):
+        self.__zsk_keysize = value
+
+    @property
+    def zsk_method(self):
+        return self.__zsk_method
+
+    @zsk_method.setter
+    def zsk_method(self, value):
+        self.__zsk_method = value
+
+    @property
+    def key_style(self):
+        return self.__key_style
+
+    @key_style.setter
+    def key_style(self, value):
+        if value not in ('single', 'split'):
+            raise Exception('Invalid key_style: {}'. format(value))
+        self.__key_style = value
+
+    @property
+    def version(self):
+        return self.__version
+
+    @version.setter
+    def version(self, val):
+        if val != 1:
+            raise Exception('{} is not a valid version!')
+        self.__version = val
+
+    def __repr__(self):
+        return 'DomainConfig({})'.format(
+            ', '.join(['{} = "{}"'.format(k, self.__getattribute__(k)) for k in
+                       ["version", "ksk_frequency", "ksk_algo", "ksk_keysize", "ksk_method", "zsk_frequency",
+                        "zsk_algo", "zsk_keysize", "zsk_method", "key_style"]]))
+    def __str__(self):
+        return(json_tricks.dumps({
+            'version': self.version,
+            'ksk_frequency': self.ksk_frequency,
+            'ksk_algo': self.ksk_algo,
+            'ksk_keysize': self.ksk_keysize,
+            'ksk_method': self.ksk_method,
+            'zsk_frequency': self.zsk_frequency,
+            'zsk_algo': self.zsk_algo,
+            'zsk_keysize': self.zsk_keysize,
+            'zsk_method': self.zsk_method,
+            'key_style': self.key_style,
+        }))
diff --git a/pdns/keyroller/pdnskeyroller/domainstate.py b/pdns/keyroller/pdnskeyroller/domainstate.py
new file mode 100644 (file)
index 0000000..c5034ff
--- /dev/null
@@ -0,0 +1,153 @@
+import logging
+import pdnsapi.api
+from datetime import datetime
+import json_tricks.nonp as json_tricks
+from pdnskeyroller import PDNSKEYROLLER_STATE_metadata_kind
+from pdnskeyroller.keyroll import KeyRoll
+from pdnskeyroller.prepublishkeyroll import PrePublishKeyRoll
+
+DOMAINSTATE_VERSION = 1
+logger = logging.getLogger(__name__)
+
+
+def from_api(zone, api):
+    """
+    Get the keyroller state from the API
+
+    :param string zone: The zone to het the state for
+    :param pdnsapi.api.PDNSApi api: the API endpoint to use
+    :return: The state for ``zone``
+    :rtype: DomainState
+    :raises: ValueError if the JSON from the domain metadata cannot be unpacked
+    """
+    if not isinstance(api, pdnsapi.api.PDNSApi):
+        raise Exception('api must be a PDNSApi instance, not a {}'.format(type(api)))
+    tmp_state = api.get_zone_metadata(zone, PDNSKEYROLLER_STATE_metadata_kind).metadata
+
+    if not tmp_state:
+        return DomainState()
+
+    if len(tmp_state) > 1:
+        raise Exception('More than one {} metadata found!'.format(PDNSKEYROLLER_STATE_metadata_kind))
+
+    try:
+        state = json_tricks.loads(tmp_state[0])
+    except Exception as e:
+        raise ValueError(e)
+
+    return DomainState(**state)
+
+
+def to_api(zone, api, state):
+    """
+
+    :param zone:
+    :param api:
+    :param state:
+    :return:
+    """
+    if not isinstance(api, pdnsapi.api.PDNSApi):
+        raise Exception('api must be a PDNSApi instance, not a {}'.format(type(api)))
+    if not isinstance(state, DomainState):
+        raise Exception('state must be a DomainState instance, not a {}'.format(type(state)))
+
+    if state.current_roll.complete:
+        state.set_last_roll_date(state.current_roll.keytype, state.current_roll.step_datetimes[-1])
+        state.current_roll = KeyRoll()
+
+    api.set_zone_metadata(zone, PDNSKEYROLLER_STATE_metadata_kind, str(state))
+
+
+class DomainState:
+    __last_zsk_roll_datetime = None
+    __last_ksk_roll_datetime = None
+    __current_roll = None
+    __version = DOMAINSTATE_VERSION
+
+    def __init__(self, version=DOMAINSTATE_VERSION, last_ksk_roll_datetime=datetime.min,
+                 last_zsk_roll_datetime=datetime.min, current_roll=KeyRoll(), **kwargs):
+
+        self.version = version
+        self.last_ksk_roll_datetime = last_ksk_roll_datetime if isinstance(last_ksk_roll_datetime, datetime) else datetime.fromtimestamp(last_ksk_roll_datetime)
+        self.last_zsk_roll_datetime = last_zsk_roll_datetime if isinstance(last_zsk_roll_datetime, datetime) else datetime.fromtimestamp(last_zsk_roll_datetime)
+        self.current_roll = current_roll
+        if kwargs:
+            logger.warning('Unknown keys passed: {}'.format(', '.join(
+                [k for k, v in kwargs.items()])))
+
+    @property
+    def last_zsk_roll_datetime(self):
+        return self.__last_zsk_roll_datetime
+
+    @last_zsk_roll_datetime.setter
+    def last_zsk_roll_datetime(self, val):
+        if not isinstance(val, datetime):
+            raise Exception('Can not set last_zsk_roll_datetime: not a datetime object')
+        self.__last_zsk_roll_datetime = val
+
+    @property
+    def last_ksk_roll_datetime(self):
+        return self.__last_ksk_roll_datetime
+
+    @last_ksk_roll_datetime.setter
+    def last_ksk_roll_datetime(self, val):
+        if not isinstance(val, datetime):
+            raise Exception('Can not set last_ksk_roll_datetime: not a datetime object')
+        self.__last_ksk_roll_datetime = val
+
+    @property
+    def last_ksk_roll_str(self):
+        return "never" if self.last_ksk_roll_datetime == datetime.min else \
+            str(self.last_ksk_roll_datetime)
+    @property
+    def last_zsk_roll_str(self):
+        return "never" if self.last_zsk_roll_datetime == datetime.min else \
+            str(self.last_zsk_roll_datetime)
+
+    @property
+    def current_roll(self):
+        return self.__current_roll
+
+    @current_roll.setter
+    def current_roll(self, val):
+        if not isinstance(val, (KeyRoll, PrePublishKeyRoll)):
+            raise Exception('Roll is not a KeyRoll')
+        self.__current_roll = val
+
+    @property
+    def version(self):
+        return self.__version
+
+    @version.setter
+    def version(self, val):
+        if val != 1:
+            raise Exception('{} is not a valid version!')
+        self.__version = val
+
+    def __repr__(self):
+        return 'DomainState({})'.format(
+            ', '.join(['{}={}'.format(k, v) for k, v in [
+                ('version', self.version),
+                ('last_ksk_roll_datetime', self.last_ksk_roll_datetime.timestamp() if self.last_ksk_roll_datetime > datetime.fromtimestamp(0) else 0),
+                ('last_zsk_roll_datetime', self.last_zsk_roll_datetime.timestamp() if self.last_zsk_roll_datetime > datetime.fromtimestamp(0) else 0),
+                ('current_roll', self.current_roll),
+            ]])
+        )
+
+    def __str__(self):
+        return(json_tricks.dumps({
+            'version': self.version,
+            'last_ksk_roll_datetime': self.last_ksk_roll_datetime.timestamp() if self.last_ksk_roll_datetime > datetime.fromtimestamp(0) else 0,
+            'last_zsk_roll_datetime': self.last_zsk_roll_datetime.timestamp() if self.last_zsk_roll_datetime > datetime.fromtimestamp(0) else 0,
+            'current_roll': self.current_roll,
+        }))
+
+    def set_last_roll_date(self, keytype, date):
+        self.__setattr__('last_{}_roll_datetime'.format(keytype), date)
+
+    def last_roll_date(self, keytype):
+        return self.__getattribute__('last_{}_roll_datetime'.format(keytype))
+
+    @property
+    def is_rolling(self):
+        return bool(not self.current_roll.complete and self.current_roll.started)
diff --git a/pdns/keyroller/pdnskeyroller/keyroll.py b/pdns/keyroller/pdnskeyroller/keyroll.py
new file mode 100644 (file)
index 0000000..59b205f
--- /dev/null
@@ -0,0 +1,27 @@
+class KeyRoll:
+    def __init__(self, **kwargs):
+        self.rolltype = kwargs.get('rolltype')
+        self.complete = False
+
+    def initiate(self, zone, api, **kwargs):
+        raise NotImplementedError()
+
+    def step(self, zone, api):
+        raise NotImplementedError()
+
+    def validate(self, zone, api):
+        raise NotImplementedError()
+
+    def __str__(self):
+        return ''
+
+    def __repr__(self):
+        raise NotImplementedError()
+
+    @property
+    def started(self):
+        return False
+
+    @property
+    def current_step_name(self):
+        raise NotImplementedError()
diff --git a/pdns/keyroller/pdnskeyroller/keyrollerdomain.py b/pdns/keyroller/pdnskeyroller/keyrollerdomain.py
new file mode 100644 (file)
index 0000000..f09d38d
--- /dev/null
@@ -0,0 +1,87 @@
+from pdnsapi.api import PDNSApi
+import logging
+import pdnskeyroller.domainconfig
+import pdnskeyroller.domainstate
+from pytimeparse.timeparse import timeparse
+import datetime
+
+logger = logging.getLogger(__name__)
+
+class KeyrollerDomain:
+    def __init__(self, zone, api, config=None, state=None):
+        if not isinstance(api, PDNSApi):
+            raise Exception('api is not a PDNSApi')
+
+        self.zone = zone
+        self.api = api
+        if not config:
+            config = pdnskeyroller.domainconfig.from_api(zone, api)
+
+        if not isinstance(config, pdnskeyroller.domainconfig.DomainConfig):
+            raise Exception('config is not a DomainConfig')
+
+        self.config = config
+
+        if not state:
+            state = pdnskeyroller.domainstate.from_api(zone, api)
+
+        if not isinstance(state, pdnskeyroller.domainstate.DomainState):
+            raise Exception('state is not a DomainState')
+
+        self.state = state
+
+    def next_ksk_roll(self):
+        if not self.state.is_rolling:
+            if self.config.ksk_frequency != 0 :
+                return self.state.last_roll_date('ksk') + datetime.timedelta(seconds=timeparse(self.config.ksk_frequency))
+        return None
+
+    def next_zsk_roll(self):
+        if not self.state.is_rolling:
+            if self.config.zsk_frequency != 0:
+                return self.state.last_roll_date('zsk') + datetime.timedelta(seconds=timeparse(self.config.zsk_frequency))
+        return None
+
+    @property
+    def current_step_name(self):
+        if not self.state.is_rolling:
+            return None
+        return self.state.current_roll.current_step_name
+
+    def step(self, force=False, customttl=0):
+        if not self.state.is_rolling:
+            return
+        self.state.current_roll.step(self.zone, self.api, force, customttl)
+        pdnskeyroller.domainstate.to_api(self.zone, self.api, self.state)
+
+    @property
+    def next_action_datetime(self):
+        """
+        The datetime for the next roll or action
+
+        :return:
+        """
+        ret = []
+        if self.state.is_rolling:
+            nextaction = self.state.current_roll.current_step_datetime
+            ret.append(nextaction)
+            logger.debug("{}: Next roll step {}".format(self.zone, nextaction))
+        else:
+            if self.config.zsk_frequency != 0:
+                nextaction = self.next_zsk_roll()
+                ret.append(nextaction)
+                logger.debug("{}: Next ZSK roll {}".format(self.zone, nextaction))
+            if self.config.ksk_frequency != 0:
+                nextaction = self.next_ksk_roll()
+                ret.append(nextaction)
+                logger.debug("{}: Next KSK roll {}".format(self.zone, nextaction))
+        if ret:
+            ret.sort()
+            return ret[0]
+        return None
+
+
+    def __repr__(self):
+        return 'keyrollerDomain("{}", {}, {}, {})'.format(
+            self.zone, self.api, self.config, self.state
+        )
diff --git a/pdns/keyroller/pdnskeyroller/prepublishkeyroll.py b/pdns/keyroller/pdnskeyroller/prepublishkeyroll.py
new file mode 100644 (file)
index 0000000..d9ebbca
--- /dev/null
@@ -0,0 +1,220 @@
+import pdnsapi.api
+import json_tricks.nonp as json_tricks
+from pdnskeyroller.util import (get_keys_of_type, DNSKEY_ALGO_TO_MNEMONIC, DNSKEY_MNEMONIC_TO_ALGO, validate_api)
+from datetime import datetime, timedelta
+from pdnskeyroller.keyroll import KeyRoll
+
+_step_to_name = {
+    0: 'initial',
+    1: 'new DNSKEY',
+    2: 'new DS/new RRSIGs',
+    3: 'DNSKEY removal',
+}
+
+
+class PrePublishKeyRoll(KeyRoll):
+    def __init__(self, **kwargs):
+        super().__init__(rolltype='prepublish')
+        self.current_step = kwargs.get('current_step', 0)
+        self.complete = kwargs.get('complete', False)
+        self.step_datetimes = list(map(lambda x: datetime.fromtimestamp(x), kwargs.get('step_datetimes', [])))
+        self.current_step_datetime = datetime.fromtimestamp(kwargs.get('current_step_datetime', datetime.now().timestamp()))
+        self.keytype = kwargs.get('keytype')
+        self.algo = kwargs.get('algo')
+        self.old_keyids = kwargs.get('old_keyids')
+        self.new_keyid = kwargs.get('new_keyid')
+
+    def initiate(self, zone, api, keytype, algo, bits=None, published=True):
+        """
+        Initiate a pre-publish rollover (:rfc:`RFC 6781 §4.1.1.1 <6781#section-4.1.1.1>`) for the ``keytype`` key of algorithm
+    ``algo`` for ``zone``.
+
+        The roll will **only** be initiated if there exists a ``keytype`` key of algorithm ``algo`` for the domain ``zone``.
+
+        :param string zone: The zone to roll for
+        :param pdnsapi.api.PDNSApi api: The API endpoint to use
+        :param string keytype: The keytype to roll, must be one of 'ksk', 'zsk' or 'csk'
+        :param string algo: The algorithm to roll the ``keytype`` for
+        :param int bits: If needed, use this many bits for the new key for ``algo``
+        """
+        if self.started:
+            raise Exception('Already rolling the {} for {}'.format(
+                self.keytype, zone))
+        validate_api(api)
+
+        keytype = keytype.lower()
+        if keytype not in ('ksk', 'zsk'):
+            raise Exception('Invalid key type: {}'.format(keytype))
+
+        current_keys = get_keys_of_type(zone, api, keytype)
+        algo = DNSKEY_ALGO_TO_MNEMONIC.get(algo, algo)
+        if not current_keys:
+            raise Exception('There are no keys of type {} in zone {}, cannot roll!'.format(keytype, zone))
+        if not any([k.algo == algo and k.keytype == keytype for k in current_keys]):
+            raise Exception('No keys for algorithm {} in zone {}, cannot roll!'.format(algo, zone))
+
+        active = True
+        published = True
+        if keytype == "zsk":
+            active = False
+        new_key = api.add_cryptokey(zone, keytype, active=active, algo=algo, bits=bits, published=published)
+        self.current_step = 1
+        self.complete = False
+        self.step_datetimes = [datetime.now()]
+        self.keytype = keytype
+        self.algo = algo
+        self.old_keyids = [k.id for k in current_keys if k.algo == algo and k.keytype == keytype]
+        self.new_keyid = new_key.id
+        httl = self._get_highest_ttl(zone, api)
+        self.current_step_datetime = datetime.now() + timedelta(seconds=httl)
+
+        api.bump_soa(zone);
+
+    def _get_highest_ttl(self, zone, api, zoneobject=None):
+        if zoneobject is None:
+            zoneobject = api.get_zone(zone)
+        httl = 0
+        for rrset in zoneobject.rrsets:
+            httl = max(rrset.ttl, httl)
+
+        return httl
+
+    def is_waiting_ds(self):
+        return self.started and self.keytype == "ksk" and self.current_step == 1
+
+    def step(self, zone, api, force=False, customttl=0):
+        """
+        Perform the next step in the keyroll
+
+        :param string zone: The zone we are rolling for
+        :param pdnsapi.api.PDNSApi api: The API endpoint to use
+        :raises: Exception when a sanity check fails
+        """
+        validate_api(api)
+        if not self.validate(zone, api):
+            raise Exception('Keys for zone {}  do not match keys initially found. Refusing to continue'.format(zone))
+
+        if not self.started:
+            raise Exception('Can not go to the next step in phase "{}", did you mean to call initialize()?'.format(
+                self.current_step_name))
+
+        # make sure we are passed the expected datetime
+        if self.current_step_datetime > datetime.now():
+            return
+
+        if self.current_step == 1:
+            if self.keytype == "zsk":
+                # activate the new keys and deactivate the old ones
+                api.set_cryptokey_active(zone, self.new_keyid, active=True)
+                for keyid in self.old_keyids:
+                    api.set_cryptokey_active(zone, keyid, active=False)
+
+                api.bump_soa(zone);
+
+                httl = self._get_highest_ttl(zone, api)
+                self.current_step_datetime = datetime.now() + timedelta(seconds=httl)
+                self.step_datetimes.append(datetime.now())
+                self.current_step = 2
+
+            elif self.keytype == "ksk":
+                if force == True and isinstance(customttl, int):
+                    self.current_step_datetime = datetime.now() + timedelta(seconds=customttl)
+                    self.step_datetimes.append(datetime.now())
+                    self.current_step = 3
+
+        elif self.current_step == 2:
+            if self.keytype == "zsk":
+                # remove the old keys
+                for keyid in self.old_keyids:
+                    api.delete_cryptokey(zone, keyid)
+                api.bump_soa(zone);
+                # rollover is finished
+                self.complete = True
+                self.step_datetimes.append(datetime.now())
+
+
+        elif self.current_step == 3:
+            if self.keytype == "ksk":
+                # remove the old keys
+                for keyid in self.old_keyids:
+                    api.delete_cryptokey(zone, keyid)
+                api.bump_soa(zone);
+                # rollover is finished
+                self.complete = True
+                self.step_datetimes.append(datetime.now())
+
+        else:
+            raise Exception("Unknown step number {}".format(self.current_step))
+
+    def validate(self, zone, api):
+        """
+        Checks if the current keys in the zone matches what we have
+
+        :param string zone: The zone to check in
+        :param pdnsapi.api.PDNSApi api: The API endpoint to use
+        :return: True if the keys in the zone indeed match, False otherwise
+        :rtype: bool
+        """
+        validate_api(api)
+        to_match = self.old_keyids.copy()
+        to_match.append(self.new_keyid)
+        return all([k.id in to_match for k in api.get_cryptokeys(zone)
+                    if k.algo == self.algo and k.keytype == self.keytype])
+
+    def __str__(self):
+        return json_tricks.dumps({
+            'rolltype': 'prepublish',
+            'current_step': self.current_step,
+            'complete': self.complete,
+            'current_step_datetime': self.current_step_datetime.timestamp(),
+            'step_datetimes': list(map(lambda d: d.timestamp(), self.step_datetimes)),
+            'keytype': self.keytype,
+            'algo': self.algo,
+            'old_keyids': self.old_keyids,
+            'new_keyid': self.new_keyid,
+        })
+    def __json_encode__(self):
+        # should return primitive, serializable types like dict, list, int, string, float...
+        return {
+            'rolltype': 'prepublish',
+            'current_step': self.current_step,
+            'complete': self.complete,
+            'current_step_datetime': self.current_step_datetime.timestamp(),
+            'step_datetimes': list(map(lambda d: d.timestamp(), self.step_datetimes)),
+            'keytype': self.keytype,
+            'algo': self.algo,
+            'old_keyids': self.old_keyids,
+            'new_keyid': self.new_keyid,
+        }
+
+    def __json_decode__(self, **kwargs):
+        super().__init__(rolltype='prepublish')
+        self.current_step = kwargs.get('current_step', 0)
+        self.complete = kwargs.get('complete', False)
+        self.step_datetimes = list(map(lambda x: datetime.fromtimestamp(x), kwargs.get('step_datetimes', [])))
+        self.current_step_datetime = datetime.fromtimestamp(kwargs.get('current_step_datetime', datetime.now().timestamp()))
+        self.keytype = kwargs.get('keytype')
+        self.algo = kwargs.get('algo')
+        self.old_keyids = kwargs.get('old_keyids')
+        self.new_keyid = kwargs.get('new_keyid')
+
+    def __repr__(self):
+        return 'PrePublishRoll({})'.format(
+            ', '.join(['{}={}'.format(k, v) for k, v in [
+                ('current_step', self.current_step),
+                ('complete', self.complete),
+                ('current_step_datetime', self.current_step_datetime.timestamp()),
+                ('step_datetimes', list(map(lambda d: d.timestamp(), self.step_datetimes))),
+                ('keytype', self.keytype),
+                ('algo', self.algo),
+                ('old_keyids', self.old_keyids),
+                ('new_keyid', self.new_keyid),
+            ]]))
+
+    @property
+    def started(self):
+        return self.current_step > 0
+
+    @property
+    def current_step_name(self):
+        return _step_to_name.get(self.current_step)
diff --git a/pdns/keyroller/pdnskeyroller/util.py b/pdns/keyroller/pdnskeyroller/util.py
new file mode 100644 (file)
index 0000000..50bcd66
--- /dev/null
@@ -0,0 +1,98 @@
+import pdnsapi.api
+import logging
+
+logger = logging.getLogger()
+
+"""
+Helper functions for the keyrollers and the daemon
+"""
+
+DNSKEY_ALGO_TO_MNEMONIC = {
+        1: "RSAMD5",
+        2: "DH",
+        3: "DSA",
+        5: "RSASHA1",
+        6: "DSA-NSEC3-SHA1",
+        7: "RSASHA1-NSEC3-SHA1",
+        8: "RSASHA256",
+        10: "RSASHA512",
+        12: "ECC-GOST",
+        13: "ECDSAP256",
+        14: "ECDSAP384",
+        15: "ED25519",
+        16: "ED448",
+    }
+
+DNSKEY_MNEMONIC_TO_ALGO = {v: k for k, v in DNSKEY_ALGO_TO_MNEMONIC.items()}
+
+def parse_algo(algo):
+    res = 0
+    try:
+        res = int(algo)
+    except:
+        res = DNSKEY_MNEMONIC_TO_ALGO.get(algo.upper())
+
+    if DNSKEY_ALGO_TO_MNEMONIC.get(res) is None:
+        raise Exception('Unknown key algorithm {}'.format(algo))
+
+    return res
+
+
+def validate_api(api):
+    if not isinstance(api, pdnsapi.api.PDNSApi):
+        raise Exception('api is not a PDNSApi')
+
+def validate_keytype(keytype):
+    keytype = keytype.lower()
+    if keytype not in ('ksk', 'zsk', 'csk'):
+        raise Exception('{} is not a valid key type'.format(keytype))
+
+
+def get_keystyle(zone, api):
+    """
+    Determines the current style of DNSSEC keying for ``zone``.
+    The style will be one of:
+
+    * single (one or more CSKs)
+    * split (KSK(s) and ZSK(s) exist)
+    * mixed (There are CSK(s), KSK(s) and ZSK(s))
+
+    :param string zone: The zone to check
+    :param pdnsapi.api.PDNSApi api: The API endpoint to use
+    :return: The description of the current key-style
+    :rtype: string
+    """
+    validate_api(api)
+    cryptokeys = api.get_cryptokeys(zone)
+
+    if not len(cryptokeys):
+        raise Exception('No cryptokeys for zone {}'.format(zone))
+
+    got_ksk = any([cryptokey.keytype.lower() == 'ksk' for cryptokey in cryptokeys])
+    got_zsk = any([cryptokey.keytype.lower() == 'zsk' for cryptokey in cryptokeys])
+    got_csk = any([cryptokey.keytype.lower() == 'csk' for cryptokey in cryptokeys])
+
+    if got_csk and not any([got_ksk, got_zsk]):
+        return 'single'
+    if all([got_ksk, got_zsk]) and not got_csk:
+        return 'split'
+    if all([got_ksk, got_zsk, got_csk]):
+        return 'mixed'
+
+
+def get_keys_of_type(zone, api, keytype):
+    """
+    Returns all the keys of type ``keytype`` for ``zone``
+
+    :param string zone: The zone to get the keys from
+    :param pdnsapi.api.PDNSApi api: The API endpoint to use
+    :param string keytype: 'ksk', 'zsk' or 'csk'
+    :return: All the keys of the requested type
+    :rtype: list(pdnsapi.zone.CryptoKey)
+    """
+    validate_api(api)
+    keytype = keytype.lower()
+    validate_keytype(keytype)
+
+    cryptokeys = api.get_cryptokeys(zone)
+    return [k for k in cryptokeys if k.keytype == keytype]
diff --git a/pdns/keyroller/requirements-test.txt b/pdns/keyroller/requirements-test.txt
new file mode 100644 (file)
index 0000000..145e520
--- /dev/null
@@ -0,0 +1,2 @@
+nose
+requests-mock
diff --git a/pdns/keyroller/requirements.txt b/pdns/keyroller/requirements.txt
new file mode 100644 (file)
index 0000000..f09318e
--- /dev/null
@@ -0,0 +1,5 @@
+PyYAML
+pytimeparse
+requests
+json_tricks
+nose
diff --git a/pdns/keyroller/setup.py b/pdns/keyroller/setup.py
new file mode 100644 (file)
index 0000000..d89c879
--- /dev/null
@@ -0,0 +1,56 @@
+import os
+from setuptools import setup, find_packages
+
+install_reqs = list()
+
+# Use pipenv for dependencies, setuptools otherwise.
+# This makes the installation for the packages easier (no pipenv needed)
+try:
+    from pipenv.project import Project
+    from pipenv.utils import convert_deps_to_pip
+    pfile = Project(chdir=False).parsed_pipfile
+    install_reqs = convert_deps_to_pip(pfile['packages'], r=False)
+except ImportError:
+    from pkg_resources import parse_requirements
+    import pathlib
+    with pathlib.Path('requirements.txt').open() as requirements_txt:
+        install_reqs = [
+            str(r)
+            for r
+            in parse_requirements(requirements_txt)]
+
+
+def exists(fname):
+    return os.path.exists(os.path.join(os.path.dirname(__file__), fname))
+
+
+# Utility function to read the README file.
+# Used for the long_description.  It's nice, because now 1) we have a top level
+# README file and 2) it's easier to type in the README file than to put a raw
+# string in below ...
+def read(fname):
+    return open(os.path.join(os.path.dirname(__file__), fname),
+                'r', encoding='utf-8').read()
+
+
+version = os.environ.get('BUILDER_VERSION', '0.0.0')
+
+if exists('version.txt'):
+    version = read('version.txt').strip()
+
+setup(
+    name = "pdns-keyroller",
+    version = version,
+    author = "PowerDNS.COM BV",
+    author_email = "powerdns.support@powerdns.com",
+    description = ("PowerDNS keyroller"),
+    license = "GNU GPLv2",
+    keywords = "PowerDNS keyroller",
+    url = "https://www.powerdns.com/",
+    packages = find_packages(),
+    install_requires=install_reqs,
+    include_package_data = True,
+    scripts=['pdns-keyroller.py', 'pdns-keyroller-ctl.py'],
+    long_description=read('README.md'),
+    classifiers=[],
+)
index f777b8080819a63b1941dead3506cdddb92c8f15..e843332601f2a74cfb4644125bf93aa5990ea6c9 100644 (file)
@@ -38,7 +38,7 @@
 class KqueueFDMultiplexer : public FDMultiplexer
 {
 public:
-  KqueueFDMultiplexer();
+  KqueueFDMultiplexer(unsigned int maxEventsHint);
   ~KqueueFDMultiplexer()
   {
     if (d_kqueuefd >= 0) {
@@ -60,14 +60,11 @@ public:
 private:
   int d_kqueuefd;
   std::vector<struct kevent> d_kevents;
-  static unsigned int s_maxevents; // not a hard maximum
 };
 
-unsigned int KqueueFDMultiplexer::s_maxevents = 1024;
-
-static FDMultiplexer* make()
+static FDMultiplexer* make(unsigned int maxEventsHint)
 {
-  return new KqueueFDMultiplexer();
+  return new KqueueFDMultiplexer(maxEventsHint);
 }
 
 static struct KqueueRegisterOurselves
@@ -78,8 +75,8 @@ static struct KqueueRegisterOurselves
   }
 } kQueueDoIt;
 
-KqueueFDMultiplexer::KqueueFDMultiplexer() :
-  d_kevents(s_maxevents)
+KqueueFDMultiplexer::KqueueFDMultiplexer(unsigned int maxEventsHint) :
+  d_kevents(maxEventsHint)
 {
   d_kqueuefd = kqueue();
   if (d_kqueuefd < 0) {
@@ -148,7 +145,7 @@ void KqueueFDMultiplexer::getAvailableFDs(std::vector<int>& fds, int timeout)
   ts.tv_sec = timeout / 1000;
   ts.tv_nsec = (timeout % 1000) * 1000000;
 
-  int ret = kevent(d_kqueuefd, 0, 0, d_kevents.data(), s_maxevents, &ts);
+  int ret = kevent(d_kqueuefd, 0, 0, d_kevents.data(), d_kevents.size(), timeout != -1 ? &ts : nullptr);
 
   if (ret < 0 && errno != EINTR) {
     throw FDMultiplexerException("kqueue returned error: " + stringerror());
@@ -177,7 +174,7 @@ int KqueueFDMultiplexer::run(struct timeval* now, int timeout)
   ts.tv_sec = timeout / 1000;
   ts.tv_nsec = (timeout % 1000) * 1000000;
 
-  int ret = kevent(d_kqueuefd, 0, 0, d_kevents.data(), s_maxevents, &ts);
+  int ret = kevent(d_kqueuefd, 0, 0, d_kevents.data(), d_kevents.size(), timeout != -1 ? &ts : nullptr);
   gettimeofday(now, nullptr); // MANDATORY!
 
   if (ret < 0 && errno != EINTR) {
diff --git a/pdns/lazy_allocator.hh b/pdns/lazy_allocator.hh
deleted file mode 100644 (file)
index 986f91b..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.
- */
-#pragma once
-#include <cstddef>
-#include <utility>
-#include <type_traits>
-#include <new>
-#include <sys/mman.h>
-
-// On OpenBSD mem used as stack should be marked MAP_STACK
-#if !defined(MAP_STACK)
-#define MAP_STACK 0
-#endif
-
-template <typename T>
-struct lazy_allocator {
-    using value_type = T;
-    using pointer = T*;
-    using size_type = std::size_t;
-    static_assert (std::is_trivial<T>::value,
-                   "lazy_allocator must only be used with trivial types");
-
-    pointer
-    allocate (size_type const n) {
-#ifdef __OpenBSD__
-        void *p = mmap(nullptr, n * sizeof(value_type),
-          PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_STACK, -1, 0);
-        if (p == MAP_FAILED)
-          throw std::bad_alloc();
-        return static_cast<pointer>(p);
-#else
-        return static_cast<pointer>(::operator new (n * sizeof(value_type)));
-#endif
-    }
-
-    void
-    deallocate (pointer const ptr, size_type const n) noexcept {
-#ifdef __OpenBSD__
-        munmap(ptr, n * sizeof(value_type));
-#else
-#if defined(__cpp_sized_deallocation) &&  (__cpp_sized_deallocation >= 201309)
-        ::operator delete (ptr, n * sizeof(value_type));
-#else
-        (void) n;
-        ::operator delete (ptr);
-#endif
-#endif
-    }
-
-    void construct (T*) const noexcept {}
-
-    template <typename X, typename... Args>
-    void
-    construct (X* place, Args&&... args) const noexcept {
-        new (static_cast<void*>(place)) X (std::forward<Args>(args)...);
-    }
-};
-
-template <typename T> inline
-bool operator== (lazy_allocator<T> const&, lazy_allocator<T> const&) noexcept {
-    return true;
-}
-
-template <typename T> inline
-bool operator!= (lazy_allocator<T> const&, lazy_allocator<T> const&) noexcept {
-    return false;
-}
index f2ee87073f513cb395c6bb1830527901a2d8deba..ca35757f0c2ede7a21c55b00dd78eeca5de9110a 100644 (file)
 #include <openssl/engine.h>
 #endif
 #include <openssl/err.h>
+#ifndef DISABLE_OCSP_STAPLING
 #include <openssl/ocsp.h>
+#endif /* DISABLE_OCSP_STAPLING */
 #include <openssl/pkcs12.h>
+#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3
+#include <openssl/provider.h>
+#endif /* defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 */
 #include <openssl/rand.h>
 #include <openssl/ssl.h>
 #include <fcntl.h>
 
+#if OPENSSL_VERSION_MAJOR >= 3
+#include <openssl/param_build.h>
+#include <openssl/core.h>
+#include <openssl/core_names.h>
+#include <openssl/evp.h>
+#else
+#include <openssl/hmac.h>
+#endif
+
 #ifdef HAVE_LIBSODIUM
 #include <sodium.h>
 #endif /* HAVE_LIBSODIUM */
@@ -68,9 +82,15 @@ static void openssl_thread_cleanup()
 #endif /* (OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2090100fL) */
 
 static std::atomic<uint64_t> s_users;
+
+#if OPENSSL_VERSION_MAJOR >= 3 && defined(HAVE_TLS_PROVIDERS)
+static LockGuarded<std::unordered_map<std::string, std::unique_ptr<OSSL_PROVIDER, decltype(&OSSL_PROVIDER_unload)>>> s_providers;
+#else
 #ifndef OPENSSL_NO_ENGINE
-static LockGuarded<std::unordered_map<std::string, std::unique_ptr<ENGINE, int(*)(ENGINE*)>>> s_engines;
+static LockGuarded<std::unordered_map<std::string, std::unique_ptr<ENGINE, decltype(&ENGINE_free)>>> s_engines;
+#endif
 #endif
+
 static int s_ticketsKeyIndex{-1};
 static int s_countersIndex{-1};
 static int s_keyLogIndex{-1};
@@ -79,13 +99,28 @@ void registerOpenSSLUser()
 {
   if (s_users.fetch_add(1) == 0) {
 #ifdef HAVE_OPENSSL_INIT_CRYPTO
+#ifndef DISABLE_OPENSSL_ERROR_STRINGS
+    uint64_t cryptoOpts = OPENSSL_INIT_LOAD_CONFIG;
+    const uint64_t sslOpts = 0;
+#else /* DISABLE_OPENSSL_ERROR_STRINGS */
+    uint64_t cryptoOpts = OPENSSL_INIT_LOAD_CONFIG|OPENSSL_INIT_NO_LOAD_CRYPTO_STRINGS;
+    const uint64_t sslOpts = OPENSSL_INIT_NO_LOAD_SSL_STRINGS;
+#endif /* DISABLE_OPENSSL_ERROR_STRINGS */
     /* load the default configuration file (or one specified via OPENSSL_CONF),
        which can then be used to load engines.
-       Do not load all ciphers and digests, we only need a few of them and these
+    */
+#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3
+    /* Since 661595ca0933fe631faeadd14a189acd5d4185e0 we can no longer rely on the ciphers and digests
+       required for TLS to be loaded by OPENSSL_init_ssl(), so let's give up and load everything */
+#else /* OPENSSL_VERSION_MAJOR >= 3 */
+    /* Do not load all ciphers and digests, we only need a few of them and these
        will be loaded by OPENSSL_init_ssl(). */
-    OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG|OPENSSL_INIT_NO_ADD_ALL_CIPHERS|OPENSSL_INIT_NO_ADD_ALL_DIGESTS, nullptr);
-    OPENSSL_init_ssl(0, nullptr);
-#endif
+    cryptoOpts |= OPENSSL_INIT_NO_ADD_ALL_CIPHERS|OPENSSL_INIT_NO_ADD_ALL_DIGESTS;
+#endif /* OPENSSL_VERSION_MAJOR >= 3 */
+
+    OPENSSL_init_crypto(cryptoOpts, nullptr);
+    OPENSSL_init_ssl(sslOpts, nullptr);
+#endif /* HAVE_OPENSSL_INIT_CRYPTO */
 
 #if (OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined LIBRESSL_VERSION_NUMBER && LIBRESSL_VERSION_NUMBER < 0x2090100fL))
     /* load error strings for both libcrypto and libssl */
@@ -117,6 +152,7 @@ void registerOpenSSLUser()
 void unregisterOpenSSLUser()
 {
   if (s_users.fetch_sub(1) == 1) {
+#if OPENSSL_VERSION_MAJOR < 3 || !defined(HAVE_TLS_PROVIDERS)
 #ifndef OPENSSL_NO_ENGINE
     for (auto& [name, engine] : *s_engines.lock()) {
       ENGINE_finish(engine.get());
@@ -124,6 +160,7 @@ void unregisterOpenSSLUser()
     }
     s_engines.lock()->clear();
 #endif
+#endif
 #if (OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined LIBRESSL_VERSION_NUMBER && LIBRESSL_VERSION_NUMBER < 0x2090100fL))
     ERR_free_strings();
 
@@ -139,7 +176,33 @@ void unregisterOpenSSLUser()
   }
 }
 
-std::pair<bool, std::string> libssl_load_engine(const std::string& engineName, const std::optional<std::string>& defaultString)
+#if defined(HAVE_LIBSSL) && OPENSSL_VERSION_MAJOR >= 3 && defined(HAVE_TLS_PROVIDERS)
+std::pair<bool, std::string> libssl_load_provider(const std::string& providerName)
+{
+  if (s_users.load() == 0) {
+    /* We need to make sure that OpenSSL has been properly initialized before loading an engine.
+       This messes up our accounting a bit, so some memory might not be properly released when
+       the program exits when engines are in use. */
+    registerOpenSSLUser();
+  }
+
+  auto providers = s_providers.lock();
+  if (providers->count(providerName) > 0) {
+    return { false, "TLS provider already loaded" };
+  }
+
+  auto provider = std::unique_ptr<OSSL_PROVIDER, decltype(&OSSL_PROVIDER_unload)>(OSSL_PROVIDER_load(nullptr, providerName.c_str()), OSSL_PROVIDER_unload);
+  if (provider == nullptr) {
+    return { false, "unable to load TLS provider '" + providerName + "'" };
+  }
+
+  providers->insert({providerName, std::move(provider)});
+  return { true, "" };
+}
+#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([[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" };
@@ -156,14 +219,11 @@ std::pair<bool, std::string> libssl_load_engine(const std::string& engineName, c
     return { false, "TLS engine already loaded" };
   }
 
-  ENGINE* enginePtr = ENGINE_by_id(engineName.c_str());
-  if (enginePtr == nullptr) {
+  auto engine = std::unique_ptr<ENGINE, decltype(&ENGINE_free)>(ENGINE_by_id(engineName.c_str()), ENGINE_free);
+  if (engine == nullptr) {
     return { false, "unable to load TLS engine '" + engineName + "'" };
   }
 
-  auto engine = std::unique_ptr<ENGINE, int(*)(ENGINE*)>(enginePtr, ENGINE_free);
-  enginePtr = nullptr;
-
   if (!ENGINE_init(engine.get())) {
     return { false, "Unable to init TLS engine '" + engineName + "'" };
   }
@@ -178,6 +238,7 @@ std::pair<bool, std::string> libssl_load_engine(const std::string& engineName, c
   return { true, "" };
 #endif
 }
+#endif /* HAVE_LIBSSL && !HAVE_TLS_PROVIDERS */
 
 void* libssl_get_ticket_key_callback_data(SSL* s)
 {
@@ -194,9 +255,13 @@ void libssl_set_ticket_key_callback_data(SSL_CTX* ctx, void* data)
   SSL_CTX_set_ex_data(ctx, s_ticketsKeyIndex, data);
 }
 
-int libssl_ticket_key_callback(SSL *s, OpenSSLTLSTicketKeysRing& keyring, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx, int enc)
+#if OPENSSL_VERSION_MAJOR >= 3
+int libssl_ticket_key_callback(SSL* /* s */, OpenSSLTLSTicketKeysRing& keyring, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* iv, EVP_CIPHER_CTX* ectx, EVP_MAC_CTX* hctx, int enc)
+#else
+int libssl_ticket_key_callback(SSL* /* s */, OpenSSLTLSTicketKeysRing& keyring, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* iv, EVP_CIPHER_CTX* ectx, HMAC_CTX* hctx, int enc)
+#endif
 {
-  if (enc) {
+  if (enc != 0) {
     const auto key = keyring.getEncryptionKey();
     if (key == nullptr) {
       return -1;
@@ -213,7 +278,7 @@ int libssl_ticket_key_callback(SSL *s, OpenSSLTLSTicketKeysRing& keyring, unsign
     return 0;
   }
 
-  if (key->decrypt(iv, ectx, hctx) == false) {
+  if (!key->decrypt(iv, ectx, hctx)) {
     return -1;
   }
 
@@ -225,19 +290,16 @@ int libssl_ticket_key_callback(SSL *s, OpenSSLTLSTicketKeysRing& keyring, unsign
   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;
   }
 
   return SSL_TLSEXT_ERR_NOACK;
 }
 
-static void libssl_info_callback(const SSL *ssl, int where, int ret)
+static void libssl_info_callback(const SSL *ssl, int where, int /* ret */)
 {
   SSL_CTX* sslCtx = SSL_get_SSL_CTX(ssl);
   if (sslCtx == nullptr) {
@@ -287,12 +349,13 @@ static void libssl_info_callback(const SSL *ssl, int where, int ret)
   }
 }
 
-void libssl_set_error_counters_callback(std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)>& ctx, TLSErrorCounters* counters)
+void libssl_set_error_counters_callback(std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>& ctx, TLSErrorCounters* counters)
 {
   SSL_CTX_set_ex_data(ctx.get(), s_countersIndex, counters);
   SSL_CTX_set_info_callback(ctx.get(), libssl_info_callback);
 }
 
+#ifndef DISABLE_OCSP_STAPLING
 int libssl_ocsp_stapling_callback(SSL* ssl, const std::map<int, std::string>& ocspMap)
 {
   auto pkey = SSL_get_privatekey(ssl);
@@ -365,7 +428,7 @@ static bool libssl_validate_ocsp_response(const std::string& response)
   return true;
 }
 
-std::map<int, std::string> libssl_load_ocsp_responses(const std::vector<std::string>& ocspFiles, std::vector<int> keyTypes)
+static std::map<int, std::string> libssl_load_ocsp_responses(const std::vector<std::string>& ocspFiles, std::vector<int> keyTypes, std::vector<std::string>& warnings)
 {
   std::map<int, std::string> ocspResponses;
 
@@ -377,12 +440,13 @@ std::map<int, std::string> libssl_load_ocsp_responses(const std::vector<std::str
   for (const auto& filename : ocspFiles) {
     std::ifstream file(filename, std::ios::binary);
     std::string content;
-    while(file) {
+    while (file) {
       char buffer[4096];
       file.read(buffer, sizeof(buffer));
       if (file.bad()) {
         file.close();
-        throw std::runtime_error("Unable to load OCSP response from '" + filename + "'");
+        warnings.push_back("Unable to load OCSP response from " + filename);
+        continue;
       }
       content.append(buffer, file.gcount());
     }
@@ -393,7 +457,8 @@ std::map<int, std::string> libssl_load_ocsp_responses(const std::vector<std::str
       ocspResponses.insert({keyTypes.at(count), std::move(content)});
     }
     catch (const std::exception& e) {
-      throw std::runtime_error("Error checking the validity of OCSP response from '" + filename + "': " + e.what());
+      warnings.push_back("Error checking the validity of OCSP response from '" + filename + "': " + e.what());
+      continue;
     }
     ++count;
   }
@@ -401,25 +466,6 @@ std::map<int, std::string> libssl_load_ocsp_responses(const std::vector<std::str
   return ocspResponses;
 }
 
-int libssl_get_last_key_type(std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)>& ctx)
-{
-#ifdef HAVE_SSL_CTX_GET0_PRIVATEKEY
-  auto pkey = SSL_CTX_get0_privatekey(ctx.get());
-#else
-  auto temp = std::unique_ptr<SSL, void(*)(SSL*)>(SSL_new(ctx.get()), SSL_free);
-  if (!temp) {
-    return -1;
-  }
-  auto pkey = SSL_get_privatekey(temp.get());
-#endif
-
-  if (!pkey) {
-    return -1;
-  }
-
-  return EVP_PKEY_base_id(pkey);
-}
-
 #ifdef HAVE_OCSP_BASIC_SIGN
 bool libssl_generate_ocsp_response(const std::string& certFile, const std::string& caCert, const std::string& caKey, const std::string& outFile, int ndays, int nmin)
 {
@@ -466,6 +512,26 @@ bool libssl_generate_ocsp_response(const std::string& certFile, const std::strin
   return true;
 }
 #endif /* HAVE_OCSP_BASIC_SIGN */
+#endif /* DISABLE_OCSP_STAPLING */
+
+static int libssl_get_last_key_type(std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>& ctx)
+{
+#ifdef HAVE_SSL_CTX_GET0_PRIVATEKEY
+  auto pkey = SSL_CTX_get0_privatekey(ctx.get());
+#else
+  auto temp = std::unique_ptr<SSL, void(*)(SSL*)>(SSL_new(ctx.get()), SSL_free);
+  if (!temp) {
+    return -1;
+  }
+  auto pkey = SSL_get_privatekey(temp.get());
+#endif
+
+  if (!pkey) {
+    return -1;
+  }
+
+  return EVP_PKEY_base_id(pkey);
+}
 
 LibsslTLSVersion libssl_tls_version_from_string(const std::string& str)
 {
@@ -500,7 +566,7 @@ const std::string& libssl_tls_version_to_string(LibsslTLSVersion version)
   return it->second;
 }
 
-bool libssl_set_min_tls_version(std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)>& ctx, LibsslTLSVersion version)
+bool libssl_set_min_tls_version(std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>& ctx, LibsslTLSVersion version)
 {
 #if defined(HAVE_SSL_CTX_SET_MIN_PROTO_VERSION) || defined(SSL_CTX_set_min_proto_version)
   /* These functions have been introduced in 1.1.0, and the use of SSL_OP_NO_* is deprecated
@@ -560,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()
@@ -598,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());
@@ -613,10 +677,10 @@ void OpenSSLTLSTicketKeysRing::loadTicketsKeys(const std::string& keyFile)
   file.close();
 }
 
-void OpenSSLTLSTicketKeysRing::rotateTicketsKey(time_t now)
+void OpenSSLTLSTicketKeysRing::rotateTicketsKey(time_t /* now */)
 {
   auto newKey = std::make_shared<OpenSSLTLSTicketKey>();
-  addKey(newKey);
+  addKey(std::move(newKey));
 }
 
 OpenSSLTLSTicketKey::OpenSSLTLSTicketKey()
@@ -673,7 +737,15 @@ bool OpenSSLTLSTicketKey::nameMatches(const unsigned char name[TLS_TICKETS_KEY_N
   return (memcmp(d_name, name, sizeof(d_name)) == 0);
 }
 
-int OpenSSLTLSTicketKey::encrypt(unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx) const
+#if OPENSSL_VERSION_MAJOR >= 3
+static const std::string sha256KeyName{"sha256"};
+#endif
+
+#if OPENSSL_VERSION_MAJOR >= 3
+int OpenSSLTLSTicketKey::encrypt(unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* iv, EVP_CIPHER_CTX* ectx, EVP_MAC_CTX* hctx) const
+#else
+int OpenSSLTLSTicketKey::encrypt(unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* iv, EVP_CIPHER_CTX* ectx, HMAC_CTX* hctx) const
+#endif
 {
   memcpy(keyName, d_name, sizeof(d_name));
 
@@ -685,18 +757,76 @@ int OpenSSLTLSTicketKey::encrypt(unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE
     return -1;
   }
 
+#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) {
+    return -1;
+  }
+
+  if (OSSL_PARAM_BLD_push_utf8_string(params_build.get(), OSSL_MAC_PARAM_DIGEST, sha256KeyName.c_str(), sha256KeyName.size()) == 0) {
+    return -1;
+  }
+
+  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.get()) == 0) {
+    return -1;
+  }
+
+  if (EVP_MAC_init(hctx, d_hmacKey, sizeof(d_hmacKey), nullptr) == 0) {
+    return -1;
+  }
+#else
   if (HMAC_Init_ex(hctx, d_hmacKey, sizeof(d_hmacKey), TLS_TICKETS_MAC_ALGO(), nullptr) != 1) {
     return -1;
   }
+#endif
 
   return 1;
 }
 
-bool OpenSSLTLSTicketKey::decrypt(const unsigned char* iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx) const
+#if OPENSSL_VERSION_MAJOR >= 3
+bool OpenSSLTLSTicketKey::decrypt(const unsigned char* iv, EVP_CIPHER_CTX* ectx, EVP_MAC_CTX* hctx) const
+#else
+bool OpenSSLTLSTicketKey::decrypt(const unsigned char* iv, EVP_CIPHER_CTX* ectx, HMAC_CTX* hctx) const
+#endif
 {
+#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) {
+    return false;
+  }
+
+  if (OSSL_PARAM_BLD_push_utf8_string(params_build.get(), OSSL_MAC_PARAM_DIGEST, sha256KeyName.c_str(), sha256KeyName.size()) == 0) {
+    return false;
+  }
+
+  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.get()) == 0) {
+    return false;
+  }
+
+  if (EVP_MAC_init(hctx, d_hmacKey, sizeof(d_hmacKey), nullptr) == 0) {
+    return false;
+  }
+#else
   if (HMAC_Init_ex(hctx, d_hmacKey, sizeof(d_hmacKey), TLS_TICKETS_MAC_ALGO(), nullptr) != 1) {
     return false;
   }
+#endif
 
   if (EVP_DecryptInit_ex(ectx, TLS_TICKETS_CIPHER_ALGO(), nullptr, d_cipherKey, iv) != 1) {
     return false;
@@ -705,10 +835,15 @@ bool OpenSSLTLSTicketKey::decrypt(const unsigned char* iv, EVP_CIPHER_CTX *ectx,
   return true;
 }
 
-std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> libssl_init_server_context(const TLSConfig& config,
-                                                                       std::map<int, std::string>& ocspResponses)
+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)
 {
-  auto ctx = std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)>(SSL_CTX_new(SSLv23_server_method()), SSL_CTX_free);
+  std::vector<std::string> warnings;
+  auto ctx = std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>(SSL_CTX_new(SSLv23_server_method()), SSL_CTX_free);
+
+  if (!ctx) {
+    throw pdns::OpenSSL::error("Error creating an OpenSSL server context");
+  }
 
   int sslOptions =
     SSL_OP_NO_SSLv2 |
@@ -728,6 +863,12 @@ std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> libssl_init_server_context(const TLS
 #endif /* HAVE_SSL_CTX_SET_NUM_TICKETS */
   }
 
+  if (config.d_ktls) {
+#ifdef SSL_OP_ENABLE_KTLS
+    sslOptions |= SSL_OP_ENABLE_KTLS;
+#endif /* SSL_OP_ENABLE_KTLS */
+  }
+
   if (config.d_sessionTimeout > 0) {
     SSL_CTX_set_timeout(ctx.get(), config.d_sessionTimeout);
   }
@@ -747,6 +888,10 @@ std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> libssl_init_server_context(const TLS
 #endif
   }
 
+#ifdef SSL_OP_IGNORE_UNEXPECTED_EOF
+  sslOptions |= SSL_OP_IGNORE_UNEXPECTED_EOF;
+#endif
+
   SSL_CTX_set_options(ctx.get(), sslOptions);
   if (!libssl_set_min_tls_version(ctx, config.d_minTLSVersion)) {
     throw std::runtime_error("Failed to set the minimum version to '" + libssl_tls_version_to_string(config.d_minTLSVersion));
@@ -777,7 +922,7 @@ std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> libssl_init_server_context(const TLS
 #ifdef SSL_MODE_ASYNC
     mode |= SSL_MODE_ASYNC;
 #else
-    cerr<<"Warning: TLS async mode requested but not supported"<<endl;
+    warnings.push_back("Warning: TLS async mode requested but not supported");
 #endif
   }
 
@@ -792,7 +937,7 @@ std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> libssl_init_server_context(const TLS
   /* 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) {
@@ -805,10 +950,30 @@ std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> libssl_init_server_context(const TLS
       EVP_PKEY *keyptr = nullptr;
       X509 *certptr = nullptr;
       STACK_OF(X509) *captr = nullptr;
-      if (!PKCS12_parse(p12.get(), (pair.d_password ? pair.d_password->c_str() : nullptr), &keyptr, &certptr, &captr))
-      {
-        ERR_print_errors_fp(stderr);
-        throw std::runtime_error("An error occured while parsing PKCS12 file " + pair.d_cert);
+      if (!PKCS12_parse(p12.get(), (pair.d_password ? pair.d_password->c_str() : nullptr), &keyptr, &certptr, &captr)) {
+#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3
+        bool failed = true;
+        /* we might be opening a PKCS12 file that uses RC2 CBC or 3DES CBC which, since OpenSSL 3.0.0, requires loading the legacy provider */
+        auto libCtx = OSSL_LIB_CTX_get0_global_default();
+        /* check whether the legacy provider is already loaded */
+        if (!OSSL_PROVIDER_available(libCtx, "legacy")) {
+          /* it's not */
+          auto provider = OSSL_PROVIDER_load(libCtx, "legacy");
+          if (provider != nullptr) {
+            if (PKCS12_parse(p12.get(), (pair.d_password ? pair.d_password->c_str() : nullptr), &keyptr, &certptr, &captr)) {
+              failed = false;
+            }
+            /* we do not want to keep that provider around after that */
+            OSSL_PROVIDER_unload(provider);
+          }
+        }
+        if (failed) {
+#endif /* defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 */
+          ERR_print_errors_fp(stderr);
+          throw std::runtime_error("An error occured while parsing PKCS12 file " + pair.d_cert);
+#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3
+        }
+#endif /* defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 */
       }
       auto key = std::unique_ptr<EVP_PKEY, void(*)(EVP_PKEY*)>(keyptr, EVP_PKEY_free);
       auto cert = std::unique_ptr<X509, void(*)(X509*)>(certptr, X509_free);
@@ -843,14 +1008,16 @@ std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> libssl_init_server_context(const TLS
     keyTypes.push_back(keyType);
  }
 
+#ifndef DISABLE_OCSP_STAPLING
   if (!config.d_ocspFiles.empty()) {
     try {
-      ocspResponses = libssl_load_ocsp_responses(config.d_ocspFiles, keyTypes);
+      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()));
     }
   }
+#endif /* DISABLE_OCSP_STAPLING */
 
   if (!config.d_ciphers.empty() && SSL_CTX_set_cipher_list(ctx.get(), config.d_ciphers.c_str()) != 1) {
     throw std::runtime_error("The TLS ciphers could not be set: " + config.d_ciphers);
@@ -862,7 +1029,7 @@ std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> libssl_init_server_context(const TLS
   }
 #endif /* HAVE_SSL_CTX_SET_CIPHERSUITES */
 
-  return ctx;
+  return {std::move(ctx), std::move(warnings)};
 }
 
 #ifdef HAVE_SSL_CTX_SET_KEYLOG_CALLBACK
@@ -883,7 +1050,7 @@ static void libssl_key_log_file_callback(const SSL* ssl, const char* line)
 }
 #endif /* HAVE_SSL_CTX_SET_KEYLOG_CALLBACK */
 
-std::unique_ptr<FILE, int(*)(FILE*)> libssl_set_key_log_file(std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)>& ctx, const std::string& logFile)
+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)
 {
 #ifdef HAVE_SSL_CTX_SET_KEYLOG_CALLBACK
   int fd = open(logFile.c_str(),  O_WRONLY | O_CREAT | O_APPEND, 0600);
@@ -907,12 +1074,14 @@ std::unique_ptr<FILE, int(*)(FILE*)> libssl_set_key_log_file(std::unique_ptr<SSL
 }
 
 /* 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
 void libssl_set_npn_select_callback(SSL_CTX* ctx, int (*cb)(SSL* s, unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen, void* arg), void* arg)
 {
 #ifdef HAVE_SSL_CTX_SET_NEXT_PROTO_SELECT_CB
   SSL_CTX_set_next_proto_select_cb(ctx, cb, arg);
 #endif
 }
+#endif /* DISABLE_NPN */
 
 void libssl_set_alpn_select_callback(SSL_CTX* ctx, int (*cb)(SSL* s, const unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen, void* arg), void* arg)
 {
index aeb4059ef5127a18e9687a8795b295222db891ba..327fed32a67756300f5b4c904d68f02f87fda528 100644 (file)
@@ -21,7 +21,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)) {
   }
 };
 
@@ -51,6 +51,10 @@ public:
   bool d_enableRenegotiation{false};
   /* enable TLS async mode, if supported by any engine */
   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
@@ -91,8 +95,14 @@ public:
   ~OpenSSLTLSTicketKey();
 
   bool nameMatches(const unsigned char name[TLS_TICKETS_KEY_NAME_SIZE]) const;
-  int encrypt(unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx) const;
-  bool decrypt(const unsigned char* iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx) const;
+
+#if OPENSSL_VERSION_MAJOR >= 3
+  int encrypt(unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* iv, EVP_CIPHER_CTX* ectx, EVP_MAC_CTX* hctx) const;
+  bool decrypt(const unsigned char* iv, EVP_CIPHER_CTX* ectx, EVP_MAC_CTX* hctx) const;
+#else
+  int encrypt(unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* iv, EVP_CIPHER_CTX* ectx, HMAC_CTX* hctx) const;
+  bool decrypt(const unsigned char* iv, EVP_CIPHER_CTX* ectx, HMAC_CTX* hctx) const;
+#endif
 
 private:
   unsigned char d_name[TLS_TICKETS_KEY_NAME_SIZE];
@@ -105,7 +115,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();
@@ -113,35 +122,45 @@ public:
   void rotateTicketsKey(time_t now);
 
 private:
+  void addKey(std::shared_ptr<OpenSSLTLSTicketKey>&& newKey);
+
   SharedLockGuarded<boost::circular_buffer<std::shared_ptr<OpenSSLTLSTicketKey> > > d_ticketKeys;
 };
 
 void* libssl_get_ticket_key_callback_data(SSL* s);
 void libssl_set_ticket_key_callback_data(SSL_CTX* ctx, void* data);
-int libssl_ticket_key_callback(SSL *s, OpenSSLTLSTicketKeysRing& keyring, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx, int enc);
-
-int libssl_ocsp_stapling_callback(SSL* ssl, const std::map<int, std::string>& ocspMap);
 
-std::map<int, std::string> libssl_load_ocsp_responses(const std::vector<std::string>& ocspFiles, std::vector<int> keyTypes);
-int libssl_get_last_key_type(std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)>& ctx);
+#if OPENSSL_VERSION_MAJOR >= 3
+int libssl_ticket_key_callback(SSL* s, OpenSSLTLSTicketKeysRing& keyring, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* iv, EVP_CIPHER_CTX* ectx, EVP_MAC_CTX* hctx, int enc);
+#else
+int libssl_ticket_key_callback(SSL* s, OpenSSLTLSTicketKeysRing& keyring, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* iv, EVP_CIPHER_CTX* ectx, HMAC_CTX* hctx, int enc);
+#endif
 
+#ifndef DISABLE_OCSP_STAPLING
+int libssl_ocsp_stapling_callback(SSL* ssl, const std::map<int, std::string>& ocspMap);
 #ifdef HAVE_OCSP_BASIC_SIGN
 bool libssl_generate_ocsp_response(const std::string& certFile, const std::string& caCert, const std::string& caKey, const std::string& outFile, int ndays, int nmin);
 #endif
+#endif /* DISABLE_OCSP_STAPLING */
 
-void libssl_set_error_counters_callback(std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)>& ctx, TLSErrorCounters* counters);
+void libssl_set_error_counters_callback(std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>& ctx, TLSErrorCounters* counters);
 
 LibsslTLSVersion libssl_tls_version_from_string(const std::string& str);
 const std::string& libssl_tls_version_to_string(LibsslTLSVersion version);
-bool libssl_set_min_tls_version(std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)>& ctx, LibsslTLSVersion version);
+bool libssl_set_min_tls_version(std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>& ctx, LibsslTLSVersion version);
 
-std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> libssl_init_server_context(const TLSConfig& config,
-                                                                       std::map<int, std::string>& ocspResponses);
+/* return the created context, and a list of warning messages for issues not severe enough
+   to trigger raising an exception, like failing to load an OCSP response file */
+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, void(*)(SSL_CTX*)>& ctx, const std::string& logFile);
+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);
 
 /* 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
 void libssl_set_npn_select_callback(SSL_CTX* ctx, int (*cb)(SSL* s, unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen, void* arg), void* arg);
+#endif /* DISABLE_NPN */
+
 /* called in a server context, to select an ALPN value advertised by the client if any */
 void libssl_set_alpn_select_callback(SSL_CTX* ctx, int (*cb)(SSL* s, const unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen, void* arg), void* arg);
 /* set the supported ALPN protos in client context */
@@ -149,6 +168,12 @@ bool libssl_set_alpn_protos(SSL_CTX* ctx, const std::vector<std::vector<uint8_t>
 
 std::string libssl_get_error_string();
 
+#if defined(HAVE_LIBSSL) && OPENSSL_VERSION_MAJOR >= 3 && defined(HAVE_TLS_PROVIDERS)
+std::pair<bool, std::string> libssl_load_provider(const std::string& engineName);
+#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);
+#endif /* HAVE_LIBSSL && !HAVE_TLS_PROVIDERS */
 
 #endif /* HAVE_LIBSSL */
index e8bd82988da84b0c3f1f303f925e18312430ba9c..611d8ada02a7cecac3956c7488fc602a174dee19 100644 (file)
@@ -22,6 +22,7 @@
 #pragma once
 #include <mutex>
 #include <shared_mutex>
+#include <stdexcept>
 
 /*
   This file provides several features around locks:
@@ -80,9 +81,7 @@
 class ReadWriteLock
 {
 public:
-  ReadWriteLock()
-  {
-  }
+  ReadWriteLock() = default;
 
   ReadWriteLock(const ReadWriteLock& rhs) = delete;
   ReadWriteLock(ReadWriteLock&& rhs) = delete;
@@ -110,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))
   {
   }
 
@@ -135,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))
   {
   }
 
@@ -274,9 +275,7 @@ public:
   {
   }
 
-  explicit LockGuarded()
-  {
-  }
+  explicit LockGuarded() = default;
 
   LockGuardedTryHolder<T> try_lock()
   {
@@ -288,7 +287,7 @@ public:
     return LockGuardedHolder<T>(d_value, d_mutex);
   }
 
-  LockGuardedHolder<const T> read_only_lock() const
+  LockGuardedHolder<const T> read_only_lock()
   {
     return LockGuardedHolder<const T>(d_value, d_mutex);
   }
@@ -422,9 +421,7 @@ public:
   {
   }
 
-  explicit SharedLockGuarded()
-  {
-  }
+  explicit SharedLockGuarded() = default;
 
   SharedLockGuardedTryHolder<T> try_write_lock()
   {
index 8d34e1ca4c9afca870b22363c42cde9332668542..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
@@ -202,3 +216,15 @@ Logger& Logger::operator<<(const ComboAddress& ca)
   *this << ca.toLogString();
   return *this;
 }
+
+void addTraceTS(const timeval& start, ostringstream& str)
+{
+  const auto& content = str.str();
+  if (content.empty() || content.back() == '\n') {
+    timeval time{};
+    gettimeofday(&time, nullptr);
+    auto elapsed = time - start;
+    auto diff = elapsed.tv_sec * 1000000 + static_cast<time_t>(elapsed.tv_usec);
+    str << diff << ' ';
+  }
+}
index dc86efbcd7977bb6b38cbc58e1c35854f86a11cb..9a84661442c1d9597d9f4a272340faeabd425755 100644 (file)
@@ -24,7 +24,9 @@
 #include <string>
 #include <ctime>
 #include <iostream>
+#include <optional>
 #include <sstream>
+#include <variant>
 #include <syslog.h>
 
 #include "namespaces.hh"
@@ -154,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();
@@ -166,3 +168,47 @@ Logger& getLogger();
 #else
 #define DLOG(x) ((void)0)
 #endif
+
+// The types below are used by rec, which can log to g_log (general logging) or a string stream
+// (trace-regexp). We pass an OptLog object to the code that should not know anything about this
+// That code should then log using VLOG
+
+struct LogVariant
+{
+  string prefix;
+  timeval start;
+  // 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);
+
+// 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;             \
+    }                                                                                                    \
+  }
+
+// 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;                      \
+    }                                                                                            \
+  }
diff --git a/pdns/logging.hh b/pdns/logging.hh
new file mode 100644 (file)
index 0000000..c08a8e6
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ * 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 RECURSOR
+
+#include <map>
+#include <memory>
+#include <string>
+#include <sstream>
+#include <boost/optional.hpp>
+
+#include "logr.hh"
+#include "dnsname.hh"
+#include "iputils.hh"
+
+namespace Logging
+{
+
+struct Entry
+{
+  boost::optional<std::string> name; // name parts joined with '.'
+  std::string message; // message as send to log call
+  boost::optional<std::string> error; // error if .Error() was called
+  struct timeval d_timestamp; // time of entry generation
+  std::map<std::string, std::string> values; // key-value pairs
+  size_t level; // level at which this was logged
+  Logr::Priority d_priority; // (syslog) priority)
+};
+
+// Warning: some meta-programming is going on.  We define helper
+// templates that can be used to see if specific string output
+// functions are available.  If so, we use those instead of << into an
+// ostringstream. Note that this decision happpens compile time.
+// Some hints taken from https://www.cppstories.com/2019/07/detect-overload-from-chars/
+// (I could not get function templates with enabled_if<> to work in this case)
+//
+// Default: std::string(T) is not available
+template <typename T, typename = void>
+struct is_to_string_available : std::false_type
+{
+};
+
+// If std::string(T) is available this template is used
+template <typename T>
+struct is_to_string_available<T, std::void_t<decltype(std::to_string(std::declval<T>()))>> : std::true_type
+{
+};
+
+// Same mechanism for t.toLogString() and t.toStructuredLogString()
+template <typename T, typename = void>
+struct is_toLogString_available : std::false_type
+{
+};
+
+template <typename T>
+struct is_toLogString_available<T, std::void_t<decltype(std::declval<T>().toLogString())>> : std::true_type
+{
+};
+
+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
+{
+};
+
+template <typename T>
+struct is_toString_available<T, std::void_t<decltype(std::declval<T>().toString())>> : std::true_type
+{
+};
+
+template <typename T>
+struct Loggable : public Logr::Loggable
+{
+  const T& _t;
+  Loggable(const T& v) :
+    _t(v)
+  {
+  }
+  std::string to_string() const
+  {
+    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();
+    }
+    else if constexpr (is_toString_available<T>::value) {
+      return _t.toString();
+    }
+    else if constexpr (is_to_string_available<T>::value) {
+      return std::to_string(_t);
+    }
+    else {
+      std::ostringstream oss;
+      oss << _t;
+      return oss.str();
+    }
+  }
+};
+
+template <typename T>
+struct IterLoggable : public Logr::Loggable
+{
+  const T& _t1;
+  const T& _t2;
+  IterLoggable(const T& v1, const T& v2) :
+    _t1(v1), _t2(v2)
+  {
+  }
+  std::string to_string() const
+  {
+    std::ostringstream oss;
+    bool first = true;
+    for (auto i = _t1; i != _t2; i++) {
+      if (!first) {
+        oss << ' ';
+      }
+      else {
+        first = false;
+      }
+      oss << *i;
+    }
+    return oss.str();
+  }
+};
+
+typedef void (*EntryLogger)(const Entry&);
+
+class Logger : public Logr::Logger, public std::enable_shared_from_this<const Logger>
+{
+public:
+  bool enabled(Logr::Priority) const override;
+
+  void info(const std::string& msg) const override;
+  void info(Logr::Priority, const std::string& msg) const override;
+  void error(int err, const std::string& msg) const override;
+  void error(const std::string& err, const std::string& msg) const override;
+  void error(Logr::Priority, int err, const std::string& msg) const override;
+  void error(Logr::Priority, const std::string& err, const std::string& msg) const override;
+
+  std::shared_ptr<Logr::Logger> v(size_t level) const override;
+  std::shared_ptr<Logr::Logger> withValues(const std::map<std::string, std::string>& values) const override;
+  virtual std::shared_ptr<Logr::Logger> withName(const std::string& name) const override;
+
+  static std::shared_ptr<Logger> create(EntryLogger callback);
+  static std::shared_ptr<Logger> create(EntryLogger callback, const std::string& name);
+
+  Logger(EntryLogger callback);
+  Logger(EntryLogger callback, boost::optional<std::string> name);
+  Logger(std::shared_ptr<const Logger> parent, boost::optional<std::string> name, size_t verbosity, size_t lvl, EntryLogger callback);
+  virtual ~Logger();
+
+  size_t getVerbosity() const;
+  void setVerbosity(size_t verbosity);
+
+private:
+  void logMessage(const std::string& msg, boost::optional<const std::string> err) const;
+  void logMessage(const std::string& msg, Logr::Priority p, boost::optional<const std::string> err) const;
+  std::shared_ptr<const Logger> getptr() const;
+
+  std::shared_ptr<const Logger> _parent{nullptr};
+  EntryLogger _callback;
+  boost::optional<std::string> _name;
+  std::map<std::string, std::string> _values;
+  // current Logger's level. the higher the more verbose.
+  size_t _level{0};
+  // verbosity settings. messages with level higher's than verbosity won't appear
+  size_t _verbosity{0};
+};
+}
+
+extern std::shared_ptr<Logging::Logger> g_slog;
+
+// Prefer structured logging?
+extern bool g_slogStructured;
+
+// A helper macro to switch between old-style logging and new-style (structured logging)
+// A typical use:
+//
+// 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) {      \
+      slogCall;                  \
+    }                            \
+    else {                       \
+      oldStyle;                  \
+    }                            \
+  } while (0)
+
+#else // No structured logging (e.g. auth)
+// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
+#define SLOG(oldStyle, slogCall) \
+  do {                           \
+    oldStyle;                    \
+  } 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 bac43dd2670db29f48339a8e4fb4a17ef16d8ecb..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;
@@ -43,4 +44,4 @@ private:
   luacall_prequery_t d_prequery;
 };
 std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, const DNSName& qname,
-                                                   const DNSName& zone, int zoneid, const DNSPacket& dnsp, uint16_t qtype);
+                                                   const DNSName& zone, int zoneid, const DNSPacket& dnsp, uint16_t qtype, unique_ptr<AuthLua4>& LUA);
index 31c2da0cf730c6a79e4525199dc117ebcb653a37..3984fe62e974583b2a7e8b6d85703d119ee0c3bc 100644 (file)
@@ -1,8 +1,10 @@
+#include <cassert>
 #include <fstream>
 #include <unordered_set>
 #include <unordered_map>
 #include <typeinfo>
 #include "logger.hh"
+#include "logging.hh"
 #include "iputils.hh"
 #include "dnsname.hh"
 #include "dnsparser.hh"
 #include "ext/luawrapper/include/LuaContext.hpp"
 #include "dns_random.hh"
 
-BaseLua4::BaseLua4() {
-}
+BaseLua4::BaseLua4() = default;
 
-int BaseLua4::loadFile(const std::string &fname) {
-  int ret = 0;
+void BaseLua4::loadFile(const std::string& fname)
+{
   std::ifstream ifs(fname);
   if (!ifs) {
-    ret = errno;
-    g_log<<Logger::Error<<"Unable to read configuration file from '"<<fname<<"': "<<stringerror()<<endl;
-    return ret;
+    auto ret = errno;
+    auto msg = stringerror(ret);
+    SLOG(g_log << Logger::Error << "Unable to read configuration file from '" << fname << "': " << msg << endl,
+         g_slog->withName("lua")->error(Logr::Error, ret, "Unable to read configuration file", "file", Logging::Loggable(fname), "msg", Logging::Loggable(msg)));
+    throw std::runtime_error(msg);
   }
   loadStream(ifs);
-  return 0;
 };
 
 void BaseLua4::loadString(const std::string &script) {
@@ -43,7 +45,7 @@ void BaseLua4::prepareContext() {
   Features features;
   getFeatures(features);
   d_lw->writeVariable("pdns_features", features);
-  
+
   // dnsheader
   d_lw->registerFunction<int(dnsheader::*)()>("getID", [](dnsheader& dh) { return ntohs(dh.id); });
   d_lw->registerFunction<bool(dnsheader::*)()>("getCD", [](dnsheader& dh) { return dh.cd; });
@@ -167,7 +169,10 @@ void BaseLua4::prepareContext() {
       else
         cas.insert(boost::get<ComboAddress>(in));
       }
-      catch(std::exception& e) { g_log <<Logger::Error<<e.what()<<endl; }
+      catch(std::exception& e) {
+        SLOG(g_log <<Logger::Error<<e.what()<<endl,
+             g_slog->withName("lua")->error(Logr::Error, e.what(), "Exception in newCAS", "exception", Logging::Loggable("std::exception")));
+      }
     });
   d_lw->registerFunction<bool(cas_t::*)(const ComboAddress&)>("check",[](const cas_t& cas, const ComboAddress&ca) { return cas.count(ca)>0; });
 
@@ -211,26 +216,31 @@ void BaseLua4::prepareContext() {
   d_lw->registerFunction("match", (bool (NetmaskGroup::*)(const ComboAddress&) const)&NetmaskGroup::match);
 
   // DNSRecord
-  d_lw->writeFunction("newDR", [](const DNSName &name, const std::string &type, unsigned int ttl, const std::string &content, int place){ QType qtype; qtype = type; auto dr = DNSRecord(); dr.d_name = name; dr.d_type = qtype.getCode(); dr.d_ttl = ttl; dr.d_content = shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(dr.d_type, 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);
   d_lw->registerMember("place", &DNSRecord::d_place);
-  d_lw->registerFunction<string(DNSRecord::*)()>("getContent", [](const DNSRecord& dr) { return dr.d_content->getZoneRepresentation(); });
+  d_lw->registerFunction<string(DNSRecord::*)()>("getContent", [](const DNSRecord& dr) { return dr.getContent()->getZoneRepresentation(); });
   d_lw->registerFunction<boost::optional<ComboAddress>(DNSRecord::*)()>("getCA", [](const DNSRecord& dr) {
       boost::optional<ComboAddress> ret;
 
-      if(auto arec = std::dynamic_pointer_cast<ARecordContent>(dr.d_content))
+      if(auto arec = getRR<ARecordContent>(dr))
         ret=arec->getCA(53);
-      else if(auto aaaarec = std::dynamic_pointer_cast<AAAARecordContent>(dr.d_content))
+      else if(auto aaaarec = getRR<AAAARecordContent>(dr))
         ret=aaaarec->getCA(53);
       return ret;
     });
-  d_lw->registerFunction<void(DNSRecord::*)(const std::string&)>("changeContent", [](DNSRecord& dr, const std::string& newContent) { dr.d_content = 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) { g_log << (Logger::Urgency)loglevel.get_value_or(Logger::Warning) << msg<<endl; });
-  d_lw->writeFunction("pdnsrandom", [](boost::optional<uint32_t> maximum) { return dns_random(maximum.get_value_or(0xffffffff)); });
+  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 maximum ? dns_random(*maximum) : dns_random_uint32();
+  });
 
   // certain constants
 
@@ -285,4 +295,4 @@ void BaseLua4::loadStream(std::istream &is) {
   postLoad();
 }
 
-BaseLua4::~BaseLua4() { }
+BaseLua4::~BaseLua4() = default;
index 9394de4f8466c5390d8763f9a4e3b09a573956fc..20b0159716bc93504d003209beccf1cb292395d5 100644 (file)
@@ -13,16 +13,16 @@ protected:
 
 public:
   BaseLua4();
-  int loadFile(const std::string &fname);
-  void loadString(const std::string &script);
-  void loadStream(std::istream &is);
+  void loadFile(const std::string& fname);
+  void loadString(const std::stringscript);
+  void loadStream(std::istreamis);
   virtual ~BaseLua4(); // this is so unique_ptr works with an incomplete type
 protected:
   void prepareContext();
   virtual void postPrepareContext() = 0;
   virtual void postLoad() = 0;
-  typedef vector<pair<string, int> > in_t;
-  vector<pair<string, boost::variant<int, in_t, struct timeval* > > > d_pd;
-  typedef vector<pair<string, boost::variant<string,bool,int,double> > > Features;
+  typedef vector<pair<string, int>> in_t;
+  vector<pair<string, boost::variant<int, in_t, struct timeval*>>> d_pd;
+  typedef vector<pair<string, boost::variant<string, bool, int, double>>> Features;
   virtual void getFeatures(Features&);
 };
index 3571facf89b613c8ac6d79fd4037f65d8912f221..5f3e7cb6156b717e76d58d4d5a1a81afffc42f1c 100644 (file)
@@ -1,7 +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"
@@ -9,9 +14,8 @@
 #include "sstuff.hh"
 #include "minicurl.hh"
 #include "ueberbackend.hh"
-#include "dnsrecords.hh"
 #include "dns_random.hh"
-#include "common_startup.hh"
+#include "auth-main.hh"
 #include "../modules/geoipbackend/geoipinterface.hh" // only for the enum
 
 /* to do:
@@ -58,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
@@ -76,10 +80,9 @@ private:
 public:
   IsUpOracle()
   {
+    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);
@@ -97,6 +100,10 @@ private:
       if (cd.opts.count("useragent")) {
         useragent = cd.opts.at("useragent");
       }
+      size_t byteslimit = 0;
+      if (cd.opts.count("byteslimit")) {
+        byteslimit = static_cast<size_t>(std::atoi(cd.opts.at("byteslimit").c_str()));
+      }
       MiniCurl mc(useragent);
 
       string content;
@@ -110,10 +117,10 @@ private:
 
       if (cd.opts.count("source")) {
         ComboAddress src(cd.opts.at("source"));
-        content=mc.getURL(cd.url, rem, &src, timeout);
+        content=mc.getURL(cd.url, rem, &src, timeout, false, false, byteslimit);
       }
       else {
-        content=mc.getURL(cd.url, rem, nullptr, timeout);
+        content=mc.getURL(cd.url, rem, nullptr, timeout, false, false, byteslimit);
       }
       if (cd.opts.count("stringmatch") && content.find(cd.opts.at("stringmatch")) == string::npos) {
         throw std::runtime_error(boost::str(boost::format("unable to match content with `%s`") % cd.opts.at("stringmatch")));
@@ -201,6 +208,7 @@ private:
   SharedLockGuarded<statuses_t> d_statuses;
 
   std::unique_ptr<std::thread> d_checkerThread;
+  std::atomic_flag d_checkerThreadStarted;
 
   void setStatus(const CheckDesc& cd, bool status)
   {
@@ -238,7 +246,7 @@ private:
 
 bool IsUpOracle::isUp(const CheckDesc& cd)
 {
-  if (!d_checkerThread) {
+  if (!d_checkerThreadStarted.test_and_set()) {
     d_checkerThread = std::make_unique<std::thread>([this] { return checkThread(); });
   }
   time_t now = time(nullptr);
@@ -293,7 +301,6 @@ bool doCompare(const T& var, const std::string& res, const C& cmp)
 }
 }
 
-
 static std::string getGeo(const std::string& ip, GeoIPInterface::GeoIPQueryAttribute qa)
 {
   static bool initialized;
@@ -309,61 +316,124 @@ static std::string getGeo(const std::string& ip, GeoIPInterface::GeoIPQueryAttri
     return g_getGeo(ip, (int)qa);
 }
 
-static ComboAddress pickrandom(const vector<ComboAddress>& ips)
+template <typename T>
+static T pickRandom(const vector<T>& items)
 {
-  if (ips.empty()) {
-    throw std::invalid_argument("The IP list cannot be empty");
+  if (items.empty()) {
+    throw std::invalid_argument("The items list cannot be empty");
   }
-  return ips[dns_random(ips.size())];
+  return items[dns_random(items.size())];
 }
 
-static ComboAddress hashed(const ComboAddress& who, const vector<ComboAddress>& ips)
+template <typename T>
+static T pickHashed(const ComboAddress& who, const vector<T>& items)
 {
-  if (ips.empty()) {
-    throw std::invalid_argument("The IP list cannot be empty");
+  if (items.empty()) {
+    throw std::invalid_argument("The items list cannot be empty");
   }
   ComboAddress::addressOnlyHash aoh;
-  return ips[aoh(who) % ips.size()];
+  return items[aoh(who) % items.size()];
 }
 
-
-static ComboAddress pickwrandom(const vector<pair<int,ComboAddress> >& wips)
+template <typename T>
+static T pickWeightedRandom(const vector< pair<int, T> >& items)
 {
-  if (wips.empty()) {
-    throw std::invalid_argument("The IP list cannot be empty");
+  if (items.empty()) {
+    throw std::invalid_argument("The items list cannot be empty");
   }
   int sum=0;
-  vector<pair<int, ComboAddress> > pick;
-  for(auto& i : wips) {
+  vector< pair<int, T> > pick;
+  pick.reserve(items.size());
+
+  for(auto& i : items) {
     sum += i.first;
     pick.emplace_back(sum, i.second);
   }
+
+  if (sum == 0) {
+    throw std::invalid_argument("The sum of items cannot be zero");
+  }
+
   int r = dns_random(sum);
-  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const decltype(pick)::value_type& a) { return rarg < a.first; });
+  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;
 }
 
-static ComboAddress pickwhashed(const ComboAddress& bestwho, vector<pair<int,ComboAddress> >& wips)
+template <typename T>
+static T pickWeightedHashed(const ComboAddress& bestwho, const vector< pair<int, T> >& items)
 {
-  if (wips.empty()) {
-    return ComboAddress();
+  if (items.empty()) {
+    throw std::invalid_argument("The items list cannot be empty");
   }
   int sum=0;
-  vector<pair<int, ComboAddress> > pick;
-  for(auto& i : wips) {
+  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) {
-    /* we should not have any weight of zero, but better safe than sorry */
-    return ComboAddress();
+    throw std::invalid_argument("The sum of items cannot be zero");
   }
+
   ComboAddress::addressOnlyHash aoh;
   int r = aoh(bestwho) % sum;
-  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const decltype(pick)::value_type& a) { return rarg < a.first; });
+  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 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)
+{
+  if (items.empty()) {
+    throw std::invalid_argument("The items list cannot be empty");
+  }
+
+  vector<T> pick;
+  pick.reserve(items.size());
+
+  for(auto& item : items) {
+    pick.push_back(item);
+  }
+
+  int count = std::min(std::max<size_t>(0, n), items.size());
+
+  if (count == 0) {
+    return vector<T>();
+  }
+
+  std::shuffle(pick.begin(), pick.end(), pdns::dns_random_engine());
+
+  vector<T> result = {pick.begin(), pick.begin() + count};
+  return result;
+}
+
 static bool getLatLon(const std::string& ip, double& lat, double& lon)
 {
   string inp = getGeo(ip, GeoIPInterface::Location);
@@ -402,14 +472,6 @@ static bool getLatLon(const std::string& ip, string& loc)
     lonhem='W';
   }
 
-  /*
-    >>> deg = int(R)
-    >>> min = int((R - int(R)) * 60.0)
-    >>> sec = (((R - int(R)) * 60.0) - min) * 60.0
-    >>> print("{}º {}' {}\"".format(deg, min, sec))
-  */
-
-
   latdeg = lat;
   latmin = (lat - latdeg)*60.0;
   latsec = (((lat - latdeg)*60.0) - latmin)*60.0;
@@ -431,7 +493,7 @@ static ComboAddress pickclosest(const ComboAddress& bestwho, const vector<ComboA
   if (wips.empty()) {
     throw std::invalid_argument("The IP list cannot be empty");
   }
-  map<double,vector<ComboAddress> > ranked;
+  map<double, vector<ComboAddress> > ranked;
   double wlat=0, wlon=0;
   getLatLon(bestwho.toString(), wlat, wlon);
   //        cout<<"bestwho "<<wlat<<", "<<wlon<<endl;
@@ -467,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;
@@ -484,55 +556,90 @@ static vector<ComboAddress> useSelector(const std::string &selector, const Combo
   if(selector=="all")
     return candidates;
   else if(selector=="random")
-    ret.emplace_back(pickrandom(candidates));
+    ret.emplace_back(pickRandom<ComboAddress>(candidates));
   else if(selector=="pickclosest")
     ret.emplace_back(pickclosest(bestwho, candidates));
   else if(selector=="hashed")
-    ret.emplace_back(hashed(bestwho, candidates));
+    ret.emplace_back(pickHashed<ComboAddress>(bestwho, candidates));
   else {
     g_log<<Logger::Warning<<"LUA Record called with unknown selector '"<<selector<<"'"<<endl;
-    ret.emplace_back(pickrandom(candidates));
+    ret.emplace_back(pickRandom<ComboAddress>(candidates));
   }
 
   return ret;
 }
 
-static vector<string> convIpListToString(const vector<ComboAddress> &comboAddresses)
+static vector<string> convComboAddressListToString(const vector<ComboAddress>& items)
 {
-  vector<string> ret;
+  vector<string> result;
+  result.reserve(items.size());
 
-  ret.reserve(comboAddresses.size());
-  for (const auto& c : comboAddresses) {
-    ret.emplace_back(c.toString());
+  for (const auto& item : items) {
+    result.emplace_back(item.toString());
   }
 
-  return ret;
+  return result;
 }
 
-static vector<ComboAddress> convIplist(const iplist_t& src)
+static vector<ComboAddress> convComboAddressList(const iplist_t& items, uint16_t port=0)
 {
-  vector<ComboAddress> ret;
+  vector<ComboAddress> result;
+  result.reserve(items.size());
 
-  for(const auto& ip : src) {
-    ret.emplace_back(ip.second);
+  for(const auto& item : items) {
+    result.emplace_back(ComboAddress(item.second, port));
   }
 
-  return ret;
+  return result;
+}
+
+/**
+ * Reads and unify single or multiple sets of ips :
+ * - {'192.0.2.1', '192.0.2.2'}
+ * - {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}}
+ */
+
+static vector<vector<ComboAddress>> convMultiComboAddressList(const boost::variant<iplist_t, ipunitlist_t>& items, uint16_t port = 0)
+{
+  vector<vector<ComboAddress>> candidates;
+
+  if(auto simple = boost::get<iplist_t>(&items)) {
+    vector<ComboAddress> unit = convComboAddressList(*simple, port);
+    candidates.push_back(unit);
+  } else {
+    auto units = boost::get<ipunitlist_t>(items);
+    for(const auto& u : units) {
+      vector<ComboAddress> unit = convComboAddressList(u.second, port);
+      candidates.push_back(unit);
+    }
+  }
+  return candidates;
 }
 
-static vector<pair<int, ComboAddress> > convWIplist(const std::unordered_map<int, wiplist_t >& src)
+static vector<string> convStringList(const iplist_t& items)
 {
-  vector<pair<int,ComboAddress> > ret;
+  vector<string> result;
+  result.reserve(items.size());
 
-  ret.reserve(src.size());
-  for(const auto& i : src) {
-    ret.emplace_back(atoi(i.second.at(1).c_str()), ComboAddress(i.second.at(2)));
+  for(const auto& item : items) {
+    result.emplace_back(item.second);
   }
 
-  return ret;
+  return result;
+}
+
+static vector< pair<int, string> > convIntStringPairList(const std::unordered_map<int, wiplist_t >& items)
+{
+  vector<pair<int,string> > result;
+  result.reserve(items.size());
+
+  for(const auto& item : items) {
+    result.emplace_back(atoi(item.second.at(1).c_str()), item.second.at(2));
+  }
+
+  return result;
 }
 
-static thread_local unique_ptr<AuthLua4> s_LUA;
 bool g_LuaRecordSharedState;
 
 typedef struct AuthLuaRecordContext
@@ -545,10 +652,190 @@ typedef struct AuthLuaRecordContext
 
 static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
 
-static void setupLuaRecords()
+/*
+ *  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()
 {
-  LuaContext& lua = *s_LUA->getLua();
+  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)
+    opts = *options;
+
+  candidates = convMultiComboAddressList(ips, port);
+
+  for(const auto& unit : candidates) {
+    vector<ComboAddress> available;
+    for(const auto& c : unit) {
+      if (upcheckf(c, opts)) {
+        available.push_back(c);
+      }
+    }
+    if(!available.empty()) {
+      vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
+      return convComboAddressListToString(res);
+    }
+  }
+
+  // All units down, apply backupSelector on all candidates
+  vector<ComboAddress> ret{};
+  for(const auto& unit : candidates) {
+    ret.insert(ret.end(), unit.begin(), unit.end());
+  }
+
+  vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
+  return convComboAddressListToString(res);
+}
+
+static void setupLuaRecords(LuaContext& lua) // NOLINT(readability-function-cognitive-complexity)
+{
   lua.writeFunction("latlon", []() {
       double lat = 0, lon = 0;
       getLatLon(s_lua_record_ctx->bestwho.toString(), lat, lon);
@@ -588,9 +875,9 @@ static void setupLuaRecords()
         auto labels = s_lua_record_ctx->qname.getRawLabels();
         if(labels.size()<4)
           return std::string("unknown");
-        
+
         vector<ComboAddress> candidates;
-        
+
         // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
         // e["1.2.3.4"]="bert.powerdns.com" then provides an exception
         if(e) {
@@ -603,7 +890,7 @@ static void setupLuaRecords()
         boost::format fmt(format);
         fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit )  );
         fmt % labels[3] % labels[2] % labels[1] % labels[0];
-        
+
         fmt % (labels[3]+"-"+labels[2]+"-"+labels[1]+"-"+labels[0]);
 
         boost::format fmt2("%02x%02x%02x%02x");
@@ -633,20 +920,29 @@ static void setupLuaRecords()
         } 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;
               }
@@ -656,8 +952,12 @@ static void setupLuaRecords()
           }
           ret.resize(ret.size() - 1); // remove trailing dot after last octet
           return ret;
-        } else if(parts[0].length() == 10 && sscanf(parts[0].c_str()+2, "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) {
-          return std::to_string(x1)+"."+std::to_string(x2)+"."+std::to_string(x3)+"."+std::to_string(x4);
+        }
+        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;
@@ -677,9 +977,27 @@ static void setupLuaRecords()
         return ca.toString();
       }
       else if(parts.size()==1) {
-        boost::replace_all(parts[0],"-",":");
-        ComboAddress ca(parts[0]);
-        return ca.toString();
+        if (parts[0].find('-') != std::string::npos) {
+          boost::replace_all(parts[0],"-",":");
+          ComboAddress ca(parts[0]);
+          return ca.toString();
+        } else {
+          if (parts[0].size() >= 32) {
+            auto ippart = parts[0].substr(parts[0].size()-32);
+            auto fulladdress =
+              ippart.substr(0, 4) + ":" +
+              ippart.substr(4, 4) + ":" +
+              ippart.substr(8, 4) + ":" +
+              ippart.substr(12, 4) + ":" +
+              ippart.substr(16, 4) + ":" +
+              ippart.substr(20, 4) + ":" +
+              ippart.substr(24, 4) + ":" +
+              ippart.substr(28, 4);
+
+            ComboAddress ca(fulladdress);
+            return ca.toString();
+          }
+        }
       }
 
       return std::string("::");
@@ -765,34 +1083,18 @@ static void setupLuaRecords()
    *
    * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
    */
-  lua.writeFunction("ifportup", [](int port, const vector<pair<int, string> >& ips, const boost::optional<std::unordered_map<string,string>> options) {
-      vector<ComboAddress> candidates, unavailables;
-      opts_t opts;
-      vector<ComboAddress > conv;
-      std::string selector;
-
-      if(options)
-        opts = *options;
-      for(const auto& i : ips) {
-        ComboAddress rem(i.second, port);
-        if(g_up.isUp(rem, opts)) {
-          candidates.push_back(rem);
-        }
-        else {
-          unavailables.push_back(rem);
-        }
+  lua.writeFunction("ifportup", [](int port, const boost::variant<iplist_t, ipunitlist_t>& ips, const boost::optional<std::unordered_map<string,string>> options) {
+      if (port < 0) {
+        port = 0;
       }
-      if(!candidates.empty()) {
-        // use regular selector
-        selector = getOptionValue(options, "selector", "random");
-      } else {
-        // All units are down, apply backupSelector on all candidates
-        candidates = std::move(unavailables);
-        selector = getOptionValue(options, "backupSelector", "random");
+      if (port > std::numeric_limits<uint16_t>::max()) {
+        port = std::numeric_limits<uint16_t>::max();
       }
 
-      vector<ComboAddress> res = useSelector(selector, s_lua_record_ctx->bestwho, candidates);
-      return convIpListToString(res);
+      auto checker = [](const ComboAddress& addr, const opts_t& opts) {
+        return g_up.isUp(addr, opts);
+      };
+      return genericIfUp(ips, options, checker, port);
     });
 
   lua.writeFunction("ifurlextup", [](const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options) {
@@ -819,75 +1121,50 @@ static void setupLuaRecords()
         }
         if(!available.empty()) {
           vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
-          return convIpListToString(res);
+          return convComboAddressListToString(res);
         }
       }
 
       // All units down, apply backupSelector on all candidates
       vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates);
-      return convIpListToString(res);
+      return convComboAddressListToString(res);
     });
 
   lua.writeFunction("ifurlup", [](const std::string& url,
                                           const boost::variant<iplist_t, ipunitlist_t>& ips,
                                           boost::optional<opts_t> options) {
-      vector<vector<ComboAddress> > candidates;
-      opts_t opts;
-      if(options)
-        opts = *options;
-      if(auto simple = boost::get<iplist_t>(&ips)) {
-        vector<ComboAddress> unit = convIplist(*simple);
-        candidates.push_back(unit);
-      } else {
-        auto units = boost::get<ipunitlist_t>(ips);
-        for(const auto& u : units) {
-          vector<ComboAddress> unit = convIplist(u.second);
-          candidates.push_back(unit);
-        }
-      }
-
-      for(const auto& unit : candidates) {
-        vector<ComboAddress> available;
-        for(const auto& c : unit) {
-          if(g_up.isUp(c, url, opts)) {
-            available.push_back(c);
-          }
-        }
-        if(!available.empty()) {
-          vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
-          return convIpListToString(res);
-        }
-      }
 
-      // All units down, apply backupSelector on all candidates
-      vector<ComboAddress> ret{};
-      for(const auto& unit : candidates) {
-        ret.insert(ret.end(), unit.begin(), unit.end());
-      }
-
-      vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
-      return convIpListToString(res);
+    auto checker = [&url](const ComboAddress& addr, const opts_t& opts) {
+        return g_up.isUp(addr, url, opts);
+      };
+      return genericIfUp(ips, options, checker);
     });
   /*
    * Returns a random IP address from the supplied list
    * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
    */
   lua.writeFunction("pickrandom", [](const iplist_t& ips) {
-      vector<ComboAddress> conv = convIplist(ips);
-
-      return pickrandom(conv).toString();
+      vector<string> items = convStringList(ips);
+      return pickRandom<string>(items);
     });
 
+  lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) {
+      vector<string> items = convStringList(ips);
+         return pickRandomSample<string>(n, items);
+    });
 
+  lua.writeFunction("pickhashed", [](const iplist_t& ips) {
+      vector<string> items = convStringList(ips);
+      return pickHashed<string>(s_lua_record_ctx->bestwho, items);
+    });
   /*
    * Returns a random IP address from the supplied list, as weighted by the
    * various ``weight`` parameters
    * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
    */
   lua.writeFunction("pickwrandom", [](std::unordered_map<int, wiplist_t> ips) {
-      vector<pair<int,ComboAddress> > conv = convWIplist(ips);
-
-      return pickwrandom(conv).toString();
+      vector< pair<int, string> > items = convIntStringPairList(ips);
+      return pickWeightedRandom<string>(items);
     });
 
   /*
@@ -896,18 +1173,50 @@ static void setupLuaRecords()
    * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
    */
   lua.writeFunction("pickwhashed", [](std::unordered_map<int, wiplist_t > ips) {
-      vector<pair<int,ComboAddress> > conv;
+      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);
+    });
 
-      conv.reserve(ips.size());
+  /*
+   * 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)
-        conv.emplace_back(atoi(i.second[1].c_str()), ComboAddress(i.second[2]));
+      {
+        items.emplace_back(atoi(i.second[1].c_str()), i.second[2]);
+      }
 
-      return pickwhashed(s_lua_record_ctx->bestwho, conv).toString();
+      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 = convIplist(ips);
+      vector<ComboAddress> conv = convComboAddressList(ips);
 
       return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
 
@@ -917,7 +1226,7 @@ static void setupLuaRecords()
       lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
   }
 
-  lua.writeFunction("report", [](string event, boost::optional<string> line){
+  lua.writeFunction("report", [](string /* event */, boost::optional<string> /* line */){
       throw std::runtime_error("Script took too long");
     });
 
@@ -926,25 +1235,57 @@ static void setupLuaRecords()
   });
 
   typedef const boost::variant<string,vector<pair<int,string> > > combovar_t;
+
+  lua.writeFunction("asnum", [](const combovar_t& asns) {
+      string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::ASn);
+      return doCompare(asns, res, [](const std::string& a, const std::string& b) {
+          return !strcasecmp(a.c_str(), b.c_str());
+        });
+    });
   lua.writeFunction("continent", [](const combovar_t& continent) {
      string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
       return doCompare(continent, res, [](const std::string& a, const std::string& b) {
           return !strcasecmp(a.c_str(), b.c_str());
         });
     });
-  lua.writeFunction("asnum", [](const combovar_t& asns) {
-      string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::ASn);
-      return doCompare(asns, res, [](const std::string& a, const std::string& b) {
+  lua.writeFunction("continentCode", []() {
+      string unknown("unknown");
+      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
+      if ( res == unknown ) {
+       return std::string("--");
+      }
+      return res;
+    });
+  lua.writeFunction("country", [](const combovar_t& var) {
+      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
+      return doCompare(var, res, [](const std::string& a, const std::string& b) {
           return !strcasecmp(a.c_str(), b.c_str());
         });
+
     });
-  lua.writeFunction("country", [](const combovar_t& var) {
+  lua.writeFunction("countryCode", []() {
+      string unknown("unknown");
       string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
+      if ( res == unknown ) {
+       return std::string("--");
+      }
+      return res;
+    });
+  lua.writeFunction("region", [](const combovar_t& var) {
+      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
       return doCompare(var, res, [](const std::string& a, const std::string& b) {
           return !strcasecmp(a.c_str(), b.c_str());
         });
 
     });
+  lua.writeFunction("regionCode", []() {
+      string unknown("unknown");
+      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
+      if ( res == unknown ) {
+       return std::string("--");
+      }
+      return res;
+    });
   lua.writeFunction("netmask", [](const iplist_t& ips) {
       for(const auto& i :ips) {
         Netmask nm(i.second);
@@ -978,9 +1319,53 @@ static void setupLuaRecords()
         }
       }
       return std::string();
+    });
+
+  lua.writeFunction("all", [](const vector< pair<int,string> >& ips) {
+      vector<string> result;
+         result.reserve(ips.size());
+
+      for(const auto& ip : ips) {
+          result.emplace_back(ip.second);
+      }
+      if(result.empty()) {
+        throw std::invalid_argument("The IP list cannot be empty");
+      }
+      return result;
+    });
+
+  lua.writeFunction("dblookup", [](const string& record, const string& type) {
+    DNSName rec;
+    QType qtype;
+    vector<string> ret;
+    try {
+      rec = DNSName(record);
+      qtype = type;
+      if (qtype.getCode() == 0) {
+        throw std::invalid_argument("unknown type");
+      }
+    }
+    catch (const std::exception& e) {
+      g_log << Logger::Error << "DB lookup cannot be performed, the name (" << record << ") or type (" << type << ") 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;
@@ -1003,27 +1388,28 @@ static void setupLuaRecords()
     });
 }
 
-std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, const DNSName& query, const DNSName& zone, int zoneid, const DNSPacket& dnsp, uint16_t qtype)
+std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, const DNSName& query, const DNSName& zone, int zoneid, const DNSPacket& dnsp, uint16_t qtype, unique_ptr<AuthLua4>& LUA)
 {
-  if(!s_LUA ||                  // we don't have a Lua state yet
+  if(!LUA ||                  // we don't have a Lua state yet
      !g_LuaRecordSharedState) { // or we want a new one even if we had one
-    s_LUA = make_unique<AuthLua4>();
-    setupLuaRecords();
+    LUA = make_unique<AuthLua4>();
+    setupLuaRecords(*LUA->getLua());
   }
 
   std::vector<shared_ptr<DNSRecordContent>> ret;
 
-  LuaContext& lua = *s_LUA->getLua();
+  LuaContext& lua = *LUA->getLua();
 
   s_lua_record_ctx = std::make_unique<lua_record_ctx_t>();
   s_lua_record_ctx->qname = query;
   s_lua_record_ctx->zone = zone;
   s_lua_record_ctx->zoneid = zoneid;
-  
+
   lua.writeVariable("qname", query);
   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);
@@ -1056,9 +1442,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();
diff --git a/pdns/lua-recursor4-ffi.hh b/pdns/lua-recursor4-ffi.hh
deleted file mode 100644 (file)
index f5785bd..0000000
+++ /dev/null
@@ -1,130 +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 pdns_ffi_param pdns_ffi_param_t;
-
-  typedef struct pdns_ednsoption
-  {
-    uint16_t optionCode;
-    uint16_t len;
-    const void* data;
-  } pdns_ednsoption_t;
-
-  typedef struct pdns_proxyprotocol_value
-  {
-    uint8_t type;
-    uint16_t len;
-    const void* data;
-  } pdns_proxyprotocol_value_t;
-
-  typedef enum
-  {
-    pdns_record_place_answer = 1,
-    pdns_record_place_authority = 2,
-    pdns_record_place_additional = 3
-  } pdns_record_place_t;
-
-  // Must match DNSFilterEngine::PolicyKind
-  typedef enum
-  {
-    pdns_policy_kind_noaction = 0,
-    pdns_policy_kind_drop = 1,
-    pdns_policy_kind_nxdomain = 2,
-    pdns_policy_kind_nodata = 3,
-    pdns_policy_kind_truncate = 4,
-    pdns_policy_kind_custom = 5
-  } pdns_policy_kind_t;
-
-  typedef struct pdns_ffi_record
-  {
-    const char* name;
-    size_t name_len;
-    const char* content;
-    size_t content_len;
-    uint32_t ttl;
-    pdns_record_place_t place;
-    uint16_t type;
-  } pdns_ffi_record_t;
-
-  const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref) __attribute__((visibility("default")));
-  void pdns_ffi_param_get_qname_raw(pdns_ffi_param_t* ref, const char** qname, size_t* qnameSize) __attribute__((visibility("default")));
-  uint16_t pdns_ffi_param_get_qtype(const pdns_ffi_param_t* ref) __attribute__((visibility("default")));
-  const char* pdns_ffi_param_get_remote(pdns_ffi_param_t* ref) __attribute__((visibility("default")));
-  void pdns_ffi_param_get_remote_raw(pdns_ffi_param_t* ref, const void** addr, size_t* addrSize) __attribute__((visibility("default")));
-  uint16_t pdns_ffi_param_get_remote_port(const pdns_ffi_param_t* ref) __attribute__((visibility("default")));
-  const char* pdns_ffi_param_get_local(pdns_ffi_param_t* ref) __attribute__((visibility("default")));
-  void pdns_ffi_param_get_local_raw(pdns_ffi_param_t* ref, const void** addr, size_t* addrSize) __attribute__((visibility("default")));
-  uint16_t pdns_ffi_param_get_local_port(const pdns_ffi_param_t* ref) __attribute__((visibility("default")));
-  const char* pdns_ffi_param_get_edns_cs(pdns_ffi_param_t* ref) __attribute__((visibility("default")));
-  void pdns_ffi_param_get_edns_cs_raw(pdns_ffi_param_t* ref, const void** net, size_t* netSize) __attribute__((visibility("default")));
-  uint8_t pdns_ffi_param_get_edns_cs_source_mask(const pdns_ffi_param_t* ref) __attribute__((visibility("default")));
-
-  // returns the length of the resulting 'out' array. 'out' is not set if the length is 0
-  size_t pdns_ffi_param_get_edns_options(pdns_ffi_param_t* ref, const pdns_ednsoption_t** out) __attribute__((visibility("default")));
-  size_t pdns_ffi_param_get_edns_options_by_code(pdns_ffi_param_t* ref, uint16_t optionCode, const pdns_ednsoption_t** out) __attribute__((visibility("default")));
-
-  // returns the length of the resulting 'out' array. 'out' is not set if the length is 0
-  size_t pdns_ffi_param_get_proxy_protocol_values(pdns_ffi_param_t* ref, const pdns_proxyprotocol_value_t** out) __attribute__((visibility("default")));
-
-  void pdns_ffi_param_set_tag(pdns_ffi_param_t* ref, unsigned int tag) __attribute__((visibility("default")));
-  void pdns_ffi_param_add_policytag(pdns_ffi_param_t* ref, const char* name) __attribute__((visibility("default")));
-  void pdns_ffi_param_set_requestorid(pdns_ffi_param_t* ref, const char* name) __attribute__((visibility("default")));
-  void pdns_ffi_param_set_devicename(pdns_ffi_param_t* ref, const char* name) __attribute__((visibility("default")));
-  void pdns_ffi_param_set_deviceid(pdns_ffi_param_t* ref, size_t len, const void* name) __attribute__((visibility("default")));
-  void pdns_ffi_param_set_routingtag(pdns_ffi_param_t* ref, const char* name) __attribute__((visibility("default")));
-
-  void pdns_ffi_param_set_variable(pdns_ffi_param_t* ref, bool variable) __attribute__((visibility("default")));
-  void pdns_ffi_param_set_ttl_cap(pdns_ffi_param_t* ref, uint32_t ttl) __attribute__((visibility("default")));
-  void pdns_ffi_param_set_log_query(pdns_ffi_param_t* ref, bool logQuery) __attribute__((visibility("default")));
-  void pdns_ffi_param_set_log_response(pdns_ffi_param_t* ref, bool logResponse) __attribute__((visibility("default")));
-  void pdns_ffi_param_set_rcode(pdns_ffi_param_t* ref, int rcode) __attribute__((visibility("default")));
-  void pdns_ffi_param_set_follow_cname_records(pdns_ffi_param_t* ref, bool follow) __attribute__((visibility("default")));
-
-  void pdns_ffi_param_set_extended_error_code(pdns_ffi_param_t* ref, uint16_t code) __attribute__((visibility("default")));
-  void pdns_ffi_param_set_extended_error_extra(pdns_ffi_param_t* ref, size_t len, const char* extra) __attribute__((visibility("default")));
-
-  /* returns true if the record was correctly added, false if something went wrong.
-     Passing a NULL pointer to 'name' will result in the qname being used for the record owner name. */
-  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) __attribute__((visibility("default")));
-
-  void pdns_ffi_param_set_padding_disabled(pdns_ffi_param_t* ref, bool disabled) __attribute__((visibility("default")));
-  void pdns_ffi_param_add_meta_single_string_kv(pdns_ffi_param_t* ref, const char* key, const char* val) __attribute__((visibility("default")));
-  void pdns_ffi_param_add_meta_single_int64_kv(pdns_ffi_param_t* ref, const char* key, int64_t val) __attribute__((visibility("default")));
-
-  typedef struct pdns_postresolve_ffi_handle pdns_postresolve_ffi_handle_t;
-
-  const char* pdns_postresolve_ffi_handle_get_qname(pdns_postresolve_ffi_handle_t* ref) __attribute__((visibility("default")));
-  void pdns_postresolve_ffi_handle_get_qname_raw(pdns_postresolve_ffi_handle_t* ref, const char** qname, size_t* qnameSize) __attribute__((visibility("default")));
-  uint16_t pdns_postresolve_ffi_handle_get_qtype(const pdns_postresolve_ffi_handle_t* ref) __attribute__((visibility("default")));
-  uint16_t pdns_postresolve_ffi_handle_get_rcode(const pdns_postresolve_ffi_handle_t* ref) __attribute__((visibility("default")));
-  void pdns_postresolve_ffi_handle_set_rcode(const pdns_postresolve_ffi_handle_t* ref, uint16_t rcode) __attribute__((visibility("default")));
-  pdns_policy_kind_t pdns_postresolve_ffi_handle_get_appliedpolicy_kind(const pdns_postresolve_ffi_handle_t* ref) __attribute__((visibility("default")));
-  void pdns_postresolve_ffi_handle_set_appliedpolicy_kind(pdns_postresolve_ffi_handle_t* ref, pdns_policy_kind_t kind) __attribute__((visibility("default")));
-  bool pdns_postresolve_ffi_handle_get_record(pdns_postresolve_ffi_handle_t* ref, unsigned int i, pdns_ffi_record_t* record, bool raw) __attribute__((visibility("default")));
-  bool pdns_postresolve_ffi_handle_set_record(pdns_postresolve_ffi_handle_t* ref, unsigned int i, const char* content, size_t contentLen, bool raw) __attribute__((visibility("default")));
-  void pdns_postresolve_ffi_handle_clear_records(pdns_postresolve_ffi_handle_t* ref) __attribute__((visibility("default")));
-  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) __attribute__((visibility("default")));
-  const char* pdns_postresolve_ffi_handle_get_authip(pdns_postresolve_ffi_handle_t* ref) __attribute__((visibility("default")));
-  void pdns_postresolve_ffi_handle_get_authip_raw(pdns_postresolve_ffi_handle_t* ref, const void** addr, size_t* addrSize) __attribute__((visibility("default")));
-}
diff --git a/pdns/lua-recursor4.cc b/pdns/lua-recursor4.cc
deleted file mode 100644 (file)
index 1c8cbcb..0000000
+++ /dev/null
@@ -1,1220 +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 "lua-recursor4.hh"
-#include <fstream>
-#include "logger.hh"
-#include "dnsparser.hh"
-#include "syncres.hh"
-#include "namespaces.hh"
-#include "rec_channel.hh"
-#include "ednsoptions.hh"
-#include "ednssubnet.hh"
-#include "filterpo.hh"
-#include "rec-snmp.hh"
-#include <unordered_set>
-#include "rec-main.hh"
-
-RecursorLua4::RecursorLua4() { prepareContext(); }
-
-boost::optional<dnsheader> RecursorLua4::DNSQuestion::getDH() const
-{
-  if (dh)
-    return *dh;
-  return boost::optional<dnsheader>();
-}
-
-vector<string> RecursorLua4::DNSQuestion::getEDNSFlags() const
-{
-  vector<string> ret;
-  if (ednsFlags) {
-    if (*ednsFlags & EDNSOpts::DNSSECOK)
-      ret.push_back("DO");
-  }
-  return ret;
-}
-
-bool RecursorLua4::DNSQuestion::getEDNSFlag(string flag) const
-{
-  if (ednsFlags) {
-    if (flag == "DO" && (*ednsFlags & EDNSOpts::DNSSECOK))
-      return true;
-  }
-  return false;
-}
-
-vector<pair<uint16_t, string>> RecursorLua4::DNSQuestion::getEDNSOptions() const
-{
-  if (ednsOptions)
-    return *ednsOptions;
-  else
-    return vector<pair<uint16_t, string>>();
-}
-
-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>();
-}
-
-boost::optional<Netmask> RecursorLua4::DNSQuestion::getEDNSSubnet() const
-{
-  if (ednsOptions) {
-    for (const auto& o : *ednsOptions) {
-      if (o.first == EDNSOptionCode::ECS) {
-        EDNSSubnetOpts eso;
-        if (getEDNSSubnetOptsFromString(o.second, &eso))
-          return eso.source;
-        else
-          break;
-      }
-    }
-  }
-  return boost::optional<Netmask>();
-}
-
-std::vector<std::pair<int, ProxyProtocolValue>> RecursorLua4::DNSQuestion::getProxyProtocolValues() const
-{
-  std::vector<std::pair<int, ProxyProtocolValue>> result;
-  if (proxyProtocolValues) {
-    result.reserve(proxyProtocolValues->size());
-
-    int idx = 1;
-    for (const auto& value : *proxyProtocolValues) {
-      result.push_back({idx++, value});
-    }
-  }
-
-  return result;
-}
-
-vector<pair<int, DNSRecord>> RecursorLua4::DNSQuestion::getRecords() const
-{
-  vector<pair<int, DNSRecord>> ret;
-  int num = 1;
-  for (const auto& r : records) {
-    ret.push_back({num++, r});
-  }
-  return ret;
-}
-void RecursorLua4::DNSQuestion::setRecords(const vector<pair<int, DNSRecord>>& recs)
-{
-  records.clear();
-  for (const auto& p : recs) {
-    records.push_back(p.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.d_content = DNSRecordContent::mastermake(type, QClass::IN, content);
-  records.push_back(dr);
-}
-
-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);
-}
-
-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; }
-};
-
-// 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<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("rcode", &DNSQuestion::rcode);
-  d_lw->registerMember("tag", &DNSQuestion::tag);
-  d_lw->registerMember("requestorId", &DNSQuestion::requestorId);
-  d_lw->registerMember("deviceId", &DNSQuestion::deviceId);
-  d_lw->registerMember("deviceName", &DNSQuestion::deviceName);
-  d_lw->registerMember("followupFunction", &DNSQuestion::followupFunction);
-  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);
-      }
-      return 0;
-    },
-    [](DNSQuestion& dq, uint16_t newCode) {
-      if (dq.extendedErrorCode) {
-        *dq.extendedErrorCode = newCode;
-      }
-    });
-  d_lw->registerMember<std::string (DNSQuestion::*)>("extendedErrorExtra", [](const DNSQuestion& dq) -> std::string {
-      if (dq.extendedErrorExtra) {
-        return *dq.extendedErrorExtra;
-      }
-      return "";
-    },
-    [](DNSQuestion& dq, const std::string& newExtra) {
-      if (dq.extendedErrorExtra) {
-        *dq.extendedErrorExtra = newExtra;
-      }
-    });
-  d_lw->registerMember("udpQuery", &DNSQuestion::udpQuery);
-  d_lw->registerMember("udpAnswer", &DNSQuestion::udpAnswer);
-  d_lw->registerMember("udpQueryDest", &DNSQuestion::udpQueryDest);
-  d_lw->registerMember("udpCallback", &DNSQuestion::udpCallback);
-  d_lw->registerMember("appliedPolicy", &DNSQuestion::appliedPolicy);
-
-  d_lw->registerMember<DNSFilterEngine::Policy, std::string>("policyName",
-    [](const DNSFilterEngine::Policy& pol) -> std::string {
-      return pol.getName();
-    },
-    [](DNSFilterEngine::Policy& pol, const std::string& name) {
-      pol.setName(name);
-    });
-  d_lw->registerMember("policyKind", &DNSFilterEngine::Policy::d_kind);
-  d_lw->registerMember("policyType", &DNSFilterEngine::Policy::d_type);
-  d_lw->registerMember("policyTTL", &DNSFilterEngine::Policy::d_ttl);
-  d_lw->registerMember("policyTrigger", &DNSFilterEngine::Policy::d_trigger);
-  d_lw->registerMember("policyHit", &DNSFilterEngine::Policy::d_hit);
-  d_lw->registerMember<DNSFilterEngine::Policy, std::string>("policyCustom",
-    [](const DNSFilterEngine::Policy& pol) -> std::string {
-      std::string result;
-      if (pol.d_kind != DNSFilterEngine::PolicyKind::Custom) {
-        return result;
-      }
-
-      for (const auto& dr : pol.d_custom) {
-        if (!result.empty()) {
-          result += "\n";
-        }
-        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));
-    }
-  );
-  d_lw->registerFunction("getDH", &DNSQuestion::getDH);
-  d_lw->registerFunction("getEDNSOptions", &DNSQuestion::getEDNSOptions);
-  d_lw->registerFunction("getEDNSOption", &DNSQuestion::getEDNSOption);
-  d_lw->registerFunction("getEDNSSubnet", &DNSQuestion::getEDNSSubnet);
-  d_lw->registerFunction("getProxyProtocolValues", &DNSQuestion::getProxyProtocolValues);
-  d_lw->registerFunction("getEDNSFlags", &DNSQuestion::getEDNSFlags);
-  d_lw->registerFunction("getEDNSFlag", &DNSQuestion::getEDNSFlag);
-
-  d_lw->registerMember("name", &DNSRecord::d_name);
-  d_lw->registerMember("type", &DNSRecord::d_type);
-  d_lw->registerMember("ttl", &DNSRecord::d_ttl);
-  d_lw->registerMember("place", &DNSRecord::d_place);
-
-  d_lw->registerMember("size", &EDNSOptionViewValue::size);
-  d_lw->registerFunction<std::string(EDNSOptionViewValue::*)()>("getContent", [](const EDNSOptionViewValue& value) { return std::string(value.content, value.size); });
-  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;
-      for (const auto& value : option.values) {
-        values.push_back(std::string(value.content, value.size));
-      }
-      return values;
-    });
-
-  /* pre 4.2 API compatibility, when we had only one value for a given EDNS option */
-  d_lw->registerMember<uint16_t(EDNSOptionView::*)>("size", [](const EDNSOptionView& option) -> uint16_t {
-      uint16_t result = 0;
-
-      if (!option.values.empty()) {
-        result = option.values.at(0).size;
-      }
-      return result;
-    },
-    [](EDNSOptionView& option, uint16_t newSize) { (void) newSize; });
-  d_lw->registerFunction<std::string(EDNSOptionView::*)()>("getContent", [](const EDNSOptionView& option) {
-      if (option.values.empty()) {
-        return std::string();
-      }
-      return std::string(option.values.at(0).content, option.values.at(0).size); });
-
-  d_lw->registerFunction<string(DNSRecord::*)()>("getContent", [](const DNSRecord& dr) { return dr.d_content->getZoneRepresentation(); });
-  d_lw->registerFunction<boost::optional<ComboAddress>(DNSRecord::*)()>("getCA", [](const DNSRecord& dr) { 
-      boost::optional<ComboAddress> ret;
-
-      if(auto rec = std::dynamic_pointer_cast<ARecordContent>(dr.d_content))
-        ret=rec->getCA(53);
-      else if(auto aaaarec = std::dynamic_pointer_cast<AAAARecordContent>(dr.d_content))
-        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, uint8_t()>("getType", [](const ProxyProtocolValue& value) { return value.type; });
-
-  d_lw->registerFunction<void(DNSRecord::*)(const std::string&)>("changeContent", [](DNSRecord& dr, const std::string& newContent) { dr.d_content = DNSRecordContent::mastermake(dr.d_type, 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());
-        for (const auto& tag : tags) {
-          dq.policyTags->insert(tag.second);
-        }
-      }
-    });
-  d_lw->registerFunction<std::vector<std::pair<int, std::string> >(DNSQuestion::*)()>("getPolicyTags", [](const DNSQuestion& dq) {
-      std::vector<std::pair<int, std::string> > ret;
-      if (dq.policyTags) {
-        int count = 1;
-        ret.reserve(dq.policyTags->size());
-        for (const auto& tag : *dq.policyTags) {
-          ret.push_back({count++, tag});
-        }
-      }
-      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->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){
-      try {
-        if(auto s = boost::get<string>(&in)) {
-          smn.add(DNSName(*s));
-        }
-        else if(auto v = boost::get<vector<pair<unsigned int, string> > >(&in)) {
-          for(const auto& entry : *v)
-            smn.add(DNSName(entry.second));
-        }
-        else {
-          smn.add(boost::get<DNSName>(in));
-        }
-      }
-      catch(std::exception& e) {
-        g_log <<Logger::Error<<e.what()<<endl;
-      }
-    }
-  );
-
-  d_lw->registerFunction("check",(bool (SuffixMatchNode::*)(const DNSName&) const) &SuffixMatchNode::check);
-  d_lw->registerFunction("toString",(string (SuffixMatchNode::*)() const) &SuffixMatchNode::toString);
-
-  d_pd.push_back({"policykinds", in_t {
-    {"NoAction", (int)DNSFilterEngine::PolicyKind::NoAction},
-    {"Drop",     (int)DNSFilterEngine::PolicyKind::Drop    },
-    {"NXDOMAIN", (int)DNSFilterEngine::PolicyKind::NXDOMAIN},
-    {"NODATA",   (int)DNSFilterEngine::PolicyKind::NODATA  },
-    {"Truncate", (int)DNSFilterEngine::PolicyKind::Truncate},
-    {"Custom",   (int)DNSFilterEngine::PolicyKind::Custom  }
-    }});
-
-  d_pd.push_back({"policytypes", in_t {
-    {"None",       (int)DNSFilterEngine::PolicyType::None       },
-    {"QName",      (int)DNSFilterEngine::PolicyType::QName      },
-    {"ClientIP",   (int)DNSFilterEngine::PolicyType::ClientIP   },
-    {"ResponseIP", (int)DNSFilterEngine::PolicyType::ResponseIP },
-    {"NSDName",    (int)DNSFilterEngine::PolicyType::NSDName    },
-    {"NSIP",       (int)DNSFilterEngine::PolicyType::NSIP       }
-    }});
-
-  for(const auto& n : QType::names)
-    d_pd.push_back({n.first, n.second});
-
-  d_pd.push_back({"validationstates", in_t{
-        {"Indeterminate", static_cast<unsigned int>(vState::Indeterminate) },
-        {"BogusNoValidDNSKEY", static_cast<unsigned int>(vState::BogusNoValidDNSKEY) },
-        {"BogusInvalidDenial", static_cast<unsigned int>(vState::BogusInvalidDenial) },
-        {"BogusUnableToGetDSs", static_cast<unsigned int>(vState::BogusUnableToGetDSs) },
-        {"BogusUnableToGetDNSKEYs", static_cast<unsigned int>(vState::BogusUnableToGetDNSKEYs) },
-        {"BogusSelfSignedDS", static_cast<unsigned int>(vState::BogusSelfSignedDS) },
-        {"BogusNoRRSIG", static_cast<unsigned int>(vState::BogusNoRRSIG) },
-        {"BogusNoValidRRSIG", static_cast<unsigned int>(vState::BogusNoValidRRSIG) },
-        {"BogusMissingNegativeIndication", static_cast<unsigned int>(vState::BogusMissingNegativeIndication) },
-        {"BogusSignatureNotYetValid", static_cast<unsigned int>(vState::BogusSignatureNotYetValid)},
-        {"BogusSignatureExpired", static_cast<unsigned int>(vState::BogusSignatureExpired)},
-        {"BogusUnsupportedDNSKEYAlgo", static_cast<unsigned int>(vState::BogusUnsupportedDNSKEYAlgo)},
-        {"BogusUnsupportedDSDigestType", static_cast<unsigned int>(vState::BogusUnsupportedDSDigestType)},
-        {"BogusNoZoneKeyBitSet", static_cast<unsigned int>(vState::BogusNoZoneKeyBitSet)},
-        {"BogusRevokedDNSKEY", static_cast<unsigned int>(vState::BogusRevokedDNSKEY)},
-        {"BogusInvalidDNSKEYProtocol", static_cast<unsigned int>(vState::BogusInvalidDNSKEYProtocol)},
-        {"Insecure", static_cast<unsigned int>(vState::Insecure) },
-        {"Secure", static_cast<unsigned int>(vState::Secure) },
-        /* in order not to break compatibility with older scripts: */
-        {"Bogus", static_cast<unsigned int>(255) },
-  }});
-
-  d_lw->writeFunction("isValidationStateBogus", [](vState state) {
-    return vStateIsBogus(state);
-  });
-
-  d_pd.push_back({"now", &g_now});
-
-  d_lw->writeFunction("getMetric", [](const std::string& str, boost::optional<std::string> prometheusName) {
-    return DynMetric{getDynMetric(str, prometheusName ? *prometheusName : "")};
-    });
-
-  d_lw->registerFunction("inc", &DynMetric::inc);
-  d_lw->registerFunction("incBy", &DynMetric::incBy);
-  d_lw->registerFunction("set", &DynMetric::set);
-  d_lw->registerFunction("get", &DynMetric::get);
-
-  d_lw->writeFunction("getStat", [](const std::string& str) {
-      uint64_t result = 0;
-      auto value = getStatByName(str);
-      if (value) {
-        result = *value;
-      }
-      return result;
-    });
-
-  d_lw->writeFunction("getRecursorThreadId", []() {
-    return RecThreadInfo::id();
-  });
-
-  d_lw->writeFunction("sendCustomSNMPTrap", [](const std::string& str) {
-      if (g_snmpAgent) {
-        g_snmpAgent->sendCustomTrap(str);
-      }
-    });
-
-  d_lw->writeFunction("getregisteredname", [](const DNSName &dname) {
-      return getRegisteredName(dname);
-  });
-
-  d_lw->registerMember<const DNSName (PolicyEvent::*)>("qname", [](const PolicyEvent& event) -> const DNSName& { return event.qname; }, [](PolicyEvent& event, const DNSName& newName) { (void) newName; });
-  d_lw->registerMember<uint16_t (PolicyEvent::*)>("qtype", [](const PolicyEvent& event) -> uint16_t { return event.qtype.getCode(); }, [](PolicyEvent& event, uint16_t newType) { (void) newType; });
-  d_lw->registerMember<bool (PolicyEvent::*)>("isTcp", [](const PolicyEvent& event) -> bool { return event.isTcp; }, [](PolicyEvent& event, bool newTcp) { (void) newTcp; });
-  d_lw->registerMember<const ComboAddress (PolicyEvent::*)>("remote", [](const PolicyEvent& event) -> const ComboAddress& { return event.remote; }, [](PolicyEvent& event, const ComboAddress& newRemote) { (void) newRemote; });
-  d_lw->registerMember("appliedPolicy", &PolicyEvent::appliedPolicy);
-  d_lw->registerFunction<void(PolicyEvent::*)(const std::string&)>("addPolicyTag", [](PolicyEvent& event, const std::string& tag) { if (event.policyTags) { event.policyTags->insert(tag); } });
-  d_lw->registerFunction<void(PolicyEvent::*)(const std::vector<std::pair<int, std::string> >&)>("setPolicyTags", [](PolicyEvent& event, const std::vector<std::pair<int, std::string> >& tags) {
-      if (event.policyTags) {
-        event.policyTags->clear();
-        event.policyTags->reserve(tags.size());
-        for (const auto& tag : tags) {
-          event.policyTags->insert(tag.second);
-        }
-      }
-    });
-  d_lw->registerFunction<std::vector<std::pair<int, std::string> >(PolicyEvent::*)()>("getPolicyTags", [](const PolicyEvent& event) {
-      std::vector<std::pair<int, std::string> > ret;
-      if (event.policyTags) {
-        int count = 1;
-        ret.reserve(event.policyTags->size());
-        for (const auto& tag : *event.policyTags) {
-          ret.push_back({count++, tag});
-        }
-      }
-      return ret;
-    });
-  d_lw->registerFunction<void(PolicyEvent::*)(const std::string&)>("discardPolicy", [](PolicyEvent& event, const std::string& policy) {
-    if (event.discardedPolicies) {
-      (*event.discardedPolicies)[policy] = true;
-    }
-  });
-}
-
-// clang-format on
-
-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_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_policyHitEventFilter = d_lw->readVariable<boost::optional<policyEventFilter_t>>("policyEventFilter").get_value_or(0);
-}
-
-void RecursorLua4::getFeatures(Features& features)
-{
-  // Add key-values pairs below.
-  // Make sure you add string values explicitly converted to string.
-  // e.g. features.emplace_back("somekey", string("stringvalue");
-  // Both int and double end up as a lua number type.
-  features.emplace_back("PR8001_devicename", true);
-}
-
-static void warnDrop(const RecursorLua4::DNSQuestion& dq)
-{
-  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;
-    // 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.
-  }
-}
-
-void RecursorLua4::maintenance() const
-{
-  if (d_maintenance) {
-    d_maintenance();
-  }
-}
-
-bool RecursorLua4::prerpz(DNSQuestion& dq, int& ret, RecEventTrace& et) 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;
-}
-
-bool RecursorLua4::preresolve(DNSQuestion& dq, int& ret, RecEventTrace& et) 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;
-}
-
-bool RecursorLua4::nxdomain(DNSQuestion& dq, int& ret, RecEventTrace& et) 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;
-}
-
-bool RecursorLua4::nodata(DNSQuestion& dq, int& ret, RecEventTrace& et) 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;
-}
-
-bool RecursorLua4::postresolve(DNSQuestion& dq, int& ret, RecEventTrace& et) 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;
-}
-
-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
-{
-  if (!d_preoutquery) {
-    return false;
-  }
-  bool variableAnswer = false;
-  bool wantsRPZ = false;
-  bool logQuery = false;
-  bool addPaddingToResponse = false;
-  RecursorLua4::DNSQuestion dq(ns, requestor, query, qtype.getCode(), isTcp, variableAnswer, wantsRPZ, logQuery, addPaddingToResponse);
-  dq.currentRecords = &res;
-  et.add(RecEventTrace::LuaPreOutQuery);
-  bool ok = genhook(d_preoutquery, dq, ret);
-  et.add(RecEventTrace::LuaPreOutQuery, ok, false);
-  warnDrop(dq);
-  return ok;
-}
-
-bool RecursorLua4::ipfilter(const ComboAddress& remote, const ComboAddress& local, const struct dnsheader& dh, RecEventTrace& et) 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;
-}
-
-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
-{
-  if (!d_policyHitEventFilter) {
-    return false;
-  }
-
-  PolicyEvent event(remote, qname, qtype, tcp);
-  event.appliedPolicy = &policy;
-  event.policyTags = &tags;
-  event.discardedPolicies = &discardedPolicies;
-
-  if (d_policyHitEventFilter(event)) {
-    return true;
-  }
-  else {
-    return false;
-  }
-}
-
-unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, const std::vector<ProxyProtocolValue>& proxyProtocolValues) const
-{
-  if (d_gettag) {
-    std::vector<std::pair<int, const ProxyProtocolValue*>> proxyProtocolValuesMap;
-    proxyProtocolValuesMap.reserve(proxyProtocolValues.size());
-    int num = 1;
-    for (const auto& value : proxyProtocolValues) {
-      proxyProtocolValuesMap.emplace_back(num++, &value);
-    }
-
-    auto ret = d_gettag(remote, ednssubnet, local, qname, qtype, ednsOptions, tcp, proxyProtocolValuesMap);
-
-    if (policyTags) {
-      const auto& tags = std::get<1>(ret);
-      if (tags) {
-        policyTags->reserve(policyTags->size() + tags->size());
-        for (const auto& tag : *tags) {
-          policyTags->insert(tag.second);
-        }
-      }
-    }
-    const auto dataret = std::get<2>(ret);
-    if (dataret) {
-      data = *dataret;
-    }
-    const auto reqIdret = std::get<3>(ret);
-    if (reqIdret) {
-      requestorId = *reqIdret;
-    }
-    const auto deviceIdret = std::get<4>(ret);
-    if (deviceIdret) {
-      deviceId = *deviceIdret;
-    }
-
-    const auto deviceNameret = std::get<5>(ret);
-    if (deviceNameret) {
-      deviceName = *deviceNameret;
-    }
-
-    const auto routingTarget = std::get<6>(ret);
-    if (routingTarget) {
-      routingTag = *routingTarget;
-    }
-
-    return std::get<0>(ret);
-  }
-  return 0;
-}
-
-struct pdns_ffi_param
-{
-public:
-  pdns_ffi_param(RecursorLua4::FFIParams& params_) :
-    params(params_)
-  {
-  }
-
-  RecursorLua4::FFIParams& params;
-  std::unique_ptr<std::string> qnameStr{nullptr};
-  std::unique_ptr<std::string> localStr{nullptr};
-  std::unique_ptr<std::string> remoteStr{nullptr};
-  std::unique_ptr<std::string> ednssubnetStr{nullptr};
-  std::vector<pdns_ednsoption_t> ednsOptionsVect;
-  std::vector<pdns_proxyprotocol_value_t> proxyProtocolValuesVect;
-};
-
-unsigned int RecursorLua4::gettag_ffi(RecursorLua4::FFIParams& params) const
-{
-  if (d_gettag_ffi) {
-    pdns_ffi_param_t param(params);
-
-    auto ret = d_gettag_ffi(&param);
-    if (ret) {
-      params.data = *ret;
-    }
-
-    return param.params.tag;
-  }
-  return 0;
-}
-
-bool RecursorLua4::genhook(const luacall_t& func, DNSQuestion& dq, int& ret) const
-{
-  if (!func)
-    return false;
-
-  if (dq.currentRecords) {
-    dq.records = *dq.currentRecords;
-  }
-  else {
-    dq.records.clear();
-  }
-
-  dq.followupFunction.clear();
-  dq.followupPrefix.clear();
-  dq.followupName.clear();
-  dq.udpQuery.clear();
-  dq.udpAnswer.clear();
-  dq.udpCallback.clear();
-
-  dq.rcode = ret;
-  bool handled = func(&dq);
-
-  if (handled) {
-  loop:;
-    ret = dq.rcode;
-
-    if (!dq.followupFunction.empty()) {
-      if (dq.followupFunction == "followCNAMERecords") {
-        ret = followCNAMERecords(dq.records, QType(dq.qtype), ret);
-      }
-      else if (dq.followupFunction == "getFakeAAAARecords") {
-        ret = getFakeAAAARecords(dq.followupName, ComboAddress(dq.followupPrefix), dq.records);
-      }
-      else if (dq.followupFunction == "getFakePTRRecords") {
-        ret = getFakePTRRecords(dq.followupName, dq.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);
-        if (!cbFunc) {
-          g_log << Logger::Error << "Attempted callback for Lua UDP Query/Response which could not be found" << endl;
-          return false;
-        }
-        bool result = cbFunc(&dq);
-        if (!result) {
-          return false;
-        }
-        goto loop;
-      }
-    }
-    if (dq.currentRecords) {
-      *dq.currentRecords = dq.records;
-    }
-  }
-
-  // see if they added followup work for us too
-  return handled;
-}
-
-RecursorLua4::~RecursorLua4() {}
-
-const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref)
-{
-  if (!ref->qnameStr) {
-    ref->qnameStr = std::make_unique<std::string>(ref->params.qname.toStringNoDot());
-  }
-
-  return ref->qnameStr->c_str();
-}
-
-void pdns_ffi_param_get_qname_raw(pdns_ffi_param_t* ref, const char** qname, size_t* qnameSize)
-{
-  const auto& storage = ref->params.qname.getStorage();
-  *qname = storage.data();
-  *qnameSize = storage.size();
-}
-
-uint16_t pdns_ffi_param_get_qtype(const pdns_ffi_param_t* ref)
-{
-  return ref->params.qtype;
-}
-
-const char* pdns_ffi_param_get_remote(pdns_ffi_param_t* ref)
-{
-  if (!ref->remoteStr) {
-    ref->remoteStr = std::make_unique<std::string>(ref->params.remote.toString());
-  }
-
-  return ref->remoteStr->c_str();
-}
-
-static void pdns_ffi_comboaddress_to_raw(const ComboAddress& ca, const void** addr, size_t* addrSize)
-{
-  if (ca.isIPv4()) {
-    *addr = &ca.sin4.sin_addr.s_addr;
-    *addrSize = sizeof(ca.sin4.sin_addr.s_addr);
-  }
-  else {
-    *addr = &ca.sin6.sin6_addr.s6_addr;
-    *addrSize = sizeof(ca.sin6.sin6_addr.s6_addr);
-  }
-}
-
-void pdns_ffi_param_get_remote_raw(pdns_ffi_param_t* ref, const void** addr, size_t* addrSize)
-{
-  pdns_ffi_comboaddress_to_raw(ref->params.remote, addr, addrSize);
-}
-
-uint16_t pdns_ffi_param_get_remote_port(const pdns_ffi_param_t* ref)
-{
-  return ref->params.remote.getPort();
-}
-
-const char* pdns_ffi_param_get_local(pdns_ffi_param_t* ref)
-{
-  if (!ref->localStr) {
-    ref->localStr = std::make_unique<std::string>(ref->params.local.toString());
-  }
-
-  return ref->localStr->c_str();
-}
-
-void pdns_ffi_param_get_local_raw(pdns_ffi_param_t* ref, const void** addr, size_t* addrSize)
-{
-  pdns_ffi_comboaddress_to_raw(ref->params.local, addr, addrSize);
-}
-
-uint16_t pdns_ffi_param_get_local_port(const pdns_ffi_param_t* ref)
-{
-  return ref->params.local.getPort();
-}
-
-const char* pdns_ffi_param_get_edns_cs(pdns_ffi_param_t* ref)
-{
-  if (ref->params.ednssubnet.empty()) {
-    return nullptr;
-  }
-
-  if (!ref->ednssubnetStr) {
-    ref->ednssubnetStr = std::make_unique<std::string>(ref->params.ednssubnet.toStringNoMask());
-  }
-
-  return ref->ednssubnetStr->c_str();
-}
-
-void pdns_ffi_param_get_edns_cs_raw(pdns_ffi_param_t* ref, const void** net, size_t* netSize)
-{
-  if (ref->params.ednssubnet.empty()) {
-    *net = nullptr;
-    *netSize = 0;
-    return;
-  }
-
-  pdns_ffi_comboaddress_to_raw(ref->params.ednssubnet.getNetwork(), net, netSize);
-}
-
-uint8_t pdns_ffi_param_get_edns_cs_source_mask(const pdns_ffi_param_t* ref)
-{
-  return ref->params.ednssubnet.getBits();
-}
-
-static void fill_edns_option(const EDNSOptionViewValue& value, pdns_ednsoption_t& option)
-{
-  option.len = value.size;
-  option.data = nullptr;
-
-  if (value.size > 0) {
-    option.data = value.content;
-  }
-}
-
-size_t pdns_ffi_param_get_edns_options(pdns_ffi_param_t* ref, const pdns_ednsoption_t** out)
-{
-  if (ref->params.ednsOptions.empty()) {
-    return 0;
-  }
-
-  size_t totalCount = 0;
-  for (const auto& option : ref->params.ednsOptions) {
-    totalCount += option.second.values.size();
-  }
-
-  ref->ednsOptionsVect.resize(totalCount);
-
-  size_t pos = 0;
-  for (const auto& option : ref->params.ednsOptions) {
-    for (const auto& entry : option.second.values) {
-      fill_edns_option(entry, ref->ednsOptionsVect.at(pos));
-      ref->ednsOptionsVect.at(pos).optionCode = option.first;
-      pos++;
-    }
-  }
-
-  *out = ref->ednsOptionsVect.data();
-
-  return totalCount;
-}
-
-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()) {
-    return 0;
-  }
-
-  ref->ednsOptionsVect.resize(it->second.values.size());
-
-  size_t pos = 0;
-  for (const auto& entry : it->second.values) {
-    fill_edns_option(entry, ref->ednsOptionsVect.at(pos));
-    ref->ednsOptionsVect.at(pos).optionCode = optionCode;
-    pos++;
-  }
-
-  *out = ref->ednsOptionsVect.data();
-
-  return pos;
-}
-
-size_t pdns_ffi_param_get_proxy_protocol_values(pdns_ffi_param_t* ref, const pdns_proxyprotocol_value_t** out)
-{
-  if (ref->params.proxyProtocolValues.empty()) {
-    return 0;
-  }
-
-  ref->proxyProtocolValuesVect.resize(ref->params.proxyProtocolValues.size());
-
-  size_t pos = 0;
-  for (const auto& value : ref->params.proxyProtocolValues) {
-    auto& dest = ref->proxyProtocolValuesVect.at(pos);
-    dest.type = value.type;
-    dest.len = value.content.size();
-    if (dest.len > 0) {
-      dest.data = value.content.data();
-    }
-    pos++;
-  }
-
-  *out = ref->proxyProtocolValuesVect.data();
-
-  return ref->proxyProtocolValuesVect.size();
-}
-
-void pdns_ffi_param_set_tag(pdns_ffi_param_t* ref, unsigned int tag)
-{
-  ref->params.tag = tag;
-}
-
-void pdns_ffi_param_add_policytag(pdns_ffi_param_t* ref, const char* name)
-{
-  ref->params.policyTags.insert(std::string(name));
-}
-
-void pdns_ffi_param_set_requestorid(pdns_ffi_param_t* ref, const char* name)
-{
-  ref->params.requestorId = std::string(name);
-}
-
-void pdns_ffi_param_set_devicename(pdns_ffi_param_t* ref, const char* name)
-{
-  ref->params.deviceName = std::string(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);
-}
-
-void pdns_ffi_param_set_routingtag(pdns_ffi_param_t* ref, const char* rtag)
-{
-  ref->params.routingTag = std::string(rtag);
-}
-
-void pdns_ffi_param_set_variable(pdns_ffi_param_t* ref, bool variable)
-{
-  ref->params.variable = variable;
-}
-
-void pdns_ffi_param_set_ttl_cap(pdns_ffi_param_t* ref, uint32_t ttl)
-{
-  ref->params.ttlCap = ttl;
-}
-
-void pdns_ffi_param_set_log_query(pdns_ffi_param_t* ref, bool logQuery)
-{
-  ref->params.logQuery = logQuery;
-}
-
-void pdns_ffi_param_set_log_response(pdns_ffi_param_t* ref, bool logResponse)
-{
-  ref->params.logResponse = logResponse;
-}
-
-void pdns_ffi_param_set_rcode(pdns_ffi_param_t* ref, int rcode)
-{
-  ref->params.rcode = rcode;
-}
-
-void pdns_ffi_param_set_follow_cname_records(pdns_ffi_param_t* ref, bool follow)
-{
-  ref->params.followCNAMERecords = follow;
-}
-
-void pdns_ffi_param_set_extended_error_code(pdns_ffi_param_t* ref, uint16_t code)
-{
-  ref->params.extendedErrorCode = code;
-}
-
-void pdns_ffi_param_set_extended_error_extra(pdns_ffi_param_t* ref, size_t len, const char* extra)
-{
-  ref->params.extendedErrorExtra = std::string(extra, 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.d_content = DNSRecordContent::mastermake(type, QClass::IN, std::string(content, contentSize));
-    ref->params.records.push_back(std::move(dr));
-
-    return true;
-  }
-  catch (const std::exception& e) {
-    g_log << Logger::Error << "Error attempting to add a record from Lua via pdns_ffi_param_add_record(): " << e.what() << endl;
-    return false;
-  }
-}
-
-void pdns_ffi_param_set_padding_disabled(pdns_ffi_param_t* ref, bool disabled)
-{
-  ref->params.disablePadding = disabled;
-}
-
-void pdns_ffi_param_add_meta_single_string_kv(pdns_ffi_param_t* ref, const char* key, const char* val)
-{
-  ref->params.meta[std::string(key)].stringVal.insert(std::string(val));
-}
-
-void pdns_ffi_param_add_meta_single_int64_kv(pdns_ffi_param_t* ref, const char* key, int64_t val)
-{
-  ref->params.meta[std::string(key)].intVal.insert(val);
-}
-
-struct pdns_postresolve_ffi_handle
-{
-public:
-  pdns_postresolve_ffi_handle(RecursorLua4::PostResolveFFIHandle& h) :
-    handle(h)
-  {
-  }
-  RecursorLua4::PostResolveFFIHandle& handle;
-  auto insert(std::string&& str)
-  {
-    const auto it = pool.insert(std::move(str)).first;
-    return it;
-  }
-
-private:
-  std::unordered_set<std::string> pool;
-};
-
-bool RecursorLua4::postresolve_ffi(RecursorLua4::PostResolveFFIHandle& h) const
-{
-  if (d_postresolve_ffi) {
-    pdns_postresolve_ffi_handle_t handle(h);
-
-    auto ret = d_postresolve_ffi(&handle);
-    return ret;
-  }
-  return false;
-}
-
-const char* pdns_postresolve_ffi_handle_get_qname(pdns_postresolve_ffi_handle_t* ref)
-{
-  auto str = ref->insert(ref->handle.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();
-  *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;
-}
-
-uint16_t pdns_postresolve_ffi_handle_get_rcode(const pdns_postresolve_ffi_handle_t* ref)
-{
-  return ref->handle.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;
-}
-
-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);
-}
-
-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);
-}
-
-bool pdns_postresolve_ffi_handle_get_record(pdns_postresolve_ffi_handle_t* ref, unsigned int i, pdns_ffi_record_t* record, bool raw)
-{
-  if (i >= ref->handle.d_dq.currentRecords->size()) {
-    return false;
-  }
-  try {
-    DNSRecord& r = ref->handle.d_dq.currentRecords->at(i);
-    if (raw) {
-      const auto& storage = r.d_name.getStorage();
-      record->name = storage.data();
-      record->name_len = storage.size();
-    }
-    else {
-      std::string name = r.d_name.toStringNoDot();
-      record->name_len = name.size();
-      record->name = ref->insert(std::move(name))->c_str();
-    }
-    if (raw) {
-      auto content = ref->insert(r.d_content->serialize(r.d_name, true));
-      record->content = content->data();
-      record->content_len = content->size();
-    }
-    else {
-      auto content = ref->insert(r.d_content->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;
-  }
-  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;
-    return false;
-  }
-
-  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)
-{
-  if (i >= ref->handle.d_dq.currentRecords->size()) {
-    return false;
-  }
-  try {
-    DNSRecord& r = ref->handle.d_dq.currentRecords->at(i);
-    if (raw) {
-      r.d_content = DNSRecordContent::deserialize(r.d_name, r.d_type, string(content, contentLen));
-    }
-    else {
-      r.d_content = DNSRecordContent::mastermake(r.d_type, QClass::IN, string(content, contentLen));
-    }
-
-    return true;
-  }
-  catch (const std::exception& e) {
-    g_log << Logger::Error << "Error attempting to set record content from Lua via pdns_postresolve_ffi_handle_set_record(): " << e.what() << endl;
-    return false;
-  }
-}
-
-void pdns_postresolve_ffi_handle_clear_records(pdns_postresolve_ffi_handle_t* ref)
-{
-  ref->handle.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);
-    if (raw) {
-      dr.d_content = DNSRecordContent::deserialize(dr.d_name, dr.d_type, string(content, contentLen));
-    }
-    else {
-      dr.d_content = DNSRecordContent::mastermake(type, QClass::IN, string(content, contentLen));
-    }
-    ref->handle.d_dq.currentRecords->push_back(std::move(dr));
-
-    return true;
-  }
-  catch (const std::exception& e) {
-    g_log << Logger::Error << "Error attempting to add a record from Lua via pdns_postresolve_ffi_handle_add_record(): " << e.what() << endl;
-    return false;
-  }
-}
-
-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();
-}
-
-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);
-}
diff --git a/pdns/lua-recursor4.hh b/pdns/lua-recursor4.hh
deleted file mode 100644 (file)
index 2fabcc0..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
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include "iputils.hh"
-#include "dnsname.hh"
-#include "namespaces.hh"
-#include "dnsrecords.hh"
-#include "filterpo.hh"
-#include "ednsoptions.hh"
-#include "validate.hh"
-#include "lua-base4.hh"
-#include "proxy-protocol.hh"
-#include "noinitvector.hh"
-#include "rec-eventtrace.hh"
-
-#include <unordered_map>
-
-#include "lua-recursor4-ffi.hh"
-
-// pdns_ffi_param_t is a lightuserdata
-template <>
-struct LuaContext::Pusher<pdns_ffi_param*>
-{
-  static const int minSize = 1;
-  static const int maxSize = 1;
-
-  static PushedObject push(lua_State* state, pdns_ffi_param* ptr) noexcept
-  {
-    lua_pushlightuserdata(state, ptr);
-    return PushedObject{state, 1};
-  }
-};
-
-// pdns_postresolve_ffi_handle is a lightuserdata
-template <>
-struct LuaContext::Pusher<pdns_postresolve_ffi_handle*>
-{
-  static const int minSize = 1;
-  static const int maxSize = 1;
-
-  static PushedObject push(lua_State* state, pdns_postresolve_ffi_handle* ptr) noexcept
-  {
-    lua_pushlightuserdata(state, ptr);
-    return PushedObject{state, 1};
-  }
-};
-
-class RecursorLua4 : public BaseLua4
-{
-public:
-  RecursorLua4();
-  ~RecursorLua4(); // this is so unique_ptr works with an incomplete type
-
-  struct MetaValue
-  {
-    std::unordered_set<std::string> stringVal;
-    std::unordered_set<int64_t> intVal;
-  };
-  struct DNSQuestion
-  {
-    DNSQuestion(const ComboAddress& rem, const ComboAddress& loc, const DNSName& query, uint16_t type, bool tcp, bool& variable_, bool& wantsRPZ_, bool& logResponse_, bool& addPaddingToResponse_) :
-      qname(query), qtype(type), local(loc), remote(rem), isTcp(tcp), variable(variable_), wantsRPZ(wantsRPZ_), logResponse(logResponse_), addPaddingToResponse(addPaddingToResponse_)
-    {
-    }
-    const DNSName& qname;
-    const uint16_t qtype;
-    const ComboAddress& local;
-    const ComboAddress& remote;
-    const ComboAddress* fromAuthIP{nullptr};
-    const struct dnsheader* dh{nullptr};
-    const bool isTcp;
-    const std::vector<pair<uint16_t, string>>* ednsOptions{nullptr};
-    const uint16_t* ednsFlags{nullptr};
-    vector<DNSRecord>* currentRecords{nullptr};
-    DNSFilterEngine::Policy* appliedPolicy{nullptr};
-    std::unordered_set<std::string>* policyTags{nullptr};
-    const std::vector<ProxyProtocolValue>* proxyProtocolValues{nullptr};
-    std::unordered_map<std::string, bool>* discardedPolicies{nullptr};
-    std::string* extendedErrorExtra{nullptr};
-    boost::optional<uint16_t>* extendedErrorCode{nullptr};
-    std::string requestorId;
-    std::string deviceId;
-    std::string deviceName;
-    vState validationState{vState::Indeterminate};
-    bool& variable;
-    bool& wantsRPZ;
-    bool& logResponse;
-    bool& addPaddingToResponse;
-    unsigned int tag{0};
-    std::map<std::string, MetaValue> meta;
-
-    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);
-
-    int rcode{0};
-    // struct dnsheader, packet length would be great
-    vector<DNSRecord> records;
-
-    string followupFunction;
-    string followupPrefix;
-
-    string udpQuery;
-    ComboAddress udpQueryDest;
-    string udpAnswer;
-    string udpCallback;
-
-    LuaContext::LuaObject data;
-    DNSName followupName;
-  };
-
-  struct PolicyEvent
-  {
-    PolicyEvent(const ComboAddress& rem, const DNSName& name, const QType& type, bool tcp) :
-      qname(name), qtype(type), remote(rem), isTcp(tcp)
-    {
-    }
-    const DNSName& qname;
-    const QType qtype;
-    const ComboAddress& remote;
-    const bool isTcp;
-    DNSFilterEngine::Policy* appliedPolicy{nullptr};
-    std::unordered_set<std::string>* policyTags{nullptr};
-    std::unordered_map<std::string, bool>* discardedPolicies{nullptr};
-  };
-
-  struct FFIParams
-  {
-  public:
-    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_)
-    {
-    }
-
-    LuaContext::LuaObject& data;
-    const DNSName& qname;
-    const ComboAddress& local;
-    const ComboAddress& remote;
-    const Netmask& ednssubnet;
-    std::unordered_set<std::string>& policyTags;
-    std::vector<DNSRecord>& records;
-    const EDNSOptionViewMap& ednsOptions;
-    const std::vector<ProxyProtocolValue>& proxyProtocolValues;
-    std::string& requestorId;
-    std::string& deviceId;
-    std::string& deviceName;
-    std::string& routingTag;
-    std::string& extendedErrorExtra;
-    boost::optional<int>& rcode;
-    boost::optional<uint16_t>& extendedErrorCode;
-    uint32_t& ttlCap;
-    bool& variable;
-    bool& logQuery;
-    bool& logResponse;
-    bool& followCNAMERecords;
-    bool& disablePadding;
-
-    unsigned int tag{0};
-    uint16_t qtype;
-    bool tcp;
-
-    std::map<std::string, MetaValue>& meta;
-  };
-
-  unsigned int gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap&, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, const std::vector<ProxyProtocolValue>& proxyProtocolValues) const;
-  unsigned int gettag_ffi(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 preoutquery(const ComboAddress& ns, const ComboAddress& requestor, const DNSName& query, const QType& qtype, bool isTcp, vector<DNSRecord>& res, int& ret, RecEventTrace& et) 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
-  {
-    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)
-    {
-    }
-    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;
-
-protected:
-  virtual void postPrepareContext() override;
-  virtual void postLoad() override;
-  virtual void getFeatures(Features& features) override;
-
-private:
-  typedef std::function<void()> luamaintenance_t;
-  luamaintenance_t d_maintenance;
-  typedef std::function<bool(DNSQuestion*)> luacall_t;
-  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;
-  ipfilter_t d_ipfilter;
-  typedef std::function<bool(PolicyEvent&)> policyEventFilter_t;
-  policyEventFilter_t d_policyHitEventFilter;
-};
diff --git a/pdns/lwres.cc b/pdns/lwres.cc
deleted file mode 100644 (file)
index 933b469..0000000
+++ /dev/null
@@ -1,593 +0,0 @@
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-#include "utility.hh"
-#include "lwres.hh"
-#include <iostream>
-#include "dnsrecords.hh"
-#include <errno.h>
-#include "misc.hh"
-#include <algorithm>
-#include <sstream>
-#include <cstring>
-#include <string>
-#include <vector>
-#include "dns.hh"
-#include "qtype.hh"
-#include "pdnsexception.hh"
-#include "arguments.hh"
-#include "sstuff.hh"
-#include "syncres.hh"
-#include "dnswriter.hh"
-#include "dnsparser.hh"
-#include "logger.hh"
-#include "dns_random.hh"
-#include <boost/scoped_array.hpp>
-#include <boost/algorithm/string.hpp>
-#include "validate-recursor.hh"
-#include "ednssubnet.hh"
-#include "query-local-address.hh"
-#include "tcpiohandler.hh"
-
-#include "rec-protozero.hh"
-#include "uuid-utils.hh"
-#include "rec-tcpout.hh"
-
-thread_local TCPOutConnectionManager t_tcp_manager;
-
-#ifdef HAVE_FSTRM
-#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) {
-    return false;
-  }
-  for (auto& logger : *fstreamLoggers) {
-    if (logger->logQueries()) {
-      return true;
-    }
-  }
-  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)
-{
-  if (fstreamLoggers == nullptr)
-    return;
-
-  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);
-
-  for (auto& logger : *fstreamLoggers) {
-    logger->queueData(str);
-  }
-}
-
-static bool isEnabledForResponses(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers)
-{
-  if (fstreamLoggers == nullptr) {
-    return false;
-  }
-  for (auto& logger : *fstreamLoggers) {
-    if (logger->logResponses()) {
-      return true;
-    }
-  }
-  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)
-{
-  if (fstreamLoggers == nullptr)
-    return;
-
-  struct timespec ts1, ts2;
-  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);
-
-  for (auto& logger : *fstreamLoggers) {
-    logger->queueData(str);
-  }
-}
-
-#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)
-{
-  if (!outgoingLoggers) {
-    return;
-  }
-
-  bool log = false;
-  for (auto& logger : *outgoingLoggers) {
-    if (logger->logQueries()) {
-      log = true;
-      break;
-    }
-  }
-
-  if (!log) {
-    return;
-  }
-
-  static thread_local std::string buffer;
-  buffer.clear();
-  pdns::ProtoZero::Message m{buffer};
-  m.setType(pdns::ProtoZero::Message::MessageType::DNSOutgoingQueryType);
-  m.setMessageIdentity(uuid);
-  m.setSocketFamily(ip.sin4.sin_family);
-  if (!doTCP) {
-    m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::UDP);
-  }
-  else if (!tls) {
-    m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::TCP);
-  }
-  else {
-    m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::DoT);
-  }
-
-  m.setTo(ip);
-  m.setInBytes(bytes);
-  m.setTime();
-  m.setId(qid);
-  m.setQuestion(domain, type, QClass::IN);
-  m.setToPort(ip.getPort());
-  m.setServerIdentity(SyncRes::s_serverID);
-
-  if (initialRequestId) {
-    m.setInitialRequestID(*initialRequestId);
-  }
-
-  if (srcmask) {
-    m.setEDNSSubnet(*srcmask, 128);
-  }
-
-  for (auto& logger : *outgoingLoggers) {
-    if (logger->logQueries()) {
-      logger->queueData(buffer);
-    }
-  }
-}
-
-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)
-{
-  if (!outgoingLoggers) {
-    return;
-  }
-
-  bool log = false;
-  for (auto& logger : *outgoingLoggers) {
-    if (logger->logResponses()) {
-      log = true;
-      break;
-    }
-  }
-
-  if (!log) {
-    return;
-  }
-
-  static thread_local std::string buffer;
-  buffer.clear();
-  pdns::ProtoZero::RecMessage m{buffer};
-  m.setType(pdns::ProtoZero::Message::MessageType::DNSIncomingResponseType);
-  m.setMessageIdentity(uuid);
-  m.setSocketFamily(ip.sin4.sin_family);
-  if (!doTCP) {
-    m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::UDP);
-  }
-  else if (!tls) {
-    m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::TCP);
-  }
-  else {
-    m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::DoT);
-  }
-  m.setTo(ip);
-  m.setInBytes(bytes);
-  m.setTime();
-  m.setId(qid);
-  m.setQuestion(domain, type, QClass::IN);
-  m.setToPort(ip.getPort());
-  m.setServerIdentity(SyncRes::s_serverID);
-
-  if (initialRequestId) {
-    m.setInitialRequestID(*initialRequestId);
-  }
-
-  if (srcmask) {
-    m.setEDNSSubnet(*srcmask, 128);
-  }
-
-  m.startResponse();
-  m.setQueryTime(queryTime.tv_sec, queryTime.tv_usec);
-  if (rcode == -1) {
-    m.setNetworkErrorResponseCode();
-  }
-  else {
-    m.setResponseCode(rcode);
-  }
-
-  for (const auto& record : records) {
-    m.addRR(record, exportTypes, false);
-  }
-  m.commitResponse();
-
-  for (auto& logger : *outgoingLoggers) {
-    if (logger->logResponses()) {
-      logger->queueData(buffer);
-    }
-  }
-}
-
-static bool tcpconnect(const struct timeval& now, const ComboAddress& ip, TCPOutConnectionManager::Connection& connection, bool& dnsOverTLS, const std::string& nsName)
-{
-  dnsOverTLS = SyncRes::s_dot_to_port_853 && ip.getPort() == 853;
-
-  connection = t_tcp_manager.get(ip);
-  if (connection.d_handler) {
-    return false;
-  }
-
-  const struct timeval timeout{ g_networkTimeoutMsec / 1000, static_cast<suseconds_t>(g_networkTimeoutMsec) % 1000 * 1000};
-  Socket s(ip.sin4.sin_family, SOCK_STREAM);
-  s.setNonBlocking();
-  ComboAddress localip = pdns::getQueryLocalAddress(ip.sin4.sin_family, 0);
-  s.bind(localip);
-
-  std::shared_ptr<TLSCtx> tlsCtx{nullptr};
-  if (dnsOverTLS) {
-    TLSContextParameters tlsParams;
-    tlsParams.d_provider = "openssl";
-    tlsParams.d_validateCertificates = false;
-    // tlsParams.d_caStore
-    tlsCtx = getTLSContext(tlsParams);
-    if (tlsCtx == nullptr) {
-      g_log << Logger::Error << "DoT to " << ip << " requested but not available" << endl;
-      dnsOverTLS = false;
-    }
-  }
-  connection.d_handler = std::make_shared<TCPIOHandler>(nsName, false, s.releaseHandle(), timeout, tlsCtx, now.tv_sec);
-  // Returned state ignored
-  // This can throw an exception, retry will need to happen at higher level
-  connection.d_handler->tryConnect(SyncRes::s_tcp_fast_open_connect, ip);
-  return true;
-}
-
-static LWResult::Result tcpsendrecv(const ComboAddress& ip, TCPOutConnectionManager::Connection& connection,
-                                    ComboAddress& localip, const vector<uint8_t>& vpacket, size_t& len, PacketBuffer& buf)
-{
-  socklen_t slen = ip.getSocklen();
-  uint16_t tlen = htons(vpacket.size());
-  const char *lenP = reinterpret_cast<const char*>(&tlen);
-
-  localip.sin4.sin_family = ip.sin4.sin_family;
-  getsockname(connection.d_handler->getDescriptor(), reinterpret_cast<sockaddr*>(&localip), &slen);
-
-  PacketBuffer packet;
-  packet.reserve(2 + vpacket.size());
-  packet.insert(packet.end(), lenP, lenP + 2);
-  packet.insert(packet.end(), vpacket.begin(), vpacket.end());
-
-  LWResult::Result ret = asendtcp(packet, connection.d_handler);
-  if (ret != LWResult::Result::Success) {
-    return ret;
-  }
-
-  ret = arecvtcp(packet, 2, connection.d_handler, false);
-  if (ret != LWResult::Result::Success) {
-    return ret;
-  }
-
-  memcpy(&tlen, packet.data(), sizeof(tlen));
-  len = ntohs(tlen); // switch to the 'len' shared with the rest of the calling function
-
-  // XXX receive into buf directly?
-  packet.resize(len);
-  ret = arecvtcp(packet, len, connection.d_handler, false);
-  if (ret != LWResult::Result::Success) {
-    return ret;
-  }
-  buf.resize(len);
-  memcpy(buf.data(), packet.data(), len);
-  return LWResult::Result::Success;
-}
-
-/** 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)
-{
-  size_t len;
-  size_t bufsize=g_outgoingEDNSBufsize;
-  PacketBuffer buf;
-  buf.resize(bufsize);
-  vector<uint8_t> vpacket;
-  //  string mapped0x20=dns0x20(domain);
-  uint16_t qid = dns_random_uint16();
-  DNSPacketWriter pw(vpacket, domain, type);
-
-  pw.getHeader()->rd=sendRDQuery;
-  pw.getHeader()->id=qid;
-  /* RFC 6840 section 5.9:
-   *  This document further specifies that validating resolvers SHOULD set
-   *  the CD bit on every upstream query.  This is regardless of whether
-   *  the CD bit was set on the incoming query [...]
-   *
-   * sendRDQuery is only true if the qname is part of a forward-zone-recurse (or
-   * set in the forward-zone-file), so we use this as an indicator for it being
-   * an "upstream query". To stay true to "dnssec=off means 3.X behaviour", we
-   * only set +CD on forwarded query in any mode other than dnssec=off.
-   */
-  pw.getHeader()->cd=(sendRDQuery && g_dnssecmode != DNSSECMode::Off);
-
-  string ping;
-  bool weWantEDNSSubnet=false;
-  uint8_t outgoingECSBits = 0;
-  ComboAddress outgoingECSAddr;
-  if(EDNS0Level > 0) {
-    DNSPacketWriter::optvect_t opts;
-    if(srcmask) {
-      EDNSSubnetOpts eo;
-      eo.source = *srcmask;
-      outgoingECSBits = srcmask->getBits();
-      outgoingECSAddr = srcmask->getNetwork();
-      //      cout<<"Adding request mask: "<<eo.source.toString()<<endl;
-      opts.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(eo));
-      weWantEDNSSubnet=true;
-    }
-
-    pw.addOpt(g_outgoingEDNSBufsize, 0, g_dnssecmode == DNSSECMode::Off ? 0 : EDNSOpts::DNSSECOK, opts); 
-    pw.commit();
-  }
-  lwr->d_rcode = 0;
-  lwr->d_haveEDNS = false;
-  LWResult::Result ret;
-
-  DTime dt;
-  dt.set();
-  *now=dt.getTimeval();
-
-  boost::uuids::uuid uuid;
-  const struct timeval queryTime = *now;
-  bool dnsOverTLS = SyncRes::s_dot_to_port_853 && ip.getPort() == 853;
-
-  if (outgoingLoggers) {
-    uuid = getUniqueID();
-    logOutgoingQuery(outgoingLoggers, context ? context->d_initialRequestId : boost::none, uuid, ip, domain, type, qid, doTCP, dnsOverTLS, vpacket.size(), srcmask);
-  }
-
-  srcmask = boost::none; // this is also our return value, even if EDNS0Level == 0
-
-  // We only store the localip if needed for fstrm logging
-  ComboAddress localip;
-#ifdef HAVE_FSTRM
-  bool fstrmQEnabled = false;
-  bool fstrmREnabled = false;
-  
-  if (isEnabledForQueries(fstrmLoggers)) {
-    fstrmQEnabled = true;
-  }
-  if (isEnabledForResponses(fstrmLoggers)) {
-    fstrmREnabled = true;
-  }
-#endif
-
-  if(!doTCP) {
-    int queryfd;
-    if (ip.sin4.sin_family==AF_INET6) {
-      g_stats.ipv6queries++;
-    }
-
-    ret = asendto((const char*)&*vpacket.begin(), vpacket.size(), 0, ip, qid, domain, type, &queryfd);
-
-    if (ret != LWResult::Result::Success) {
-      return ret;
-    }
-
-    if (queryfd == -1) {
-      *chained = true;
-    }
-
-#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);
-      }
-      if (fstrmQEnabled) {
-        logFstreamQuery(fstrmLoggers, queryTime, localip, ip, DnstapMessage::ProtocolType::DoUDP, context ? 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);
-  }
-  else {
-    bool isNew;
-    do {
-      try {
-        // If we get a new (not re-used) TCP connection that does not
-        // work, we give up. For reused connections, we assume the
-        // peer has closed it on error, so we retry. At some point we
-        // *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
-        std::string nsName;
-        if (context && !context->d_nsName.empty()) {
-          nsName = context->d_nsName.toStringNoDot();
-        }
-        isNew = tcpconnect(*now, ip, connection, dnsOverTLS, nsName);
-        ret = tcpsendrecv(ip, 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);
-        }
-#endif /* HAVE_FSTRM */
-        if (ret == LWResult::Result::Success) {
-          break;
-        }
-        connection.d_handler->close();
-      }
-      catch (const NetworkError&) {
-        ret = LWResult::Result::OSLimitError; // OS limits error
-      }
-      catch (const runtime_error&) {
-        ret = LWResult::Result::OSLimitError; // OS limits error (PermanentError is transport related)
-      }
-    } while (!isNew);
-  }
-
-  lwr->d_usec=dt.udiff();
-  *now=dt.getTimeval();
-
-  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);
-      }
-    return ret;
-  }
-
-  buf.resize(len);
-
-#ifdef HAVE_FSTRM
-  if (fstrmREnabled && (!*chained || doTCP)) {
-    DnstapMessage::ProtocolType protocol = doTCP ? DnstapMessage::ProtocolType::DoTCP : DnstapMessage::ProtocolType::DoUDP;
-    if (dnsOverTLS) {
-      protocol = DnstapMessage::ProtocolType::DoT;
-    }
-    logFstreamResponse(fstrmLoggers, localip, ip, protocol, context ? context->d_auth : boost::none, buf, queryTime, *now);
-  }
-#endif /* HAVE_FSTRM */
-
-  lwr->d_records.clear();
-  try {
-    lwr->d_tcbit=0;
-    MOADNSParser mdp(false, reinterpret_cast<const char*>(buf.data()), buf.size());
-    lwr->d_aabit=mdp.d_header.aa;
-    lwr->d_tcbit=mdp.d_header.tc;
-    lwr->d_rcode=mdp.d_header.rcode;
-    
-    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);
-      }
-      lwr->d_validpacket = true;
-      return LWResult::Result::Success; // this is "success", the error is set in lwr->d_rcode
-    }
-
-    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
-        g_log<<Logger::Notice<<"Packet purporting to come from remote server "<<ip.toString()<<" contained wrong answer: '" << domain << "' != '" << mdp.d_qname << "'" << endl;
-      }
-      // unexpected count has already been done @ pdns_recursor.cc
-      goto out;
-    }
-
-    lwr->d_records.reserve(mdp.d_answers.size());
-    for(const auto& a : mdp.d_answers)
-      lwr->d_records.push_back(a.first);
-
-    EDNSOpts edo;
-    if(EDNS0Level > 0 && getEDNSOpts(mdp, &edo)) {
-      lwr->d_haveEDNS = true;
-
-      if(weWantEDNSSubnet) {
-        for(const auto& opt : edo.d_options) {
-          if(opt.first==EDNSOptionCode::ECS) {
-            EDNSSubnetOpts reso;
-            if(getEDNSSubnetOptsFromString(opt.second, &reso)) {
-              //           cerr<<"EDNS Subnet response: "<<reso.source.toString()<<", scope: "<<reso.scope.toString()<<", family = "<<reso.scope.getNetwork().sin4.sin_family<<endl;
-              /* rfc7871 states that 0 "indicate[s] that the answer is suitable for all addresses in FAMILY",
-                 so we might want to still pass the information along to be able to differentiate between
-                 IPv4 and IPv6. Still I'm pretty sure it doesn't matter in real life, so let's not duplicate
-                 entries in our cache. */
-              if(reso.scope.getBits()) {
-                uint8_t bits = std::min(reso.scope.getBits(), outgoingECSBits);
-                outgoingECSAddr.truncate(bits);
-                srcmask = Netmask(outgoingECSAddr, bits);
-              }
-            }
-          }
-        }
-      }
-    }
-        
-    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);
-    }
-    
-    lwr->d_validpacket = true;
-    return LWResult::Result::Success;
-  }
-  catch (const std::exception &mde) {
-    if (::arg().mustDo("log-common-errors")) {
-      g_log<<Logger::Notice<<"Unable to parse packet from remote server "<<ip.toString()<<": "<<mde.what()<<endl;
-    }
-
-    lwr->d_rcode = RCode::FormErr;
-    lwr->d_validpacket = false;
-    g_stats.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);
-    }
-
-    return LWResult::Result::Success; // success - oddly enough
-  }
-  catch (...) {
-    g_log<<Logger::Notice<<"Unknown error parsing packet from remote server"<<endl;
-  }
-  
-  g_stats.serverParseError++; 
-  
- out:
-  if (!lwr->d_rcode) {
-    lwr->d_rcode=RCode::ServFail;
-  }
-
-  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)
-{
-  TCPOutConnectionManager::Connection connection;
-  auto ret = asyncresolve(ip, 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));
-    }
-  }
-  return ret;
-}
-
diff --git a/pdns/lwres.hh b/pdns/lwres.hh
deleted file mode 100644 (file)
index ea039bb..0000000
+++ /dev/null
@@ -1,74 +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 <vector>
-#include <sys/types.h>
-#include "misc.hh"
-#include "iputils.hh"
-#include <netdb.h>
-#include <unistd.h>
-#include <sys/time.h>
-#include <sys/uio.h>
-#include <fcntl.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include "dnsparser.hh"
-#include <arpa/inet.h>
-#undef res_mkquery
-
-#include "pdnsexception.hh"
-#include "dns.hh"
-#include "namespaces.hh"
-#include "remote_logger.hh"
-#include "fstrm_logger.hh"
-#include "resolve-context.hh"
-#include "noinitvector.hh"
-
-class LWResException : public PDNSException
-{
-public:
-  LWResException(const string &reason_) : PDNSException(reason_){}
-};
-
-//! LWRes class 
-class LWResult
-{
-public:
-  LWResult() : d_usec(0) {}
-
-  enum class Result : uint8_t { Timeout=0, Success=1, PermanentError=2 /* not transport related */, OSLimitError=3, Spoofed=4 /* Spoofing attempt (too many near-misses) */ };
-
-  vector<DNSRecord> d_records;
-  int d_rcode{0};
-  bool d_validpacket{false};
-  bool d_aabit{false}, d_tcbit{false};
-  uint32_t d_usec{0};
-  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,  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 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);
diff --git a/pdns/mastercommunicator.cc b/pdns/mastercommunicator.cc
deleted file mode 100644 (file)
index efee904..0000000
+++ /dev/null
@@ -1,323 +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 "auth-caches.hh"
-#include "utility.hh"
-#include <errno.h>
-#include "communicator.hh"
-#include <set>
-#include <boost/utility.hpp>
-
-#include "dnsbackend.hh"
-#include "ueberbackend.hh"
-#include "packethandler.hh"
-#include "nameserver.hh"
-#include "resolver.hh"
-#include "logger.hh"
-#include "dns.hh"
-#include "arguments.hh"
-#include "packetcache.hh"
-#include "base64.hh"
-#include "namespaces.hh"
-#include "query-local-address.hh"
-
-
-void CommunicatorClass::queueNotifyDomain(const DomainInfo& di, UeberBackend* B)
-{
-  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());
-          }
-        }
-    }
-
-    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;
-    }
-  }
-  }
-  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) {
-    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) {
-    try {
-      const ComboAddress caIp(j, 53);
-      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());
-      }
-      hasQueuedItem=true;
-    }
-    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;
-}
-
-
-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;
-    return false;
-  }
-  queueNotifyDomain(di, B);
-  // call backend and tell them we sent out the notification - even though that is premature
-  if (di.serial != di.notified_serial)
-    di.backend->setNotified(di.id, di.serial);
-
-  return true; 
-}
-
-void NotificationQueue::dump()
-{
-  cerr<<"Waiting for notification responses: "<<endl;
-  for(NotificationRequest& nr :  d_nqueue) {
-    cerr<<nr.domain<<", "<<nr.ip<<endl;
-  }
-}
-
-void CommunicatorClass::masterUpdateCheck(PacketHandler *P)
-{
-  if(!::arg().mustDo("primary"))
-    return; 
-
-  UeberBackend *B=P->getBackend();
-  vector<DomainInfo> cmdomains;
-  B->getUpdatedMasters(&cmdomains);
-  
-  if(cmdomains.empty()) {
-    if(d_masterschanged)
-      g_log<<Logger::Info<<"No master domains need notifications"<<endl;
-    d_masterschanged=false;
-  }
-  else {
-    d_masterschanged=true;
-    g_log<<Logger::Notice<<cmdomains.size()<<" domain"<<(cmdomains.size()>1 ? "s" : "")<<" for which we are master need"<<
-      (cmdomains.size()>1 ? "" : "s")<<
-      " notifications"<<endl;
-  }
-
-  // figure out A records of everybody needing notification
-  // do this via the FindNS class, d_fns
-  
-  for(auto& di : cmdomains) {
-    purgeAuthCachesExact(di.zone);
-    queueNotifyDomain(di, B);
-    di.backend->setNotified(di.id, di.serial);
-  }
-}
-
-time_t CommunicatorClass::doNotifications(PacketHandler *P)
-{
-  UeberBackend *B=P->getBackend();
-  ComboAddress from;
-  Utility::socklen_t fromlen;
-  char buffer[1500];
-  int sock;
-  ssize_t size;
-  set<int> fds = {d_nsock4, d_nsock6};
-
-  // receive incoming notifications on the nonblocking socket and take them off the list
-  while(waitForMultiData(fds, 0, 0, &sock) > 0) {
-    fromlen=sizeof(from);
-    size=recvfrom(sock,buffer,sizeof(buffer),0,(struct sockaddr *)&from,&fromlen);
-    if(size < 0)
-      break;
-    DNSPacket p(true);
-
-    p.setRemote(&from);
-
-    if(p.parse(buffer,(size_t)size)<0) {
-      g_log<<Logger::Warning<<"Unable to parse SOA notification answer from "<<p.getRemote()<<endl;
-      continue;
-    }
-
-    if(p.d.rcode)
-      g_log<<Logger::Warning<<"Received unsuccessful notification report for '"<<p.qdomain<<"' from "<<from.toStringWithPort()<<", error: "<<RCode::to_s(p.d.rcode)<<endl;      
-
-    if(d_nq.removeIf(from.toStringWithPort(), p.d.id, p.qdomain))
-      g_log<<Logger::Notice<<"Removed from notification list: '"<<p.qdomain<<"' to "<<from.toStringWithPort()<<" "<< (p.d.rcode ? RCode::to_s(p.d.rcode) : "(was acknowledged)")<<endl;
-    else {
-      g_log<<Logger::Warning<<"Received spurious notify answer for '"<<p.qdomain<<"' from "<< from.toStringWithPort()<<endl;
-      //d_nq.dump();
-    }
-  }
-
-  // send out possible new notifications
-  DNSName domain;
-  string ip;
-  uint16_t id=0;
-
-  bool purged;
-  while(d_nq.getOne(domain, ip, &id, purged)) {
-    if(!purged) {
-      try {
-        ComboAddress remote(ip, 53); // default to 53
-        if((d_nsock6 < 0 && remote.sin4.sin_family == AF_INET6) ||
-           (d_nsock4 < 0 && remote.sin4.sin_family == AF_INET)) {
-             g_log<<Logger::Warning<<"Unable to notify "<<remote.toStringWithPort()<<" for domain '"<<domain<<"', address family is disabled. Is an IPv"<<(remote.sin4.sin_family == AF_INET ? "4" : "6")<<" address set in query-local-address?"<<endl;
-             d_nq.removeIf(remote.toStringWithPort(), id, domain); // Remove, we'll never be able to notify
-             continue; // don't try to notify what we can't!
-        }
-        if(d_preventSelfNotification && AddressIsUs(remote))
-          continue;
-
-        sendNotification(remote.sin4.sin_family == AF_INET ? d_nsock4 : d_nsock6, domain, remote, id, B);
-        drillHole(domain, ip);
-      }
-      catch(ResolverException &re) {
-        g_log<<Logger::Warning<<"Error trying to resolve '"<<ip<<"' for notifying '"<<domain<<"' to server: "<<re.reason<<endl;
-      }
-    }
-    else
-      g_log<<Logger::Warning<<"Notification for "<<domain<<" to "<<ip<<" failed after retries"<<endl;
-  }
-
-  return d_nq.earliest();
-}
-
-void CommunicatorClass::sendNotification(int sock, const DNSName& domain, const ComboAddress& remote, uint16_t id, UeberBackend *B)
-{
-  vector<string> meta;
-  DNSName tsigkeyname;
-  DNSName tsigalgorithm;
-  string tsigsecret64;
-  string tsigsecret;
-
-  if (::arg().mustDo("send-signed-notify") && B->getDomainMetadata(domain, "TSIG-ALLOW-AXFR", meta) && meta.size() > 0) {
-    tsigkeyname = DNSName(meta[0]);
-  }
-
-  vector<uint8_t> packet;
-  DNSPacketWriter pw(packet, domain, QType::SOA, 1, Opcode::Notify);
-  pw.getHeader()->id = id;
-  pw.getHeader()->aa = true; 
-
-  if (tsigkeyname.empty() == false) {
-    if (!B->getTSIGKey(tsigkeyname, &tsigalgorithm, &tsigsecret64)) {
-      g_log<<Logger::Warning<<"TSIG key '"<<tsigkeyname<<"' for domain '"<<domain<<"' not found"<<endl;
-      return;
-    }
-    TSIGRecordContent trc;
-    if (tsigalgorithm.toStringNoDot() == "hmac-md5")
-      trc.d_algoName = DNSName(tsigalgorithm.toStringNoDot() + ".sig-alg.reg.int.");
-    else
-      trc.d_algoName = tsigalgorithm;
-    trc.d_time = time(nullptr);
-    trc.d_fudge = 300;
-    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;
-      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());
-  }
-}
-
-void CommunicatorClass::drillHole(const DNSName &domain, const string &ip)
-{
-  (*d_holes.lock())[pair(domain,ip)]=time(nullptr);
-}
-
-bool CommunicatorClass::justNotified(const DNSName &domain, const string &ip)
-{
-  auto holes = d_holes.lock();
-  auto it = holes->find(pair(domain,ip));
-  if (it == holes->end()) {
-    // no hole
-    return false;
-  }
-
-  if (it->second > time(nullptr)-900) {
-    // recent hole
-    return true;
-  }
-
-  // do we want to purge this? XXX FIXME 
-  return false;
-}
-
-void CommunicatorClass::makeNotifySockets()
-{
-  if(pdns::isQueryLocalAddressFamilyEnabled(AF_INET)) {
-    d_nsock4 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET, 0), true, ::arg().mustDo("non-local-bind"));
-  } else {
-    d_nsock4 = -1;
-  }
-  if(pdns::isQueryLocalAddressFamilyEnabled(AF_INET6)) {
-    d_nsock6 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET6, 0), true, ::arg().mustDo("non-local-bind"));
-  } else {
-    d_nsock6 = -1;
-  }
-}
-
-void CommunicatorClass::notify(const DNSName &domain, const string &ip)
-{
-  d_nq.add(domain, ip);
-  d_any_sem.post();
-}
index d5981c4e417d9bf325dfda05b402bb56806d241a..c1e16b810c7a5db4fe66f15b3fd76599373a03df 100644 (file)
  */
 
 #include "minicurl.hh"
-#include <curl/curl.h>
 #include <stdexcept>
 #include <boost/format.hpp>
 
+#ifdef CURL_STRICTER
+#define getCURLPtr(x) \
+  x.get()
+#else
+#define getCURLPtr(x) \
+  x
+#endif
+
 void MiniCurl::init()
 {
   static std::atomic_flag s_init = ATOMIC_FLAG_INIT;
@@ -42,25 +49,59 @@ void MiniCurl::init()
 
 MiniCurl::MiniCurl(const string& useragent)
 {
+#ifdef CURL_STRICTER
+  d_curl = std::unique_ptr<CURL, decltype(&curl_easy_cleanup)>(curl_easy_init(), curl_easy_cleanup);
+#else
   d_curl = curl_easy_init();
+#endif
   if (d_curl == nullptr) {
     throw std::runtime_error("Error creating a MiniCurl session");
   }
-  curl_easy_setopt(d_curl, CURLOPT_USERAGENT, useragent.c_str());
+  curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_USERAGENT, useragent.c_str());
 }
 
 MiniCurl::~MiniCurl()
 {
-  // NEEDS TO CLEAN HOSTLIST
+  clearHeaders();
+  clearHostsList();
+#ifndef CURL_STRICTER
   curl_easy_cleanup(d_curl);
+#endif
 }
 
 size_t MiniCurl::write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
 {
-  MiniCurl* us = (MiniCurl*)userdata;
-  us->d_data.append(ptr, size*nmemb);
-  return size*nmemb;
+  if (userdata != nullptr) {
+    MiniCurl* us = static_cast<MiniCurl*>(userdata);
+    us->d_data.append(ptr, size * nmemb);
+    return size * nmemb;
+  }
+  return 0;
+}
+
+#if defined(LIBCURL_VERSION_NUM) && LIBCURL_VERSION_NUM >= 0x072000 // 7.32.0
+size_t MiniCurl::progress_callback(void *clientp, curl_off_t /* dltotal */, curl_off_t dlnow, curl_off_t /* ultotal */, curl_off_t /* ulnow */)
+{
+  if (clientp != nullptr) {
+    MiniCurl* us = static_cast<MiniCurl*>(clientp);
+    if (us->d_byteslimit > 0 && static_cast<size_t>(dlnow) > us->d_byteslimit) {
+      return static_cast<size_t>(dlnow);
+    }
+  }
+  return 0;
+}
+#else
+size_t MiniCurl::progress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)
+{
+  if (clientp != nullptr) {
+    MiniCurl* us = static_cast<MiniCurl*>(clientp);
+    if (us->d_byteslimit > 0 && dlnow > static_cast<double>(us->d_byteslimit)) {
+      return static_cast<size_t>(dlnow);
+    }
+  }
+  return 0;
 }
+#endif
 
 static string extractHostFromURL(const std::string& url)
 {
@@ -75,9 +116,18 @@ static string extractHostFromURL(const std::string& url)
   return url.substr(pos, endpos-pos);
 }
 
-void MiniCurl::setupURL(const std::string& str, const ComboAddress* rem, const ComboAddress* src, int timeout, bool fastopen, bool verify)
+void MiniCurl::setupURL(const std::string& str, const ComboAddress* rem, const ComboAddress* src, int timeout, size_t byteslimit, [[maybe_unused]] bool fastopen, bool verify)
 {
-  if(rem) {
+  if (!d_fresh) {
+    curl_easy_reset(getCURLPtr(d_curl));
+  }
+  else {
+    d_fresh = false;
+  }
+
+  clearHostsList();
+
+  if (rem) {
     struct curl_slist *hostlist = nullptr; // THIS SHOULD BE FREED
 
     // url = http://hostname.enzo/url
@@ -98,56 +148,82 @@ void MiniCurl::setupURL(const std::string& str, const ComboAddress* rem, const C
       hostlist = curl_slist_append(hostlist, hcode.c_str());
     }
 
-    curl_easy_setopt(d_curl, CURLOPT_RESOLVE, hostlist);
+#ifdef CURL_STRICTER
+    d_host_list = std::unique_ptr<struct curl_slist, decltype(&curl_slist_free_all)>(hostlist, curl_slist_free_all);
+#else
+    d_host_list = hostlist;
+#endif
+
+    curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_RESOLVE, getCURLPtr(d_host_list));
   }
   if(src) {
-    curl_easy_setopt(d_curl, CURLOPT_INTERFACE, src->toString().c_str());
+    curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_INTERFACE, src->toString().c_str());
   }
-  curl_easy_setopt(d_curl, CURLOPT_FOLLOWLOCATION, true);
+  curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_FOLLOWLOCATION, true);
+
   /* only allow HTTP and HTTPS */
-  curl_easy_setopt(d_curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
-  curl_easy_setopt(d_curl, CURLOPT_SSL_VERIFYPEER, verify);
-  curl_easy_setopt(d_curl, CURLOPT_SSL_VERIFYHOST, verify ? 2 : 0);
-  curl_easy_setopt(d_curl, CURLOPT_FAILONERROR, true);
-  curl_easy_setopt(d_curl, CURLOPT_URL, str.c_str());
-  curl_easy_setopt(d_curl, CURLOPT_WRITEFUNCTION, write_callback);
-  curl_easy_setopt(d_curl, CURLOPT_WRITEDATA, this);
-  curl_easy_setopt(d_curl, CURLOPT_TIMEOUT, static_cast<long>(timeout));
+#if defined(LIBCURL_VERSION_NUM) && LIBCURL_VERSION_NUM >= 0x075500 // 7.85.0
+  curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_PROTOCOLS_STR, "http,https");
+#else
+  curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
+#endif
+
+  curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_SSL_VERIFYPEER, verify);
+  curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_SSL_VERIFYHOST, verify ? 2 : 0);
+  curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_FAILONERROR, true);
+  curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_URL, str.c_str());
+  curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_WRITEFUNCTION, write_callback);
+  curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_WRITEDATA, this);
+
+  d_byteslimit = byteslimit;
+  if (d_byteslimit > 0) {
+    /* enable progress meter */
+    curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_NOPROGRESS, 0L);
+#if defined(LIBCURL_VERSION_NUM) && LIBCURL_VERSION_NUM >= 0x072000 // 7.32.0
+    curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_XFERINFOFUNCTION, progress_callback);
+    curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_XFERINFODATA, this);
+#else
+    curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_PROGRESSFUNCTION, progress_callback);
+    curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_PROGRESSDATA, this);
+#endif
+  }
+
+  curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_TIMEOUT, static_cast<long>(timeout));
 #if defined(CURL_AT_LEAST_VERSION)
 #if CURL_AT_LEAST_VERSION(7, 49, 0) && defined(__linux__)
-  curl_easy_setopt(d_curl, CURLOPT_TCP_FASTOPEN, fastopen);
+  curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_TCP_FASTOPEN, fastopen);
 #endif
 #endif
   clearHeaders();
   d_data.clear();
 }
 
-std::string MiniCurl::getURL(const std::string& str, const ComboAddress* rem, const ComboAddress* src, int timeout, bool fastopen, bool verify)
+std::string MiniCurl::getURL(const std::string& str, const ComboAddress* rem, const ComboAddress* src, int timeout, [[maybe_unused]] bool fastopen, bool verify, size_t byteslimit)
 {
-  setupURL(str, rem, src, timeout, fastopen, verify);
-  auto res = curl_easy_perform(d_curl);
+  setupURL(str, rem, src, timeout, byteslimit, fastopen, verify);
+  auto res = curl_easy_perform(getCURLPtr(d_curl));
   long http_code = 0;
-  curl_easy_getinfo(d_curl, CURLINFO_RESPONSE_CODE, &http_code);
+  curl_easy_getinfo(getCURLPtr(d_curl), CURLINFO_RESPONSE_CODE, &http_code);
 
-  if(res != CURLE_OK || http_code != 200)  {
+  if ((res != CURLE_OK && res != CURLE_ABORTED_BY_CALLBACK) || http_code != 200)  {
     throw std::runtime_error("Unable to retrieve URL ("+std::to_string(http_code)+"): "+string(curl_easy_strerror(res)));
   }
-  std::string ret=d_data;
+  std::string ret = d_data;
   d_data.clear();
   return ret;
 }
 
 std::string MiniCurl::postURL(const std::string& str, const std::string& postdata, MiniCurlHeaders& headers, int timeout, bool fastopen, bool verify)
 {
-  setupURL(str, nullptr, nullptr, timeout, fastopen, verify);
+  setupURL(str, nullptr, nullptr, timeout, 0, fastopen, verify);
   setHeaders(headers);
-  curl_easy_setopt(d_curl, CURLOPT_POSTFIELDSIZE, postdata.size());
-  curl_easy_setopt(d_curl, CURLOPT_POSTFIELDS, postdata.c_str());
+  curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_POSTFIELDSIZE, postdata.size());
+  curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_POSTFIELDS, postdata.c_str());
 
-  auto res = curl_easy_perform(d_curl);
+  auto res = curl_easy_perform(getCURLPtr(d_curl));
 
   long http_code = 0;
-  curl_easy_getinfo(d_curl, CURLINFO_RESPONSE_CODE, &http_code);
+  curl_easy_getinfo(getCURLPtr(d_curl), CURLINFO_RESPONSE_CODE, &http_code);
 
   if(res != CURLE_OK)
     throw std::runtime_error("Unable to post URL ("+std::to_string(http_code)+"): "+string(curl_easy_strerror(res)));
@@ -161,11 +237,26 @@ std::string MiniCurl::postURL(const std::string& str, const std::string& postdat
 void MiniCurl::clearHeaders()
 {
   if (d_curl) {
-    curl_easy_setopt(d_curl, CURLOPT_HTTPHEADER, NULL);
-    if (d_header_list != nullptr) {
-      curl_slist_free_all(d_header_list);
-      d_header_list = nullptr;
-    }
+    curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_HTTPHEADER, nullptr);
+#ifdef CURL_STRICTER
+    d_header_list.reset();
+#else
+    curl_slist_free_all(d_header_list);
+    d_header_list = nullptr;
+#endif
+  }
+}
+
+void MiniCurl::clearHostsList()
+{
+  if (d_curl) {
+    curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_RESOLVE, nullptr);
+#ifdef CURL_STRICTER
+    d_host_list.reset();
+#else
+    curl_slist_free_all(d_host_list);
+    d_host_list = nullptr;
+#endif
   }
 }
 
@@ -175,8 +266,16 @@ void MiniCurl::setHeaders(const MiniCurlHeaders& headers)
     for (auto& header : headers) {
       std::stringstream header_ss;
       header_ss << header.first << ": " << header.second;
+#ifdef CURL_STRICTER
+      struct curl_slist * list = nullptr;
+      if (d_header_list) {
+        list = d_header_list.release();
+      }
+      d_header_list = std::unique_ptr<struct curl_slist, decltype(&curl_slist_free_all)>(curl_slist_append(list, header_ss.str().c_str()), curl_slist_free_all);
+#else
       d_header_list = curl_slist_append(d_header_list, header_ss.str().c_str());
+#endif
     }
-    curl_easy_setopt(d_curl, CURLOPT_HTTPHEADER, d_header_list);
+    curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_HTTPHEADER, getCURLPtr(d_header_list));
   }
 }
index b59656ae71d8d2f8307488850bc46238c66ec245..08c88a6d75a22e5a07140344b71918b54ebcea44 100644 (file)
 
 #pragma once
 #include <string>
+
+#include <curl/curlver.h>
+#if defined(LIBCURL_VERSION_NUM) && LIBCURL_VERSION_NUM >= 0x073200
+/* we need this so that 'CURL' is not typedef'd to void,
+   which prevents us from wrapping it in a unique_ptr.
+   Wrapping in a shared_ptr is fine because of type erasure,
+   but it is a bit wasteful. */
+#define CURL_STRICTER 1
+#endif
 #include <curl/curl.h>
 #include "iputils.hh"
-// turns out 'CURL' is currently typedef for void which means we can't easily forward declare it
 
 class MiniCurl
 {
@@ -38,14 +46,33 @@ public:
   MiniCurl(const string& useragent="MiniCurl/0.0");
   ~MiniCurl();
   MiniCurl& operator=(const MiniCurl&) = delete;
-  std::string getURL(const std::string& str, const ComboAddress* rem=nullptr, const ComboAddress* src=nullptr, int timeout = 2, bool fastopen = false, bool verify = false);
+
+  std::string getURL(const std::string& str, const ComboAddress* rem=nullptr, const ComboAddress* src=nullptr, int timeout = 2, bool fastopen = false, bool verify = false, size_t byteslimit = 0);
   std::string postURL(const std::string& str, const std::string& postdata, MiniCurlHeaders& headers, int timeout = 2, bool fastopen = false, bool verify = false);
+
 private:
-  CURL *d_curl;
   static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
+#if defined(LIBCURL_VERSION_NUM) && LIBCURL_VERSION_NUM >= 0x072000 // 7.32.0
+  static size_t progress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
+#else
+  static size_t progress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow);
+#endif
+
+#ifdef CURL_STRICTER
+  std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> d_curl{nullptr, curl_easy_cleanup};
+  std::unique_ptr<struct curl_slist, decltype(&curl_slist_free_all)> d_header_list{nullptr, curl_slist_free_all};
+  std::unique_ptr<struct curl_slist, decltype(&curl_slist_free_all)> d_host_list{nullptr, curl_slist_free_all};
+#else
+  CURL* d_curl{};
+  struct curl_slist* d_header_list{};
+  struct curl_slist* d_host_list{};
+#endif
   std::string d_data;
-  struct curl_slist* d_header_list = nullptr;
-  void setupURL(const std::string& str, const ComboAddress* rem, const ComboAddress* src, int timeout, bool fastopen, bool verify);
+  size_t d_byteslimit{};
+  bool d_fresh{true};
+
+  void setupURL(const std::string& str, const ComboAddress* rem, const ComboAddress* src, int timeout, size_t byteslimit, bool fastopen, bool verify);
   void setHeaders(const MiniCurlHeaders& headers);
   void clearHeaders();
+  void clearHostsList();
 };
index 90ec60ade31dd3b8fc068910efac48d950a658b7..3e9fa6b68a05e8facd3b08c7e58df9085511633d 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.
  */
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
+
 #include <sys/param.h>
 #include <sys/socket.h>
 #include <fcntl.h>
 #include <netdb.h>
 #include <sys/time.h>
-#include <time.h>
+#include <ctime>
 #include <sys/resource.h>
 #include <netinet/in.h>
 #include <sys/un.h>
@@ -35,6 +37,7 @@
 #include <fstream>
 #include "misc.hh"
 #include <vector>
+#include <string>
 #include <sstream>
 #include <cerrno>
 #include <cstring>
 #include <poll.h>
 #include <iomanip>
 #include <netinet/tcp.h>
-#include <string.h>
-#include <stdlib.h>
-#include <stdio.h>
+#include <optional>
+#include <cstdlib>
+#include <cstdio>
 #include "pdnsexception.hh"
-#include <sys/types.h>
 #include <boost/algorithm/string.hpp>
 #include <boost/format.hpp>
 #include "iputils.hh"
 #include "dnsparser.hh"
-#include <sys/types.h>
+#include "dns_random.hh"
 #include <pwd.h>
 #include <grp.h>
-#include <limits.h>
+#include <climits>
 #ifdef __FreeBSD__
 #  include <pthread_np.h>
 #endif
 #  include <sched.h>
 #endif
 
-size_t writen2(int fd, const void *buf, size_t count)
+#if defined(HAVE_LIBCRYPTO)
+#include <openssl/err.h>
+#endif // HAVE_LIBCRYPTO
+
+size_t writen2(int fileDesc, const void *buf, size_t count)
 {
-  const char *ptr = reinterpret_cast<const char*>(buf);
+  const char *ptr = static_cast<const char*>(buf);
   const char *eptr = ptr + count;
 
-  ssize_t res;
-  while(ptr != eptr) {
-    res = ::write(fd, ptr, eptr - ptr);
-    if(res < 0) {
-      if (errno == EAGAIN)
+  while (ptr != eptr) {
+    auto res = ::write(fileDesc, ptr, eptr - ptr);
+    if (res < 0) {
+      if (errno == EAGAIN) {
         throw std::runtime_error("used writen2 on non-blocking socket, got EAGAIN");
-      else
-        unixDie("failed in writen2");
+      }
+      unixDie("failed in writen2");
     }
-    else if (res == 0)
+    else if (res == 0) {
       throw std::runtime_error("could not write all bytes, got eof in writen2");
+    }
 
-    ptr += (size_t) res;
+    ptr += res;
   }
 
   return count;
@@ -205,7 +211,7 @@ auto pdns::getMessageFromErrno(const int errnum) -> std::string
   errMsgData.resize(errLen);
 
   const char* errMsg = nullptr;
-#ifdef _GNU_SOURCE
+#ifdef STRERROR_R_CHAR_P
   errMsg = strerror_r(errnum, errMsgData.data(), errMsgData.length());
 #else
   // This can fail, and when it does, it sets errno. We ignore that and
@@ -224,68 +230,120 @@ auto pdns::getMessageFromErrno(const int errnum) -> std::string
   return message;
 }
 
-string nowTime()
+#if defined(HAVE_LIBCRYPTO)
+auto pdns::OpenSSL::error(const std::string& errorMessage) -> std::runtime_error
 {
-  time_t now = time(nullptr);
-  struct tm tm;
-  localtime_r(&now, &tm);
-  char buffer[30];
-  // YYYY-mm-dd HH:MM:SS TZOFF
-  strftime(buffer, sizeof(buffer), "%F %T %z", &tm);
-  buffer[sizeof(buffer)-1] = '\0';
-  return string(buffer);
-}
+  unsigned long errorCode = 0;
+  auto fullErrorMessage{errorMessage};
+#if OPENSSL_VERSION_MAJOR >= 3
+  const char* filename = nullptr;
+  const char* functionName = nullptr;
+  int lineNumber = 0;
+  while ((errorCode = ERR_get_error_all(&filename, &lineNumber, &functionName, nullptr, nullptr)) != 0) {
+    fullErrorMessage += std::string(": ") + std::to_string(errorCode);
 
-uint16_t getShort(const unsigned char *p)
-{
-  return p[0] * 256 + p[1];
-}
+    const auto* lib = ERR_lib_error_string(errorCode);
+    if (lib != nullptr) {
+      fullErrorMessage += std::string(":") + lib;
+    }
 
+    const auto* reason = ERR_reason_error_string(errorCode);
+    if (reason != nullptr) {
+      fullErrorMessage += std::string("::") + reason;
+    }
 
-uint16_t getShort(const char *p)
-{
-  return getShort((const unsigned char *)p);
+    if (filename != nullptr) {
+      fullErrorMessage += std::string(" - ") + filename;
+    }
+    if (lineNumber != 0) {
+      fullErrorMessage += std::string(":") + std::to_string(lineNumber);
+    }
+    if (functionName != nullptr) {
+      fullErrorMessage += std::string(" - ") + functionName;
+    }
+  }
+#else
+  while ((errorCode = ERR_get_error()) != 0) {
+    fullErrorMessage += std::string(": ") + std::to_string(errorCode);
+
+    const auto* lib = ERR_lib_error_string(errorCode);
+    if (lib != nullptr) {
+      fullErrorMessage += std::string(":") + lib;
+    }
+
+    const auto* func = ERR_func_error_string(errorCode);
+    if (func != nullptr) {
+      fullErrorMessage += std::string(":") + func;
+    }
+
+    const auto* reason = ERR_reason_error_string(errorCode);
+    if (reason != nullptr) {
+      fullErrorMessage += std::string("::") + reason;
+    }
+  }
+#endif
+  return std::runtime_error(fullErrorMessage);
 }
 
-uint32_t getLong(const unsigned char* p)
+auto pdns::OpenSSL::error(const std::string& componentName, const std::string& errorMessage) -> std::runtime_error
 {
-  return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3];
+  return pdns::OpenSSL::error(componentName + ": " + errorMessage);
 }
+#endif // HAVE_LIBCRYPTO
 
-uint32_t getLong(const char* p)
+string nowTime()
 {
-  return getLong(reinterpret_cast<const unsigned char *>(p));
+  time_t now = time(nullptr);
+  struct tm theTime{};
+  localtime_r(&now, &theTime);
+  std::array<char, 30> buffer{};
+  // YYYY-mm-dd HH:MM:SS TZOFF
+  size_t ret = strftime(buffer.data(), buffer.size(), "%F %T %z", &theTime);
+  if (ret == 0) {
+    buffer[0] = '\0';
+  }
+  return {buffer.data()};
 }
 
-static bool ciEqual(const string& a, const string& b)
+static bool ciEqual(const string& lhs, const string& rhs)
 {
-  if(a.size()!=b.size())
+  if (lhs.size() != rhs.size()) {
     return false;
+  }
 
-  string::size_type pos=0, epos=a.size();
-  for(;pos < epos; ++pos)
-    if(dns_tolower(a[pos])!=dns_tolower(b[pos]))
+  string::size_type pos = 0;
+  const string::size_type epos = lhs.size();
+  for (; pos < epos; ++pos) {
+    if (dns_tolower(lhs[pos]) != dns_tolower(rhs[pos])) {
       return false;
+    }
+  }
   return true;
 }
 
 /** does domain end on suffix? Is smart about "wwwds9a.nl" "ds9a.nl" not matching */
 static bool endsOn(const string &domain, const string &suffix)
 {
-  if( suffix.empty() || ciEqual(domain, suffix) )
+  if( suffix.empty() || ciEqual(domain, suffix) ) {
     return true;
+  }
 
-  if(domain.size()<=suffix.size())
+  if(domain.size() <= suffix.size()) {
     return false;
+  }
 
-  string::size_type dpos=domain.size()-suffix.size()-1, spos=0;
+  string::size_type dpos = domain.size() - suffix.size() - 1;
+  string::size_type spos = 0;
 
-  if(domain[dpos++]!='.')
+  if (domain[dpos++] != '.') {
     return false;
+  }
 
-  for(; dpos < domain.size(); ++dpos, ++spos)
-    if(dns_tolower(domain[dpos]) != dns_tolower(suffix[spos]))
+  for(; dpos < domain.size(); ++dpos, ++spos) {
+    if (dns_tolower(domain[dpos]) != dns_tolower(suffix[spos])) {
       return false;
+    }
+  }
 
   return true;
 }
@@ -293,90 +351,48 @@ static bool endsOn(const string &domain, const string &suffix)
 /** strips a domain suffix from a domain, returns true if it stripped */
 bool stripDomainSuffix(string *qname, const string &domain)
 {
-  if(!endsOn(*qname, domain))
+  if (!endsOn(*qname, domain)) {
     return false;
+  }
 
-  if(toLower(*qname)==toLower(domain))
+  if (toLower(*qname) == toLower(domain)) {
     *qname="@";
+  }
   else {
-    if((*qname)[qname->size()-domain.size()-1]!='.')
+    if ((*qname)[qname->size() - domain.size() - 1] != '.') {
       return false;
+    }
 
-    qname->resize(qname->size()-domain.size()-1);
+    qname->resize(qname->size() - domain.size()-1);
   }
   return true;
 }
 
-static void parseService4(const string& descr, ServiceTuple& st)
-{
-  vector<string> parts;
-  stringtok(parts, descr, ":");
-  if (parts.empty()) {
-    throw PDNSException("Unable to parse '" + descr + "' as a service");
-  }
-  st.host = parts[0];
-  if (parts.size() > 1) {
-    pdns::checked_stoi_into(st.port, parts[1]);
-  }
-}
-
-static void parseService6(const string& descr, ServiceTuple& st)
+// returns -1 in case if error, 0 if no data is available, 1 if there is. In the first two cases, errno is set
+int waitForData(int fileDesc, int seconds, int useconds)
 {
-  string::size_type pos = descr.find(']');
-  if (pos == string::npos) {
-    throw PDNSException("Unable to parse '" + descr + "' as an IPv6 service");
-  }
-
-  st.host = descr.substr(1, pos - 1);
-  if (pos + 2 < descr.length()) {
-    pdns::checked_stoi_into(st.port, descr.substr(pos + 2));
-  }
+  return waitForRWData(fileDesc, true, seconds, useconds);
 }
 
-void parseService(const string &descr, ServiceTuple &st)
+int waitForRWData(int fileDesc, bool waitForRead, int seconds, int useconds, bool* error, bool* disconnected)
 {
-  if(descr.empty())
-    throw PDNSException("Unable to parse '"+descr+"' as a service");
-
-  vector<string> parts;
-  stringtok(parts, descr, ":");
+  struct pollfd pfd{};
+  memset(&pfd, 0, sizeof(pfd));
+  pfd.fd = fileDesc;
 
-  if(descr[0]=='[') {
-    parseService6(descr, st);
-  }
-  else if(descr[0]==':' || parts.size() > 2 || descr.find("::") != string::npos) {
-    st.host=descr;
+  if (waitForRead) {
+    pfd.events = POLLIN;
   }
   else {
-    parseService4(descr, st);
+    pfd.events = POLLOUT;
   }
-}
-
-// returns -1 in case if error, 0 if no data is available, 1 if there is. In the first two cases, errno is set
-int waitForData(int fd, int seconds, int useconds)
-{
-  return waitForRWData(fd, true, seconds, useconds);
-}
 
-int waitForRWData(int fd, bool waitForRead, int seconds, int useconds, bool* error, bool* disconnected)
-{
-  int ret;
-
-  struct pollfd pfd;
-  memset(&pfd, 0, sizeof(pfd));
-  pfd.fd = fd;
-
-  if(waitForRead)
-    pfd.events=POLLIN;
-  else
-    pfd.events=POLLOUT;
-
-  ret = poll(&pfd, 1, seconds * 1000 + useconds/1000);
+  int ret = poll(&pfd, 1, seconds * 1000 + useconds/1000);
   if (ret > 0) {
-    if (error && (pfd.revents & POLLERR)) {
+    if ((error != nullptr) && (pfd.revents & POLLERR) != 0) {
       *error = true;
     }
-    if (disconnected && (pfd.revents & POLLHUP)) {
+    if ((disconnected != nullptr) && (pfd.revents & POLLHUP) != 0) {
       *disconnected = true;
     }
   }
@@ -417,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;
 }
@@ -474,7 +495,7 @@ string humanDuration(time_t passed)
   return ret.str();
 }
 
-const string unquotify(const string &item)
+string unquotify(const string &item)
 {
   if(item.size()<2)
     return item;
@@ -507,31 +528,46 @@ string urlEncode(const string &text)
   return ret;
 }
 
-string getHostname()
+static size_t getMaxHostNameSize()
 {
-#ifndef MAXHOSTNAMELEN
-#define MAXHOSTNAMELEN 255
+#if defined(HOST_NAME_MAX)
+  return HOST_NAME_MAX;
 #endif
 
-  char tmp[MAXHOSTNAMELEN];
-  if(gethostname(tmp, MAXHOSTNAMELEN))
-    return "UNKNOWN";
+#if defined(_SC_HOST_NAME_MAX)
+  auto tmp = sysconf(_SC_HOST_NAME_MAX);
+  if (tmp != -1) {
+    return tmp;
+  }
+#endif
 
-  return string(tmp);
+  const size_t maxHostNameSize = 255;
+  return maxHostNameSize;
 }
 
-string itoa(int i)
+std::optional<string> getHostname()
 {
-  ostringstream o;
-  o<<i;
-  return o.str();
+  const size_t maxHostNameBufSize = getMaxHostNameSize() + 1;
+  std::string hostname;
+  hostname.resize(maxHostNameBufSize, 0);
+
+  if (gethostname(hostname.data(), maxHostNameBufSize) == -1) {
+    return std::nullopt;
+  }
+
+  hostname.resize(strlen(hostname.c_str()));
+  return std::make_optional(hostname);
 }
 
-string uitoa(unsigned int i) // MSVC 6 doesn't grok overloading (un)signed
+std::string getCarbonHostName()
 {
-  ostringstream o;
-  o<<i;
-  return o.str();
+  auto hostname = getHostname();
+  if (!hostname.has_value()) {
+    throw std::runtime_error(stringerror());
+  }
+
+  boost::replace_all(*hostname, ".", "_");
+  return *hostname;
 }
 
 string bitFlip(const string &str)
@@ -544,29 +580,27 @@ string bitFlip(const string &str)
   return ret;
 }
 
-string stringerror(int err)
-{
-  return strerror(err);
-}
-
-string stringerror()
-{
-  return strerror(errno);
-}
-
 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()) {
@@ -596,13 +630,13 @@ string U32ToIP(uint32_t val)
 
 string makeHexDump(const string& str)
 {
-  char tmp[5];
+  std::array<char, 5> tmp;
   string ret;
-  ret.reserve((int)(str.size()*2.2));
+  ret.reserve(static_cast<size_t>(str.size()*2.2));
 
-  for(char n : str) {
-    snprintf(tmp, sizeof(tmp), "%02x ", (unsigned char)n);
-    ret+=tmp;
+  for (char n : str) {
+    snprintf(tmp.data(), tmp.size(), "%02x ", static_cast<unsigned char>(n));
+    ret += tmp.data();
   }
   return ret;
 }
@@ -612,14 +646,17 @@ string makeBytesFromHex(const string &in) {
     throw std::range_error("odd number of bytes in hex string");
   }
   string ret;
-  ret.reserve(in.size());
-  unsigned int num;
-  for (size_t i = 0; i < in.size(); i+=2) {
-    string numStr = in.substr(i, 2);
-    num = 0;
-    sscanf(numStr.c_str(), "%02x", &num);
-    ret.push_back((uint8_t)num);
+  ret.reserve(in.size() / 2);
+
+  for (size_t i = 0; i < in.size(); i += 2) {
+    const auto numStr = in.substr(i, 2);
+    unsigned int num = 0;
+    if (sscanf(numStr.c_str(), "%02x", &num) != 1) {
+      throw std::range_error("Invalid value while parsing the hex string '" + in + "'");
+    }
+    ret.push_back(static_cast<uint8_t>(num));
   }
+
   return ret;
 }
 
@@ -635,7 +672,7 @@ void normalizeTV(struct timeval& tv)
   }
 }
 
-const struct timeval operator+(const struct timeval& lhs, const struct timeval& rhs)
+struct timeval operator+(const struct timeval& lhs, const struct timeval& rhs)
 {
   struct timeval ret;
   ret.tv_sec=lhs.tv_sec + rhs.tv_sec;
@@ -644,7 +681,7 @@ const struct timeval operator+(const struct timeval& lhs, const struct timeval&
   return ret;
 }
 
-const struct timeval operator-(const struct timeval& lhs, const struct timeval& rhs)
+struct timeval operator-(const struct timeval& lhs, const struct timeval& rhs)
 {
   struct timeval ret;
   ret.tv_sec=lhs.tv_sec - rhs.tv_sec;
@@ -825,12 +862,10 @@ bool readFileIfThere(const char* fname, std::string* line)
 {
   line->clear();
   auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(fname, "r"), fclose);
-  if(!fp)
+  if (!fp) {
     return false;
-  stringfgets(fp.get(), *line);
-  fp.reset();
-
-  return true;
+  }
+  return stringfgets(fp.get(), *line);
 }
 
 Regex::Regex(const string &expr)
@@ -942,120 +977,6 @@ void setFilenumLimit(unsigned int lim)
     unixDie("Setting number of available file descriptors");
 }
 
-#define burtlemix(a,b,c) \
-{ \
-  a -= b; a -= c; a ^= (c>>13); \
-  b -= c; b -= a; b ^= (a<<8); \
-  c -= a; c -= b; c ^= (b>>13); \
-  a -= b; a -= c; a ^= (c>>12);  \
-  b -= c; b -= a; b ^= (a<<16); \
-  c -= a; c -= b; c ^= (b>>5); \
-  a -= b; a -= c; a ^= (c>>3);  \
-  b -= c; b -= a; b ^= (a<<10); \
-  c -= a; c -= b; c ^= (b>>15); \
-}
-
-uint32_t burtle(const unsigned char* k, uint32_t length, uint32_t initval)
-{
-  uint32_t a,b,c,len;
-
-   /* Set up the internal state */
-  len = length;
-  a = b = 0x9e3779b9;  /* the golden ratio; an arbitrary value */
-  c = initval;         /* the previous hash value */
-
-  /*---------------------------------------- handle most of the key */
-  while (len >= 12) {
-    a += (k[0] +((uint32_t)k[1]<<8) +((uint32_t)k[2]<<16) +((uint32_t)k[3]<<24));
-    b += (k[4] +((uint32_t)k[5]<<8) +((uint32_t)k[6]<<16) +((uint32_t)k[7]<<24));
-    c += (k[8] +((uint32_t)k[9]<<8) +((uint32_t)k[10]<<16)+((uint32_t)k[11]<<24));
-    burtlemix(a,b,c);
-    k += 12; len -= 12;
-  }
-
-  /*------------------------------------- handle the last 11 bytes */
-  c += length;
-  switch(len) {             /* all the case statements fall through */
-  case 11: c+=((uint32_t)k[10]<<24);
-    /* fall-through */
-  case 10: c+=((uint32_t)k[9]<<16);
-    /* fall-through */
-  case 9 : c+=((uint32_t)k[8]<<8);
-    /* the first byte of c is reserved for the length */
-    /* fall-through */
-  case 8 : b+=((uint32_t)k[7]<<24);
-    /* fall-through */
-  case 7 : b+=((uint32_t)k[6]<<16);
-    /* fall-through */
-  case 6 : b+=((uint32_t)k[5]<<8);
-    /* fall-through */
-  case 5 : b+=k[4];
-    /* fall-through */
-  case 4 : a+=((uint32_t)k[3]<<24);
-    /* fall-through */
-  case 3 : a+=((uint32_t)k[2]<<16);
-    /* fall-through */
-  case 2 : a+=((uint32_t)k[1]<<8);
-    /* fall-through */
-  case 1 : a+=k[0];
-    /* case 0: nothing left to add */
-  }
-  burtlemix(a,b,c);
-  /*-------------------------------------------- report the result */
-  return c;
-}
-
-uint32_t burtleCI(const unsigned char* k, uint32_t length, uint32_t initval)
-{
-  uint32_t a,b,c,len;
-
-   /* Set up the internal state */
-  len = length;
-  a = b = 0x9e3779b9;  /* the golden ratio; an arbitrary value */
-  c = initval;         /* the previous hash value */
-
-  /*---------------------------------------- handle most of the key */
-  while (len >= 12) {
-    a += (dns_tolower(k[0]) +((uint32_t)dns_tolower(k[1])<<8) +((uint32_t)dns_tolower(k[2])<<16) +((uint32_t)dns_tolower(k[3])<<24));
-    b += (dns_tolower(k[4]) +((uint32_t)dns_tolower(k[5])<<8) +((uint32_t)dns_tolower(k[6])<<16) +((uint32_t)dns_tolower(k[7])<<24));
-    c += (dns_tolower(k[8]) +((uint32_t)dns_tolower(k[9])<<8) +((uint32_t)dns_tolower(k[10])<<16)+((uint32_t)dns_tolower(k[11])<<24));
-    burtlemix(a,b,c);
-    k += 12; len -= 12;
-  }
-
-  /*------------------------------------- handle the last 11 bytes */
-  c += length;
-  switch(len) {             /* all the case statements fall through */
-  case 11: c+=((uint32_t)dns_tolower(k[10])<<24);
-    /* fall-through */
-  case 10: c+=((uint32_t)dns_tolower(k[9])<<16);
-    /* fall-through */
-  case 9 : c+=((uint32_t)dns_tolower(k[8])<<8);
-    /* the first byte of c is reserved for the length */
-    /* fall-through */
-  case 8 : b+=((uint32_t)dns_tolower(k[7])<<24);
-    /* fall-through */
-  case 7 : b+=((uint32_t)dns_tolower(k[6])<<16);
-    /* fall-through */
-  case 6 : b+=((uint32_t)dns_tolower(k[5])<<8);
-    /* fall-through */
-  case 5 : b+=dns_tolower(k[4]);
-    /* fall-through */
-  case 4 : a+=((uint32_t)dns_tolower(k[3])<<24);
-    /* fall-through */
-  case 3 : a+=((uint32_t)dns_tolower(k[2])<<16);
-    /* fall-through */
-  case 2 : a+=((uint32_t)dns_tolower(k[1])<<8);
-    /* fall-through */
-  case 1 : a+=dns_tolower(k[0]);
-    /* case 0: nothing left to add */
-  }
-  burtlemix(a,b,c);
-  /*-------------------------------------------- report the result */
-  return c;
-}
-
-
 bool setSocketTimestamps(int fd)
 {
 #ifdef SO_TIMESTAMP
@@ -1107,7 +1028,7 @@ bool isNonBlocking(int sock)
   return flags & O_NONBLOCK;
 }
 
-bool setReceiveSocketErrors(int sock, int af)
+bool setReceiveSocketErrors([[maybe_unused]] int sock, [[maybe_unused]] int af)
 {
 #ifdef __linux__
   int tmp = 1, ret;
@@ -1124,13 +1045,16 @@ bool setReceiveSocketErrors(int sock, int af)
 }
 
 // Closes a socket.
-int closesocket( int socket )
+int closesocket(int socket)
 {
-  int ret=::close(socket);
-  if(ret < 0 && errno == ECONNRESET) // see ticket 192, odd BSD behaviour
+  int ret = ::close(socket);
+  if(ret < 0 && errno == ECONNRESET) // see ticket 192, odd BSD behaviour
     return 0;
-  if(ret < 0)
-    throw PDNSException("Error closing socket: "+stringerror());
+  }
+  if (ret < 0) {
+    int err = errno;
+    throw PDNSException("Error closing socket: " + stringerror(err));
+  }
   return ret;
 }
 
@@ -1142,33 +1066,119 @@ bool setCloseOnExec(int sock)
   return true;
 }
 
-int getMACAddress(const ComboAddress& ca, char* dest, size_t len)
-{
 #ifdef __linux__
-  ifstream ifs("/proc/net/arp");
-  if (len < 6) {
-    return EINVAL;
+#include <linux/rtnetlink.h>
+
+int getMACAddress(const ComboAddress& ca, char* dest, size_t destLen)
+{
+  struct {
+    struct nlmsghdr headermsg;
+    struct ndmsg neighbormsg;
+  } request;
+
+  std::array<char, 8192> buffer;
+
+  auto sock = FDWrapper(socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE));
+  if (sock.getHandle() == -1) {
+    return errno;
   }
-  if (!ifs) {
-    return EIO;
+
+  memset(&request, 0, sizeof(request));
+  request.headermsg.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg));
+  request.headermsg.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+  request.headermsg.nlmsg_type = RTM_GETNEIGH;
+  request.neighbormsg.ndm_family = ca.sin4.sin_family;
+
+  while (true) {
+    ssize_t sent = send(sock.getHandle(), &request, sizeof(request), 0);
+    if (sent == -1) {
+      if (errno == EINTR) {
+        continue;
+      }
+      return errno;
+    }
+    else if (static_cast<size_t>(sent) != sizeof(request)) {
+      return EIO;
+    }
+    break;
   }
-  string line;
-  string match = ca.toString() + ' ';
-  while(getline(ifs, line)) {
-    if(boost::starts_with(line, match)) {
-      vector<string> parts;
-      stringtok(parts, line, " \n\t\r");
-      if (parts.size() < 4)
-        return ENOENT;
-      if (sscanf(parts[3].c_str(), "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", dest, dest+1, dest+2, dest+3, dest+4, dest+5) != 6) {
-        return ENOENT;
+
+  bool done = false;
+  bool foundIP = false;
+  bool foundMAC = false;
+  do {
+    ssize_t got = recv(sock.getHandle(), buffer.data(), buffer.size(), 0);
+
+    if (got < 0) {
+      if (errno == EINTR) {
+        continue;
+      }
+      return errno;
+    }
+
+    size_t remaining = static_cast<size_t>(got);
+    for (struct nlmsghdr* nlmsgheader = reinterpret_cast<struct nlmsghdr*>(buffer.data());
+         done == false && NLMSG_OK (nlmsgheader, remaining);
+         nlmsgheader = reinterpret_cast<struct nlmsghdr*>(NLMSG_NEXT(nlmsgheader, remaining))) {
+
+      if (nlmsgheader->nlmsg_type == NLMSG_DONE) {
+        done = true;
+        break;
+      }
+
+      auto nd = reinterpret_cast<struct ndmsg*>(NLMSG_DATA(nlmsgheader));
+      auto rtatp = reinterpret_cast<struct rtattr*>(reinterpret_cast<char*>(nd) + NLMSG_ALIGN(sizeof(struct ndmsg)));
+      int rtattrlen = nlmsgheader->nlmsg_len - NLMSG_LENGTH(sizeof(struct ndmsg));
+
+      if (nd->ndm_family != ca.sin4.sin_family) {
+        continue;
+      }
+
+      if (ca.sin4.sin_family == AF_INET6 && ca.sin6.sin6_scope_id != 0 && static_cast<int32_t>(ca.sin6.sin6_scope_id) != nd->ndm_ifindex) {
+        continue;
+      }
+
+      for (; done == false && RTA_OK(rtatp, rtattrlen); rtatp = RTA_NEXT(rtatp, rtattrlen)) {
+        if (rtatp->rta_type == NDA_DST){
+          if (nd->ndm_family == AF_INET) {
+            auto inp = reinterpret_cast<struct in_addr*>(RTA_DATA(rtatp));
+            if (inp->s_addr == ca.sin4.sin_addr.s_addr) {
+              foundIP = true;
+            }
+          }
+          else if (nd->ndm_family == AF_INET6) {
+            auto inp = reinterpret_cast<struct in6_addr *>(RTA_DATA(rtatp));
+            if (memcmp(inp->s6_addr, ca.sin6.sin6_addr.s6_addr, sizeof(ca.sin6.sin6_addr.s6_addr)) == 0) {
+              foundIP = true;
+            }
+          }
+        }
+        else if (rtatp->rta_type == NDA_LLADDR) {
+          if (foundIP) {
+            size_t addrLen = rtatp->rta_len - sizeof(struct rtattr);
+            if (addrLen > destLen) {
+              return ENOBUFS;
+            }
+            memcpy(dest, reinterpret_cast<const char*>(rtatp) + sizeof(struct rtattr), addrLen);
+            foundMAC = true;
+            done = true;
+            break;
+          }
+        }
       }
-      return 0;
     }
   }
-#endif
+  while (done == false);
+
+  return foundMAC ? 0 : ENOENT;
+}
+#else
+int getMACAddress(const ComboAddress& /* ca */, char* /* dest */, size_t /* len */)
+{
   return ENOENT;
 }
+#endif /* __linux__ */
+
 string getMACAddress(const ComboAddress& ca)
 {
   string ret;
@@ -1179,7 +1189,7 @@ string getMACAddress(const ComboAddress& ca)
   return ret;
 }
 
-uint64_t udpErrorStats(const std::string& str)
+uint64_t udpErrorStats([[maybe_unused]] const std::string& str)
 {
 #ifdef __linux__
   ifstream ifs("/proc/net/snmp");
@@ -1221,7 +1231,7 @@ uint64_t udpErrorStats(const std::string& str)
   return 0;
 }
 
-uint64_t udp6ErrorStats(const std::string& str)
+uint64_t udp6ErrorStats([[maybe_unused]] const std::string& str)
 {
 #ifdef __linux__
   const std::map<std::string, std::string> keys = {
@@ -1261,7 +1271,7 @@ uint64_t udp6ErrorStats(const std::string& str)
   return 0;
 }
 
-uint64_t tcpErrorStats(const std::string& str)
+uint64_t tcpErrorStats(const std::string& /* str */)
 {
 #ifdef __linux__
   ifstream ifs("/proc/net/netstat");
@@ -1286,7 +1296,7 @@ uint64_t tcpErrorStats(const std::string& str)
   return 0;
 }
 
-uint64_t getCPUIOWait(const std::string& str)
+uint64_t getCPUIOWait(const std::string& /* str */)
 {
 #ifdef __linux__
   ifstream ifs("/proc/stat");
@@ -1311,7 +1321,7 @@ uint64_t getCPUIOWait(const std::string& str)
   return 0;
 }
 
-uint64_t getCPUSteal(const std::string& str)
+uint64_t getCPUSteal(const std::string& /* str */)
 {
 #ifdef __linux__
   ifstream ifs("/proc/stat");
@@ -1375,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
 }
 
@@ -1460,14 +1469,14 @@ uint64_t getCPUTimeSystem(const std::string&)
 
 double DiffTime(const struct timespec& first, const struct timespec& second)
 {
-  int seconds=second.tv_sec - first.tv_sec;
-  int nseconds=second.tv_nsec - first.tv_nsec;
+  auto seconds = second.tv_sec - first.tv_sec;
+  auto nseconds = second.tv_nsec - first.tv_nsec;
 
-  if(nseconds < 0) {
-    seconds-=1;
-    nseconds+=1000000000;
+  if (nseconds < 0) {
+    seconds -= 1;
+    nseconds += 1000000000;
   }
-  return seconds + nseconds/1000000000.0;
+  return static_cast<double>(seconds) + static_cast<double>(nseconds) / 1000000000.0;
 }
 
 double DiffTime(const struct timeval& first, const struct timeval& second)
@@ -1549,7 +1558,7 @@ bool isSettingThreadCPUAffinitySupported()
 #endif
 }
 
-int mapThreadToCPUList(pthread_t tid, const std::set<int>& cpus)
+int mapThreadToCPUList([[maybe_unused]] pthread_t tid, [[maybe_unused]] const std::set<int>& cpus)
 {
 #ifdef HAVE_PTHREAD_SETAFFINITY_NP
 #  ifdef __NetBSD__
@@ -1603,7 +1612,7 @@ std::vector<ComboAddress> getResolvers(const std::string& resolvConfPath)
     if (boost::starts_with(line, "nameserver ") || boost::starts_with(line, "nameserver\t")) {
       vector<string> parts;
       stringtok(parts, line, " \t,"); // be REALLY nice
-      for(vector<string>::const_iterator iter = parts.begin() + 1; iter != parts.end(); ++iter) {
+      for (auto iter = parts.begin() + 1; iter != parts.end(); ++iter) {
         try {
           results.emplace_back(*iter, 53);
         }
@@ -1617,7 +1626,7 @@ std::vector<ComboAddress> getResolvers(const std::string& resolvConfPath)
   return results;
 }
 
-size_t getPipeBufferSize(int fd)
+size_t getPipeBufferSize([[maybe_unused]] int fd)
 {
 #ifdef F_GETPIPE_SZ
   int res = fcntl(fd, F_GETPIPE_SZ);
@@ -1631,7 +1640,7 @@ size_t getPipeBufferSize(int fd)
 #endif /* F_GETPIPE_SZ */
 }
 
-bool setPipeBufferSize(int fd, size_t size)
+bool setPipeBufferSize([[maybe_unused]] int fd, [[maybe_unused]] size_t size)
 {
 #ifdef F_SETPIPE_SZ
   if (size > static_cast<size_t>(std::numeric_limits<int>::max())) {
@@ -1677,38 +1686,6 @@ DNSName reverseNameFromIP(const ComboAddress& ip)
   throw std::runtime_error("Calling reverseNameFromIP() for an address which is neither an IPv4 nor an IPv6");
 }
 
-static size_t getMaxHostNameSize()
-{
-#if defined(HOST_NAME_MAX)
-  return HOST_NAME_MAX;
-#endif
-
-#if defined(_SC_HOST_NAME_MAX)
-  auto tmp = sysconf(_SC_HOST_NAME_MAX);
-  if (tmp != -1) {
-    return tmp;
-  }
-#endif
-
-  /* _POSIX_HOST_NAME_MAX */
-  return 255;
-}
-
-std::string getCarbonHostName()
-{
-  std::string hostname;
-  hostname.resize(getMaxHostNameSize() + 1, 0);
-
-  if (gethostname(const_cast<char*>(hostname.c_str()), hostname.size()) != 0) {
-    throw std::runtime_error(stringerror());
-  }
-
-  boost::replace_all(hostname, ".", "_");
-  hostname.resize(strlen(hostname.c_str()));
-
-  return hostname;
-}
-
 std::string makeLuaString(const std::string& in)
 {
   ostringstream str;
@@ -1771,3 +1748,26 @@ bool constantTimeStringEquals(const std::string& a, const std::string& b)
 #endif /* !HAVE_SODIUM_MEMCMP */
 #endif /* !HAVE_CRYPTO_MEMCMP */
 }
+
+namespace pdns
+{
+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, decltype(&closedir)>(opendir(directory.c_str()), closedir);
+  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;
+}
+}
index 4d27de70179662c112fa0eed9b19101d0b43c964..209de32e060d23b9cdf91ca684be95f6feee9757 100644 (file)
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #pragma once
-#include <inttypes.h>
+#include <cinttypes>
 #include <cstring>
 #include <cstdio>
 #include <regex.h>
-#include <limits.h>
+#include <climits>
 #include <type_traits>
 
 #include <boost/algorithm/string.hpp>
 #include <sys/time.h>
 #include <sys/types.h>
 #include <sys/socket.h>
-#include <time.h>
+#include <ctime>
 #include <syslog.h>
 #include <stdexcept>
 #include <string>
-#include <ctype.h>
+#include <cctype>
 #include <vector>
 
 #include "namespaces.hh"
-#include "dnsname.hh"
 
-typedef enum { TSIG_MD5, TSIG_SHA1, TSIG_SHA224, TSIG_SHA256, TSIG_SHA384, TSIG_SHA512, TSIG_GSS } TSIGHashEnum;
+class DNSName;
 
+// Do not change to "using TSIGHashEnum ..." until you know CodeQL does not choke on it
+typedef enum { TSIG_MD5, TSIG_SHA1, TSIG_SHA224, TSIG_SHA256, TSIG_SHA384, TSIG_SHA512, TSIG_GSS } TSIGHashEnum;
 namespace pdns
 {
 /**
@@ -60,35 +61,45 @@ namespace pdns
  * \return The `std::string` error message.
  */
 auto getMessageFromErrno(int errnum) -> std::string;
+
+#if defined(HAVE_LIBCRYPTO)
+namespace OpenSSL
+{
+  /**
+   * \brief Throws a `std::runtime_error` with the current OpenSSL error.
+   *
+   * \param[in] errorMessage The message to attach in addition to the OpenSSL error.
+   */
+  [[nodiscard]] auto error(const std::string& errorMessage) -> std::runtime_error;
+
+  /**
+   * \brief Throws a `std::runtime_error` with a name and the current OpenSSL error.
+   *
+   * \param[in] componentName The name of the component to mark the error message with.
+   * \param[in] errorMessage The message to attach in addition to the OpenSSL error.
+   */
+  [[nodiscard]] auto error(const std::string& componentName, const std::string& errorMessage) -> std::runtime_error;
+}
+#endif // HAVE_LIBCRYPTO
 }
 
 string nowTime();
-const string unquotify(const string &item);
+string unquotify(const string &item);
 string humanDuration(time_t passed);
 bool stripDomainSuffix(string *qname, const string &domain);
 void stripLine(string &line);
-string getHostname();
+std::optional<string> getHostname();
+std::string getCarbonHostName();
 string urlEncode(const string &text);
-int waitForData(int fd, int seconds, int useconds=0);
+int waitForData(int fileDesc, int seconds, int useconds = 0);
 int waitFor2Data(int fd1, int fd2, int seconds, int useconds, int* fd);
 int waitForMultiData(const set<int>& fds, const int seconds, const int useconds, int* fd);
-int waitForRWData(int fd, bool waitForRead, int seconds, int useconds, bool* error=nullptr, bool* disconnected=nullptr);
-uint16_t getShort(const unsigned char *p);
-uint16_t getShort(const char *p);
-uint32_t getLong(const unsigned char *p);
-uint32_t getLong(const char *p);
+int waitForRWData(int fileDesc, bool waitForRead, int seconds, int useconds, bool* error = nullptr, bool* disconnected = nullptr);
 bool getTSIGHashEnum(const DNSName& algoName, TSIGHashEnum& algoEnum);
 DNSName getTSIGAlgoName(TSIGHashEnum& algoEnum);
 
 int logFacilityToLOG(unsigned int facility);
 
-struct ServiceTuple
-{
-  string host;
-  uint16_t port;
-};
-void parseService(const string &descr, ServiceTuple &st);
-
 template<typename Container>
 void
 stringtok (Container &container, string const &in,
@@ -120,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}
@@ -166,10 +176,12 @@ const string toLower(const string &upper);
 const string toLowerCanonic(const string &upper);
 bool IpToU32(const string &str, uint32_t *ip);
 string U32ToIP(uint32_t);
-string stringerror(int);
-string stringerror();
-string itoa(int i);
-string uitoa(unsigned int i);
+
+inline string stringerror(int err = errno)
+{
+  return pdns::getMessageFromErrno(err);
+}
+
 string bitFlip(const string &str);
 
 void dropPrivs(int uid, int gid);
@@ -317,8 +329,9 @@ string makeHexDump(const string& str);
 string makeBytesFromHex(const string &in);
 
 void normalizeTV(struct timeval& tv);
-const struct timeval operator+(const struct timeval& lhs, const struct timeval& rhs);
-const struct timeval operator-(const struct timeval& lhs, const struct timeval& rhs);
+struct timeval operator+(const struct timeval& lhs, const struct timeval& rhs);
+struct timeval operator-(const struct timeval& lhs, const struct timeval& rhs);
+
 inline float makeFloat(const struct timeval& tv)
 {
   return tv.tv_sec + tv.tv_usec/1000000.0f;
@@ -400,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();
    }
 };
 
@@ -557,7 +574,6 @@ void addCMsgSrcAddr(struct msghdr* msgh, cmsgbuf_aligned* cbuf, const ComboAddre
 unsigned int getFilenumLimit(bool hardOrSoft=0);
 void setFilenumLimit(unsigned int lim);
 bool readFileIfThere(const char* fname, std::string* line);
-uint32_t burtle(const unsigned char* k, uint32_t length, uint32_t init);
 bool setSocketTimestamps(int fd);
 
 //! Sets the socket into blocking mode.
@@ -569,7 +585,7 @@ bool setTCPNoDelay(int sock);
 bool setReuseAddr(int sock);
 bool isNonBlocking(int sock);
 bool setReceiveSocketErrors(int sock, int af);
-int closesocket(int fd);
+int closesocket(int socket);
 bool setCloseOnExec(int sock);
 
 size_t getPipeBufferSize(int fd);
@@ -603,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) {
@@ -613,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);
@@ -656,8 +672,20 @@ auto checked_conv(F from) -> T
   static_assert((std::numeric_limits<F>::is_signed && std::numeric_limits<T>::is_signed) || (!std::numeric_limits<F>::is_signed && !std::numeric_limits<T>::is_signed),
                 "checked_conv: The `T` and `F` types must either both be signed or unsigned");
 
-  if (from < std::numeric_limits<T>::min() || from > std::numeric_limits<T>::max()) {
-    throw std::out_of_range("checked_conv: conversion from value that is out of range for target type");
+  constexpr auto tMin = std::numeric_limits<T>::min();
+  if constexpr (std::numeric_limits<F>::min() != tMin) {
+    if (from < tMin) {
+      string s = "checked_conv: source value " + std::to_string(from) + " is smaller than target's minimum possible value " + std::to_string(tMin);
+      throw std::out_of_range(s);
+    }
+  }
+
+  constexpr auto tMax = std::numeric_limits<T>::max();
+  if constexpr (std::numeric_limits<F>::max() != tMax) {
+    if (from > tMax) {
+      string s = "checked_conv: source value " + std::to_string(from) + " is larger than target's maximum possible value " + std::to_string(tMax);
+      throw std::out_of_range(s);
+    }
   }
 
   return static_cast<T>(from);
@@ -683,7 +711,7 @@ auto checked_conv(F from) -> T
  * \param[in] str The input string to be converted.
  *
  * \param[in] idx Location to store the index at which processing
- * stopped.
+ * stopped. If the input `str` is empty, `*idx` shall be set to 0.
  *
  * \param[in] base The numerical base for conversion.
  *
@@ -695,6 +723,10 @@ auto checked_stoi(const std::string& str, size_t* idx = nullptr, int base = 10)
   static_assert(std::numeric_limits<T>::is_integer, "checked_stoi: The `T` type must be an integer");
 
   if (str.empty()) {
+    if (idx != nullptr) {
+      *idx = 0;
+    }
+
     return 0; // compatibility
   }
 
@@ -720,7 +752,7 @@ auto checked_stoi(const std::string& str, size_t* idx = nullptr, int base = 10)
  * \param[in] str The input string to be converted.
  *
  * \param[in] idx Location to store the index at which processing
- * stopped.
+ * stopped. If the input `str` is empty, `*idx` shall be set to 0.
  *
  * \param[in] base The numerical base for conversion.
  *
@@ -740,7 +772,6 @@ std::vector<ComboAddress> getResolvers(const std::string& resolvConfPath);
 
 DNSName reverseNameFromIP(const ComboAddress& ip);
 
-std::string getCarbonHostName();
 size_t parseRFC1035CharString(const std::string &in, std::string &val); // from ragel
 size_t parseSVCBValueListFromParsedRFC1035CharString(const std::string &in, vector<std::string> &val); // from ragel
 size_t parseSVCBValueList(const std::string &in, vector<std::string> &val);
@@ -754,28 +785,23 @@ struct NodeOrLocatorID { uint8_t content[8]; };
 
 struct FDWrapper
 {
-  FDWrapper()
-  {
-  }
+  FDWrapper() = default;
+  FDWrapper(int desc): d_fd(desc) {}
+  FDWrapper(const FDWrapper&) = delete;
+  FDWrapper& operator=(const FDWrapper& rhs) = delete;
 
-  FDWrapper(int desc): d_fd(desc)
-  {
-  }
 
   ~FDWrapper()
   {
-    if (d_fd != -1) {
-      close(d_fd);
-      d_fd = -1;
-    }
+    reset();
   }
 
-  FDWrapper(FDWrapper&& rhs): d_fd(rhs.d_fd)
+  FDWrapper(FDWrapper&& rhs) noexcept : d_fd(rhs.d_fd)
   {
     rhs.d_fd = -1;
   }
 
-  FDWrapper& operator=(FDWrapper&& rhs)
+  FDWrapper& operator=(FDWrapper&& rhs) noexcept
   {
     if (d_fd != -1) {
       close(d_fd);
@@ -785,7 +811,7 @@ struct FDWrapper
     return *this;
   }
 
-  int getHandle() const
+  [[nodiscard]] int getHandle() const
   {
     return d_fd;
   }
@@ -795,6 +821,19 @@ 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);
+}
diff --git a/pdns/mkpubsuffixcc b/pdns/mkpubsuffixcc
deleted file mode 100755 (executable)
index a65e97a..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-
-(echo "const char* g_pubsuffix[]={"; 
-       for a in $(grep -v "//" effective_tld_names.dat  | grep \\. | egrep "^[.0-9a-z-]*$")
-       do 
-               echo \"$a\",
-       done 
-echo "0};") > pubsuffix.cc
index 9ff4ab18c1693f7a8680ac6fa05c36fbc2cfa8d8..0fab1075883f23b0bdd86d085eb05915482921a3 100644 (file)
@@ -73,13 +73,17 @@ public:
   FDMultiplexer() :
     d_inrun(false)
   {}
-  virtual ~FDMultiplexer()
-  {}
+  virtual ~FDMultiplexer() = default;
 
-  static FDMultiplexer* getMultiplexerSilent();
+  // The maximum number of events processed in a single run, not the maximum of watched descriptors
+  static constexpr unsigned int s_maxevents = 1024;
+  /* The maximum number of events processed in a single run will be capped to the
+     minimum value of maxEventsHint and s_maxevents, to reduce memory usage. */
+  static FDMultiplexer* getMultiplexerSilent(unsigned int maxEventsHint = s_maxevents);
 
   /* tv will be updated to 'now' before run returns */
-  /* timeout is in ms */
+  /* timeout is in ms, 0 will return immediately, -1 will block until at
+     least one descriptor is ready */
   /* returns 0 on timeout, -1 in case of error (but all implementations
      actually throw in that case) and the number of ready events otherwise.
      Note that We might have two events (read AND write) for the same descriptor */
@@ -101,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!
@@ -117,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!
@@ -180,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)
@@ -206,7 +210,7 @@ public:
     return ret;
   }
 
-  typedef FDMultiplexer* getMultiplexer_t();
+  typedef FDMultiplexer* getMultiplexer_t(unsigned int);
   typedef std::multimap<int, getMultiplexer_t*> FDMultiplexermap_t;
 
   static FDMultiplexermap_t& getMultiplexerMap()
@@ -277,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 c02e277..0000000
+++ /dev/null
@@ -1,414 +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;
-}
-
-//! 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=std::make_shared<pdns_ucontext_t>();
-  
-  uc->uc_link = &d_kernel; // come back to kernel after dying
-  uc->uc_stack.resize (d_stacksize+1);
-#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 */
-
-  ++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()) {
-    d_threads.erase(d_zombiesQueue.front());
-    --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 62bf590..0000000
+++ /dev/null
@@ -1,145 +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 <stdint.h>
-#include <queue>
-#include <vector>
-#include <map>
-#include <time.h>
-#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"
-#include <memory>
-#include <boost/function.hpp>
-using namespace ::boost::multi_index;
-
-// #define MTASKERTIMING 1
-
-struct KeyTag {};
-
-//! 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;
-       boost::function<void(void)> start;
-       char* startOfStack;
-       char* highestStackSeen;
-#ifdef MTASKERTIMING
-       CPUTime dt;
-       unsigned int totTime;
-#endif
-  };
-
-  typedef std::map<int, ThreadInfo> mthreads_t;
-  mthreads_t d_threads;
-  size_t d_stacksize;
-  size_t d_threadsCount;
-  int d_tid;
-  int d_maxtid;
-
-  EventVal d_waitval;
-  enum waitstatusenum {Error=-1,TimeOut=0,Answer} d_waitstatus;
-
-public:
-  struct Waiter
-  {
-    EventKey key;
-    std::shared_ptr<pdns_ucontext_t> context;
-    struct timeval ttd;
-    int tid;
-  };
-
-  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);
-#endif
-#if 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());
-#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) : d_stacksize(stacksize), d_threadsCount(0), d_tid(0), d_maxtid(0), 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:
-  EventKey d_eventkey;   // for waitEvent, contains exact key it was awoken for
-};
-#include "mtasker.cc"
diff --git a/pdns/mtasker_context.hh b/pdns/mtasker_context.hh
deleted file mode 100644 (file)
index 3c1a95b..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.
- */
-#pragma once
-#include "lazy_allocator.hh"
-#include <boost/function.hpp>
-#include <vector>
-#include <exception>
-
-struct pdns_ucontext_t {
-    pdns_ucontext_t ();
-    pdns_ucontext_t (pdns_ucontext_t const&) = delete;
-    pdns_ucontext_t& operator= (pdns_ucontext_t const&) = delete;
-    ~pdns_ucontext_t ();
-
-    void* uc_mcontext;
-    pdns_ucontext_t* uc_link;
-    std::vector<char, lazy_allocator<char>> uc_stack;
-    std::exception_ptr exception;
-#ifdef PDNS_USE_VALGRIND
-    int valgrind_id;
-#endif /* PDNS_USE_VALGRIND */
-};
-
-void
-pdns_swapcontext
-(pdns_ucontext_t& __restrict octx, pdns_ucontext_t const& __restrict ctx);
-
-void
-pdns_makecontext
-(pdns_ucontext_t& ctx, boost::function<void(void)>& start);
-
-#ifdef HAVE_FIBER_SANITIZER
-#include <sanitizer/common_interface_defs.h>
-#endif /* HAVE_FIBER_SANITIZER */
-
-#ifdef HAVE_FIBER_SANITIZER
-extern __thread void* t_mainStack;
-extern __thread size_t t_mainStackSize;
-#endif /* HAVE_FIBER_SANITIZER */
-
-static inline void notifyStackSwitch(void* startOfStack, size_t stackSize)
-{
-#ifdef HAVE_FIBER_SANITIZER
-  __sanitizer_start_switch_fiber(nullptr, startOfStack, stackSize);
-#endif /* HAVE_FIBER_SANITIZER */
-}
-
-static inline void notifyStackSwitchToKernel()
-{
-#ifdef HAVE_FIBER_SANITIZER
-  notifyStackSwitch(t_mainStack, t_mainStackSize);
-#endif /* HAVE_FIBER_SANITIZER */
-}
-
-static inline void notifyStackSwitchDone()
-{
-#ifdef HAVE_FIBER_SANITIZER
-#ifdef HAVE_SANITIZER_FINISH_SWITCH_FIBER_SINGLE_PTR
-  __sanitizer_finish_switch_fiber(nullptr);
-#else /* HAVE_SANITIZER_FINISH_SWITCH_FIBER_SINGLE_PTR */
-#ifdef HAVE_SANITIZER_FINISH_SWITCH_FIBER_THREE_PTRS
-  __sanitizer_finish_switch_fiber(nullptr, nullptr, nullptr);
-#endif /* HAVE_SANITIZER_FINISH_SWITCH_FIBER_THREE_PTRS */
-#endif /* HAVE_SANITIZER_FINISH_SWITCH_FIBER_SINGLE_PTR */
-#endif /* HAVE_FIBER_SANITIZER */
-}
diff --git a/pdns/mtasker_fcontext.cc b/pdns/mtasker_fcontext.cc
deleted file mode 100644 (file)
index 72627a7..0000000
+++ /dev/null
@@ -1,238 +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 "mtasker_context.hh"
-#include <exception>
-#include <cassert>
-#include <type_traits>
-#include <boost/version.hpp>
-#if BOOST_VERSION < 106100
-#include <boost/context/fcontext.hpp>
-using boost::context::make_fcontext;
-#else
-#include <boost/context/detail/fcontext.hpp>
-using boost::context::detail::make_fcontext;
-#endif /* BOOST_VERSION < 106100 */
-
-#ifdef PDNS_USE_VALGRIND
-#include <valgrind/valgrind.h>
-#endif /* PDNS_USE_VALGRIND */
-
-#ifdef HAVE_FIBER_SANITIZER
-__thread void* t_mainStack{nullptr};
-__thread size_t t_mainStackSize{0};
-#endif /* HAVE_FIBER_SANITIZER */
-
-#if BOOST_VERSION < 105600
-/* Note: This typedef means functions taking fcontext_t*, like jump_fcontext(),
- * now require a reinterpret_cast rather than a static_cast, since we're
- * casting from pdns_context_t->uc_mcontext, which is void**, to
- * some_opaque_struct**. In later versions fcontext_t is already void*. So if
- * you remove this, then fix the ugly.
- */
-using fcontext_t = boost::context::fcontext_t*;
-
-/* Emulate the >= 1.56 API for Boost 1.52 through 1.55 */
-static inline intptr_t
-jump_fcontext (fcontext_t* const ofc, fcontext_t const nfc, 
-               intptr_t const arg) {
-    /* If the fcontext_t is preallocated then use it, otherwise allocate one
-     * on the stack ('self') and stash a pointer away in *ofc so the returning
-     * MThread can access it. This is safe because we're suspended, so the
-     * context object always outlives the jump.
-     */
-    if (*ofc) {
-        return boost::context::jump_fcontext (*ofc, nfc, arg);
-    } else {
-        boost::context::fcontext_t self;
-        *ofc = &self;
-        auto ret = boost::context::jump_fcontext (*ofc, nfc, arg);
-        *ofc = nullptr;
-        return ret;
-    }
-}
-#else
-
-#if BOOST_VERSION < 106100
-using boost::context::fcontext_t;
-using boost::context::jump_fcontext;
-#else
-using boost::context::detail::fcontext_t;
-using boost::context::detail::jump_fcontext;
-using boost::context::detail::transfer_t;
-#endif /* BOOST_VERSION < 106100 */
-
-static_assert (std::is_pointer<fcontext_t>::value,
-               "Boost Context has changed the fcontext_t type again :-(");
-#endif
-
-/* Boost context only provides a means of passing a single argument across a
- * jump. args_t simply provides a way to pass more by reference.
- */
-struct args_t {
-#if BOOST_VERSION < 106100
-    fcontext_t prev_ctx = nullptr;
-#endif
-    pdns_ucontext_t* self = nullptr;
-    boost::function<void(void)>* work = nullptr;
-};
-
-extern "C" {
-static
-void
-#if BOOST_VERSION < 106100
-threadWrapper (intptr_t const xargs) {
-#else
-threadWrapper (transfer_t const t) {
-#endif
-    /* Access the args passed from pdns_makecontext, and copy them directly from
-     * the calling stack on to ours (we're now using the MThreads stack).
-     * This saves heap allocating an args object, at the cost of an extra
-     * context switch to fashion this constructor-like init phase. The work
-     * function object is still only moved after we're (re)started, so may
-     * still be set or changed after a call to pdns_makecontext. This matches
-     * the behaviour of the System V implementation, which can inherently only
-     * be passed ints and pointers.
-     */
-    notifyStackSwitchDone();
-#if BOOST_VERSION < 106100
-    auto args = reinterpret_cast<args_t*>(xargs);
-#else
-    auto args = reinterpret_cast<args_t*>(t.data);
-#endif
-    auto ctx = args->self;
-    auto work = args->work;
-    /* we switch back to pdns_makecontext() */
-    notifyStackSwitchToKernel();
-#if BOOST_VERSION < 106100
-    jump_fcontext (reinterpret_cast<fcontext_t*>(&ctx->uc_mcontext),
-                   static_cast<fcontext_t>(args->prev_ctx), 0);
-#else
-    transfer_t res = jump_fcontext (t.fctx, 0);
-    /* we got switched back from pdns_swapcontext() */
-    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
-         switch back to it later. */
-      fcontext_t* ptr = static_cast<fcontext_t*>(res.data);
-      *ptr = res.fctx;
-    }
-#endif
-    notifyStackSwitchDone();
-    args = nullptr;
-
-    try {
-        auto start = std::move (*work);
-        start();
-    } catch (...) {
-        ctx->exception = std::current_exception();
-    }
-
-    notifyStackSwitchToKernel();
-    /* Emulate the System V uc_link feature. */
-    auto const next_ctx = ctx->uc_link->uc_mcontext;
-#if BOOST_VERSION < 106100
-    jump_fcontext (reinterpret_cast<fcontext_t*>(&ctx->uc_mcontext),
-                   static_cast<fcontext_t>(next_ctx),
-                   reinterpret_cast<intptr_t>(ctx));
-#else
-    jump_fcontext (static_cast<fcontext_t>(next_ctx), 0);
-#endif
-
-#ifdef NDEBUG
-    __builtin_unreachable();
-#endif
-}
-}
-
-pdns_ucontext_t::pdns_ucontext_t
-(): uc_mcontext(nullptr), uc_link(nullptr) {
-#ifdef PDNS_USE_VALGRIND
-  valgrind_id = 0;
-#endif /* PDNS_USE_VALGRIND */
-}
-
-pdns_ucontext_t::~pdns_ucontext_t
-() {
-    /* There's nothing to delete here since fcontext doesn't require anything
-     * to be heap allocated.
-     */
-#ifdef PDNS_USE_VALGRIND
-  if (valgrind_id != 0) {
-    VALGRIND_STACK_DEREGISTER(valgrind_id);
-  }
-#endif /* PDNS_USE_VALGRIND */
-}
-
-void
-pdns_swapcontext
-(pdns_ucontext_t& __restrict octx, pdns_ucontext_t const& __restrict ctx) {
-  /* we either switch back to threadwrapper() if it's the first time,
-     or we switch back to pdns_swapcontext(),
-     in both case we will be returning from a call to jump_fcontext(). */
-#if BOOST_VERSION < 106100
-    intptr_t ptr = jump_fcontext(reinterpret_cast<fcontext_t*>(&octx.uc_mcontext),
-                                 static_cast<fcontext_t>(ctx.uc_mcontext), 0);
-
-    auto origctx = reinterpret_cast<pdns_ucontext_t*>(ptr);
-    if(origctx && origctx->exception)
-        std::rethrow_exception (origctx->exception);
-#else
-  transfer_t res = jump_fcontext (static_cast<fcontext_t>(ctx.uc_mcontext), &octx.uc_mcontext);
-  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
-       switch back to it later. */
-    fcontext_t* ptr = static_cast<fcontext_t*>(res.data);
-    *ptr = res.fctx;
-  }
-  if (ctx.exception) {
-    std::rethrow_exception (ctx.exception);
-  }
-#endif
-}
-
-void
-pdns_makecontext
-(pdns_ucontext_t& ctx, boost::function<void(void)>& start) {
-    assert (ctx.uc_link);
-    assert (ctx.uc_stack.size() >= 8192);
-    assert (!ctx.uc_mcontext);
-    ctx.uc_mcontext = make_fcontext (&ctx.uc_stack[ctx.uc_stack.size()-1],
-                                     ctx.uc_stack.size()-1, &threadWrapper);
-    args_t args;
-    args.self = &ctx;
-    args.work = &start;
-    /* jumping to threadwrapper */
-    notifyStackSwitch(&ctx.uc_stack[ctx.uc_stack.size()-1], ctx.uc_stack.size()-1);
-#if BOOST_VERSION < 106100
-    jump_fcontext (reinterpret_cast<fcontext_t*>(&args.prev_ctx),
-                   static_cast<fcontext_t>(ctx.uc_mcontext),
-                   reinterpret_cast<intptr_t>(&args));
-#else
-    transfer_t res = jump_fcontext (static_cast<fcontext_t>(ctx.uc_mcontext),
-                                    &args);
-    /* back from threadwrapper, updating the context */
-    ctx.uc_mcontext = res.fctx;
-#endif
-    notifyStackSwitchDone();
-
-}
diff --git a/pdns/mtasker_ucontext.cc b/pdns/mtasker_ucontext.cc
deleted file mode 100644 (file)
index 2cecc97..0000000
+++ /dev/null
@@ -1,145 +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 "mtasker_context.hh"
-#include <system_error>
-#include <exception>
-#include <cstring>
-#include <cassert>
-#include <signal.h>
-#include <ucontext.h>
-
-#ifdef PDNS_USE_VALGRIND
-#include <valgrind/valgrind.h>
-#endif /* PDNS_USE_VALGRIND */
-
-#ifdef HAVE_FIBER_SANITIZER
-__thread void* t_mainStack{nullptr};
-__thread size_t t_mainStackSize{0};
-#endif /* HAVE_FIBER_SANITIZER */
-
-template <typename Message> static __attribute__((noinline, cold, noreturn))
-void
-throw_errno (Message&& msg) {
-    throw std::system_error
-            (errno, std::system_category(), std::forward<Message>(msg));
-}
-
-static inline
-std::pair<int, int>
-splitPointer (void* const ptr) noexcept {
-    static_assert (sizeof(int) == 4, "splitPointer() requires an 4 byte 'int'");
-// In theory, we need this assertion. In practice, it prevents compilation
-// on EL6 i386. Without the assertion, everything works.
-// If you ever run into trouble with this code, please heed the warnings at
-// http://man7.org/linux/man-pages/man3/makecontext.3.html#NOTES
-//    static_assert (sizeof(uintptr_t) == 8,
-//                    "splitPointer() requires an 8 byte 'uintptr_t'");
-    std::pair<int, int> words;
-    auto rep = reinterpret_cast<uintptr_t>(ptr);
-    uint32_t const hw = rep >> 32;
-    auto const lw = static_cast<uint32_t>(rep);
-    std::memcpy (&words.first, &hw, 4);
-    std::memcpy (&words.second, &lw, 4);
-    return words;
-}
-
-template <typename T> static inline
-T*
-joinPtr (int const first, int const second) noexcept {
-    static_assert (sizeof(int) == 4, "joinPtr() requires an 4 byte 'int'");
-// See above.
-//    static_assert (sizeof(uintptr_t) == 8,
-//                    "joinPtr() requires an 8 byte 'uintptr_t'");
-    uint32_t hw;
-    uint32_t lw;
-    std::memcpy (&hw, &first, 4);
-    std::memcpy (&lw, &second, 4);
-    return reinterpret_cast<T*>((static_cast<uintptr_t>(hw) << 32) | lw);
-}
-
-extern "C" {
-static
-void
-threadWrapper (int const ctx0, int const ctx1, int const fun0, int const fun1) {
-    notifyStackSwitchDone();
-    auto ctx = joinPtr<pdns_ucontext_t>(ctx0, ctx1);
-    try {
-        auto start = std::move(*joinPtr<boost::function<void()>>(fun0, fun1));
-        start();
-    } catch (...) {
-        ctx->exception = std::current_exception();
-    }
-    notifyStackSwitchToKernel();
-}
-} // extern "C"
-
-pdns_ucontext_t::pdns_ucontext_t() {
-    uc_mcontext = new ::ucontext_t();
-    uc_link = nullptr;
-#ifdef PDNS_USE_VALGRIND
-    valgrind_id = 0;
-#endif /* PDNS_USE_VALGRIND */
-}
-
-pdns_ucontext_t::~pdns_ucontext_t() {
-    delete static_cast<ucontext_t*>(uc_mcontext);
-#ifdef PDNS_USE_VALGRIND
-    if (valgrind_id != 0) {
-      VALGRIND_STACK_DEREGISTER(valgrind_id);
-    }
-#endif /* PDNS_USE_VALGRIND */
-}
-
-void
-pdns_swapcontext
-(pdns_ucontext_t& __restrict octx, pdns_ucontext_t const& __restrict ctx) {
-    if (::swapcontext (static_cast<ucontext_t*>(octx.uc_mcontext),
-                       static_cast<ucontext_t*>(ctx.uc_mcontext))) {
-        throw_errno ("swapcontext() failed");
-    }
-    if (ctx.exception) {
-        std::rethrow_exception (ctx.exception);
-    }
-}
-
-void
-pdns_makecontext
-(pdns_ucontext_t& ctx, boost::function<void(void)>& start) {
-    assert (ctx.uc_link);
-    assert (ctx.uc_stack.size());
-
-    auto const mcp = static_cast<ucontext_t*>(ctx.uc_mcontext);
-    auto const next = static_cast<ucontext_t*>(ctx.uc_link->uc_mcontext);
-    if (::getcontext (mcp)) {
-        throw_errno ("getcontext() failed");
-    }
-    mcp->uc_link = next;
-    mcp->uc_stack.ss_sp = ctx.uc_stack.data();
-    mcp->uc_stack.ss_size = ctx.uc_stack.size()-1;
-    mcp->uc_stack.ss_flags = 0;
-
-    auto ctxarg = splitPointer (&ctx);
-    auto funarg = splitPointer (&start);
-    return ::makecontext (mcp, reinterpret_cast<void(*)(void)>(&threadWrapper),
-                          4, ctxarg.first, ctxarg.second,
-                          funarg.first, funarg.second);
-}
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 183033dd6e495574a4a197c0f3f72c1a927bc66c..db66bc9e62164dbffa087766f39dc0352fe0d839 100644 (file)
@@ -32,7 +32,7 @@
 #include <sys/types.h>
 #include "responsestats.hh"
 
-#include "common_startup.hh"
+#include "auth-main.hh"
 #include "dns.hh"
 #include "dnsbackend.hh"
 #include "dnspacket.hh"
@@ -83,7 +83,7 @@ extern StatBag S;
     These statistics are made available via the UeberBackend on the same socket that is used for dynamic module commands.
 
     \section Main Main 
-    The main() of PowerDNS can be found in receiver.cc - start reading there for further insights into the operation of the nameserver
+    The main() of PowerDNS can be found in auth-main.cc - start reading there for further insights into the operation of the nameserver
 */
 
 vector<ComboAddress> g_localaddresses; // not static, our unit tests need to poke this
index f6e92df644c6659048f8cd83e7951b147cbd7b1c..4ad9b177a9d93a8067857c4a663aa1521f9d8abe 100644 (file)
@@ -94,5 +94,3 @@ private:
 };
 
 bool AddressIsUs(const ComboAddress& remote);
-
-extern ResponseStats g_rs;
index 5c8b4224a69742588ec119f552c01738055ee549..f7489fe8591156a5069bdd1ab2a4bc7591da2f37 100644 (file)
@@ -48,5 +48,3 @@ using std::shared_ptr;
 using std::string;
 using std::unique_ptr;
 using std::vector;
-
-using pdns_string_view = std::string_view;
diff --git a/pdns/nod.cc b/pdns/nod.cc
deleted file mode 100644 (file)
index 07d3928..0000000
+++ /dev/null
@@ -1,266 +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 "nod.hh"
-#include <fstream>
-#include "pdnsexception.hh"
-#include <iostream>
-#include <iomanip>
-#include <ctime>
-#include <thread>
-#include "threadname.hh"
-#include <stdlib.h>
-#include "logger.hh"
-#include "misc.hh"
-
-using namespace nod;
-namespace filesystem = boost::filesystem;
-
-// PersistentSBF Implementation 
-
-std::mutex PersistentSBF::d_cachedir_mutex;
-
-void PersistentSBF::remove_tmp_files(const filesystem::path& p, std::lock_guard<std::mutex>& lock)
-{
-  Regex file_regex(d_prefix + ".*\\." + bf_suffix + "\\..{8}$");
-  for (filesystem::directory_iterator i(p); i != filesystem::directory_iterator(); ++i) {
-    if (filesystem::is_regular_file(i->path()) && file_regex.match(i->path().filename().string())) {
-      filesystem::remove(*i);
-    }
-  }
-}
-
-// This looks for an old (per-thread) snapshot. The first one it finds,
-// it restores from that. Then immediately snapshots with the current thread id,// before removing the old snapshot
-// In this way, we can have per-thread SBFs, but still snapshot and restore.
-// The mutex has to be static because we can't have multiple (i.e. per-thread)
-// instances iterating and writing to the cache dir at the same time
-bool PersistentSBF::init(bool ignore_pid) {
-  if (d_init)
-    return false;
-
-  std::lock_guard<std::mutex> lock(d_cachedir_mutex);
-  if (d_cachedir.length()) {
-    filesystem::path p(d_cachedir);
-    try {
-      if (filesystem::exists(p) && filesystem::is_directory(p)) {
-        remove_tmp_files(p, lock);
-        filesystem::path newest_file;
-        std::time_t newest_time=time(nullptr);
-        Regex file_regex(d_prefix + ".*\\." + bf_suffix + "$");
-        for (filesystem::directory_iterator i(p); i != filesystem::directory_iterator(); ++i) {
-          if (filesystem::is_regular_file(i->path()) &&
-              file_regex.match(i->path().filename().string())) {
-            if (ignore_pid ||
-                (i->path().filename().string().find(std::to_string(getpid())) == std::string::npos)) {
-              // look for the newest file matching the regex
-              if ((last_write_time(i->path()) < newest_time) ||
-                  newest_file.empty()) {
-                newest_time = last_write_time(i->path());
-                newest_file = i->path();
-              }
-            }
-          }
-        }
-        if (filesystem::exists(newest_file)) {
-          std::string filename = newest_file.string();
-          std::ifstream infile;
-          try {
-            infile.open(filename, std::ios::in | std::ios::binary);
-            g_log << Logger::Warning << "Found SBF file " << filename << endl;
-            // read the file into the sbf
-            d_sbf.lock()->restore(infile);
-            infile.close();
-            // now dump it out again with new thread id & process id
-            snapshotCurrent(std::this_thread::get_id());
-            // Remove the old file we just read to stop proliferation
-            filesystem::remove(newest_file);
-          }
-          catch (const std::runtime_error& e) {
-            infile.close();
-            filesystem::remove(newest_file);
-            g_log<<Logger::Warning<<"NODDB init: Cannot parse file: " << filename << ": " << e.what() << "; removed" << endl;
-          }
-        }
-      }
-    }
-    catch (const filesystem::filesystem_error& e) {
-      g_log<<Logger::Warning<<"NODDB init failed:: " << e.what() << endl;
-      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 non-existent directory: " + cachedir);
-    else if (!is_directory(p))
-      throw PDNSException("NODDB setCacheDir specified a file not a directory: " + cachedir);
-    d_cachedir = cachedir;
-  }
-}
-
-// Dump the SBF to a file
-// To spend the least amount of time inside the mutex, we dump to an
-// intermediate stringstream, otherwise the lock would be waiting for
-// file IO to complete
-bool PersistentSBF::snapshotCurrent(std::thread::id tid)
-{
-  if (d_cachedir.length()) {
-    filesystem::path p(d_cachedir);
-    filesystem::path f(d_cachedir);
-    std::stringstream ss;
-    ss << d_prefix << "_" << tid;
-    f /= ss.str() + "_" + std::to_string(getpid()) + "." + bf_suffix;
-    if (filesystem::exists(p) && filesystem::is_directory(p)) {
-      try {
-        std::ofstream ofile;
-        std::stringstream iss;
-        {
-          // only lock while dumping to a stringstream
-          d_sbf.lock()->dump(iss);
-        }
-        // Now write it out to the file
-        std::string ftmp = f.string() + ".XXXXXXXX";
-        int fd = mkstemp(&ftmp.at(0));
-        if (fd == -1) {
-          throw std::runtime_error("Cannot create temp file: " + stringerror());
-        }
-        std::string str = iss.str();
-        ssize_t len = write(fd,  str.data(), str.length());
-        if (len != static_cast<ssize_t>(str.length())) {
-          close(fd);
-          filesystem::remove(ftmp.c_str());
-          throw std::runtime_error("Failed to write to file:" + ftmp);
-        }
-        if (close(fd) != 0) {
-          filesystem::remove(ftmp);
-          throw std::runtime_error("Failed to write to file:" + ftmp);
-        }
-        try {
-          filesystem::rename(ftmp, f);
-        }
-        catch (const std::runtime_error& e) {
-          g_log<<Logger::Warning<<"NODDB snapshot: Cannot rename file: " << e.what() << endl;
-          filesystem::remove(ftmp);
-          throw;
-        }
-        return true;
-      }
-      catch (const std::runtime_error& e) {
-        g_log<<Logger::Warning<<"NODDB snapshot: Cannot write file: " << e.what() << endl;
-      }
-    }
-    else {
-      g_log<<Logger::Warning<<"NODDB snapshot: Cannot write file: " << f.string() << endl;
-    }
-  }
-  return false;
-}
-
-// NODDB Implementation
-
-void NODDB::housekeepingThread(std::thread::id tid)
-{
-  setThreadName("pdns-r/NOD-hk");
-  for (;;) {
-    sleep(d_snapshot_interval);
-    {
-      snapshotCurrent(tid);
-    }
-  }
-}
-
-bool NODDB::isNewDomain(const std::string& domain)
-{
-  DNSName dname(domain);
-  return isNewDomain(dname);
-}
-
-bool NODDB::isNewDomain(const DNSName& dname)
-{
-  std::string dname_lc = dname.toDNSStringLC();
-  // The only time this should block is when snapshotting from the
-  // housekeeping thread
-  // the result is always the inverse of what is returned by the SBF
-  return !d_psbf.testAndAdd(dname_lc);
-}
-
-bool NODDB::isNewDomainWithParent(const std::string& domain, std::string& observed)
-{
-  DNSName dname(domain);
-  return isNewDomainWithParent(dname, observed);
-}
-
-bool NODDB::isNewDomainWithParent(const DNSName& dname, std::string& observed)
-{
-  bool ret = isNewDomain(dname);
-  if (ret == true) {
-    DNSName mdname = dname;
-    while (mdname.chopOff()) {
-      if (!isNewDomain(mdname)) {
-        observed = mdname.toString();
-        break;
-      }
-    }
-  }
-  return ret;
-}
-
-void NODDB::addDomain(const DNSName& dname)
-{
-  std::string native_domain = dname.toDNSStringLC();
-  d_psbf.add(native_domain);
-}
-
-void NODDB::addDomain(const std::string& domain)
-{
-  DNSName dname(domain);
-  addDomain(dname);
-}
-
-// UniqueResponseDB Implementation
-bool UniqueResponseDB::isUniqueResponse(const std::string& response)
-{
-  return !d_psbf.testAndAdd(response);
-}
-
-void UniqueResponseDB::addResponse(const std::string& response)
-{
-  d_psbf.add(response);
-}
-
-void UniqueResponseDB::housekeepingThread(std::thread::id tid)
-{
-  setThreadName("pdns-r/UDR-hk");
-  for (;;) {
-    sleep(d_snapshot_interval);
-    {
-      snapshotCurrent(tid);
-    }
-  }
-}
diff --git a/pdns/nod.hh b/pdns/nod.hh
deleted file mode 100644 (file)
index e6ecf6c..0000000
+++ /dev/null
@@ -1,121 +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 <mutex>
-#include <thread>
-#include <boost/filesystem.hpp>
-#include "dnsname.hh"
-#include "lock.hh"
-#include "stable-bloom.hh"
-
-namespace nod {
-  const float c_fp_rate = 0.01;
-  const size_t c_num_cells = 67108864;
-  const uint8_t c_num_dec = 10;
-  const unsigned int snapshot_interval_default = 600;
-  const std::string bf_suffix = "bf";
-  const std::string sbf_prefix = "sbf";
-
-  // Theses classes are not designed to be shared between threads
-  // Use a new instance per-thread, e.g. using thread local storage
-  // Synchronization (at the class level) is still needed for reading from
-  // and writing to the cache dir
-  // Synchronization (at the instance level) is needed when snapshotting
-  class PersistentSBF {
-  public:
-    PersistentSBF() : d_sbf(bf::stableBF(c_fp_rate, c_num_cells, c_num_dec)) {}
-    PersistentSBF(uint32_t num_cells) : d_sbf(bf::stableBF(c_fp_rate, num_cells, c_num_dec)) {}
-    bool init(bool ignore_pid=false);
-    void setPrefix(const std::string& prefix) { d_prefix = prefix; } // Added to filenames in cachedir
-    void setCacheDir(const std::string& cachedir);
-    bool snapshotCurrent(std::thread::id tid); // Write the current file out to disk
-    void add(const std::string& data) { 
-      // The only time this should block is when snapshotting
-      d_sbf.lock()->add(data);
-    }
-    bool test(const std::string& data) { return d_sbf.lock()->test(data); }
-    bool testAndAdd(const std::string& data) { 
-      // The only time this should block is when snapshotting
-      return d_sbf.lock()->testAndAdd(data);
-    }
-  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;
-    static std::mutex d_cachedir_mutex; // One mutex for all instances of this class
-  };
-
-  class NODDB {
-  public:
-    NODDB() : d_psbf{} {}
-    NODDB(uint32_t num_cells) : d_psbf{num_cells} {}
-    // Set ignore_pid to true if you don't mind loading files
-    // created by the current process
-    bool init(bool ignore_pid=false) { 
-      d_psbf.setPrefix("nod");
-      return d_psbf.init(ignore_pid); 
-    }
-    bool isNewDomain(const std::string& domain); // Returns true if newly observed domain
-    bool isNewDomain(const DNSName& dname); // As above
-    bool isNewDomainWithParent(const std::string& domain, std::string& observed); // Returns true if newly observed domain, in which case "observed" contains the parent domain which *was* observed (or "" if domain is . or no parent domains observed)
-    bool isNewDomainWithParent(const DNSName& dname, std::string& observed); // As above
-    void addDomain(const DNSName& dname); // You need to add this to refresh frequently used domains
-    void addDomain(const std::string& domain); // As above
-    void setSnapshotInterval(unsigned int secs) { d_snapshot_interval = secs; }
-    void setCacheDir(const std::string& cachedir) { d_psbf.setCacheDir(cachedir); }
-    bool snapshotCurrent(std::thread::id tid) { return d_psbf.snapshotCurrent(tid); }
-    static void startHousekeepingThread(std::shared_ptr<NODDB> noddbp, std::thread::id tid) {
-      noddbp->housekeepingThread(tid);
-    }
-  private:
-    PersistentSBF d_psbf;
-    unsigned int d_snapshot_interval{snapshot_interval_default}; // Number seconds between snapshots    
-    void housekeepingThread(std::thread::id tid);
-  };
-
-  class UniqueResponseDB {
-  public:
-    UniqueResponseDB() : d_psbf{} {}
-    UniqueResponseDB(uint32_t num_cells) : d_psbf{num_cells} {}
-    bool init(bool ignore_pid=false) {
-      d_psbf.setPrefix("udr");
-      return d_psbf.init(ignore_pid); 
-    }
-    bool isUniqueResponse(const std::string& response);
-    void addResponse(const std::string& response);
-    void setSnapshotInterval(unsigned int secs) { d_snapshot_interval = secs; }
-    void setCacheDir(const std::string& cachedir) { d_psbf.setCacheDir(cachedir); }
-    bool snapshotCurrent(std::thread::id tid) { return d_psbf.snapshotCurrent(tid); }
-    static void startHousekeepingThread(std::shared_ptr<UniqueResponseDB> udrdbp, std::thread::id tid) {
-      udrdbp->housekeepingThread(tid);
-    }
-  private:
-    PersistentSBF d_psbf;
-    unsigned int d_snapshot_interval{snapshot_interval_default}; // Number seconds between snapshots    
-    void housekeepingThread(std::thread::id tid); 
-  };
-
-}
index 13dcdf057194d9025c1df5826f7af0e2ac2ddd8f..f47ed2c58b5e642cf05042db2ecc905c695f1cba 100644 (file)
@@ -171,7 +171,7 @@ try
     {
       // cerr<<"got nsec3 ["<<i->first.d_name<<"]"<<endl;
       // cerr<<i->first.d_content->getZoneRepresentation()<<endl;
-      const auto r = std::dynamic_pointer_cast<NSEC3RecordContent>(i->first.d_content);
+      const auto r = getRR<NSEC3RecordContent>(i->first);
       if (!r) {
         continue;
       }
@@ -191,11 +191,11 @@ try
 
     if(i->first.d_type == QType::CNAME)
     {
-      namesseen.insert(DNSName(i->first.d_content->getZoneRepresentation()));
+      namesseen.insert(DNSName(i->first.getContent()->getZoneRepresentation()));
     }
 
-    cout<<i->first.d_place-1<<"\t"<<i->first.d_name.toString()<<"\tIN\t"<<DNSRecordContent::NumberToType(i->first.d_type);
-    cout<<"\t"<<i->first.d_ttl<<"\t"<< i->first.d_content->getZoneRepresentation()<<"\n";
+    cout << i->first.d_place - 1 << "\t" << i->first.d_name.toString() << "\t" << i->first.d_ttl << "\tIN\t" << DNSRecordContent::NumberToType(i->first.d_type);
+    cout << "\t" << i->first.getContent()->getZoneRepresentation() << "\n";
   }
 
 #if 0
index 33d0808290355d07ea5ce283f1168217808dfcbf..815e3fcaac6ab5cece06fbf60a61777cf6f17b27 100644 (file)
@@ -72,7 +72,7 @@ private:
   string tmp;
 };
 
-void NSECBitmap::toPacket(DNSPacketWriter& pw)
+void NSECBitmap::toPacket(DNSPacketWriter& pw) const
 {
   NSECBitmapGenerator nbg(pw);
   if (d_bitset) {
@@ -108,7 +108,7 @@ void NSECBitmap::fromPacket(PacketReader& pr)
   if(bitmap.size() < 2) {
     throw MOADNSException("NSEC record with impossibly small bitmap");
   }
-  
+
   for(unsigned int n = 0; n+1 < bitmap.size();) {
     uint8_t window=static_cast<uint8_t>(bitmap[n++]);
     uint8_t blen=static_cast<uint8_t>(bitmap[n++]);
@@ -186,13 +186,13 @@ NSECRecordContent::NSECRecordContent(const string& content, const DNSName& zone)
   }
 }
 
-void NSECRecordContent::toPacket(DNSPacketWriter& pw)
+void NSECRecordContent::toPacket(DNSPacketWriter& pw) const
 {
   pw.xfrName(d_next);
   d_bitmap.toPacket(pw);
 }
 
-std::shared_ptr<NSECRecordContent::DNSRecordContent> NSECRecordContent::make(const DNSRecord &dr, PacketReader& pr)
+std::shared_ptr<NSECRecordContent::DNSRecordContent> NSECRecordContent::make(const DNSRecord & /* dr */, PacketReader& pr)
 {
   auto ret=std::make_shared<NSECRecordContent>();
   pr.xfrName(ret->d_next);
@@ -202,7 +202,7 @@ std::shared_ptr<NSECRecordContent::DNSRecordContent> NSECRecordContent::make(con
   return ret;
 }
 
-string NSECRecordContent::getZoneRepresentation(bool noDot) const
+string NSECRecordContent::getZoneRepresentation(bool /* noDot */) const
 {
   string ret;
   RecordTextWriter rtw(ret);
@@ -232,7 +232,7 @@ NSEC3RecordContent::NSEC3RecordContent(const string& content, const DNSName& zon
 
   rtr.xfrHexBlob(d_salt);
   rtr.xfrBase32HexBlob(d_nexthash);
-  
+
   while(!rtr.eof()) {
     uint16_t type;
     rtr.xfrType(type);
@@ -240,7 +240,7 @@ NSEC3RecordContent::NSEC3RecordContent(const string& content, const DNSName& zon
   }
 }
 
-void NSEC3RecordContent::toPacket(DNSPacketWriter& pw) 
+void NSEC3RecordContent::toPacket(DNSPacketWriter& pw) const
 {
   pw.xfr8BitInt(d_algorithm);
   pw.xfr8BitInt(d_flags);
@@ -254,7 +254,7 @@ void NSEC3RecordContent::toPacket(DNSPacketWriter& pw)
   d_bitmap.toPacket(pw);
 }
 
-std::shared_ptr<NSEC3RecordContent::DNSRecordContent> NSEC3RecordContent::make(const DNSRecord &dr, PacketReader& pr)
+std::shared_ptr<NSEC3RecordContent::DNSRecordContent> NSEC3RecordContent::make(const DNSRecord& /* dr */, PacketReader& pr)
 {
   auto ret=std::make_shared<NSEC3RecordContent>();
   pr.xfr8BitInt(ret->d_algorithm);
@@ -271,7 +271,7 @@ std::shared_ptr<NSEC3RecordContent::DNSRecordContent> NSEC3RecordContent::make(c
   return ret;
 }
 
-string NSEC3RecordContent::getZoneRepresentation(bool noDot) const
+string NSEC3RecordContent::getZoneRepresentation(bool /* noDot */) const
 {
   string ret;
   RecordTextWriter rtw(ret);
@@ -300,41 +300,41 @@ std::shared_ptr<DNSRecordContent> NSEC3PARAMRecordContent::make(const string& co
 NSEC3PARAMRecordContent::NSEC3PARAMRecordContent(const string& content, const DNSName& zone)
 {
   RecordTextReader rtr(content, zone);
-  rtr.xfr8BitInt(d_algorithm); 
-  rtr.xfr8BitInt(d_flags); 
-  rtr.xfr16BitInt(d_iterations); 
+  rtr.xfr8BitInt(d_algorithm);
+  rtr.xfr8BitInt(d_flags);
+  rtr.xfr16BitInt(d_iterations);
   rtr.xfrHexBlob(d_salt);
 }
 
-void NSEC3PARAMRecordContent::toPacket(DNSPacketWriter& pw) 
+void NSEC3PARAMRecordContent::toPacket(DNSPacketWriter& pw) const
 {
-  pw.xfr8BitInt(d_algorithm); 
-        pw.xfr8BitInt(d_flags); 
-        pw.xfr16BitInt(d_iterations); 
+  pw.xfr8BitInt(d_algorithm);
+        pw.xfr8BitInt(d_flags);
+        pw.xfr16BitInt(d_iterations);
   pw.xfr8BitInt(d_salt.length());
   // cerr<<"salt: '"<<makeHexDump(d_salt)<<"', "<<d_salt.length()<<endl;
   pw.xfrBlob(d_salt);
 }
 
-std::shared_ptr<NSEC3PARAMRecordContent::DNSRecordContent> NSEC3PARAMRecordContent::make(const DNSRecord &dr, PacketReader& pr)
+std::shared_ptr<NSEC3PARAMRecordContent::DNSRecordContent> NSEC3PARAMRecordContent::make(const DNSRecord& /* dr */, PacketReader& pr)
 {
   auto ret=std::make_shared<NSEC3PARAMRecordContent>();
-  pr.xfr8BitInt(ret->d_algorithm); 
-        pr.xfr8BitInt(ret->d_flags); 
-        pr.xfr16BitInt(ret->d_iterations); 
+  pr.xfr8BitInt(ret->d_algorithm);
+        pr.xfr8BitInt(ret->d_flags);
+        pr.xfr16BitInt(ret->d_iterations);
   uint8_t len;
   pr.xfr8BitInt(len);
   pr.xfrHexBlob(ret->d_salt, len);
   return ret;
 }
 
-string NSEC3PARAMRecordContent::getZoneRepresentation(bool noDot) const
+string NSEC3PARAMRecordContent::getZoneRepresentation(bool /* noDot */) const
 {
   string ret;
   RecordTextWriter rtw(ret);
-  rtw.xfr8BitInt(d_algorithm); 
-        rtw.xfr8BitInt(d_flags); 
-        rtw.xfr16BitInt(d_iterations); 
+  rtw.xfr8BitInt(d_algorithm);
+        rtw.xfr8BitInt(d_flags);
+        rtw.xfr16BitInt(d_iterations);
   rtw.xfrHexBlob(d_salt);
   return ret;
 }
@@ -366,7 +366,7 @@ CSYNCRecordContent::CSYNCRecordContent(const string& content, const DNSName& zon
   }
 }
 
-void CSYNCRecordContent::toPacket(DNSPacketWriter& pw)
+void CSYNCRecordContent::toPacket(DNSPacketWriter& pw) const
 {
   pw.xfr32BitInt(d_serial);
   pw.xfr16BitInt(d_flags);
@@ -374,7 +374,7 @@ void CSYNCRecordContent::toPacket(DNSPacketWriter& pw)
   d_bitmap.toPacket(pw);
 }
 
-std::shared_ptr<CSYNCRecordContent::DNSRecordContent> CSYNCRecordContent::make(const DNSRecord &dr, PacketReader& pr)
+std::shared_ptr<CSYNCRecordContent::DNSRecordContent> CSYNCRecordContent::make(const DNSRecord& /* dr */, PacketReader& pr)
 {
   auto ret=std::make_shared<CSYNCRecordContent>();
   pr.xfr32BitInt(ret->d_serial);
@@ -384,7 +384,7 @@ std::shared_ptr<CSYNCRecordContent::DNSRecordContent> CSYNCRecordContent::make(c
   return ret;
 }
 
-string CSYNCRecordContent::getZoneRepresentation(bool noDot) const
+string CSYNCRecordContent::getZoneRepresentation(bool /* noDot */) const
 {
   string ret;
   RecordTextWriter rtw(ret);
index 280d8b15efb27a092c5f037f9b15ce55b6631df5..9b593aa2da7ce762a8f751745ca05afed48c855b 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 "misc.hh"
+#include <memory>
+#include <openssl/crypto.h>
+#include <openssl/ec.h>
+#include <optional>
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 #include <openssl/sha.h>
 #include <openssl/rand.h>
 #include <openssl/rsa.h>
+#if OPENSSL_VERSION_MAJOR >= 3
+#include <openssl/types.h>
+#include <openssl/core_names.h>
+#include <openssl/param_build.h>
+#include <openssl/params.h>
+#endif
 #include <openssl/opensslv.h>
 #include <openssl/err.h>
 #include <openssl/pem.h>
 #include "lock.hh"
 static std::vector<std::mutex> openssllocks;
 
-extern "C" {
-static void openssl_pthreads_locking_callback(int mode, int type, const char *file, int line)
+extern "C"
 {
-  if (mode & CRYPTO_LOCK) {
-    openssllocks.at(type).lock();
-
-  } else {
-    openssllocks.at(type).unlock();
+  static void openssl_pthreads_locking_callback(int mode, int type, const char* file, int line)
+  {
+    if (mode & CRYPTO_LOCK) {
+      openssllocks.at(type).lock();
+    }
+    else {
+      openssllocks.at(type).unlock();
+    }
   }
-}
 
-static unsigned long openssl_pthreads_id_callback(void)
-{
-  return (unsigned long)pthread_self();
-}
+  static unsigned long openssl_pthreads_id_callback(void)
+  {
+    return (unsigned long)pthread_self();
+  }
 }
 
 void openssl_thread_setup()
@@ -79,13 +91,15 @@ void openssl_thread_cleanup()
 #ifndef HAVE_RSA_GET0_KEY
 /* those symbols are defined in LibreSSL 2.7.0+ */
 /* compat helpers. These DO NOT do any of the checking that the libssl 1.1 functions do. */
-static inline void RSA_get0_key(const RSA* rsakey, const BIGNUM** n, const BIGNUM** e, const BIGNUM** d) {
+static inline void RSA_get0_key(const RSA* rsakey, const BIGNUM** n, const BIGNUM** e, const BIGNUM** d)
+{
   *n = rsakey->n;
   *e = rsakey->e;
   *d = rsakey->d;
 }
 
-static inline int RSA_set0_key(RSA* rsakey, BIGNUM* n, BIGNUM* e, BIGNUM* d) {
+static inline int RSA_set0_key(RSA* rsakey, BIGNUM* n, BIGNUM* e, BIGNUM* d)
+{
   if (n) {
     BN_clear_free(rsakey->n);
     rsakey->n = n;
@@ -101,12 +115,14 @@ static inline int RSA_set0_key(RSA* rsakey, BIGNUM* n, BIGNUM* e, BIGNUM* d) {
   return 1;
 }
 
-static inline void RSA_get0_factors(const RSA* rsakey, const BIGNUM** p, const BIGNUM** q) {
+static inline void RSA_get0_factors(const RSA* rsakey, const BIGNUM** p, const BIGNUM** q)
+{
   *p = rsakey->p;
   *q = rsakey->q;
 }
 
-static inline int RSA_set0_factors(RSA* rsakey, BIGNUM* p, BIGNUM* q) {
+static inline int RSA_set0_factors(RSA* rsakey, BIGNUM* p, BIGNUM* q)
+{
   BN_clear_free(rsakey->p);
   rsakey->p = p;
   BN_clear_free(rsakey->q);
@@ -114,13 +130,15 @@ static inline int RSA_set0_factors(RSA* rsakey, BIGNUM* p, BIGNUM* q) {
   return 1;
 }
 
-static inline void RSA_get0_crt_params(const RSA* rsakey, const BIGNUM** dmp1, const BIGNUM** dmq1, const BIGNUM** iqmp) {
+static inline void RSA_get0_crt_params(const RSA* rsakey, const BIGNUM** dmp1, const BIGNUM** dmq1, const BIGNUM** iqmp)
+{
   *dmp1 = rsakey->dmp1;
   *dmq1 = rsakey->dmq1;
   *iqmp = rsakey->iqmp;
 }
 
-static inline int RSA_set0_crt_params(RSA* rsakey, BIGNUM* dmp1, BIGNUM* dmq1, BIGNUM* iqmp) {
+static inline int RSA_set0_crt_params(RSA* rsakey, BIGNUM* dmp1, BIGNUM* dmq1, BIGNUM* iqmp)
+{
   BN_clear_free(rsakey->dmp1);
   rsakey->dmp1 = dmp1;
   BN_clear_free(rsakey->dmq1);
@@ -131,12 +149,14 @@ static inline int RSA_set0_crt_params(RSA* rsakey, BIGNUM* dmp1, BIGNUM* dmq1, B
 }
 
 #ifdef HAVE_LIBCRYPTO_ECDSA
-static inline void ECDSA_SIG_get0(const ECDSA_SIG* signature, const BIGNUM** pr, const BIGNUM** ps) {
+static inline void ECDSA_SIG_get0(const ECDSA_SIG* signature, const BIGNUM** pr, const BIGNUM** ps)
+{
   *pr = signature->r;
   *ps = signature->s;
 }
 
-static inline int ECDSA_SIG_set0(ECDSA_SIG* signature, BIGNUM* pr, BIGNUM* ps) {
+static inline int ECDSA_SIG_set0(ECDSA_SIG* signature, BIGNUM* pr, BIGNUM* ps)
+{
   BN_clear_free(signature->r);
   BN_clear_free(signature->s);
   signature->r = pr;
@@ -152,53 +172,84 @@ void openssl_thread_setup() {}
 void openssl_thread_cleanup() {}
 #endif
 
-
 /* seeding PRNG */
-
 void openssl_seed()
 {
   std::string entropy;
   entropy.reserve(1024);
 
   unsigned int r;
-  for(int i=0; i<1024; i+=4) {
-    r=dns_random(0xffffffff);
+  for (int i = 0; i < 1024; i += 4) {
+    r = dns_random_uint32();
     entropy.append((const char*)&r, 4);
   }
 
   RAND_seed((const unsigned char*)entropy.c_str(), 1024);
 }
 
+using BigNum = unique_ptr<BIGNUM, decltype(&BN_clear_free)>;
 
-class OpenSSLRSADNSCryptoKeyEngine : public DNSCryptoKeyEngine
+static auto mapToBN(const std::string& componentName, const std::map<std::string, std::string>& stormap, const std::string& key) -> BigNum
 {
-public:
-  explicit OpenSSLRSADNSCryptoKeyEngine(unsigned int algo): DNSCryptoKeyEngine(algo), d_key(std::unique_ptr<RSA, void(*)(RSA*)>(nullptr, RSA_free))
-  {
-    int ret = RAND_status();
-    if (ret != 1) {
-      throw runtime_error(getName()+" insufficient entropy");
-    }
-  }
+  const std::string& value = stormap.at(key);
 
-  ~OpenSSLRSADNSCryptoKeyEngine()
-  {
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  const auto* valueCStr = reinterpret_cast<const unsigned char*>(value.c_str());
+  auto number = BigNum{BN_bin2bn(valueCStr, static_cast<int>(value.length()), nullptr), BN_clear_free};
+  if (number == nullptr) {
+    throw pdns::OpenSSL::error(componentName, "Failed to parse key `" + key + "`");
   }
 
-  string getName() const override { return "OpenSSL RSA"; }
-  int getBits() const override { return RSA_size(d_key.get()) << 3; }
+  return number;
+}
 
+class OpenSSLRSADNSCryptoKeyEngine : public DNSCryptoKeyEngine
+{
+public:
+  explicit OpenSSLRSADNSCryptoKeyEngine(unsigned int algo);
+
+  [[nodiscard]] string getName() const override { return "OpenSSL RSA"; }
+  [[nodiscard]] int getBits() const override;
   void create(unsigned int bits) override;
-  storvector_t convertToISCVector() const override;
-  std::string hash(const std::string& hash) const override;
-  std::string sign(const std::string& hash) const override;
-  bool verify(const std::string& hash, const std::string& signature) const override;
-  std::string getPubKeyHash() const override;
-  std::string getPublicKeyString() const override;
-  std::unique_ptr<BIGNUM, void(*)(BIGNUM*)>parse(std::map<std::string, std::string>& stormap, const std::string& key) const;
+
+  /**
+   * \brief Creates an RSA key engine from a PEM file.
+   *
+   * Receives an open file handle with PEM contents and creates an RSA key engine.
+   *
+   * \param[in] drc Key record contents to be populated.
+   *
+   * \param[in] inputFile An open file handle to a file containing RSA PEM contents.
+   *
+   * \param[in] filename Only used for providing filename information in error messages.
+   *
+   * \return An RSA key engine populated with the contents of the PEM file.
+   */
+  void createFromPEMFile(DNSKEYRecordContent& drc, std::FILE& inputFile, std::optional<std::reference_wrapper<const std::string>> filename = std::nullopt) override;
+
+  /**
+   * \brief Writes this key's contents to a file.
+   *
+   * Receives an open file handle and writes this key's contents to the
+   * file.
+   *
+   * \param[in] outputFile An open file handle for writing.
+   *
+   * \exception std::runtime_error In case of OpenSSL errors.
+   */
+  void convertToPEMFile(std::FILE& outputFile) const override;
+
+  [[nodiscard]] storvector_t convertToISCVector() const override;
+
+  // TODO Fred: hash() can probably be completely removed. See #12464.
+  [[nodiscard]] std::string hash(const std::string& message) const override;
+  [[nodiscard]] std::string sign(const std::string& message) const override;
+  [[nodiscard]] bool verify(const std::string& message, const std::string& signature) const override;
+  [[nodiscard]] std::string getPublicKeyString() const override;
+
   void fromISCMap(DNSKEYRecordContent& drc, std::map<std::string, std::string>& stormap) override;
   void fromPublicKeyString(const std::string& content) override;
-  bool checkKey(vector<string> *errorMessages) const override;
+  [[nodiscard]] bool checkKey(std::optional<std::reference_wrapper<std::vector<std::string>>> errorMessages) const override;
 
   static std::unique_ptr<DNSCryptoKeyEngine> maker(unsigned int algorithm)
   {
@@ -206,93 +257,364 @@ public:
   }
 
 private:
+#if OPENSSL_VERSION_MAJOR >= 3
+  [[nodiscard]] BigNum getKeyParamModulus() const;
+  [[nodiscard]] BigNum getKeyParamPublicExponent() const;
+  [[nodiscard]] BigNum getKeyParamPrivateExponent() const;
+  [[nodiscard]] BigNum getKeyParamPrime1() const;
+  [[nodiscard]] BigNum getKeyParamPrime2() const;
+  [[nodiscard]] BigNum getKeyParamDmp1() const;
+  [[nodiscard]] BigNum getKeyParamDmq1() const;
+  [[nodiscard]] BigNum getKeyParamIqmp() const;
+
+  using Params = std::unique_ptr<OSSL_PARAM, decltype(&OSSL_PARAM_free)>;
+  auto makeKeyParams(const BIGNUM* modulus, const BIGNUM* publicExponent, const BIGNUM* privateExponent, const BIGNUM* prime1, const BIGNUM* prime2, const BIGNUM* dmp1, const BIGNUM* dmq1, const BIGNUM* iqmp) const -> Params;
+#endif
+
+  // TODO Fred: hashSize(), hasher() and hashSizeToKind() can probably be completely
+  // removed along with hash(). See #12464.
+  [[nodiscard]] std::size_t hashSize() const;
+  [[nodiscard]] const EVP_MD* hasher() const;
   static int hashSizeToKind(size_t hashSize);
 
-  std::unique_ptr<RSA, void(*)(RSA*)> d_key;
+#if OPENSSL_VERSION_MAJOR >= 3
+  using KeyContext = std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)>;
+  using Key = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>;
+  using MessageDigestContext = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>;
+  using ParamsBuilder = std::unique_ptr<OSSL_PARAM_BLD, decltype(&OSSL_PARAM_BLD_free)>;
+  using MessageDigest = std::unique_ptr<EVP_MD, decltype(&EVP_MD_free)>;
+#else
+  using Key = std::unique_ptr<RSA, decltype(&RSA_free)>;
+#endif
+
+  Key d_key;
 };
 
+OpenSSLRSADNSCryptoKeyEngine::OpenSSLRSADNSCryptoKeyEngine(unsigned int algo) :
+  DNSCryptoKeyEngine(algo),
+#if OPENSSL_VERSION_MAJOR >= 3
+  d_key(Key(nullptr, EVP_PKEY_free))
+#else
+  d_key(Key(nullptr, RSA_free))
+#endif
+{
+  int ret = RAND_status();
+  if (ret != 1) {
+    throw runtime_error(getName() + " insufficient entropy");
+  }
+}
+
+int OpenSSLRSADNSCryptoKeyEngine::getBits() const
+{
+#if OPENSSL_VERSION_MAJOR >= 3
+  return EVP_PKEY_get_bits(d_key.get());
+#else
+  return RSA_size(d_key.get()) << 3;
+#endif
+}
 
 void OpenSSLRSADNSCryptoKeyEngine::create(unsigned int bits)
 {
   // When changing the bitsizes, also edit them in ::checkKey
   if ((d_algorithm == DNSSECKeeper::RSASHA1 || d_algorithm == DNSSECKeeper::RSASHA1NSEC3SHA1) && (bits < 512 || bits > 4096)) {
     /* RFC3110 */
-    throw runtime_error(getName()+" RSASHA1 key generation failed for invalid bits size " + std::to_string(bits));
+    throw runtime_error(getName() + " RSASHA1 key generation failed for invalid bits size " + std::to_string(bits));
   }
   if (d_algorithm == DNSSECKeeper::RSASHA256 && (bits < 512 || bits > 4096)) {
     /* RFC5702 */
-    throw runtime_error(getName()+" RSASHA256 key generation failed for invalid bits size " + std::to_string(bits));
+    throw runtime_error(getName() + " RSASHA256 key generation failed for invalid bits size " + std::to_string(bits));
   }
   if (d_algorithm == DNSSECKeeper::RSASHA512 && (bits < 1024 || bits > 4096)) {
     /* RFC5702 */
-    throw runtime_error(getName()+" RSASHA512 key generation failed for invalid bits size " + std::to_string(bits));
+    throw runtime_error(getName() + " RSASHA512 key generation failed for invalid bits size " + std::to_string(bits));
   }
 
-  auto e = std::unique_ptr<BIGNUM, void(*)(BIGNUM*)>(BN_new(), BN_clear_free);
-  if (!e) {
-    throw runtime_error(getName()+" key generation failed, unable to allocate e");
+  auto exponent = BigNum(BN_new(), BN_clear_free);
+  if (!exponent) {
+    throw runtime_error(getName() + " key generation failed, unable to allocate e");
   }
 
   /* RSA_F4 is a public exponent value of 65537 */
-  int res = BN_set_word(e.get(), RSA_F4);
+  int res = BN_set_word(exponent.get(), RSA_F4);
 
   if (res == 0) {
-    throw runtime_error(getName()+" key generation failed while setting e");
+    throw runtime_error(getName() + " key generation failed while setting e");
+  }
+
+#if OPENSSL_VERSION_MAJOR >= 3
+  auto ctx = KeyContext(EVP_PKEY_CTX_new_from_name(nullptr, "RSA", nullptr), EVP_PKEY_CTX_free);
+  if (ctx == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Could not initialize context");
+  }
+
+  if (EVP_PKEY_keygen_init(ctx.get()) != 1) {
+    throw pdns::OpenSSL::error(getName(), "Could not initialize keygen");
+  }
+
+  if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx.get(), (int)bits) <= 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not set keygen bits to " + std::to_string(bits));
+  }
+
+  if (EVP_PKEY_CTX_set1_rsa_keygen_pubexp(ctx.get(), exponent.get()) <= 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not set keygen public exponent");
   }
 
-  auto key = std::unique_ptr<RSA, void(*)(RSA*)>(RSA_new(), RSA_free);
+  EVP_PKEY* key = nullptr;
+  if (EVP_PKEY_generate(ctx.get(), &key) != 1) {
+    throw pdns::OpenSSL::error(getName(), "Could not generate key");
+  }
+
+  d_key.reset(key);
+#else
+  auto key = Key(RSA_new(), RSA_free);
   if (!key) {
-    throw runtime_error(getName()+" allocation of key structure failed");
+    throw runtime_error(getName() + " allocation of key structure failed");
   }
 
-  res = RSA_generate_key_ex(key.get(), bits, e.get(), nullptr);
+  res = RSA_generate_key_ex(key.get(), bits, exponent.get(), nullptr);
   if (res == 0) {
-    throw runtime_error(getName()+" key generation failed");
+    throw runtime_error(getName() + " key generation failed");
   }
 
   d_key = std::move(key);
+#endif
+}
+
+void OpenSSLRSADNSCryptoKeyEngine::createFromPEMFile(DNSKEYRecordContent& drc, std::FILE& inputFile, const std::optional<std::reference_wrapper<const std::string>> filename)
+{
+  drc.d_algorithm = d_algorithm;
+
+#if OPENSSL_VERSION_MAJOR >= 3
+  EVP_PKEY* key = nullptr;
+  if (PEM_read_PrivateKey(&inputFile, &key, nullptr, nullptr) == nullptr) {
+    if (filename.has_value()) {
+      throw pdns::OpenSSL::error(getName(), "Could not read private key from PEM file `" + filename->get() + "`");
+    }
+
+    throw pdns::OpenSSL::error(getName(), "Could not read private key from PEM contents");
+  }
+
+  d_key.reset(key);
+#else
+  d_key = Key(PEM_read_RSAPrivateKey(&inputFile, nullptr, nullptr, nullptr), &RSA_free);
+  if (d_key == nullptr) {
+    if (filename.has_value()) {
+      throw runtime_error(getName() + ": Failed to read private key from PEM file `" + filename->get() + "`");
+    }
+
+    throw runtime_error(getName() + ": Failed to read private key from PEM contents");
+  }
+#endif
 }
 
+void OpenSSLRSADNSCryptoKeyEngine::convertToPEMFile(std::FILE& outputFile) const
+{
+#if OPENSSL_VERSION_MAJOR >= 3
+  if (PEM_write_PrivateKey(&outputFile, d_key.get(), nullptr, nullptr, 0, nullptr, nullptr) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not convert private key to PEM");
+  }
+#else
+  auto ret = PEM_write_RSAPrivateKey(&outputFile, d_key.get(), nullptr, nullptr, 0, nullptr, nullptr);
+  if (ret == 0) {
+    throw runtime_error(getName() + ": Could not convert private key to PEM");
+  }
+#endif
+}
+
+#if OPENSSL_VERSION_MAJOR >= 3
+BigNum OpenSSLRSADNSCryptoKeyEngine::getKeyParamModulus() const
+{
+  BIGNUM* modulus = nullptr;
+  if (EVP_PKEY_get_bn_param(d_key.get(), OSSL_PKEY_PARAM_RSA_N, &modulus) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not get key's modulus (n) parameter");
+  }
+  return BigNum{modulus, BN_clear_free};
+}
+
+BigNum OpenSSLRSADNSCryptoKeyEngine::getKeyParamPublicExponent() const
+{
+  BIGNUM* publicExponent = nullptr;
+  if (EVP_PKEY_get_bn_param(d_key.get(), OSSL_PKEY_PARAM_RSA_E, &publicExponent) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not get key's public exponent (e) parameter");
+  }
+  return BigNum{publicExponent, BN_clear_free};
+}
+
+BigNum OpenSSLRSADNSCryptoKeyEngine::getKeyParamPrivateExponent() const
+{
+  BIGNUM* privateExponent = nullptr;
+  if (EVP_PKEY_get_bn_param(d_key.get(), OSSL_PKEY_PARAM_RSA_D, &privateExponent) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not get key's private exponent (d) parameter");
+  }
+  return BigNum{privateExponent, BN_clear_free};
+}
+
+BigNum OpenSSLRSADNSCryptoKeyEngine::getKeyParamPrime1() const
+{
+  BIGNUM* prime1 = nullptr;
+  if (EVP_PKEY_get_bn_param(d_key.get(), OSSL_PKEY_PARAM_RSA_FACTOR1, &prime1) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not get key's first prime (p) parameter");
+  }
+  return BigNum{prime1, BN_clear_free};
+}
+
+BigNum OpenSSLRSADNSCryptoKeyEngine::getKeyParamPrime2() const
+{
+  BIGNUM* prime2 = nullptr;
+  if (EVP_PKEY_get_bn_param(d_key.get(), OSSL_PKEY_PARAM_RSA_FACTOR2, &prime2) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not get key's second prime (q) parameter");
+  }
+  return BigNum{prime2, BN_clear_free};
+}
+
+BigNum OpenSSLRSADNSCryptoKeyEngine::getKeyParamDmp1() const
+{
+  BIGNUM* dmp1 = nullptr;
+  if (EVP_PKEY_get_bn_param(d_key.get(), OSSL_PKEY_PARAM_RSA_EXPONENT1, &dmp1) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not get key's first exponent parameter");
+  }
+  return BigNum{dmp1, BN_clear_free};
+}
+
+BigNum OpenSSLRSADNSCryptoKeyEngine::getKeyParamDmq1() const
+{
+  BIGNUM* dmq1 = nullptr;
+  if (EVP_PKEY_get_bn_param(d_key.get(), OSSL_PKEY_PARAM_RSA_EXPONENT2, &dmq1) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not get key's second exponent parameter");
+  }
+  return BigNum{dmq1, BN_clear_free};
+}
+
+BigNum OpenSSLRSADNSCryptoKeyEngine::getKeyParamIqmp() const
+{
+  BIGNUM* iqmp = nullptr;
+  if (EVP_PKEY_get_bn_param(d_key.get(), OSSL_PKEY_PARAM_RSA_COEFFICIENT1, &iqmp) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not get key's first coefficient parameter");
+  }
+  return BigNum{iqmp, BN_clear_free};
+}
+#endif
+
+#if OPENSSL_VERSION_MAJOR >= 3
+auto OpenSSLRSADNSCryptoKeyEngine::makeKeyParams(const BIGNUM* modulus, const BIGNUM* publicExponent, const BIGNUM* privateExponent, const BIGNUM* prime1, const BIGNUM* prime2, const BIGNUM* dmp1, const BIGNUM* dmq1, const BIGNUM* iqmp) const -> Params
+{
+  auto params_build = ParamsBuilder(OSSL_PARAM_BLD_new(), OSSL_PARAM_BLD_free);
+  if (params_build == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Could not create key's parameters builder");
+  }
+
+  if ((modulus != nullptr) && OSSL_PARAM_BLD_push_BN(params_build.get(), OSSL_PKEY_PARAM_RSA_N, modulus) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not create key's modulus parameter");
+  }
+
+  if ((publicExponent != nullptr) && OSSL_PARAM_BLD_push_BN(params_build.get(), OSSL_PKEY_PARAM_RSA_E, publicExponent) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not create key's public exponent parameter");
+  }
+
+  if ((privateExponent != nullptr) && OSSL_PARAM_BLD_push_BN(params_build.get(), OSSL_PKEY_PARAM_RSA_D, privateExponent) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not create key's private exponent parameter");
+  }
+
+  if ((prime1 != nullptr) && OSSL_PARAM_BLD_push_BN(params_build.get(), OSSL_PKEY_PARAM_RSA_FACTOR1, prime1) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not create key's first prime parameter");
+  }
+
+  if ((prime2 != nullptr) && OSSL_PARAM_BLD_push_BN(params_build.get(), OSSL_PKEY_PARAM_RSA_FACTOR2, prime2) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not create key's second prime parameter");
+  }
+
+  if ((dmp1 != nullptr) && OSSL_PARAM_BLD_push_BN(params_build.get(), OSSL_PKEY_PARAM_RSA_EXPONENT1, dmp1) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not create key's first exponent parameter");
+  }
+
+  if ((dmq1 != nullptr) && OSSL_PARAM_BLD_push_BN(params_build.get(), OSSL_PKEY_PARAM_RSA_EXPONENT2, dmq1) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not create key's second exponent parameter");
+  }
+
+  if ((iqmp != nullptr) && OSSL_PARAM_BLD_push_BN(params_build.get(), OSSL_PKEY_PARAM_RSA_COEFFICIENT1, iqmp) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not create key's first coefficient parameter");
+  }
+
+  auto params = Params(OSSL_PARAM_BLD_to_param(params_build.get()), OSSL_PARAM_free);
+  if (params == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Could not create key's parameters");
+  }
+
+  return params;
+}
+#endif
 
 DNSCryptoKeyEngine::storvector_t OpenSSLRSADNSCryptoKeyEngine::convertToISCVector() const
 {
   storvector_t storvect;
-  typedef vector<pair<string, const BIGNUM*> > outputs_t;
+  using outputs_t = vector<pair<string, const BIGNUM*>>;
   outputs_t outputs;
-  const BIGNUM *n, *e, *d, *p, *q, *dmp1, *dmq1, *iqmp;
-  RSA_get0_key(d_key.get(), &n, &e, &d);
-  RSA_get0_factors(d_key.get(), &p, &q);
+
+#if OPENSSL_VERSION_MAJOR >= 3
+  // If any of those calls throw, we correctly free the BIGNUMs allocated before it.
+  BigNum modulusPtr = getKeyParamModulus();
+  BigNum publicExponentPtr = getKeyParamPublicExponent();
+  BigNum privateExponentPtr = getKeyParamPrivateExponent();
+  BigNum prime1Ptr = getKeyParamPrime1();
+  BigNum prime2Ptr = getKeyParamPrime2();
+  BigNum dmp1Ptr = getKeyParamDmp1();
+  BigNum dmq1Ptr = getKeyParamDmq1();
+  BigNum iqmpPtr = getKeyParamIqmp();
+
+  // All the calls succeeded, we can take references to the BIGNUM pointers.
+  BIGNUM* modulus = modulusPtr.get();
+  BIGNUM* publicExponent = publicExponentPtr.get();
+  BIGNUM* privateExponent = privateExponentPtr.get();
+  BIGNUM* prime1 = prime1Ptr.get();
+  BIGNUM* prime2 = prime2Ptr.get();
+  BIGNUM* dmp1 = dmp1Ptr.get();
+  BIGNUM* dmq1 = dmq1Ptr.get();
+  BIGNUM* iqmp = iqmpPtr.get();
+#else
+  const BIGNUM* modulus = nullptr;
+  const BIGNUM* publicExponent = nullptr;
+  const BIGNUM* privateExponent = nullptr;
+  const BIGNUM* prime1 = nullptr;
+  const BIGNUM* prime2 = nullptr;
+  const BIGNUM* dmp1 = nullptr;
+  const BIGNUM* dmq1 = nullptr;
+  const BIGNUM* iqmp = nullptr;
+  RSA_get0_key(d_key.get(), &modulus, &publicExponent, &privateExponent);
+  RSA_get0_factors(d_key.get(), &prime1, &prime2);
   RSA_get0_crt_params(d_key.get(), &dmp1, &dmq1, &iqmp);
-  outputs.emplace_back("Modulus", n);
-  outputs.emplace_back("PublicExponent", e);
-  outputs.emplace_back("PrivateExponent", d);
-  outputs.emplace_back("Prime1", p);
-  outputs.emplace_back("Prime2", q);
+#endif
+
+  outputs.emplace_back("Modulus", modulus);
+  outputs.emplace_back("PublicExponent", publicExponent);
+  outputs.emplace_back("PrivateExponent", privateExponent);
+  outputs.emplace_back("Prime1", prime1);
+  outputs.emplace_back("Prime2", prime2);
   outputs.emplace_back("Exponent1", dmp1);
   outputs.emplace_back("Exponent2", dmq1);
   outputs.emplace_back("Coefficient", iqmp);
 
-  string algorithm=std::to_string(d_algorithm);
-  switch(d_algorithm) {
-    case DNSSECKeeper::RSASHA1:
-    case DNSSECKeeper::RSASHA1NSEC3SHA1:
-      algorithm += " (RSASHA1)";
-      break;
-    case DNSSECKeeper::RSASHA256:
-      algorithm += " (RSASHA256)";
-      break;
-    case DNSSECKeeper::RSASHA512:
-      algorithm += " (RSASHA512)";
-      break;
-    default:
-      algorithm += " (?)";
+  string algorithm = std::to_string(d_algorithm);
+  switch (d_algorithm) {
+  case DNSSECKeeper::RSASHA1:
+  case DNSSECKeeper::RSASHA1NSEC3SHA1:
+    algorithm += " (RSASHA1)";
+    break;
+  case DNSSECKeeper::RSASHA256:
+    algorithm += " (RSASHA256)";
+    break;
+  case DNSSECKeeper::RSASHA512:
+    algorithm += " (RSASHA512)";
+    break;
+  default:
+    algorithm += " (?)";
   }
   storvect.emplace_back("Algorithm", algorithm);
 
-  for(const outputs_t::value_type& value :  outputs) {
+  for (const outputs_t::value_type& value : outputs) {
     std::string tmp;
     tmp.resize(BN_num_bytes(value.second));
-    int len = BN_bn2bin(value.second, reinterpret_cast<unsigned char*>(&tmp.at(0)));
+    // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+    int len = BN_bn2bin(value.second, reinterpret_cast<unsigned char*>(tmp.data()));
     if (len >= 0) {
       tmp.resize(len);
       storvect.emplace_back(value.first, tmp);
@@ -302,121 +624,194 @@ DNSCryptoKeyEngine::storvector_t OpenSSLRSADNSCryptoKeyEngine::convertToISCVecto
   return storvect;
 }
 
+std::size_t OpenSSLRSADNSCryptoKeyEngine::hashSize() const
+{
+  switch (d_algorithm) {
+  case DNSSECKeeper::RSASHA1:
+  case DNSSECKeeper::RSASHA1NSEC3SHA1:
+    return SHA_DIGEST_LENGTH;
+  case DNSSECKeeper::RSASHA256:
+    return SHA256_DIGEST_LENGTH;
+  case DNSSECKeeper::RSASHA512:
+    return SHA512_DIGEST_LENGTH;
+  default:
+    throw runtime_error(getName() + " does not support hash operations for algorithm " + std::to_string(d_algorithm));
+  }
+}
 
-std::string OpenSSLRSADNSCryptoKeyEngine::hash(const std::string& orig) const
+const EVP_MD* OpenSSLRSADNSCryptoKeyEngine::hasher() const
+{
+  const EVP_MD* messageDigest = nullptr;
+
+  switch (d_algorithm) {
+  case DNSSECKeeper::RSASHA1:
+  case DNSSECKeeper::RSASHA1NSEC3SHA1:
+    messageDigest = EVP_sha1();
+    break;
+  case DNSSECKeeper::RSASHA256:
+    messageDigest = EVP_sha256();
+    break;
+  case DNSSECKeeper::RSASHA512:
+    messageDigest = EVP_sha512();
+    break;
+  default:
+    throw runtime_error(getName() + " does not support hash operations for algorithm " + std::to_string(d_algorithm));
+  }
+
+  if (messageDigest == nullptr) {
+    throw std::runtime_error("Could not retrieve a SHA implementation of size " + std::to_string(hashSize()) + " from OpenSSL");
+  }
+
+  return messageDigest;
+}
+
+std::string OpenSSLRSADNSCryptoKeyEngine::hash(const std::string& message) const
 {
   if (d_algorithm == DNSSECKeeper::RSASHA1 || d_algorithm == DNSSECKeeper::RSASHA1NSEC3SHA1) {
-    unsigned char l_hash[SHA_DIGEST_LENGTH];
-    SHA1((unsigned char*) orig.c_str(), orig.length(), l_hash);
-    return string((char*) l_hash, sizeof(l_hash));
+    std::string l_hash{};
+    l_hash.resize(SHA_DIGEST_LENGTH);
+    // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+    SHA1(reinterpret_cast<unsigned char*>(const_cast<char*>(message.c_str())), message.length(), reinterpret_cast<unsigned char*>(l_hash.data()));
+    return l_hash;
   }
-  else if (d_algorithm == DNSSECKeeper::RSASHA256) {
-    unsigned char l_hash[SHA256_DIGEST_LENGTH];
-    SHA256((unsigned char*) orig.c_str(), orig.length(), l_hash);
-    return string((char*) l_hash, sizeof(l_hash));
+
+  if (d_algorithm == DNSSECKeeper::RSASHA256) {
+    std::string l_hash{};
+    l_hash.resize(SHA256_DIGEST_LENGTH);
+    // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+    SHA256(reinterpret_cast<unsigned char*>(const_cast<char*>(message.c_str())), message.length(), reinterpret_cast<unsigned char*>(l_hash.data()));
+    return l_hash;
   }
-  else if (d_algorithm == DNSSECKeeper::RSASHA512) {
-    unsigned char l_hash[SHA512_DIGEST_LENGTH];
-    SHA512((unsigned char*) orig.c_str(), orig.length(), l_hash);
-    return string((char*) l_hash, sizeof(l_hash));
+
+  if (d_algorithm == DNSSECKeeper::RSASHA512) {
+    std::string l_hash{};
+    l_hash.resize(SHA512_DIGEST_LENGTH);
+    // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+    SHA512(reinterpret_cast<unsigned char*>(const_cast<char*>(message.c_str())), message.length(), reinterpret_cast<unsigned char*>(l_hash.data()));
+    return l_hash;
   }
 
-  throw runtime_error(getName()+" does not support hash operation for algorithm "+std::to_string(d_algorithm));
+  throw runtime_error(getName() + " does not support hash operation for algorithm " + std::to_string(d_algorithm));
 }
 
 int OpenSSLRSADNSCryptoKeyEngine::hashSizeToKind(const size_t hashSize)
 {
-  switch(hashSize) {
-    case SHA_DIGEST_LENGTH:
-      return NID_sha1;
-    case SHA256_DIGEST_LENGTH:
-      return NID_sha256;
-    case SHA384_DIGEST_LENGTH:
-      return NID_sha384;
-    case SHA512_DIGEST_LENGTH:
-      return NID_sha512;
-    default:
-      throw runtime_error("OpenSSL RSA does not handle hash of size " + std::to_string(hashSize));
+  switch (hashSize) {
+  case SHA_DIGEST_LENGTH:
+    return NID_sha1;
+  case SHA256_DIGEST_LENGTH:
+    return NID_sha256;
+  case SHA384_DIGEST_LENGTH:
+    return NID_sha384;
+  case SHA512_DIGEST_LENGTH:
+    return NID_sha512;
+  default:
+    throw runtime_error("OpenSSL RSA does not handle hash of size " + std::to_string(hashSize));
   }
 }
 
-std::string OpenSSLRSADNSCryptoKeyEngine::sign(const std::string& msg) const
+std::string OpenSSLRSADNSCryptoKeyEngine::sign(const std::string& message) const
 {
-  string l_hash = this->hash(msg);
-  int hashKind = hashSizeToKind(l_hash.size());
   std::string signature;
-  signature.resize(RSA_size(d_key.get()));
-  unsigned int signatureLen = 0;
 
-  int res = RSA_sign(hashKind, reinterpret_cast<unsigned char*>(&l_hash.at(0)), l_hash.length(), reinterpret_cast<unsigned char*>(&signature.at(0)), &signatureLen, d_key.get());
-  if (res != 1) {
-    throw runtime_error(getName()+" failed to generate signature");
+#if OPENSSL_VERSION_MAJOR >= 3
+  auto ctx = MessageDigestContext(EVP_MD_CTX_new(), EVP_MD_CTX_free);
+  if (ctx == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Could not create context for signing");
   }
 
-  signature.resize(signatureLen);
-  return signature;
-}
+  if (EVP_DigestSignInit(ctx.get(), nullptr, hasher(), nullptr, d_key.get()) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not initialize context for signing");
+  }
 
+  std::size_t signatureLen = 0;
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  const auto* messageData = reinterpret_cast<const unsigned char*>(message.data());
+  if (EVP_DigestSign(ctx.get(), nullptr, &signatureLen, messageData, message.size()) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not get message signature length");
+  }
 
-bool OpenSSLRSADNSCryptoKeyEngine::verify(const std::string& msg, const std::string& signature) const
-{
-  string l_hash = this->hash(msg);
+  signature.resize(signatureLen);
+
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  auto* signatureData = reinterpret_cast<unsigned char*>(signature.data());
+  if (EVP_DigestSign(ctx.get(), signatureData, &signatureLen, messageData, message.size()) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not sign message");
+  }
+#else
+  unsigned int signatureLen = 0;
+  string l_hash = this->hash(message);
   int hashKind = hashSizeToKind(l_hash.size());
+  signature.resize(RSA_size(d_key.get()));
 
-  int ret = RSA_verify(hashKind, (const unsigned char*)l_hash.c_str(), l_hash.length(), (unsigned char*)signature.c_str(), signature.length(), d_key.get());
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  int res = RSA_sign(hashKind, reinterpret_cast<unsigned char*>(&l_hash.at(0)), l_hash.length(), reinterpret_cast<unsigned char*>(&signature.at(0)), &signatureLen, d_key.get());
+  if (res != 1) {
+    throw runtime_error(getName() + " failed to generate signature");
+  }
 
-  return (ret == 1);
-}
+  signature.resize(signatureLen);
+#endif
 
+  return signature;
+}
 
-std::string OpenSSLRSADNSCryptoKeyEngine::getPubKeyHash() const
+bool OpenSSLRSADNSCryptoKeyEngine::verify(const std::string& message, const std::string& signature) const
 {
-  const BIGNUM *n, *e, *d;
-  RSA_get0_key(d_key.get(), &n, &e, &d);
-  std::vector<unsigned char> tmp;
-  tmp.resize(std::max(BN_num_bytes(e), BN_num_bytes(n)));
-  unsigned char l_hash[SHA_DIGEST_LENGTH];
-  SHA_CTX ctx;
-
-  int res = SHA1_Init(&ctx);
-
-  if (res != 1) {
-    throw runtime_error(getName()+" failed to init hash context for generating the public key hash");
+#if OPENSSL_VERSION_MAJOR >= 3
+  auto ctx = MessageDigestContext(EVP_MD_CTX_new(), EVP_MD_CTX_free);
+  if (ctx == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Failed to create context for verifying signature");
   }
 
-  int len = BN_bn2bin(e, tmp.data());
-  res = SHA1_Update(&ctx, tmp.data(), len);
-  if (res != 1) {
-    throw runtime_error(getName()+" failed to update hash context for generating the public key hash");
+  if (EVP_DigestVerifyInit(ctx.get(), nullptr, hasher(), nullptr, d_key.get()) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Failed to initialize context for verifying signature");
   }
 
-  len = BN_bn2bin(n, tmp.data());
-  res = SHA1_Update(&ctx, tmp.data(), len);
-  if (res != 1) {
-    throw runtime_error(getName()+" failed to update hash context for generating the public key hash");
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  const int ret = EVP_DigestVerify(ctx.get(), reinterpret_cast<const unsigned char*>(signature.data()), signature.size(), reinterpret_cast<const unsigned char*>(message.data()), message.size());
+  if (ret < 0) {
+    throw pdns::OpenSSL::error(getName(), "Failed to verify message signature");
   }
 
-  res = SHA1_Final(l_hash, &ctx);
-  if (res != 1) {
-    throw runtime_error(getName()+" failed to finish hash context for generating the public key hash");
-  }
+  return (ret == 1);
+#else
+  string l_hash = this->hash(message);
+  int hashKind = hashSizeToKind(l_hash.size());
 
-  return string((char*)l_hash, sizeof(l_hash));
-}
+  int ret = RSA_verify(hashKind, (const unsigned char*)l_hash.c_str(), l_hash.length(), (unsigned char*)signature.c_str(), signature.length(), d_key.get());
 
+  return (ret == 1);
+#endif
+}
 
 std::string OpenSSLRSADNSCryptoKeyEngine::getPublicKeyString() const
 {
-  const BIGNUM *n, *e, *d;
-  RSA_get0_key(d_key.get(), &n, &e, &d);
+#if OPENSSL_VERSION_MAJOR >= 3
+  // If any of those calls throw, we correctly free the BIGNUMs allocated before it.
+  BigNum modulusPtr = getKeyParamModulus();
+  BigNum publicExponentPtr = getKeyParamPublicExponent();
+
+  // All the calls succeeded, we can take references to the BIGNUM pointers.
+  BIGNUM* modulus = modulusPtr.get();
+  BIGNUM* publicExponent = publicExponentPtr.get();
+#else
+  const BIGNUM* modulus = nullptr;
+  const BIGNUM* publicExponent = nullptr;
+  const BIGNUM* privateExponent = nullptr;
+  RSA_get0_key(d_key.get(), &modulus, &publicExponent, &privateExponent);
+#endif
+
   string keystring;
   std::string tmp;
-  tmp.resize(std::max(BN_num_bytes(e), BN_num_bytes(n)));
+  tmp.resize(std::max(BN_num_bytes(publicExponent), BN_num_bytes(modulus)));
 
-  int len = BN_bn2bin(e, reinterpret_cast<unsigned char*>(&tmp.at(0)));
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  int len = BN_bn2bin(publicExponent, reinterpret_cast<unsigned char*>(&tmp.at(0)));
   if (len < 255) {
-    keystring.assign(1, (char) (unsigned int) len);
-  } else {
+    keystring.assign(1, (char)(unsigned int)len);
+  }
+  else {
     keystring.assign(1, 0);
     uint16_t tempLen = len;
     tempLen = htons(tempLen);
@@ -424,194 +819,203 @@ std::string OpenSSLRSADNSCryptoKeyEngine::getPublicKeyString() const
   }
   keystring.append(&tmp.at(0), len);
 
-  len = BN_bn2bin(n, reinterpret_cast<unsigned char*>(&tmp.at(0)));
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  len = BN_bn2bin(modulus, reinterpret_cast<unsigned char*>(&tmp.at(0)));
   keystring.append(&tmp.at(0), len);
 
   return keystring;
 }
 
-
-std::unique_ptr<BIGNUM, void(*)(BIGNUM*)>OpenSSLRSADNSCryptoKeyEngine::parse(std::map<std::string, std::string>& stormap, const std::string& key) const
-{
-  const std::string& v = stormap.at(key);
-  auto n = std::unique_ptr<BIGNUM, void(*)(BIGNUM*)>(BN_bin2bn(reinterpret_cast<const unsigned char*>(v.data()), v.length(), nullptr), BN_clear_free);
-
-  if (!n) {
-    throw runtime_error(getName() + " parsing of " + key + " failed");
-  }
-  return n;
-}
-
 void OpenSSLRSADNSCryptoKeyEngine::fromISCMap(DNSKEYRecordContent& drc, std::map<std::string, std::string>& stormap)
 {
-  auto key = std::unique_ptr<RSA, void(*)(RSA*)>(RSA_new(), RSA_free);
-  if (!key) {
-    throw runtime_error(getName() + " allocation of key structure failed");
-  }
-
-  auto n = parse(stormap, "modulus");
-  auto e = parse(stormap, "publicexponent");
-  auto d = parse(stormap, "privateexponent");
+  auto modulus = mapToBN(getName(), stormap, "modulus");
+  auto publicExponent = mapToBN(getName(), stormap, "publicexponent");
+  auto privateExponent = mapToBN(getName(), stormap, "privateexponent");
 
-  auto p = parse(stormap, "prime1");
-  auto q = parse(stormap, "prime2");
+  auto prime1 = mapToBN(getName(), stormap, "prime1");
+  auto prime2 = mapToBN(getName(), stormap, "prime2");
 
-  auto dmp1 = parse(stormap, "exponent1");
-  auto dmq1 = parse(stormap, "exponent2");
-  auto iqmp = parse(stormap, "coefficient");
+  auto dmp1 = mapToBN(getName(), stormap, "exponent1");
+  auto dmq1 = mapToBN(getName(), stormap, "exponent2");
+  auto iqmp = mapToBN(getName(), stormap, "coefficient");
 
   pdns::checked_stoi_into(drc.d_algorithm, stormap["algorithm"]);
 
   if (drc.d_algorithm != d_algorithm) {
     throw runtime_error(getName() + " tried to feed an algorithm " + std::to_string(drc.d_algorithm) + " to a " + std::to_string(d_algorithm) + " key");
   }
+
+#if OPENSSL_VERSION_MAJOR >= 3
+  auto params = makeKeyParams(modulus.get(), publicExponent.get(), privateExponent.get(), prime1.get(), prime2.get(), dmp1.get(), dmq1.get(), iqmp.get());
+
+  auto ctx = KeyContext(EVP_PKEY_CTX_new_from_name(nullptr, "RSA", nullptr), EVP_PKEY_CTX_free);
+  if (ctx == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Could not create key context");
+  }
+
+  if (EVP_PKEY_fromdata_init(ctx.get()) <= 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not initialize key context for loading data from ISC");
+  }
+
+  EVP_PKEY* key = nullptr;
+  if (EVP_PKEY_fromdata(ctx.get(), &key, EVP_PKEY_KEYPAIR, params.get()) <= 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not create key from parameters");
+  }
+
+  d_key.reset(key);
+#else
+  auto key = Key(RSA_new(), RSA_free);
+  if (!key) {
+    throw runtime_error(getName() + " allocation of key structure failed");
+  }
+
   // Everything OK, we're releasing ownership since the RSA_* functions want it
-  RSA_set0_key(key.get(), n.release(), e.release(), d.release());
-  RSA_set0_factors(key.get(), p.release(), q.release());
+  RSA_set0_key(key.get(), modulus.release(), publicExponent.release(), privateExponent.release());
+  RSA_set0_factors(key.get(), prime1.release(), prime2.release());
   RSA_set0_crt_params(key.get(), dmp1.release(), dmq1.release(), iqmp.release());
 
   d_key = std::move(key);
+#endif
 }
 
-bool OpenSSLRSADNSCryptoKeyEngine::checkKey(vector<string> *errorMessages) const
+bool OpenSSLRSADNSCryptoKeyEngine::checkKey(std::optional<std::reference_wrapper<std::vector<std::string>>> errorMessages) const
 {
   bool retval = true;
   // When changing the bitsizes, also edit them in ::create
-  if ((d_algorithm == DNSSECKeeper::RSASHA1 || d_algorithm == DNSSECKeeper::RSASHA1NSEC3SHA1 || d_algorithm == DNSSECKeeper::RSASHA256) && (getBits() < 512 || getBits()> 4096)) {
+  if ((d_algorithm == DNSSECKeeper::RSASHA1 || d_algorithm == DNSSECKeeper::RSASHA1NSEC3SHA1 || d_algorithm == DNSSECKeeper::RSASHA256) && (getBits() < 512 || getBits() > 4096)) {
     retval = false;
-    if (errorMessages != nullptr) {
-      errorMessages->push_back("key is " + std::to_string(getBits()) + " bytes, should be between 512 and 4096");
+    if (errorMessages.has_value()) {
+      errorMessages->get().push_back("key is " + std::to_string(getBits()) + " bytes, should be between 512 and 4096");
     }
   }
   if (d_algorithm == DNSSECKeeper::RSASHA512 && (getBits() < 1024 || getBits() > 4096)) {
     retval = false;
-    if (errorMessages != nullptr) {
-      errorMessages->push_back("key is " + std::to_string(getBits()) + " bytes, should be between 1024 and 4096");
+    if (errorMessages.has_value()) {
+      errorMessages->get().push_back("key is " + std::to_string(getBits()) + " bytes, should be between 1024 and 4096");
     }
   }
+
+#if OPENSSL_VERSION_MAJOR >= 3
+  auto ctx = KeyContext(EVP_PKEY_CTX_new_from_pkey(nullptr, d_key.get(), nullptr), EVP_PKEY_CTX_free);
+  if (ctx == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Cannot create context to check key");
+  }
+
+  if (EVP_PKEY_pairwise_check(ctx.get()) != 1) {
+#else
   if (RSA_check_key(d_key.get()) != 1) {
+#endif
     retval = false;
-    if (errorMessages != nullptr) {
-      auto errmsg = ERR_reason_error_string(ERR_get_error());
+    if (errorMessages.has_value()) {
+      const auto* errmsg = ERR_error_string(ERR_get_error(), nullptr);
       if (errmsg == nullptr) {
         errmsg = "Unknown OpenSSL error";
       }
-      errorMessages->push_back(errmsg);
+      errorMessages->get().emplace_back(errmsg);
     }
   }
   return retval;
 }
 
-void OpenSSLRSADNSCryptoKeyEngine::fromPublicKeyString(const std::string& input)
+void OpenSSLRSADNSCryptoKeyEngine::fromPublicKeyString(const std::string& content)
 {
-  string exponent, modulus;
-  const size_t inputLen = input.length();
-  const unsigned char* raw = (const unsigned char*)input.c_str();
+  string publicExponent;
+  string modulus;
+  const size_t contentLen = content.length();
 
-  if (inputLen < 1) {
-    throw runtime_error(getName()+" invalid input size for the public key");
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  const auto* raw = reinterpret_cast<const unsigned char*>(content.c_str());
+
+  if (contentLen < 1) {
+    throw runtime_error(getName() + " invalid input size for the public key");
   }
 
   if (raw[0] != 0) {
     const size_t exponentSize = raw[0];
-    if (inputLen < (exponentSize + 2)) {
-      throw runtime_error(getName()+" invalid input size for the public key");
+    if (contentLen < (exponentSize + 2)) {
+      throw runtime_error(getName() + " invalid input size for the public key");
     }
-    exponent = input.substr(1, exponentSize);
-    modulus = input.substr(exponentSize + 1);
-  } else {
-    if (inputLen < 3) {
-      throw runtime_error(getName()+" invalid input size for the public key");
+    publicExponent = content.substr(1, exponentSize);
+    modulus = content.substr(exponentSize + 1);
+  }
+  else {
+    if (contentLen < 3) {
+      throw runtime_error(getName() + " invalid input size for the public key");
     }
-    const size_t exponentSize = raw[1]*0xff + raw[2];
-    if (inputLen < (exponentSize + 4)) {
-      throw runtime_error(getName()+" invalid input size for the public key");
+    const size_t exponentSize = raw[1] * 0xff + raw[2];
+    if (contentLen < (exponentSize + 4)) {
+      throw runtime_error(getName() + " invalid input size for the public key");
     }
-    exponent = input.substr(3, exponentSize);
-    modulus = input.substr(exponentSize + 3);
+    publicExponent = content.substr(3, exponentSize);
+    modulus = content.substr(exponentSize + 3);
   }
 
-  auto key = std::unique_ptr<RSA, void(*)(RSA*)>(RSA_new(), RSA_free);
-  if (!key) {
-    throw runtime_error(getName()+" allocation of key structure failed");
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  auto publicExponentBN = BigNum(BN_bin2bn(reinterpret_cast<unsigned char*>(const_cast<char*>(publicExponent.c_str())), static_cast<int>(publicExponent.length()), nullptr), BN_clear_free);
+  if (!publicExponentBN) {
+    throw runtime_error(getName() + " error loading public exponent (e) value of public key");
   }
 
-  auto e = std::unique_ptr<BIGNUM, void(*)(BIGNUM*)>(BN_bin2bn((unsigned char*)exponent.c_str(), exponent.length(), nullptr), BN_clear_free);
-  if (!e) {
-    throw runtime_error(getName()+" error loading e value of public key");
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  auto modulusBN = BigNum(BN_bin2bn(reinterpret_cast<unsigned char*>(const_cast<char*>(modulus.c_str())), static_cast<int>(modulus.length()), nullptr), BN_clear_free);
+  if (!modulusBN) {
+    throw runtime_error(getName() + " error loading modulus (n) value of public key");
   }
-  auto n = std::unique_ptr<BIGNUM, void(*)(BIGNUM*)>(BN_bin2bn((unsigned char*)modulus.c_str(), modulus.length(), nullptr), BN_clear_free);
-  if (!n) {
-    throw runtime_error(getName()+" error loading n value of public key");
+
+#if OPENSSL_VERSION_MAJOR >= 3
+  auto params = makeKeyParams(modulusBN.get(), publicExponentBN.get(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
+
+  auto ctx = KeyContext(EVP_PKEY_CTX_new_from_name(nullptr, "RSA", nullptr), EVP_PKEY_CTX_free);
+  if (ctx == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Cannot create context to load key from public key data");
+  }
+
+  if (EVP_PKEY_fromdata_init(ctx.get()) <= 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not initialize key context for loading data to check key");
   }
 
-  RSA_set0_key(key.get(), n.release(), e.release(), nullptr);
+  EVP_PKEY* key = nullptr;
+  if (EVP_PKEY_fromdata(ctx.get(), &key, EVP_PKEY_PUBLIC_KEY, params.get()) <= 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not create public key from parameters");
+  }
+
+  d_key.reset(key);
+#else
+  auto key = Key(RSA_new(), RSA_free);
+  if (!key) {
+    throw runtime_error(getName() + " allocation of key structure failed");
+  }
+
+  RSA_set0_key(key.get(), modulusBN.release(), publicExponentBN.release(), nullptr);
   d_key = std::move(key);
+#endif
 }
 
 #ifdef HAVE_LIBCRYPTO_ECDSA
 class OpenSSLECDSADNSCryptoKeyEngine : public DNSCryptoKeyEngine
 {
 public:
-  explicit OpenSSLECDSADNSCryptoKeyEngine(unsigned int algo) : DNSCryptoKeyEngine(algo), d_eckey(std::unique_ptr<EC_KEY, void(*)(EC_KEY*)>(EC_KEY_new(), EC_KEY_free)), d_ecgroup(std::unique_ptr<EC_GROUP, void(*)(EC_GROUP*)>(nullptr, EC_GROUP_clear_free))
-  {
+  explicit OpenSSLECDSADNSCryptoKeyEngine(unsigned int algo);
 
-    int ret = RAND_status();
-    if (ret != 1) {
-      throw runtime_error(getName()+" insufficient entropy");
-    }
-
-    if (!d_eckey) {
-      throw runtime_error(getName()+" allocation of key structure failed");
-    }
-
-    if(d_algorithm == 13) {
-      d_ecgroup = std::unique_ptr<EC_GROUP, void(*)(EC_GROUP*)>(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1), EC_GROUP_clear_free);
-      d_len = 32;
-    } else if (d_algorithm == 14) {
-      d_ecgroup = std::unique_ptr<EC_GROUP, void(*)(EC_GROUP*)>(EC_GROUP_new_by_curve_name(NID_secp384r1), EC_GROUP_clear_free);
-      d_len = 48;
-    } else {
-      throw runtime_error(getName()+" unknown algorithm "+std::to_string(d_algorithm));
-    }
-
-    if (!d_ecgroup) {
-      throw runtime_error(getName()+" allocation of group structure failed");
-    }
-
-    ret = EC_KEY_set_group(d_eckey.get(), d_ecgroup.get());
-    if (ret != 1) {
-      throw runtime_error(getName()+" setting key group failed");
-    }
-  }
-
-  ~OpenSSLECDSADNSCryptoKeyEngine()
-  {
-  }
-
-  string getName() const override { return "OpenSSL ECDSA"; }
-  int getBits() const override { return d_len << 3; }
+  [[nodiscard]] string getName() const override { return "OpenSSL ECDSA"; }
+  [[nodiscard]] int getBits() const override;
 
   void create(unsigned int bits) override;
 
   /**
    * \brief Creates an ECDSA key engine from a PEM file.
    *
-   * Receives an open file handle with PEM contents and creates an ECDSA
-   * key engine.
+   * Receives an open file handle with PEM contents and creates an ECDSA key engine.
    *
    * \param[in] drc Key record contents to be populated.
    *
-   * \param[in] filename Only used for providing filename information
-   * in error messages.
+   * \param[in] inputFile An open file handle to a file containing ECDSA PEM contents.
    *
-   * \param[in] fp An open file handle to a file containing ECDSA PEM
-   * contents.
+   * \param[in] filename Only used for providing filename information in error messages.
    *
-   * \return An ECDSA key engine populated with the contents of the PEM
-   * file.
+   * \return An ECDSA key engine populated with the contents of the PEM file.
    */
-  void createFromPEMFile(DNSKEYRecordContent& drc, const std::string& filename, std::FILE& fp) override;
+  void createFromPEMFile(DNSKEYRecordContent& drc, std::FILE& inputFile, std::optional<std::reference_wrapper<const std::string>> filename = std::nullopt) override;
 
   /**
    * \brief Writes this key's contents to a file.
@@ -619,68 +1023,177 @@ public:
    * Receives an open file handle and writes this key's contents to the
    * file.
    *
-   * \param[in] fp An open file handle for writing.
+   * \param[in] outputFile An open file handle for writing.
    *
    * \exception std::runtime_error In case of OpenSSL errors.
    */
-  void convertToPEM(std::FILE& fp) const override;
-
-  storvector_t convertToISCVector() const override;
-  std::string hash(const std::string& hash) const override;
-  std::string sign(const std::string& hash) const override;
-  bool verify(const std::string& hash, const std::string& signature) const override;
-  std::string getPubKeyHash() const override;
-  std::string getPublicKeyString() const override;
+  void convertToPEMFile(std::FILE& outputFile) const override;
+
+  [[nodiscard]] storvector_t convertToISCVector() const override;
+  [[nodiscard]] std::string hash(const std::string& message) const override;
+  [[nodiscard]] std::string sign(const std::string& message) const override;
+  [[nodiscard]] bool verify(const std::string& message, const std::string& signature) const override;
+  [[nodiscard]] std::string getPublicKeyString() const override;
   void fromISCMap(DNSKEYRecordContent& drc, std::map<std::string, std::string>& stormap) override;
   void fromPublicKeyString(const std::string& content) override;
-  bool checkKey(vector<string> *errorMessages) const override;
+  [[nodiscard]] bool checkKey(std::optional<std::reference_wrapper<std::vector<std::string>>> errorMessages) const override;
+
+  // TODO Fred: hashSize() and hasher() can probably be completely removed along with
+  // hash(). See #12464.
+  [[nodiscard]] std::size_t hashSize() const;
+  [[nodiscard]] const EVP_MD* hasher() const;
 
   static std::unique_ptr<DNSCryptoKeyEngine> maker(unsigned int algorithm)
   {
     return make_unique<OpenSSLECDSADNSCryptoKeyEngine>(algorithm);
   }
 
-private:
-  unsigned int d_len;
+private:
+#if OPENSSL_VERSION_MAJOR >= 3
+  using BigNumContext = std::unique_ptr<BN_CTX, decltype(&BN_CTX_free)>;
+  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 makeKeyParams(const std::string& group_name, const BIGNUM* privateKey, const std::optional<std::string>& publicKey) const -> Params;
+  [[nodiscard]] auto getPrivateKey() const -> BigNum;
+#endif
+
+#if OPENSSL_VERSION_MAJOR >= 3
+  using Key = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>;
+  using MessageDigestContext = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>;
+#else
+  using Key = std::unique_ptr<EC_KEY, decltype(&EC_KEY_free)>;
+#endif
+
+  using KeyContext = std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)>;
+  using Group = std::unique_ptr<EC_GROUP, decltype(&EC_GROUP_free)>;
+  using Point = std::unique_ptr<EC_POINT, decltype(&EC_POINT_free)>;
+  using Signature = std::unique_ptr<ECDSA_SIG, decltype(&ECDSA_SIG_free)>;
+
+  int d_len{0};
+  std::string d_group_name{};
+  Group d_group{nullptr, EC_GROUP_free};
+
+#if OPENSSL_VERSION_MAJOR >= 3
+  Key d_eckey{Key(nullptr, EVP_PKEY_free)};
+#else
+  Key d_eckey{Key(nullptr, EC_KEY_free)};
+#endif
+};
+
+int OpenSSLECDSADNSCryptoKeyEngine::getBits() const
+{
+  return d_len << 3;
+}
+
+OpenSSLECDSADNSCryptoKeyEngine::OpenSSLECDSADNSCryptoKeyEngine(unsigned int algo) :
+  DNSCryptoKeyEngine(algo)
+#if OPENSSL_VERSION_MAJOR < 3
+  ,
+  d_eckey(Key(EC_KEY_new(), EC_KEY_free))
+#endif
+{
+  int ret = RAND_status();
+  if (ret != 1) {
+    throw runtime_error(getName() + " insufficient entropy");
+  }
+
+#if OPENSSL_VERSION_MAJOR < 3
+  if (!d_eckey) {
+    throw runtime_error(getName() + " allocation of key structure failed");
+  }
+#endif
+
+  int d_id{0};
 
-  std::unique_ptr<EC_KEY, void(*)(EC_KEY*)> d_eckey;
-  std::unique_ptr<EC_GROUP, void(*)(EC_GROUP*)> d_ecgroup;
-};
+  if (d_algorithm == 13) {
+    d_group_name = "P-256";
+    d_len = 32;
+    d_id = NID_X9_62_prime256v1;
+  }
+  else if (d_algorithm == 14) {
+    d_group_name = "P-384";
+    d_len = 48;
+    d_id = NID_secp384r1;
+  }
+  else {
+    throw runtime_error(getName() + " unknown algorithm " + std::to_string(d_algorithm));
+  }
+
+  d_group = Group(EC_GROUP_new_by_curve_name(d_id), EC_GROUP_free);
+  if (d_group == nullptr) {
+    throw pdns::OpenSSL::error(getName(), std::string() + "Failed to create EC group `" + d_group_name + "` to export public key");
+  }
 
+#if OPENSSL_VERSION_MAJOR < 3
+  ret = EC_KEY_set_group(d_eckey.get(), d_group.get());
+  if (ret != 1) {
+    throw runtime_error(getName() + " setting key group failed");
+  }
+#endif
+}
 
 void OpenSSLECDSADNSCryptoKeyEngine::create(unsigned int bits)
 {
-  if (bits >> 3 != d_len) {
-    throw runtime_error(getName()+" unknown key length of "+std::to_string(bits)+" bits requested");
+  if (bits >> 3 != static_cast<unsigned int>(d_len)) {
+    throw runtime_error(getName() + " unknown key length of " + std::to_string(bits) + " bits requested");
   }
 
+#if OPENSSL_VERSION_MAJOR >= 3
+  // NOLINTNEXTLINE(*-vararg): Using OpenSSL C APIs.
+  EVP_PKEY* key = EVP_PKEY_Q_keygen(nullptr, nullptr, "EC", d_group_name.c_str());
+  if (key == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Failed to generate key");
+  }
+
+  d_eckey.reset(key);
+#else
   int res = EC_KEY_generate_key(d_eckey.get());
   if (res == 0) {
-    throw runtime_error(getName()+" key generation failed");
+    throw runtime_error(getName() + " key generation failed");
   }
+
+  EC_KEY_set_asn1_flag(d_eckey.get(), OPENSSL_EC_NAMED_CURVE);
+#endif
 }
 
-void OpenSSLECDSADNSCryptoKeyEngine::createFromPEMFile(DNSKEYRecordContent& drc, const string& filename, std::FILE& fp)
+void OpenSSLECDSADNSCryptoKeyEngine::createFromPEMFile(DNSKEYRecordContent& drc, std::FILE& inputFile, std::optional<std::reference_wrapper<const std::string>> filename)
 {
   drc.d_algorithm = d_algorithm;
-  d_eckey = std::unique_ptr<EC_KEY, decltype(&EC_KEY_free)>(PEM_read_ECPrivateKey(&fp, nullptr, nullptr, nullptr), &EC_KEY_free);
+
+#if OPENSSL_VERSION_MAJOR >= 3
+  EVP_PKEY* key = nullptr;
+  if (PEM_read_PrivateKey(&inputFile, &key, nullptr, nullptr) == nullptr) {
+    if (filename.has_value()) {
+      throw pdns::OpenSSL::error(getName(), "Failed to read private key from PEM file `" + filename->get() + "`");
+    }
+
+    throw pdns::OpenSSL::error(getName(), "Failed to read private key from PEM contents");
+  }
+
+  d_eckey.reset(key);
+#else
+  d_eckey = Key(PEM_read_ECPrivateKey(&inputFile, nullptr, nullptr, nullptr), &EC_KEY_free);
   if (d_eckey == nullptr) {
-    throw runtime_error(getName() + ": Failed to read private key from PEM file `" + filename + "`");
+    if (filename.has_value()) {
+      throw runtime_error(getName() + ": Failed to read private key from PEM file `" + filename->get() + "`");
+    }
+
+    throw runtime_error(getName() + ": Failed to read private key from PEM contents");
   }
 
-  int ret = EC_KEY_set_group(d_eckey.get(), d_ecgroup.get());
+  int ret = EC_KEY_set_group(d_eckey.get(), d_group.get());
   if (ret != 1) {
     throw runtime_error(getName() + " setting key group failed");
   }
 
   const BIGNUM* privateKeyBN = EC_KEY_get0_private_key(d_eckey.get());
 
-  auto pub_key = std::unique_ptr<EC_POINT, void (*)(EC_POINT*)>(EC_POINT_new(d_ecgroup.get()), EC_POINT_free);
+  auto pub_key = Point(EC_POINT_new(d_group.get()), EC_POINT_free);
   if (!pub_key) {
     throw runtime_error(getName() + " allocation of public key point failed");
   }
 
-  ret = EC_POINT_mul(d_ecgroup.get(), pub_key.get(), privateKeyBN, nullptr, nullptr, nullptr);
+  ret = EC_POINT_mul(d_group.get(), pub_key.get(), privateKeyBN, nullptr, nullptr, nullptr);
   if (ret != 1) {
     throw runtime_error(getName() + " computing public key from private failed");
   }
@@ -692,33 +1205,72 @@ void OpenSSLECDSADNSCryptoKeyEngine::createFromPEMFile(DNSKEYRecordContent& drc,
   }
 
   EC_KEY_set_asn1_flag(d_eckey.get(), OPENSSL_EC_NAMED_CURVE);
+#endif
 }
 
-void OpenSSLECDSADNSCryptoKeyEngine::convertToPEM(std::FILE& fp) const
+void OpenSSLECDSADNSCryptoKeyEngine::convertToPEMFile(std::FILE& outputFile) const
 {
-  auto ret = PEM_write_ECPrivateKey(&fp, d_eckey.get(), nullptr, nullptr, 0, nullptr, nullptr);
+#if OPENSSL_VERSION_MAJOR >= 3
+  if (PEM_write_PrivateKey(&outputFile, d_eckey.get(), nullptr, nullptr, 0, nullptr, nullptr) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Failed to convert private key to PEM");
+  }
+#else
+  auto ret = PEM_write_ECPrivateKey(&outputFile, d_eckey.get(), nullptr, nullptr, 0, nullptr, nullptr);
   if (ret == 0) {
     throw runtime_error(getName() + ": Could not convert private key to PEM");
   }
+#endif
+}
+
+#if OPENSSL_VERSION_MAJOR >= 3
+auto OpenSSLECDSADNSCryptoKeyEngine::getPrivateKey() const -> BigNum
+{
+  BIGNUM* privateKey = nullptr;
+  if (EVP_PKEY_get_bn_param(d_eckey.get(), OSSL_PKEY_PARAM_PRIV_KEY, &privateKey) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not get private key parameter");
+  }
+  return BigNum{privateKey, BN_clear_free};
 }
+#endif
 
 DNSCryptoKeyEngine::storvector_t OpenSSLECDSADNSCryptoKeyEngine::convertToISCVector() const
 {
   storvector_t storvect;
   string algorithm;
 
-  if(d_algorithm == 13)
+  if (d_algorithm == 13) {
     algorithm = "13 (ECDSAP256SHA256)";
-  else if(d_algorithm == 14)
+  }
+  else if (d_algorithm == 14) {
     algorithm = "14 (ECDSAP384SHA384)";
-  else
+  }
+  else {
     algorithm = " ? (?)";
+  }
 
   storvect.emplace_back("Algorithm", algorithm);
 
-  const BIGNUM *key = EC_KEY_get0_private_key(d_eckey.get());
+#if OPENSSL_VERSION_MAJOR >= 3
+  auto privateKeyBN = getPrivateKey();
+
+  std::string privateKey;
+  privateKey.resize(BN_num_bytes(privateKeyBN.get()));
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  int len = BN_bn2bin(privateKeyBN.get(), reinterpret_cast<unsigned char*>(privateKey.data()));
+  if (len >= 0) {
+    privateKey.resize(len);
+
+    std::string prefix;
+    if (d_len - len != 0) {
+      prefix.append(d_len - len, 0x00);
+    }
+
+    storvect.emplace_back("PrivateKey", prefix + privateKey);
+  }
+#else
+  const BIGNUM* key = EC_KEY_get0_private_key(d_eckey.get());
   if (key == nullptr) {
-    throw runtime_error(getName()+" private key not set");
+    throw runtime_error(getName() + " private key not set");
   }
 
   std::string tmp;
@@ -726,108 +1278,256 @@ DNSCryptoKeyEngine::storvector_t OpenSSLECDSADNSCryptoKeyEngine::convertToISCVec
   int len = BN_bn2bin(key, reinterpret_cast<unsigned char*>(&tmp.at(0)));
 
   string prefix;
-  if (d_len - len)
+  if (d_len - len) {
     prefix.append(d_len - len, 0x00);
+  }
 
   storvect.emplace_back("PrivateKey", prefix + tmp);
+#endif
 
   return storvect;
 }
 
+std::string OpenSSLECDSADNSCryptoKeyEngine::hash(const std::string& message) const
+{
+  if (getBits() == 256) {
+    std::string l_hash{};
+    l_hash.resize(SHA256_DIGEST_LENGTH);
+    // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+    SHA256(reinterpret_cast<unsigned char*>(const_cast<char*>(message.c_str())), message.length(), reinterpret_cast<unsigned char*>(l_hash.data()));
+    return l_hash;
+  }
+
+  if (getBits() == 384) {
+    std::string l_hash{};
+    l_hash.resize(SHA384_DIGEST_LENGTH);
+    // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+    SHA384(reinterpret_cast<unsigned char*>(const_cast<char*>(message.c_str())), message.length(), reinterpret_cast<unsigned char*>(l_hash.data()));
+    return l_hash;
+  }
+
+  throw runtime_error(getName() + " does not support a hash size of " + std::to_string(getBits()) + " bits");
+}
 
-std::string OpenSSLECDSADNSCryptoKeyEngine::hash(const std::string& orig) const
+const EVP_MD* OpenSSLECDSADNSCryptoKeyEngine::hasher() const
 {
-  if(getBits() == 256) {
-    unsigned char l_hash[SHA256_DIGEST_LENGTH];
-    SHA256((unsigned char*) orig.c_str(), orig.length(), l_hash);
-    return string((char*)l_hash, sizeof(l_hash));
+  const EVP_MD* messageDigest = nullptr;
+
+  switch (d_algorithm) {
+  case DNSSECKeeper::ECDSA256:
+    messageDigest = EVP_sha256();
+    break;
+  case DNSSECKeeper::ECDSA384:
+    messageDigest = EVP_sha384();
+    break;
+  default:
+    throw runtime_error(getName() + " does not support hash operations for algorithm " + std::to_string(d_algorithm));
   }
-  else if(getBits() == 384) {
-    unsigned char l_hash[SHA384_DIGEST_LENGTH];
-    SHA384((unsigned char*) orig.c_str(), orig.length(), l_hash);
-    return string((char*)l_hash, sizeof(l_hash));
+
+  if (messageDigest == nullptr) {
+    throw std::runtime_error("Could not retrieve a SHA implementation of size " + std::to_string(hashSize()) + " from OpenSSL");
   }
 
-  throw runtime_error(getName()+" does not support a hash size of "+std::to_string(getBits())+" bits");
+  return messageDigest;
 }
 
+std::size_t OpenSSLECDSADNSCryptoKeyEngine::hashSize() const
+{
+  switch (d_algorithm) {
+  case DNSSECKeeper::ECDSA256:
+    return SHA256_DIGEST_LENGTH;
+  case DNSSECKeeper::ECDSA384:
+    return SHA384_DIGEST_LENGTH;
+  default:
+    throw runtime_error(getName() + " does not support hash operations for algorithm " + std::to_string(d_algorithm));
+  }
+}
 
-std::string OpenSSLECDSADNSCryptoKeyEngine::sign(const std::string& msg) const
+std::string OpenSSLECDSADNSCryptoKeyEngine::sign(const std::string& message) const
 {
-  string l_hash = this->hash(msg);
+#if OPENSSL_VERSION_MAJOR >= 3
+  auto ctx = MessageDigestContext(EVP_MD_CTX_new(), EVP_MD_CTX_free);
+  if (ctx == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Could not create context for signing");
+  }
+
+  if (EVP_DigestSignInit(ctx.get(), nullptr, hasher(), nullptr, d_eckey.get()) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not initialize context for signing");
+  }
+
+  std::size_t signatureLen = 0;
+
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  const auto* messageData = reinterpret_cast<const unsigned char*>(message.data());
+  if (EVP_DigestSign(ctx.get(), nullptr, &signatureLen, messageData, message.size()) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not get message signature length");
+  }
+
+  std::string signatureBuffer;
+  signatureBuffer.resize(signatureLen);
+
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  auto* signatureData = reinterpret_cast<unsigned char*>(signatureBuffer.data());
+  if (EVP_DigestSign(ctx.get(), signatureData, &signatureLen, messageData, message.size()) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not sign message");
+  }
+
+  signatureBuffer.resize(signatureLen);
 
-  auto signature = std::unique_ptr<ECDSA_SIG, void(*)(ECDSA_SIG*)>(ECDSA_do_sign((unsigned char*) l_hash.c_str(), l_hash.length(), d_eckey.get()), ECDSA_SIG_free);
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  auto signature = Signature(d2i_ECDSA_SIG(nullptr, const_cast<const unsigned char**>(&signatureData), (long)signatureLen), ECDSA_SIG_free);
+  if (signature == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Failed to convert DER signature to internal structure");
+  }
+#else
+  string l_hash = this->hash(message);
+
+  auto signature = Signature(ECDSA_do_sign((unsigned char*)l_hash.c_str(), l_hash.length(), d_eckey.get()), ECDSA_SIG_free);
   if (!signature) {
-    throw runtime_error(getName()+" failed to generate signature");
+    throw runtime_error(getName() + " failed to generate signature");
   }
+#endif
 
   string ret;
   std::string tmp;
   tmp.resize(d_len);
 
-  const BIGNUM *pr, *ps;
-  ECDSA_SIG_get0(signature.get(), &pr, &ps);
-  int len = BN_bn2bin(pr, reinterpret_cast<unsigned char*>(&tmp.at(0)));
-  if (d_len - len)
+  const BIGNUM* prComponent = nullptr;
+  const BIGNUM* psComponent = nullptr;
+  ECDSA_SIG_get0(signature.get(), &prComponent, &psComponent);
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  int len = BN_bn2bin(prComponent, reinterpret_cast<unsigned char*>(&tmp.at(0)));
+  if ((d_len - len) != 0) {
     ret.append(d_len - len, 0x00);
+  }
   ret.append(&tmp.at(0), len);
 
-  len = BN_bn2bin(ps, reinterpret_cast<unsigned char*>(&tmp.at(0)));
-  if (d_len - len)
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  len = BN_bn2bin(psComponent, reinterpret_cast<unsigned char*>(&tmp.at(0)));
+  if ((d_len - len) != 0) {
     ret.append(d_len - len, 0x00);
+  }
   ret.append(&tmp.at(0), len);
 
   return ret;
 }
 
-
-bool OpenSSLECDSADNSCryptoKeyEngine::verify(const std::string& msg, const std::string& signature) const
+bool OpenSSLECDSADNSCryptoKeyEngine::verify(const std::string& message, const std::string& signature) const
 {
-  if (signature.length() != (d_len * 2)) {
-    throw runtime_error(getName()+" invalid signature size "+std::to_string(signature.length()));
+  if (signature.length() != (static_cast<unsigned long>(d_len) * 2)) {
+    throw runtime_error(getName() + " invalid signature size " + std::to_string(signature.length()));
   }
 
-  string l_hash = this->hash(msg);
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  auto* signatureCStr = const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(signature.c_str()));
+  auto rComponent = BigNum(BN_bin2bn(signatureCStr, d_len, nullptr), BN_free);
+  auto sComponent = BigNum(BN_bin2bn(signatureCStr + d_len, d_len, nullptr), BN_free);
+  if (!rComponent || !sComponent) {
+    throw runtime_error(getName() + " invalid signature");
+  }
 
-  auto sig = std::unique_ptr<ECDSA_SIG, void(*)(ECDSA_SIG*)>(ECDSA_SIG_new(), ECDSA_SIG_free);
+  auto sig = Signature(ECDSA_SIG_new(), ECDSA_SIG_free);
   if (!sig) {
-    throw runtime_error(getName()+" allocation of signature structure failed");
+    throw runtime_error(getName() + " allocation of signature structure failed");
   }
+  ECDSA_SIG_set0(sig.get(), rComponent.release(), sComponent.release());
 
-  auto r = std::unique_ptr<BIGNUM, void(*)(BIGNUM*)>(BN_bin2bn((unsigned char*) signature.c_str(), d_len, nullptr), BN_clear_free);
-  auto s = std::unique_ptr<BIGNUM, void(*)(BIGNUM*)>(BN_bin2bn((unsigned char*) signature.c_str() + d_len, d_len, nullptr), BN_clear_free);
-  if (!r || !s) {
-    throw runtime_error(getName()+" invalid signature");
+#if OPENSSL_VERSION_MAJOR >= 3
+  unsigned char* derBufferPointer = nullptr;
+  const int derBufferSize = i2d_ECDSA_SIG(sig.get(), &derBufferPointer);
+  if (derBufferSize < 0) {
+    throw pdns::OpenSSL::error(getName(), "Failed to convert signature to DER");
   }
+  // Because OPENSSL_free() is a macro.
+  auto derBuffer = unique_ptr<unsigned char, void (*)(unsigned char*)>{derBufferPointer, [](auto* buffer) { OPENSSL_free(buffer); }};
 
-  ECDSA_SIG_set0(sig.get(), r.release(), s.release());
-  int ret = ECDSA_do_verify((unsigned char*) l_hash.c_str(), l_hash.length(), sig.get(), d_eckey.get());
+  auto ctx = MessageDigestContext(EVP_MD_CTX_new(), EVP_MD_CTX_free);
+  if (ctx == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Could not create message digest context for signing");
+  }
+
+  if (EVP_DigestVerifyInit(ctx.get(), nullptr, hasher(), nullptr, d_eckey.get()) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not initialize context for verifying signature");
+  }
 
-  if (ret == -1){
-    throw runtime_error(getName()+" verify error");
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  const auto ret = EVP_DigestVerify(ctx.get(), derBuffer.get(), derBufferSize, reinterpret_cast<const unsigned char*>(message.data()), message.size());
+  if (ret < 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not verify message signature");
   }
 
   return (ret == 1);
-}
+#else
+  string l_hash = this->hash(message);
 
+  int ret = ECDSA_do_verify((unsigned char*)l_hash.c_str(), l_hash.length(), sig.get(), d_eckey.get());
+  if (ret == -1) {
+    throw runtime_error(getName() + " verify error");
+  }
 
-std::string OpenSSLECDSADNSCryptoKeyEngine::getPubKeyHash() const
-{
-  string pubKey = getPublicKeyString();
-  unsigned char l_hash[SHA_DIGEST_LENGTH];
-  SHA1((unsigned char*) pubKey.c_str(), pubKey.length(), l_hash);
-  return string((char*) l_hash, sizeof(l_hash));
+  return (ret == 1);
+#endif
 }
 
-
 std::string OpenSSLECDSADNSCryptoKeyEngine::getPublicKeyString() const
 {
+#if OPENSSL_VERSION_MAJOR >= 3
+  size_t bufsize = 0;
+  if (EVP_PKEY_get_octet_string_param(d_eckey.get(), OSSL_PKEY_PARAM_PUB_KEY, nullptr, 0, &bufsize) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Failed to get public key buffer size");
+  }
+
+  std::string publicKey{};
+  publicKey.resize(bufsize);
+
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  auto* publicKeyCStr = const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(publicKey.c_str()));
+  if (EVP_PKEY_get_octet_string_param(d_eckey.get(), OSSL_PKEY_PARAM_PUB_KEY, publicKeyCStr, bufsize, &bufsize) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Failed to get public key");
+  }
+
+  publicKey.resize(bufsize);
+
+  auto publicKeyECPoint = Point(EC_POINT_new(d_group.get()), EC_POINT_free);
+  if (publicKeyECPoint == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Failed to create public key point for export");
+  }
+
+  auto ctx = BigNumContext(BN_CTX_new(), BN_CTX_free);
+
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  publicKeyCStr = const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(publicKey.c_str()));
+  if (EC_POINT_oct2point(d_group.get(), publicKeyECPoint.get(), publicKeyCStr, publicKey.length(), ctx.get()) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Failed to export public key to point");
+  }
+
+  std::string publicKeyUncompressed{};
+  bufsize = EC_POINT_point2oct(d_group.get(), publicKeyECPoint.get(), POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, nullptr);
+  if (bufsize == 0) {
+    throw pdns::OpenSSL::error(getName(), "Failed to get public key binary buffer size");
+  }
+  publicKeyUncompressed.resize(bufsize);
+
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  auto* publicKeyUncompressedCStr = const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(publicKeyUncompressed.c_str()));
+  bufsize = EC_POINT_point2oct(d_group.get(), publicKeyECPoint.get(), POINT_CONVERSION_UNCOMPRESSED, publicKeyUncompressedCStr, publicKeyUncompressed.length(), nullptr);
+  if (bufsize == 0) {
+    throw pdns::OpenSSL::error(getName(), "Failed to convert public key to oct");
+  }
+
+  /* We skip the first byte as the other backends use raw field elements, as opposed to
+   * the format described in SEC1: "2.3.3 Elliptic-Curve-Point-to-Octet-String
+   * Conversion" */
+  publicKeyUncompressed.erase(0, 1);
+
+  return publicKeyUncompressed;
+#else
   std::string binaryPoint;
   binaryPoint.resize((d_len * 2) + 1);
 
-  int ret = EC_POINT_point2oct(d_ecgroup.get(), EC_KEY_get0_public_key(d_eckey.get()), POINT_CONVERSION_UNCOMPRESSED, reinterpret_cast<unsigned char*>(&binaryPoint.at(0)), binaryPoint.size(), nullptr);
+  int ret = EC_POINT_point2oct(d_group.get(), EC_KEY_get0_public_key(d_eckey.get()), POINT_CONVERSION_UNCOMPRESSED, reinterpret_cast<unsigned char*>(&binaryPoint.at(0)), binaryPoint.size(), nullptr);
   if (ret == 0) {
-    throw runtime_error(getName()+" exporting point to binary failed");
+    throw runtime_error(getName() + " exporting point to binary failed");
   }
 
   /* we skip the first byte as the other backends use
@@ -835,87 +1535,218 @@ std::string OpenSSLECDSADNSCryptoKeyEngine::getPublicKeyString() const
      SEC1: "2.3.3 Elliptic-Curve-Point-to-Octet-String Conversion" */
   binaryPoint.erase(0, 1);
   return binaryPoint;
+#endif
 }
 
+#if OPENSSL_VERSION_MAJOR >= 3
+auto OpenSSLECDSADNSCryptoKeyEngine::makeKeyParams(const std::string& group_name, const BIGNUM* privateKey, const std::optional<std::string>& publicKey) const -> Params
+{
+  auto params_build = ParamsBuilder(OSSL_PARAM_BLD_new(), OSSL_PARAM_BLD_free);
+  if (params_build == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Failed to create key's parameters builder");
+  }
+
+  if ((!group_name.empty()) && OSSL_PARAM_BLD_push_utf8_string(params_build.get(), OSSL_PKEY_PARAM_GROUP_NAME, group_name.c_str(), group_name.length()) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Failed to create key's group parameter");
+  }
+
+  if ((privateKey != nullptr) && OSSL_PARAM_BLD_push_BN(params_build.get(), OSSL_PKEY_PARAM_PRIV_KEY, privateKey) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Failed to create private key parameter");
+  }
+
+  if (publicKey.has_value()) {
+    if (OSSL_PARAM_BLD_push_octet_string(params_build.get(), OSSL_PKEY_PARAM_PUB_KEY, publicKey->c_str(), publicKey->length()) == 0) {
+      throw pdns::OpenSSL::error(getName(), "Failed to create public key parameter");
+    }
+  }
+
+  auto params = Params(OSSL_PARAM_BLD_to_param(params_build.get()), OSSL_PARAM_free);
+  if (params == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Failed to create key's parameters");
+  }
+
+  return params;
+}
+#endif
 
 void OpenSSLECDSADNSCryptoKeyEngine::fromISCMap(DNSKEYRecordContent& drc, std::map<std::string, std::string>& stormap)
 {
   drc.d_algorithm = atoi(stormap["algorithm"].c_str());
 
   if (drc.d_algorithm != d_algorithm) {
-    throw runtime_error(getName()+" tried to feed an algorithm "+std::to_string(drc.d_algorithm)+" to a "+std::to_string(d_algorithm)+" key");
+    throw runtime_error(getName() + " tried to feed an algorithm " + std::to_string(drc.d_algorithm) + " to a " + std::to_string(d_algorithm) + " key");
+  }
+
+  auto privateKey = mapToBN(getName(), stormap, "privatekey");
+
+#if OPENSSL_VERSION_MAJOR >= 3
+  auto publicKeyECPoint = Point(EC_POINT_new(d_group.get()), EC_POINT_free);
+  if (publicKeyECPoint == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Failed to create public key point to import from ISC");
+  }
+
+  if (EC_POINT_mul(d_group.get(), publicKeyECPoint.get(), privateKey.get(), nullptr, nullptr, nullptr) == 0) {
+    throw pdns::OpenSSL::error(getName(), "Failed to derive public key from ISC private key");
   }
 
-  string privateKey = stormap["privatekey"];
+  std::string publicKey{};
+  size_t bufsize = EC_POINT_point2oct(d_group.get(), publicKeyECPoint.get(), POINT_CONVERSION_COMPRESSED, nullptr, 0, nullptr);
+  if (bufsize == 0) {
+    throw pdns::OpenSSL::error(getName(), "Failed to get public key binary buffer size");
+  }
+  publicKey.resize(bufsize);
+
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  auto* publicKeyData = reinterpret_cast<unsigned char*>(publicKey.data());
+  bufsize = EC_POINT_point2oct(d_group.get(), publicKeyECPoint.get(), POINT_CONVERSION_COMPRESSED, publicKeyData, publicKey.length(), nullptr);
+  if (bufsize == 0) {
+    throw pdns::OpenSSL::error(getName(), "Failed to convert public key to oct");
+  }
+
+  auto params = makeKeyParams(d_group_name, privateKey.get(), std::make_optional(publicKey));
+
+  auto ctx = KeyContext(EVP_PKEY_CTX_new_from_name(nullptr, "EC", nullptr), EVP_PKEY_CTX_free);
+  if (ctx == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Could not create key context");
+  }
+
+  if (EVP_PKEY_fromdata_init(ctx.get()) <= 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not initialize key context for loading data from ISC");
+  }
 
-  auto prv_key = std::unique_ptr<BIGNUM, void(*)(BIGNUM*)>(BN_bin2bn((unsigned char*) privateKey.c_str(), privateKey.length(), nullptr), BN_clear_free);
-  if (!prv_key) {
-    throw runtime_error(getName()+" reading private key from binary failed");
+  EVP_PKEY* key = nullptr;
+  if (EVP_PKEY_fromdata(ctx.get(), &key, EVP_PKEY_KEYPAIR, params.get()) <= 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not create key from parameters");
   }
 
-  int ret = EC_KEY_set_private_key(d_eckey.get(), prv_key.get());
+  d_eckey.reset(key);
+#else
+  int ret = EC_KEY_set_private_key(d_eckey.get(), privateKey.get());
   if (ret != 1) {
-    throw runtime_error(getName()+" setting private key failed");
+    throw runtime_error(getName() + " setting private key failed");
   }
 
-  auto pub_key = std::unique_ptr<EC_POINT, void(*)(EC_POINT*)>(EC_POINT_new(d_ecgroup.get()), EC_POINT_free);
+  auto pub_key = Point(EC_POINT_new(d_group.get()), EC_POINT_free);
   if (!pub_key) {
-    throw runtime_error(getName()+" allocation of public key point failed");
+    throw runtime_error(getName() + " allocation of public key point failed");
   }
 
-  ret = EC_POINT_mul(d_ecgroup.get(), pub_key.get(), prv_key.get(), nullptr, nullptr, nullptr);
+  ret = EC_POINT_mul(d_group.get(), pub_key.get(), privateKey.get(), nullptr, nullptr, nullptr);
   if (ret != 1) {
-    throw runtime_error(getName()+" computing public key from private failed");
+    throw runtime_error(getName() + " computing public key from private failed");
   }
 
   ret = EC_KEY_set_public_key(d_eckey.get(), pub_key.get());
   if (ret != 1) {
-    throw runtime_error(getName()+" setting public key failed");
+    throw runtime_error(getName() + " setting public key failed");
   }
+
+  EC_KEY_set_asn1_flag(d_eckey.get(), OPENSSL_EC_NAMED_CURVE);
+#endif
 }
 
-bool OpenSSLECDSADNSCryptoKeyEngine::checkKey(vector<string> *errorMessages) const
+bool OpenSSLECDSADNSCryptoKeyEngine::checkKey(std::optional<std::reference_wrapper<std::vector<std::string>>> errorMessages) const
 {
+#if OPENSSL_VERSION_MAJOR >= 3
+  auto ctx = KeyContext{EVP_PKEY_CTX_new_from_pkey(nullptr, d_eckey.get(), nullptr), EVP_PKEY_CTX_free};
+  if (ctx == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Failed to create context to check key");
+  }
+
+  bool retval = true;
+
+  auto addOpenSSLErrorMessageOnFail = [errorMessages, &retval](const int errorCode, const auto defaultErrorMessage) {
+    // Error code of -2 means the check is not supported for the algorithm, which is fine.
+    if (errorCode != 1 && errorCode != -2) {
+      retval = false;
+
+      if (errorMessages.has_value()) {
+        const auto* errorMessage = ERR_reason_error_string(ERR_get_error());
+        if (errorMessage == nullptr) {
+          errorMessages->get().push_back(defaultErrorMessage);
+        }
+        else {
+          errorMessages->get().emplace_back(errorMessage);
+        }
+      }
+    }
+  };
+
+  addOpenSSLErrorMessageOnFail(EVP_PKEY_param_check(ctx.get()), getName() + "Unknown OpenSSL error during key param check");
+  addOpenSSLErrorMessageOnFail(EVP_PKEY_public_check(ctx.get()), getName() + "Unknown OpenSSL error during public key check");
+  addOpenSSLErrorMessageOnFail(EVP_PKEY_private_check(ctx.get()), getName() + "Unknown OpenSSL error during private key check");
+  addOpenSSLErrorMessageOnFail(EVP_PKEY_pairwise_check(ctx.get()), getName() + "Unknown OpenSSL error during key pairwise check");
+
+  return retval;
+#else
   bool retval = true;
   if (EC_KEY_check_key(d_eckey.get()) != 1) {
     retval = false;
-    if (errorMessages != nullptr) {
-      auto errmsg = ERR_reason_error_string(ERR_get_error());
+    if (errorMessages.has_value()) {
+      const auto* errmsg = ERR_reason_error_string(ERR_get_error());
       if (errmsg == nullptr) {
         errmsg = "Unknown OpenSSL error";
       }
-      errorMessages->push_back(errmsg);
+      errorMessages->get().push_back(errmsg);
     }
   }
   return retval;
+#endif
 }
 
-void OpenSSLECDSADNSCryptoKeyEngine::fromPublicKeyString(const std::string& input)
+void OpenSSLECDSADNSCryptoKeyEngine::fromPublicKeyString(const std::string& content)
 {
-  /* uncompressed point, from SEC1:
-     "2.3.4 Octet-String-to-Elliptic-Curve-Point Conversion" */
-  string ecdsaPoint= "\x04";
-  ecdsaPoint.append(input);
+#if OPENSSL_VERSION_MAJOR >= 3
+  /* uncompressed point, from SEC1: "2.3.4 Octet-String-to-Elliptic-Curve-Point
+   * Conversion"
+   */
+  std::string publicKey = "\x04";
+  publicKey.append(content);
+
+  auto params = makeKeyParams(d_group_name, nullptr, std::make_optional(publicKey));
+
+  auto ctx = KeyContext(EVP_PKEY_CTX_new_from_name(nullptr, "EC", nullptr), EVP_PKEY_CTX_free);
+  if (ctx == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Failed to create key context");
+  }
+
+  if (EVP_PKEY_fromdata_init(ctx.get()) <= 0) {
+    throw pdns::OpenSSL::error(getName(), "Failed to initialize key context for loading data from ISC");
+  }
 
-  auto pub_key = std::unique_ptr<EC_POINT, void(*)(EC_POINT*)>(EC_POINT_new(d_ecgroup.get()), EC_POINT_free);
+  EVP_PKEY* key = nullptr;
+  if (EVP_PKEY_fromdata(ctx.get(), &key, EVP_PKEY_PUBLIC_KEY, params.get()) <= 0) {
+    throw pdns::OpenSSL::error(getName(), "Failed to create key from parameters");
+  }
+
+  d_eckey.reset(key);
+#else
+  /* uncompressed point, from SEC1: "2.3.4 Octet-String-to-Elliptic-Curve-Point
+   * Conversion"
+   */
+  string ecdsaPoint = "\x04";
+  ecdsaPoint.append(content);
+
+  auto pub_key = Point(EC_POINT_new(d_group.get()), EC_POINT_free);
   if (!pub_key) {
-    throw runtime_error(getName()+" allocation of point structure failed");
+    throw runtime_error(getName() + " allocation of point structure failed");
   }
 
-  int ret = EC_POINT_oct2point(d_ecgroup.get(), pub_key.get(), (unsigned char*) ecdsaPoint.c_str(), ecdsaPoint.length(), nullptr);
+  int ret = EC_POINT_oct2point(d_group.get(), pub_key.get(), (unsigned char*)ecdsaPoint.c_str(), ecdsaPoint.length(), nullptr);
   if (ret != 1) {
-    throw runtime_error(getName()+" reading ECP point from binary failed");
+    throw runtime_error(getName() + " reading ECP point from binary failed");
   }
 
   ret = EC_KEY_set_private_key(d_eckey.get(), nullptr);
   if (ret == 1) {
-    throw runtime_error(getName()+" setting private key failed");
+    throw runtime_error(getName() + " setting private key failed");
   }
 
   ret = EC_KEY_set_public_key(d_eckey.get(), pub_key.get());
   if (ret != 1) {
-    throw runtime_error(getName()+" setting public key failed");
+    throw runtime_error(getName() + " setting public key failed");
   }
+#endif
 }
 #endif
 
@@ -923,79 +1754,171 @@ void OpenSSLECDSADNSCryptoKeyEngine::fromPublicKeyString(const std::string& inpu
 class OpenSSLEDDSADNSCryptoKeyEngine : public DNSCryptoKeyEngine
 {
 public:
-  explicit OpenSSLEDDSADNSCryptoKeyEngine(unsigned int algo) : DNSCryptoKeyEngine(algo), d_edkey(std::unique_ptr<EVP_PKEY, void(*)(EVP_PKEY*)>(nullptr, EVP_PKEY_free))
-  {
+  explicit OpenSSLEDDSADNSCryptoKeyEngine(unsigned int algo);
 
-    int ret = RAND_status();
-    if (ret != 1) {
-      throw runtime_error(getName()+" insufficient entropy");
-    }
+  [[nodiscard]] string getName() const override { return "OpenSSL EdDSA"; }
+  [[nodiscard]] int getBits() const override;
 
-#ifdef HAVE_LIBCRYPTO_ED25519
-    if(d_algorithm == 15) {
-      d_len = 32;
-      d_id = NID_ED25519;
-    }
-#endif
-#ifdef HAVE_LIBCRYPTO_ED448
-    if (d_algorithm == 16) {
-      d_len = 57;
-      d_id = NID_ED448;
-    }
-#endif
-    if (d_len == 0) {
-      throw runtime_error(getName()+" unknown algorithm "+std::to_string(d_algorithm));
-    }
-  }
+  void create(unsigned int bits) override;
 
-  ~OpenSSLEDDSADNSCryptoKeyEngine()
-  {
-  }
+  /**
+   * \brief Creates an EDDSA key engine from a PEM file.
+   *
+   * Receives an open file handle with PEM contents and creates an EDDSA key engine.
+   *
+   * \param[in] drc Key record contents to be populated.
+   *
+   * \param[in] inputFile An open file handle to a file containing EDDSA PEM contents.
+   *
+   * \param[in] filename Only used for providing filename information in error messages.
+   *
+   * \return An EDDSA key engine populated with the contents of the PEM file.
+   */
+  void createFromPEMFile(DNSKEYRecordContent& drc, std::FILE& inputFile, std::optional<std::reference_wrapper<const std::string>> filename = std::nullopt) override;
 
-  string getName() const override { return "OpenSSL EDDSA"; }
-  int getBits() const override { return d_len << 3; }
+  /**
+   * \brief Writes this key's contents to a file.
+   *
+   * Receives an open file handle and writes this key's contents to the
+   * file.
+   *
+   * \param[in] outputFile An open file handle for writing.
+   *
+   * \exception std::runtime_error In case of OpenSSL errors.
+   */
+  void convertToPEMFile(std::FILE& outputFile) const override;
 
-  void create(unsigned int bits) override;
-  storvector_t convertToISCVector() const override;
-  std::string sign(const std::string& msg) const override;
-  bool verify(const std::string& msg, const std::string& signature) const override;
-  std::string getPubKeyHash() const override;
-  std::string getPublicKeyString() const override;
+  [[nodiscard]] storvector_t convertToISCVector() const override;
+  [[nodiscard]] std::string sign(const std::string& msg) const override;
+  [[nodiscard]] bool verify(const std::string& message, const std::string& signature) const override;
+  [[nodiscard]] std::string getPublicKeyString() const override;
   void fromISCMap(DNSKEYRecordContent& drc, std::map<std::string, std::string>& stormap) override;
   void fromPublicKeyString(const std::string& content) override;
-  bool checkKey(vector<string> *errorMessages) const override;
+  [[nodiscard]] bool checkKey(std::optional<std::reference_wrapper<std::vector<std::string>>> errorMessages) const override;
 
   static std::unique_ptr<DNSCryptoKeyEngine> maker(unsigned int algorithm)
   {
     return make_unique<OpenSSLEDDSADNSCryptoKeyEngine>(algorithm);
   }
 
+  using Key = unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>;
+  using KeyContext = std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)>;
+  using MessageDigestContext = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>;
+
 private:
   size_t d_len{0};
   int d_id{0};
 
-  std::unique_ptr<EVP_PKEY, void(*)(EVP_PKEY*)> d_edkey;
+  Key d_edkey;
 };
 
-bool OpenSSLEDDSADNSCryptoKeyEngine::checkKey(vector<string> *errorMessages) const
+OpenSSLEDDSADNSCryptoKeyEngine::OpenSSLEDDSADNSCryptoKeyEngine(unsigned int algo) :
+  DNSCryptoKeyEngine(algo),
+  d_edkey(Key(nullptr, EVP_PKEY_free))
+{
+  int ret = RAND_status();
+  if (ret != 1) {
+    throw runtime_error(getName() + " insufficient entropy");
+  }
+
+#ifdef HAVE_LIBCRYPTO_ED25519
+  if (d_algorithm == 15) {
+    d_len = 32;
+    d_id = NID_ED25519;
+  }
+#endif
+#ifdef HAVE_LIBCRYPTO_ED448
+  if (d_algorithm == 16) {
+    d_len = 57;
+    d_id = NID_ED448;
+  }
+#endif
+  if (d_len == 0) {
+    throw runtime_error(getName() + " unknown algorithm " + std::to_string(d_algorithm));
+  }
+}
+
+int OpenSSLEDDSADNSCryptoKeyEngine::getBits() const
 {
+  return (int)d_len << 3;
+}
+
+bool OpenSSLEDDSADNSCryptoKeyEngine::checkKey([[maybe_unused]] std::optional<std::reference_wrapper<std::vector<std::string>>> errorMessages) const
+{
+#if OPENSSL_VERSION_MAJOR >= 3
+  auto ctx = KeyContext{EVP_PKEY_CTX_new_from_pkey(nullptr, d_edkey.get(), nullptr), EVP_PKEY_CTX_free};
+  if (ctx == nullptr) {
+    throw pdns::OpenSSL::error(getName(), "Failed to create context to check key");
+  }
+
+  bool retval = true;
+
+  auto addOpenSSLErrorMessageOnFail = [errorMessages, &retval](const int errorCode, const auto defaultErrorMessage) {
+    // Error code of -2 means the check is not supported for the algorithm, which is fine.
+    if (errorCode != 1 && errorCode != -2) {
+      retval = false;
+
+      if (errorMessages.has_value()) {
+        const auto* errorMessage = ERR_reason_error_string(ERR_get_error());
+        if (errorMessage == nullptr) {
+          errorMessages->get().push_back(defaultErrorMessage);
+        }
+        else {
+          errorMessages->get().emplace_back(errorMessage);
+        }
+      }
+    }
+  };
+
+  addOpenSSLErrorMessageOnFail(EVP_PKEY_param_check(ctx.get()), getName() + "Unknown OpenSSL error during key param check");
+  addOpenSSLErrorMessageOnFail(EVP_PKEY_public_check(ctx.get()), getName() + "Unknown OpenSSL error during public key check");
+  addOpenSSLErrorMessageOnFail(EVP_PKEY_private_check(ctx.get()), getName() + "Unknown OpenSSL error during private key check");
+  addOpenSSLErrorMessageOnFail(EVP_PKEY_pairwise_check(ctx.get()), getName() + "Unknown OpenSSL error during key pairwise check");
+
+  return retval;
+#else
   return (d_edkey ? true : false);
+#endif
 }
 
-void OpenSSLEDDSADNSCryptoKeyEngine::create(unsigned int bits)
+void OpenSSLEDDSADNSCryptoKeyEngine::create(unsigned int /* bits */)
 {
-  auto pctx = std::unique_ptr<EVP_PKEY_CTX, void(*)(EVP_PKEY_CTX*)>(EVP_PKEY_CTX_new_id(d_id, nullptr), EVP_PKEY_CTX_free);
+  auto pctx = KeyContext(EVP_PKEY_CTX_new_id(d_id, nullptr), EVP_PKEY_CTX_free);
   if (!pctx) {
-    throw runtime_error(getName()+" context initialization failed");
+    throw pdns::OpenSSL::error(getName(), "Context initialization failed");
   }
+
   if (EVP_PKEY_keygen_init(pctx.get()) < 1) {
-    throw runtime_error(getName()+" keygen initialization failed");
+    throw pdns::OpenSSL::error(getName(), "Keygen initialization failed");
   }
+
   EVP_PKEY* newKey = nullptr;
   if (EVP_PKEY_keygen(pctx.get(), &newKey) < 1) {
-    throw runtime_error(getName()+" key generation failed");
+    throw pdns::OpenSSL::error(getName(), "Key generation failed");
+  }
+
+  d_edkey.reset(newKey);
+}
+
+void OpenSSLEDDSADNSCryptoKeyEngine::createFromPEMFile(DNSKEYRecordContent& drc, std::FILE& inputFile, std::optional<std::reference_wrapper<const std::string>> filename)
+{
+  drc.d_algorithm = d_algorithm;
+  d_edkey = Key(PEM_read_PrivateKey(&inputFile, nullptr, nullptr, nullptr), &EVP_PKEY_free);
+  if (d_edkey == nullptr) {
+    if (filename.has_value()) {
+      throw pdns::OpenSSL::error(getName(), "Failed to read private key from PEM file `" + filename->get() + "`");
+    }
+
+    throw pdns::OpenSSL::error(getName(), "Failed to read private key from PEM contents");
+  }
+}
+
+void OpenSSLEDDSADNSCryptoKeyEngine::convertToPEMFile(std::FILE& outputFile) const
+{
+  auto ret = PEM_write_PrivateKey(&outputFile, d_edkey.get(), nullptr, nullptr, 0, nullptr, nullptr);
+  if (ret == 0) {
+    throw pdns::OpenSSL::error(getName(), "Could not convert private key to PEM");
   }
-  d_edkey = std::unique_ptr<EVP_PKEY, void(*)(EVP_PKEY*)>(newKey, EVP_PKEY_free);
 }
 
 DNSCryptoKeyEngine::storvector_t OpenSSLEDDSADNSCryptoKeyEngine::convertToISCVector() const
@@ -1004,12 +1927,12 @@ DNSCryptoKeyEngine::storvector_t OpenSSLEDDSADNSCryptoKeyEngine::convertToISCVec
   string algorithm;
 
 #ifdef HAVE_LIBCRYPTO_ED25519
-  if(d_algorithm == 15) {
+  if (d_algorithm == 15) {
     algorithm = "15 (ED25519)";
   }
 #endif
 #ifdef HAVE_LIBCRYPTO_ED448
-  if(d_algorithm == 16) {
+  if (d_algorithm == 16) {
     algorithm = "16 (ED448)";
   }
 #endif
@@ -1022,8 +1945,10 @@ DNSCryptoKeyEngine::storvector_t OpenSSLEDDSADNSCryptoKeyEngine::convertToISCVec
   string buf;
   size_t len = d_len;
   buf.resize(len);
+
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
   if (EVP_PKEY_get_raw_private_key(d_edkey.get(), reinterpret_cast<unsigned char*>(&buf.at(0)), &len) < 1) {
-    throw runtime_error(getName() + " Could not get private key from d_edkey");
+    throw pdns::OpenSSL::error(getName(), "Could not get private key from d_edkey");
   }
   storvect.emplace_back("PrivateKey", buf);
   return storvect;
@@ -1031,12 +1956,12 @@ DNSCryptoKeyEngine::storvector_t OpenSSLEDDSADNSCryptoKeyEngine::convertToISCVec
 
 std::string OpenSSLEDDSADNSCryptoKeyEngine::sign(const std::string& msg) const
 {
-  auto mdctx = std::unique_ptr<EVP_MD_CTX, void(*)(EVP_MD_CTX*)>(EVP_MD_CTX_new(), EVP_MD_CTX_free);
+  auto mdctx = MessageDigestContext(EVP_MD_CTX_new(), EVP_MD_CTX_free);
   if (!mdctx) {
-    throw runtime_error(getName()+" MD context initialization failed");
+    throw pdns::OpenSSL::error(getName(), "MD context initialization failed");
   }
-  if(EVP_DigestSignInit(mdctx.get(), nullptr, nullptr, nullptr, d_edkey.get()) < 1) {
-    throw runtime_error(getName()+" unable to initialize signer");
+  if (EVP_DigestSignInit(mdctx.get(), nullptr, nullptr, nullptr, d_edkey.get()) < 1) {
+    throw pdns::OpenSSL::error(getName(), "Unable to initialize signer");
   }
 
   string msgToSign = msg;
@@ -1046,40 +1971,37 @@ std::string OpenSSLEDDSADNSCryptoKeyEngine::sign(const std::string& msg) const
   signature.resize(siglen);
 
   if (EVP_DigestSign(mdctx.get(),
-        reinterpret_cast<unsigned char*>(&signature.at(0)), &siglen,
-        reinterpret_cast<unsigned char*>(&msgToSign.at(0)), msgToSign.length()) < 1) {
-    throw runtime_error(getName()+" signing error");
+                     // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+                     reinterpret_cast<unsigned char*>(&signature.at(0)), &siglen,
+                     // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+                     reinterpret_cast<unsigned char*>(&msgToSign.at(0)), msgToSign.length())
+      < 1) {
+    throw pdns::OpenSSL::error(getName(), "Signing error");
   }
 
   return signature;
 }
 
-bool OpenSSLEDDSADNSCryptoKeyEngine::verify(const std::string& msg, const std::string& signature) const
+bool OpenSSLEDDSADNSCryptoKeyEngine::verify(const std::string& message, const std::string& signature) const
 {
-  auto mdctx = std::unique_ptr<EVP_MD_CTX, void(*)(EVP_MD_CTX*)>(EVP_MD_CTX_new(), EVP_MD_CTX_free);
-  if (!mdctx) {
-    throw runtime_error(getName()+" MD context initialization failed");
+  auto ctx = MessageDigestContext(EVP_MD_CTX_new(), EVP_MD_CTX_free);
+  if (!ctx) {
+    throw pdns::OpenSSL::error(getName(), "MD context initialization failed");
   }
-  if(EVP_DigestVerifyInit(mdctx.get(), nullptr, nullptr, nullptr, d_edkey.get()) < 1) {
-    throw runtime_error(getName()+" unable to initialize signer");
+  if (EVP_DigestVerifyInit(ctx.get(), nullptr, nullptr, nullptr, d_edkey.get()) < 1) {
+    throw pdns::OpenSSL::error(getName(), "Unable to initialize signer");
   }
 
-  string checkSignature = signature;
-  string checkMsg = msg;
-
-  auto r = EVP_DigestVerify(mdctx.get(),
-      reinterpret_cast<unsigned char*>(&checkSignature.at(0)), checkSignature.length(),
-      reinterpret_cast<unsigned char*>(&checkMsg.at(0)), checkMsg.length());
-  if (r < 0) {
-    throw runtime_error(getName()+" verification failure");
+  auto ret = EVP_DigestVerify(ctx.get(),
+                              // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+                              reinterpret_cast<const unsigned char*>(&signature.at(0)), signature.length(),
+                              // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+                              reinterpret_cast<const unsigned char*>(&message.at(0)), message.length());
+  if (ret < 0) {
+    throw pdns::OpenSSL::error(getName(), "Verification failure");
   }
 
-  return (r == 1);
-}
-
-std::string OpenSSLEDDSADNSCryptoKeyEngine::getPubKeyHash() const
-{
-  return this->getPublicKeyString();
+  return (ret == 1);
 }
 
 std::string OpenSSLEDDSADNSCryptoKeyEngine::getPublicKeyString() const
@@ -1087,21 +2009,26 @@ std::string OpenSSLEDDSADNSCryptoKeyEngine::getPublicKeyString() const
   string buf;
   size_t len = d_len;
   buf.resize(len);
+
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
   if (EVP_PKEY_get_raw_public_key(d_edkey.get(), reinterpret_cast<unsigned char*>(&buf.at(0)), &len) < 1) {
-    throw std::runtime_error(getName() + " unable to get public key from key struct");
+    throw pdns::OpenSSL::error(getName(), "Unable to get public key from key struct");
   }
+
   return buf;
 }
 
-void OpenSSLEDDSADNSCryptoKeyEngine::fromISCMap(DNSKEYRecordContent& drc, std::map<std::string, std::string>& stormap) {
+void OpenSSLEDDSADNSCryptoKeyEngine::fromISCMap(DNSKEYRecordContent& drc, std::map<std::string, std::string>& stormap)
+{
   drc.d_algorithm = atoi(stormap["algorithm"].c_str());
   if (drc.d_algorithm != d_algorithm) {
-    throw runtime_error(getName()+" tried to feed an algorithm "+std::to_string(drc.d_algorithm)+" to a "+std::to_string(d_algorithm)+" key");
+    throw runtime_error(getName() + " tried to feed an algorithm " + std::to_string(drc.d_algorithm) + " to a " + std::to_string(d_algorithm) + " key");
   }
 
-  d_edkey = std::unique_ptr<EVP_PKEY, void(*)(EVP_PKEY*)>(EVP_PKEY_new_raw_private_key(d_id, nullptr, reinterpret_cast<unsigned char*>(&stormap["privatekey"].at(0)), stormap["privatekey"].length()), EVP_PKEY_free);
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  d_edkey = Key(EVP_PKEY_new_raw_private_key(d_id, nullptr, reinterpret_cast<unsigned char*>(&stormap["privatekey"].at(0)), stormap["privatekey"].length()), EVP_PKEY_free);
   if (!d_edkey) {
-    throw std::runtime_error(getName() + " could not create key structure from private key");
+    throw pdns::OpenSSL::error(getName(), "Could not create key structure from private key");
   }
 }
 
@@ -1111,34 +2038,36 @@ void OpenSSLEDDSADNSCryptoKeyEngine::fromPublicKeyString(const std::string& cont
     throw runtime_error(getName() + " wrong public key length for algorithm " + std::to_string(d_algorithm));
   }
 
-  const unsigned char* raw = reinterpret_cast<const unsigned char*>(content.c_str());
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  const auto* raw = reinterpret_cast<const unsigned char*>(content.c_str());
 
-  d_edkey = std::unique_ptr<EVP_PKEY, void(*)(EVP_PKEY*)>(EVP_PKEY_new_raw_public_key(d_id, nullptr, raw, d_len), EVP_PKEY_free);
+  d_edkey = Key(EVP_PKEY_new_raw_public_key(d_id, nullptr, raw, d_len), EVP_PKEY_free);
   if (!d_edkey) {
-    throw runtime_error(getName()+" allocation of public key structure failed");
+    throw pdns::OpenSSL::error(getName(), "Allocation of public key structure failed");
   }
 }
 #endif // HAVE_LIBCRYPTO_EDDSA
 
-namespace {
-  struct LoaderStruct
+namespace
+{
+const struct LoaderStruct
+{
+  LoaderStruct()
   {
-    LoaderStruct()
-    {
-      DNSCryptoKeyEngine::report(5, &OpenSSLRSADNSCryptoKeyEngine::maker);
-      DNSCryptoKeyEngine::report(7, &OpenSSLRSADNSCryptoKeyEngine::maker);
-      DNSCryptoKeyEngine::report(8, &OpenSSLRSADNSCryptoKeyEngine::maker);
-      DNSCryptoKeyEngine::report(10, &OpenSSLRSADNSCryptoKeyEngine::maker);
+    DNSCryptoKeyEngine::report(DNSSECKeeper::RSASHA1, &OpenSSLRSADNSCryptoKeyEngine::maker);
+    DNSCryptoKeyEngine::report(DNSSECKeeper::RSASHA1NSEC3SHA1, &OpenSSLRSADNSCryptoKeyEngine::maker);
+    DNSCryptoKeyEngine::report(DNSSECKeeper::RSASHA256, &OpenSSLRSADNSCryptoKeyEngine::maker);
+    DNSCryptoKeyEngine::report(DNSSECKeeper::RSASHA512, &OpenSSLRSADNSCryptoKeyEngine::maker);
 #ifdef HAVE_LIBCRYPTO_ECDSA
-      DNSCryptoKeyEngine::report(13, &OpenSSLECDSADNSCryptoKeyEngine::maker);
-      DNSCryptoKeyEngine::report(14, &OpenSSLECDSADNSCryptoKeyEngine::maker);
+    DNSCryptoKeyEngine::report(DNSSECKeeper::ECDSA256, &OpenSSLECDSADNSCryptoKeyEngine::maker);
+    DNSCryptoKeyEngine::report(DNSSECKeeper::ECDSA384, &OpenSSLECDSADNSCryptoKeyEngine::maker);
 #endif
 #ifdef HAVE_LIBCRYPTO_ED25519
-      DNSCryptoKeyEngine::report(15, &OpenSSLEDDSADNSCryptoKeyEngine::maker);
+    DNSCryptoKeyEngine::report(DNSSECKeeper::ED25519, &OpenSSLEDDSADNSCryptoKeyEngine::maker);
 #endif
 #ifdef HAVE_LIBCRYPTO_ED448
-      DNSCryptoKeyEngine::report(16, &OpenSSLEDDSADNSCryptoKeyEngine::maker);
+    DNSCryptoKeyEngine::report(DNSSECKeeper::ED448, &OpenSSLEDDSADNSCryptoKeyEngine::maker);
 #endif
-    }
-  } loaderOpenSSL;
+  }
+} loaderOpenSSL;
 }
index 82f22ffcf1bdc29d0e3a8c250010d00ff458e72b..583ecfc8a9f9d04227e85d4f4c00f686906d2712 100644 (file)
@@ -33,7 +33,7 @@ public:
      - EDNS Cookie options, if any ;
      - Any given option code present in optionsToSkip
   */
-  static uint32_t hashAfterQname(const pdns_string_view& packet, uint32_t currentHash, size_t pos, const std::unordered_set<uint16_t>& optionsToSkip = {EDNSOptionCode::COOKIE})
+  static uint32_t hashAfterQname(const std::string_view& packet, uint32_t currentHash, size_t pos, const std::unordered_set<uint16_t>& optionsToSkip = {EDNSOptionCode::COOKIE})
   {
     const size_t packetSize = packet.size();
     assert(packetSize >= sizeof(dnsheader));
@@ -105,27 +105,23 @@ public:
 
   static uint32_t hashHeaderAndQName(const std::string& packet, size_t& pos)
   {
-    uint32_t currentHash = 0;
     const size_t packetSize = packet.size();
     assert(packetSize >= sizeof(dnsheader));
-    currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(2)), sizeof(dnsheader) - 2, currentHash); // rest of dnsheader, skip id
-    pos = sizeof(dnsheader);
+    // Quite some bits in the header are actually irrelevant for
+    // incoming queries.  If we ever change that and ignore them for
+    // hashing, don't forget to also adapt the `queryHeaderMatches`
+    // code, as it should be consistent with the hash function.
+    uint32_t currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(2)), sizeof(dnsheader) - 2, 0); // rest of dnsheader, skip id
 
-    for (; pos < packetSize; ) {
+    for (pos = sizeof(dnsheader); pos < packetSize; ) {
       const unsigned char labelLen = static_cast<unsigned char>(packet.at(pos));
-      currentHash = burtle(&labelLen, 1, currentHash);
       ++pos;
       if (labelLen == 0) {
         break;
       }
-
-      for (size_t idx = 0; idx < labelLen && pos < packetSize; ++idx, ++pos) {
-        const unsigned char l = dns_tolower(packet.at(pos));
-        currentHash = burtle(&l, 1, currentHash);
-      }
+      pos = std::min(pos + labelLen, packetSize);
     }
-
-    return currentHash;
+    return burtleCI(reinterpret_cast<const unsigned char*>(&packet.at(sizeof(dnsheader))), pos - sizeof(dnsheader), currentHash);
   }
 
   /* hash the packet from the beginning, including the qname. This skips:
@@ -137,9 +133,8 @@ public:
   {
     size_t pos = 0;
     uint32_t currentHash = hashHeaderAndQName(packet, pos);
-    size_t packetSize = packet.size();
 
-    if (pos >= packetSize) {
+    if (pos >= packet.size()) {
       return currentHash;
     }
 
@@ -174,7 +169,8 @@ public:
        + the OPT RR rdlen (2)
        = 15
     */
-    const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(query.data());
+    const dnsheader_aligned dnsheaderdata(query.data());
+    const struct dnsheader* dh = dnsheaderdata.get();
     if (ntohs(dh->qdcount) != 1 || ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || ntohs(dh->arcount) != 1 || (pos + 15) >= querySize || optionsToIgnore.empty()) {
       return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
     }
index efb5f07936866f1076016c819be62e58af0dad7d..5c4a97e34aa9ec03018aac68a2ed22db19f8fc1a 100644 (file)
@@ -44,8 +44,9 @@
 #include "communicator.hh"
 #include "dnsproxy.hh"
 #include "version.hh"
-#include "common_startup.hh"
+#include "auth-main.hh"
 #include "trusted-notification-proxy.hh"
+#include "gss_context.hh"
 
 #if 0
 #undef DLOG
@@ -57,13 +58,13 @@ NetmaskGroup PacketHandler::s_allowNotifyFrom;
 set<string> PacketHandler::s_forwardNotify;
 bool PacketHandler::s_SVCAutohints{false};
 
-extern string s_programname;
+extern string g_programname;
 
 // See https://www.rfc-editor.org/rfc/rfc8078.txt and https://www.rfc-editor.org/errata/eid5049 for details
 const std::shared_ptr<CDNSKEYRecordContent> PacketHandler::s_deleteCDNSKEYContent = std::make_shared<CDNSKEYRecordContent>("0 3 0 AA==");
 const std::shared_ptr<CDSRecordContent> PacketHandler::s_deleteCDSContent = std::make_shared<CDSRecordContent>("0 0 0 00");
 
-PacketHandler::PacketHandler():B(s_programname), d_dk(&B)
+PacketHandler::PacketHandler():B(g_programname), d_dk(&B)
 {
   ++s_count;
   d_doDNAME=::arg().mustDo("dname-processing");
@@ -77,7 +78,7 @@ PacketHandler::PacketHandler():B(s_programname), d_dk(&B)
   else
   {
     d_pdl = std::make_unique<AuthLua4>();
-    d_pdl->loadFile(fname);
+    d_pdl->loadFile(fname); // XXX exception handling?
   }
   fname = ::arg()["lua-dnsupdate-policy-script"];
   if (fname.empty())
@@ -87,7 +88,12 @@ PacketHandler::PacketHandler():B(s_programname), d_dk(&B)
   else
   {
     d_update_policy_lua = std::make_unique<AuthLua4>();
-    d_update_policy_lua->loadFile(fname);
+    try {
+      d_update_policy_lua->loadFile(fname);
+    }
+    catch (const std::runtime_error&) {
+      d_update_policy_lua = nullptr;
+    }
   }
 }
 
@@ -123,7 +129,7 @@ bool PacketHandler::addCDNSKEY(DNSPacket& p, std::unique_ptr<DNSPacket>& r)
   rr.auth=true;
 
   if (publishCDNSKEY == "0") { // delete DS via CDNSKEY
-    rr.dr.d_content=s_deleteCDNSKEYContent;
+    rr.dr.setContent(s_deleteCDNSKEYContent);
     r->addRecord(std::move(rr));
     return true;
   }
@@ -134,7 +140,7 @@ bool PacketHandler::addCDNSKEY(DNSPacket& p, std::unique_ptr<DNSPacket>& r)
     if (!value.second.published) {
       continue;
     }
-    rr.dr.d_content=std::make_shared<DNSKEYRecordContent>(value.first.getDNSKEY());
+    rr.dr.setContent(std::make_shared<DNSKEYRecordContent>(value.first.getDNSKEY()));
     r->addRecord(DNSZoneRecord(rr));
     haveOne=true;
   }
@@ -171,7 +177,7 @@ bool PacketHandler::addDNSKEY(DNSPacket& p, std::unique_ptr<DNSPacket>& r)
     rr.dr.d_type=QType::DNSKEY;
     rr.dr.d_ttl=d_sd.minimum;
     rr.dr.d_name=p.qdomain;
-    rr.dr.d_content=std::make_shared<DNSKEYRecordContent>(value.first.getDNSKEY());
+    rr.dr.setContent(std::make_shared<DNSKEYRecordContent>(value.first.getDNSKEY()));
     rr.auth=true;
     r->addRecord(std::move(rr));
     haveOne=true;
@@ -215,7 +221,7 @@ bool PacketHandler::addCDS(DNSPacket& p, std::unique_ptr<DNSPacket>& r)
   rr.auth=true;
 
   if(std::find(digestAlgos.begin(), digestAlgos.end(), "0") != digestAlgos.end()) { // delete DS via CDS
-    rr.dr.d_content=s_deleteCDSContent;
+    rr.dr.setContent(s_deleteCDSContent);
     r->addRecord(std::move(rr));
     return true;
   }
@@ -229,7 +235,7 @@ bool PacketHandler::addCDS(DNSPacket& p, std::unique_ptr<DNSPacket>& r)
       continue;
     }
     for(auto const &digestAlgo : digestAlgos){
-      rr.dr.d_content=std::make_shared<DSRecordContent>(makeDSFromDNSKey(p.qdomain, value.first.getDNSKEY(), pdns::checked_stoi<uint8_t>(digestAlgo)));
+      rr.dr.setContent(std::make_shared<DSRecordContent>(makeDSFromDNSKey(p.qdomain, value.first.getDNSKEY(), pdns::checked_stoi<uint8_t>(digestAlgo))));
       r->addRecord(DNSZoneRecord(rr));
       haveOne=true;
     }
@@ -259,7 +265,7 @@ bool PacketHandler::addNSEC3PARAM(const DNSPacket& p, std::unique_ptr<DNSPacket>
     rr.dr.d_ttl=d_sd.minimum;
     rr.dr.d_name=p.qdomain;
     ns3prc.d_flags = 0; // the NSEC3PARAM 'flag' is defined to always be zero in RFC5155.
-    rr.dr.d_content=std::make_shared<NSEC3PARAMRecordContent>(ns3prc);
+    rr.dr.setContent(std::make_shared<NSEC3PARAMRecordContent>(ns3prc));
     rr.auth = true;
     r->addRecord(std::move(rr));
     return true;
@@ -289,7 +295,7 @@ int PacketHandler::doChaosRequest(const DNSPacket& p, std::unique_ptr<DNSPacket>
       }
       else
         content=mode;
-      rr.dr.d_content = DNSRecordContent::mastermake(QType::TXT, 1, "\""+content+"\"");
+      rr.dr.setContent(DNSRecordContent::make(QType::TXT, 1, "\"" + content + "\""));
     }
     else if (target==idserver) {
       // modes: disabled, hostname or custom
@@ -303,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.d_content=DNSRecordContent::mastermake(QType::TXT, 1, tid);
+      rr.dr.setContent(DNSRecordContent::make(QType::TXT, 1, tid));
     }
     else {
       r->setRcode(RCode::Refused);
@@ -354,7 +360,7 @@ void PacketHandler::getBestDNAMESynth(DNSPacket& p, DNSName &target, vector<DNSZ
       ret.push_back(rr);  // put in the original
       rr.dr.d_type = QType::CNAME;
       rr.dr.d_name = prefix + rr.dr.d_name;
-      rr.dr.d_content = std::make_shared<CNAMERecordContent>(CNAMERecordContent(prefix + getRR<DNAMERecordContent>(rr.dr)->getTarget()));
+      rr.dr.setContent(std::make_shared<CNAMERecordContent>(CNAMERecordContent(prefix + getRR<DNAMERecordContent>(rr.dr)->getTarget())));
       rr.auth = false; // don't sign CNAME
       target = getRR<CNAMERecordContent>(rr.dr)->getTarget();
       ret.push_back(rr);
@@ -378,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;
@@ -396,8 +403,11 @@ 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) {
+      if (rr.dr.d_type == QType::LUA && !d_dk.isPresigned(d_sd.qname)) {
         if(!doLua) {
           DLOG(g_log<<"Have a wildcard LUA match, but not doing LUA record for this zone"<<endl);
           continue;
@@ -413,11 +423,16 @@ bool PacketHandler::getBestWildcard(DNSPacket& p, const DNSName &target, DNSName
           //    noCache=true;
           DLOG(g_log<<"Executing Lua: '"<<rec->getCode()<<"'"<<endl);
           try {
-            auto recvec=luaSynth(rec->getCode(), target, d_sd.qname, d_sd.domain_id, p, rec->d_type);
-            for(const auto& r : recvec) {
+            auto recvec=luaSynth(rec->getCode(), target, d_sd.qname, d_sd.domain_id, p, rec->d_type, s_LUA);
+            for (const auto& r : recvec) {
               rr.dr.d_type = rec->d_type; // might be CNAME
-              rr.dr.d_content = r;
+              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);
             }
           }
@@ -430,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);
       }
 
@@ -453,7 +472,7 @@ bool PacketHandler::getBestWildcard(DNSPacket& p, const DNSName &target, DNSName
   return haveSomething;
 }
 
-DNSName PacketHandler::doAdditionalServiceProcessing(const DNSName &firstTarget, const uint16_t &qtype, std::unique_ptr<DNSPacket>& r, vector<DNSZoneRecord>& extraRecords) {
+DNSName PacketHandler::doAdditionalServiceProcessing(const DNSName &firstTarget, const uint16_t &qtype, std::unique_ptr<DNSPacket>& /* r */, vector<DNSZoneRecord>& extraRecords) {
   DNSName ret = firstTarget;
   size_t ctr = 5; // Max 5 SVCB Aliasforms per query
   bool done = false;
@@ -543,29 +562,40 @@ void PacketHandler::doAdditionalProcessing(DNSPacket& p, std::unique_ptr<DNSPack
     DNSName target = rrc->getTarget().isRoot() ? rec->dr.d_name : rrc->getTarget();
 
     if (rrc->hasParam(SvcParam::ipv4hint) && rrc->autoHint(SvcParam::ipv4hint)) {
+      auto newRRC = rrc->clone();
+      if (!newRRC) {
+        continue;
+      }
       if (s_SVCAutohints) {
         auto hints = getIPAddressFor(target, QType::A);
         if (hints.size() == 0) {
-          rrc->removeParam(SvcParam::ipv4hint);
+          newRRC->removeParam(SvcParam::ipv4hint);
         } else {
-          rrc->setHints(SvcParam::ipv4hint, hints);
+          newRRC->setHints(SvcParam::ipv4hint, hints);
         }
       } else {
-        rrc->removeParam(SvcParam::ipv4hint);
+        newRRC->removeParam(SvcParam::ipv4hint);
       }
+      rrc = newRRC;
+      rec->dr.setContent(std::move(newRRC));
     }
 
     if (rrc->hasParam(SvcParam::ipv6hint) && rrc->autoHint(SvcParam::ipv6hint)) {
+      auto newRRC = rrc->clone();
+      if (!newRRC) {
+        continue;
+      }
       if (s_SVCAutohints) {
         auto hints = getIPAddressFor(target, QType::AAAA);
         if (hints.size() == 0) {
-          rrc->removeParam(SvcParam::ipv6hint);
+          newRRC->removeParam(SvcParam::ipv6hint);
         } else {
-          rrc->setHints(SvcParam::ipv6hint, hints);
+          newRRC->setHints(SvcParam::ipv6hint, hints);
         }
       } else {
-        rrc->removeParam(SvcParam::ipv6hint);
+        newRRC->removeParam(SvcParam::ipv6hint);
       }
+      rec->dr.setContent(std::move(newRRC));
     }
   }
 
@@ -629,21 +659,38 @@ void PacketHandler::emitNSEC(std::unique_ptr<DNSPacket>& r, const DNSName& name,
   }
 
   DNSZoneRecord rr;
+#ifdef HAVE_LUA_RECORDS
+  bool first{true};
+  bool doLua{false};
+#endif
 
   B.lookup(QType(QType::ANY), name, d_sd.domain_id);
   while(B.get(rr)) {
 #ifdef HAVE_LUA_RECORDS
-    if(rr.dr.d_type == QType::LUA)
+    if (rr.dr.d_type == QType::LUA && first && !d_dk.isPresigned(d_sd.qname)) {
+      first = false;
+      doLua = g_doLuaRecord;
+      if (!doLua) {
+        string val;
+        d_dk.getFromMeta(d_sd.qname, "ENABLE-LUA-RECORDS", val);
+        doLua = (val == "1");
+      }
+    }
+
+    if (rr.dr.d_type == QType::LUA && doLua) {
       nrc.set(getRR<LUARecordContent>(rr.dr)->d_type);
+    }
     else
 #endif
-    if(rr.dr.d_type == QType::ALIAS) {
+      if (d_doExpandALIAS && rr.dr.d_type == QType::ALIAS) {
       // Set the A and AAAA in the NSEC bitmap so aggressive NSEC
       // does not falsely deny the type for this name.
       // This does NOT add the ALIAS to the bitmap, as that record cannot
       // be requested.
-      nrc.set(QType::A);
-      nrc.set(QType::AAAA);
+      if (!d_dk.isPresigned(d_sd.qname)) {
+        nrc.set(QType::A);
+        nrc.set(QType::AAAA);
+      }
     }
     else if((rr.dr.d_type == QType::DNSKEY || rr.dr.d_type == QType::CDS || rr.dr.d_type == QType::CDNSKEY) && !d_dk.isPresigned(d_sd.qname) && !::arg().mustDo("direct-dnskey")) {
       continue;
@@ -656,7 +703,7 @@ void PacketHandler::emitNSEC(std::unique_ptr<DNSPacket>& r, const DNSName& name,
   rr.dr.d_name = name;
   rr.dr.d_ttl = d_sd.getNegativeTTL();
   rr.dr.d_type = QType::NSEC;
-  rr.dr.d_content = std::make_shared<NSECRecordContent>(std::move(nrc));
+  rr.dr.setContent(std::make_shared<NSECRecordContent>(std::move(nrc)));
   rr.dr.d_place = (mode == 5 ) ? DNSResourceRecord::ANSWER: DNSResourceRecord::AUTHORITY;
   rr.auth = true;
 
@@ -697,20 +744,38 @@ void PacketHandler::emitNSEC3(std::unique_ptr<DNSPacket>& r, const NSEC3PARAMRec
       }
     }
 
+#ifdef HAVE_LUA_RECORDS
+    bool first{true};
+    bool doLua{false};
+#endif
+
     B.lookup(QType(QType::ANY), name, d_sd.domain_id);
     while(B.get(rr)) {
 #ifdef HAVE_LUA_RECORDS
-      if(rr.dr.d_type == QType::LUA)
+      if (rr.dr.d_type == QType::LUA && first && !d_dk.isPresigned(d_sd.qname)) {
+        first = false;
+        doLua = g_doLuaRecord;
+        if (!doLua) {
+          string val;
+          d_dk.getFromMeta(d_sd.qname, "ENABLE-LUA-RECORDS", val);
+          doLua = (val == "1");
+        }
+      }
+
+      if (rr.dr.d_type == QType::LUA && doLua) {
         n3rc.set(getRR<LUARecordContent>(rr.dr)->d_type);
+      }
       else
 #endif
-      if(rr.dr.d_type == QType::ALIAS) {
+        if (d_doExpandALIAS && rr.dr.d_type == QType::ALIAS) {
         // Set the A and AAAA in the NSEC3 bitmap so aggressive NSEC
         // does not falsely deny the type for this name.
         // This does NOT add the ALIAS to the bitmap, as that record cannot
         // be requested.
-        n3rc.set(QType::A);
-        n3rc.set(QType::AAAA);
+        if (!d_dk.isPresigned(d_sd.qname)) {
+          n3rc.set(QType::A);
+          n3rc.set(QType::AAAA);
+        }
       }
       else if((rr.dr.d_type == QType::DNSKEY || rr.dr.d_type == QType::CDS || rr.dr.d_type == QType::CDNSKEY) && !d_dk.isPresigned(d_sd.qname) && !::arg().mustDo("direct-dnskey")) {
         continue;
@@ -730,7 +795,7 @@ void PacketHandler::emitNSEC3(std::unique_ptr<DNSPacket>& r, const NSEC3PARAMRec
   rr.dr.d_name = DNSName(toBase32Hex(namehash))+d_sd.qname;
   rr.dr.d_ttl = d_sd.getNegativeTTL();
   rr.dr.d_type=QType::NSEC3;
-  rr.dr.d_content=std::make_shared<NSEC3RecordContent>(std::move(n3rc));
+  rr.dr.setContent(std::make_shared<NSEC3RecordContent>(std::move(n3rc)));
   rr.dr.d_place = (mode == 5 ) ? DNSResourceRecord::ANSWER: DNSResourceRecord::AUTHORITY;
   rr.auth = true;
 
@@ -748,7 +813,7 @@ void PacketHandler::emitNSEC3(std::unique_ptr<DNSPacket>& r, const NSEC3PARAMRec
 void PacketHandler::addNSECX(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const DNSName& target, const DNSName& wildcard, int mode)
 {
   NSEC3PARAMRecordContent ns3rc;
-  bool narrow;
+  bool narrow = false;
   if(d_dk.getNSEC3PARAM(d_sd.qname, &ns3rc, &narrow))  {
     if (mode != 5) // no direct NSEC3 queries, rfc5155 7.2.8
       addNSEC3(p, r, target, wildcard, ns3rc, narrow, mode);
@@ -868,7 +933,7 @@ void PacketHandler::addNSEC3(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const
   }
 }
 
-void PacketHandler::addNSEC(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const DNSName& target, const DNSName& wildcard, int mode)
+void PacketHandler::addNSEC(DNSPacket& /* p */, std::unique_ptr<DNSPacket>& r, const DNSName& target, const DNSName& wildcard, int mode)
 {
   DLOG(g_log<<"addNSEC() mode="<<mode<<" auth="<<d_sd.qname<<" target="<<target<<" wildcard="<<wildcard<<endl);
 
@@ -923,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)) {
@@ -970,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;
   }
 
@@ -978,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;
@@ -991,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);
@@ -1005,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;
 }
 
@@ -1018,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;
   }
 
@@ -1060,26 +1125,26 @@ 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;
   }
-  else if(::arg().mustDo("primary") && di.kind == DomainInfo::Master) {
-    g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<" but we are master (Refused)"<<endl;
+  else if (::arg().mustDo("primary") && di.isPrimaryType()) {
+    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;
   }
 
@@ -1094,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;
 }
@@ -1247,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;
@@ -1341,6 +1407,14 @@ std::unique_ptr<DNSPacket> PacketHandler::doQuestion(DNSPacket& p)
       return r;
     } else {
       getTSIGHashEnum(trc.d_algoName, p.d_tsig_algo);
+#ifdef ENABLE_GSS_TSIG
+      if (g_doGssTSIG && p.d_tsig_algo == TSIG_GSS) {
+        GssContext gssctx(keyname);
+        if (!gssctx.getPeerPrincipal(p.d_peer_principal)) {
+          g_log<<Logger::Warning<<"Failed to extract peer principal from GSS context with keyname '"<<keyname<<"'"<<endl;
+        }
+      }
+#endif
     }
     p.setTSIGDetails(trc, keyname, secret, trc.d_mac); // this will get copied by replyPacket()
     noCache=true;
@@ -1516,13 +1590,13 @@ std::unique_ptr<DNSPacket> PacketHandler::doQuestion(DNSPacket& p)
     // see what we get..
     B.lookup(QType(QType::ANY), target, d_sd.domain_id, &p);
     rrset.clear();
-    haveAlias.trimToLabels(0);
+    haveAlias.clear();
     aliasScopeMask = 0;
     weDone = weRedirected = weHaveUnauth =  false;
 
     while(B.get(rr)) {
 #ifdef HAVE_LUA_RECORDS
-      if(rr.dr.d_type == QType::LUA) {
+      if (rr.dr.d_type == QType::LUA && !d_dk.isPresigned(d_sd.qname)) {
         if(!doLua)
           continue;
         auto rec=getRR<LUARecordContent>(rr.dr);
@@ -1532,11 +1606,11 @@ std::unique_ptr<DNSPacket> PacketHandler::doQuestion(DNSPacket& p)
         if(rec->d_type == QType::CNAME || rec->d_type == p.qtype.getCode() || (p.qtype.getCode() == QType::ANY && rec->d_type != QType::RRSIG)) {
           noCache=true;
           try {
-            auto recvec=luaSynth(rec->getCode(), target, d_sd.qname, d_sd.domain_id, p, rec->d_type);
+            auto recvec=luaSynth(rec->getCode(), target, d_sd.qname, d_sd.domain_id, p, rec->d_type, s_LUA);
             if(!recvec.empty()) {
-              for(const auto& r_it : recvec) {
+              for (const auto& r_it : recvec) {
                 rr.dr.d_type = rec->d_type; // might be CNAME
-                rr.dr.d_content = r_it;
+                rr.dr.setContent(r_it);
                 rr.scopeMask = p.getRealRemote().getBits(); // this makes sure answer is a specific as your question
                 rrset.push_back(rr);
               }
@@ -1573,12 +1647,12 @@ std::unique_ptr<DNSPacket> PacketHandler::doQuestion(DNSPacket& p)
       if(rr.dr.d_type == QType::CNAME && p.qtype.getCode() != QType::CNAME)
         weRedirected=true;
 
-      if(DP && rr.dr.d_type == QType::ALIAS && (p.qtype.getCode() == QType::A || p.qtype.getCode() == QType::AAAA || p.qtype.getCode() == QType::ANY)) {
+      if (DP && rr.dr.d_type == QType::ALIAS && (p.qtype.getCode() == QType::A || p.qtype.getCode() == QType::AAAA || p.qtype.getCode() == QType::ANY) && !d_dk.isPresigned(d_sd.qname)) {
         if (!d_doExpandALIAS) {
           g_log<<Logger::Info<<"ALIAS record found for "<<target<<", but ALIAS expansion is disabled."<<endl;
           continue;
         }
-        haveAlias=getRR<ALIASRecordContent>(rr.dr)->d_content;
+        haveAlias=getRR<ALIASRecordContent>(rr.dr)->getContent();
         aliasScopeMask=rr.scopeMask;
       }
 
@@ -1689,12 +1763,20 @@ std::unique_ptr<DNSPacket> PacketHandler::doQuestion(DNSPacket& p)
     }
     else if(weDone) {
       bool haveRecords = false;
+      bool presigned = d_dk.isPresigned(d_sd.qname);
       for(const auto& loopRR: rrset) {
+        if (loopRR.dr.d_type == QType::ENT) {
+          continue;
+        }
+        if (loopRR.dr.d_type == QType::ALIAS && d_doExpandALIAS && !presigned) {
+          continue;
+        }
 #ifdef HAVE_LUA_RECORDS
-        if(loopRR.dr.d_type == QType::LUA)
-            continue;
+        if (loopRR.dr.d_type == QType::LUA && !presigned) {
+          continue;
+        }
 #endif
-        if((p.qtype.getCode() == QType::ANY || loopRR.dr.d_type == p.qtype.getCode()) && loopRR.dr.d_type && loopRR.dr.d_type != QType::ALIAS && loopRR.auth) {
+        if ((p.qtype.getCode() == QType::ANY || loopRR.dr.d_type == p.qtype.getCode()) && loopRR.auth) {
           r->addRecord(DNSZoneRecord(loopRR));
           haveRecords = true;
         }
index e9e79f391fbddf9a50374bb7053970c59cfd825c..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,11 +113,11 @@ 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;
-
+  std::unique_ptr<AuthLua4> s_LUA;
   UeberBackend B; // every thread an own instance
   DNSSECKeeper d_dk; // B is shared with DNSSECKeeper
 };
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 3c43b076dbed3ef0a9dc0b292887fdc2ff82bf8d..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
+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
@@ -40,6 +40,12 @@ RestrictRealtime=true
 RestrictSUIDSGID=true
 SystemCallArchitectures=native
 SystemCallFilter=~ @clock @debug @module @mount @raw-io @reboot @swap @cpu-emulation @obsolete
+ProtectProc=invisible
+PrivateIPC=true
+RemoveIPC=true
+DevicePolicy=closed
+# Not enabled by default because it does not play well with LuaJIT
+# MemoryDenyWriteExecute=true
 
 [Install]
 WantedBy=multi-user.target
diff --git a/pdns/pdns_recursor.cc b/pdns/pdns_recursor.cc
deleted file mode 100644 (file)
index e82b2c2..0000000
+++ /dev/null
@@ -1,2563 +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 "rec-main.hh"
-
-#include "arguments.hh"
-#include "dns_random.hh"
-#include "ednsextendederror.hh"
-#include "ednspadding.hh"
-#include "query-local-address.hh"
-#include "rec-taskqueue.hh"
-#include "responsestats.hh"
-#include "shuffle.hh"
-#include "validate-recursor.hh"
-#include "xpf.hh"
-
-#ifdef HAVE_SYSTEMD
-#include <systemd/sd-daemon.h>
-#endif
-
-#ifdef NOD_ENABLED
-#include "nod.hh"
-#include "logging.hh"
-#endif /* NOD_ENABLED */
-
-thread_local std::shared_ptr<RecursorLua4> t_pdl;
-thread_local std::shared_ptr<Regex> t_traceRegex;
-thread_local std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>> t_protobufServers{nullptr};
-thread_local std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>> t_outgoingProtobufServers{nullptr};
-
-thread_local std::unique_ptr<MT_t> MT; // the big MTasker
-std::unique_ptr<MemRecursorCache> g_recCache;
-std::unique_ptr<NegCache> g_negCache;
-
-thread_local std::unique_ptr<RecursorPacketCache> t_packetCache;
-thread_local std::unique_ptr<FDMultiplexer> t_fdm;
-thread_local std::unique_ptr<addrringbuf_t> t_remotes, t_servfailremotes, t_largeanswerremotes, t_bogusremotes;
-thread_local std::unique_ptr<boost::circular_buffer<pair<DNSName, uint16_t>>> t_queryring, t_servfailqueryring, t_bogusqueryring;
-thread_local std::shared_ptr<NetmaskGroup> t_allowFrom;
-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
-
-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())
-NetmaskGroup g_XPFAcl;
-NetmaskGroup g_paddingFrom;
-size_t g_proxyProtocolMaximumSize;
-size_t g_maxUDPQueriesPerRound;
-unsigned int g_maxMThreads;
-unsigned int g_paddingTag;
-PaddingMode g_paddingMode;
-uint16_t g_udpTruncationThreshold;
-std::atomic<bool> g_quiet;
-bool g_logCommonErrors;
-bool g_reusePort{false};
-bool g_gettagNeedsEDNSOptions{false};
-bool g_useKernelTimestamp;
-std::atomic<uint32_t> g_maxCacheEntries, g_maxPacketCacheEntries;
-#ifdef HAVE_BOOST_CONTAINER_FLAT_SET_HPP
-boost::container::flat_set<uint16_t> g_avoidUdpSourcePorts;
-#else
-std::set<uint16_t> g_avoidUdpSourcePorts;
-#endif
-uint16_t g_minUdpSourcePort;
-uint16_t g_maxUdpSourcePort;
-double g_balancingFactor;
-
-RecursorStats g_stats;
-bool g_lowercaseOutgoing;
-unsigned int g_networkTimeoutMsec;
-uint16_t g_outgoingEDNSBufsize;
-
-// Used in Syncres to counts DNSSEC stats for names in a different "universe"
-GlobalStateHolder<SuffixMatchNode> g_xdnssec;
-// Used in the Syncres to not throttle certain servers
-GlobalStateHolder<SuffixMatchNode> g_dontThrottleNames;
-GlobalStateHolder<NetmaskGroup> g_dontThrottleNetmasks;
-GlobalStateHolder<SuffixMatchNode> g_DoTToAuthNames;
-uint64_t g_latencyStatSize;
-
-LWResult::Result UDPClientSocks::getSocket(const ComboAddress& toaddr, int* fd)
-{
-  *fd = makeClientSocket(toaddr.sin4.sin_family);
-  if (*fd < 0) { // temporary error - receive exception otherwise
-    return LWResult::Result::OSLimitError;
-  }
-
-  if (connect(*fd, (struct sockaddr*)(&toaddr), toaddr.getSocklen()) < 0) {
-    int err = errno;
-    try {
-      closesocket(*fd);
-    }
-    catch (const PDNSException& e) {
-      g_log << Logger::Error << "Error closing UDP socket after connect() failed: " << e.reason << endl;
-    }
-
-    if (err == ENETUNREACH) { // Seth "My Interfaces Are Like A Yo Yo" Arnold special
-      return LWResult::Result::OSLimitError;
-    }
-
-    return LWResult::Result::PermanentError;
-  }
-
-  d_numsocks++;
-  return LWResult::Result::Success;
-}
-
-// return a socket to the pool, or simply erase it
-void UDPClientSocks::returnSocket(int fd)
-{
-  try {
-    t_fdm->removeReadFD(fd);
-  }
-  catch (const FDMultiplexerException& e) {
-    // we sometimes return a socket that has not yet been assigned to t_fdm
-  }
-
-  try {
-    closesocket(fd);
-  }
-  catch (const PDNSException& e) {
-    g_log << Logger::Error << "Error closing returned UDP socket: " << e.reason << endl;
-  }
-
-  --d_numsocks;
-}
-
-// returns -1 for errors which might go away, throws for ones that won't
-int UDPClientSocks::makeClientSocket(int family)
-{
-  int ret = socket(family, SOCK_DGRAM, 0); // turns out that setting CLO_EXEC and NONBLOCK from here is not a performance win on Linux (oddly enough)
-
-  if (ret < 0 && errno == EMFILE) { // this is not a catastrophic error
-    return ret;
-  }
-  if (ret < 0) {
-    throw PDNSException("Making a socket for resolver (family = " + std::to_string(family) + "): " + stringerror());
-  }
-
-  // The loop below runs the body with [tries-1 tries-2 ... 1]. Last iteration with tries == 1 is special: it uses a kernel
-  // allocated UDP port.
-#if !defined(__OpenBSD__)
-  int tries = 10;
-#else
-  int tries = 2; // hit the reliable kernel random case for OpenBSD immediately (because it will match tries==1 below), using sysctl net.inet.udp.baddynamic to exclude ports
-#endif
-  ComboAddress sin;
-  while (--tries) {
-    in_port_t port;
-
-    if (tries == 1) { // last iteration: fall back to kernel 'random'
-      port = 0;
-    }
-    else {
-      do {
-        port = g_minUdpSourcePort + dns_random(g_maxUdpSourcePort - g_minUdpSourcePort + 1);
-      } while (g_avoidUdpSourcePorts.count(port));
-    }
-
-    sin = pdns::getQueryLocalAddress(family, port); // does htons for us
-    if (::bind(ret, reinterpret_cast<struct sockaddr*>(&sin), sin.getSocklen()) >= 0)
-      break;
-  }
-
-  if (!tries) {
-    closesocket(ret);
-    throw PDNSException("Resolver binding to local query client socket on " + sin.toString() + ": " + stringerror());
-  }
-
-  try {
-    setReceiveSocketErrors(ret, family);
-    setNonBlocking(ret);
-  }
-  catch (...) {
-    closesocket(ret);
-    throw;
-  }
-  return ret;
-}
-
-static void handleGenUDPQueryResponse(int fd, FDMultiplexer::funcparam_t& var)
-{
-  std::shared_ptr<PacketID> 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);
-  if (fromaddr != pident->remote) {
-    g_log << Logger::Notice << "Response received from the wrong remote host (" << fromaddr.toStringWithPort() << " instead of " << pident->remote.toStringWithPort() << "), discarding" << endl;
-  }
-
-  t_fdm->removeReadFD(fd);
-  if (ret >= 0) {
-    MT->sendEvent(pident, &resp);
-  }
-  else {
-    PacketBuffer empty;
-    MT->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();
-  ComboAddress local = pdns::getQueryLocalAddress(dest.sin4.sin_family, 0);
-
-  s.bind(local);
-  s.connect(dest);
-  s.send(query);
-
-  std::shared_ptr<PacketID> pident = std::make_shared<PacketID>();
-  pident->fd = s.getHandle();
-  pident->remote = dest;
-  pident->type = 0;
-  t_fdm->addReadFD(s.getHandle(), handleGenUDPQueryResponse, pident);
-
-  PacketBuffer data;
-  int ret = MT->waitEvent(pident, &data, g_networkTimeoutMsec);
-
-  if (!ret || ret == -1) { // timeout
-    t_fdm->removeReadFD(s.getHandle());
-  }
-  else if (data.empty()) { // error, EOF or other
-    // we could special case this
-    return data;
-  }
-  return data;
-}
-
-static void handleUDPServerResponse(int fd, FDMultiplexer::funcparam_t&);
-
-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, int* fd)
-{
-
-  auto pident = std::make_shared<PacketID>();
-  pident->domain = domain;
-  pident->remote = toaddr;
-  pident->type = qtype;
-
-  // 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
-  pair<MT_t::waiters_t::iterator, MT_t::waiters_t::iterator> chain = MT->d_waiters.equal_range(pident, PacketIDBirthdayCompare());
-
-  for (; chain.first != chain.second; chain.first++) {
-    // Line below detected an issue with the two ways of ordering PackeIDs (birtday and non-birthday)
-    assert(chain.first->key->domain == pident->domain);
-    if (chain.first->key->fd > -1 && !chain.first->key->closed) { // don't chain onto existing chained waiter or a chain already processed
-      // cerr << "Insert " << id << ' ' << pident << " into chain for  " << chain.first->key << endl;
-      chain.first->key->chain.insert(id); // we can chain
-      *fd = -1; // gets used in waitEvent / sendEvent later on
-      return LWResult::Result::Success;
-    }
-  }
-
-  auto ret = t_udpclientsocks->getSocket(toaddr, fd);
-  if (ret != LWResult::Result::Success) {
-    return ret;
-  }
-
-  pident->fd = *fd;
-  pident->id = id;
-
-  t_fdm->addReadFD(*fd, handleUDPServerResponse, pident);
-  ssize_t sent = send(*fd, data, len, 0);
-
-  int tmp = errno;
-
-  if (sent < 0) {
-    t_udpclientsocks->returnSocket(*fd);
-    errno = tmp; // this is for logging purposes only
-    return LWResult::Result::PermanentError;
-  }
-
-  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)
-{
-  static const unsigned int nearMissLimit = ::arg().asNum("spoof-nearmiss-max");
-
-  auto pident = std::make_shared<PacketID>();
-  pident->fd = fd;
-  pident->id = id;
-  pident->domain = domain;
-  pident->type = qtype;
-  pident->remote = fromaddr;
-
-  int ret = MT->waitEvent(pident, &packet, g_networkTimeoutMsec, now);
-
-  /* -1 means error, 0 means timeout, 1 means a result from handleUDPServerResponse() which might still be an error */
-  if (ret > 0) {
-    /* handleUDPServerResponse() will close the socket for us no matter what */
-    if (packet.empty()) { // means "error"
-      return LWResult::Result::PermanentError;
-    }
-
-    *d_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. */
-      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_stats.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);
-    }
-  }
-
-  return ret == 0 ? LWResult::Result::Timeout : LWResult::Result::PermanentError;
-}
-
-// 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)
-    t_largeanswerremotes->push_back(remote);
-  switch (res) {
-  case RCode::ServFail:
-    if (t_servfailremotes) {
-      t_servfailremotes->push_back(remote);
-      if (query && t_servfailqueryring) // packet cache
-        t_servfailqueryring->push_back({*query, qtype});
-    }
-    g_stats.servFails++;
-    break;
-  case RCode::NXDomain:
-    g_stats.nxDomains++;
-    break;
-  case RCode::NoError:
-    g_stats.noErrors++;
-    break;
-  }
-}
-
-static string makeLoginfo(const std::unique_ptr<DNSComboWriter>& dc)
-try {
-  return "(" + dc->d_mdp.d_qname.toLogString() + "/" + DNSRecordContent::NumberToType(dc->d_mdp.d_qtype) + " from " + (dc->getRemote()) + ")";
-}
-catch (...) {
-  return "Exception making error message for exception";
-}
-
-/**
- * Chases the CNAME provided by the PolicyCustom RPZ policy.
- *
- * @param spoofed: The DNSRecord that was created by the policy, should already be added to ret
- * @param qtype: The QType of the original query
- * @param sr: A SyncRes
- * @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)
-{
-  if (spoofed.d_type == QType::CNAME) {
-    bool oldWantsRPZ = sr.getWantsRPZ();
-    sr.setWantsRPZ(false);
-    vector<DNSRecord> ans;
-    res = sr.beginResolve(DNSName(spoofed.d_content->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);
-  }
-}
-
-static bool addRecordToPacket(DNSPacketWriter& pw, const DNSRecord& rec, uint32_t& minTTL, uint32_t ttlCap, const uint16_t maxAnswerSize)
-{
-  pw.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::OPT) // their TTL ain't real
-    minTTL = min(minTTL, rec.d_ttl);
-
-  rec.d_content->toPacket(pw);
-  if (pw.size() > static_cast<size_t>(maxAnswerSize)) {
-    pw.rollback();
-    if (rec.d_place != DNSResourceRecord::ADDITIONAL) {
-      pw.getHeader()->tc = 1;
-      pw.truncate();
-    }
-    return false;
-  }
-
-  return true;
-}
-
-/**
- * A helper class that handles the TCP in-flight bookkeeping on
- * destruct. This class ise used by startDoResolve() to not forget
- * that. You can also signal that the TCP connection must be closed
- * once the in-flight connections drop to zero.
- **/
-class RunningResolveGuard
-{
-public:
-  RunningResolveGuard(std::unique_ptr<DNSComboWriter>& dc) :
-    d_dc(dc)
-  {
-    if (d_dc->d_tcp && !d_dc->d_tcpConnection) {
-      throw std::runtime_error("incoming TCP case without TCP connection");
-    }
-  }
-  ~RunningResolveGuard()
-  {
-    if (!d_handled && d_dc->d_tcp) {
-      try {
-        finishTCPReply(d_dc, false, true);
-      }
-      catch (const FDMultiplexerException&) {
-      }
-    }
-  }
-  void setHandled()
-  {
-    d_handled = true;
-  }
-  void setDropOnIdle()
-  {
-    if (d_dc->d_tcp) {
-      d_dc->d_tcpConnection->setDropOnIdle();
-    }
-  }
-
-private:
-  std::unique_ptr<DNSComboWriter>& d_dc;
-  bool d_handled{false};
-};
-
-enum class PolicyResult : uint8_t
-{
-  NoAction,
-  HaveAnswer,
-  Drop
-};
-
-static PolicyResult handlePolicyHit(const DNSFilterEngine::Policy& appliedPolicy, const std::unique_ptr<DNSComboWriter>& dc, SyncRes& sr, int& res, vector<DNSRecord>& ret, DNSPacketWriter& pw, 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) {
-    ++g_stats.policyResults[appliedPolicy.d_kind];
-    ++(g_stats.policyHits.lock()->operator[](appliedPolicy.getName()));
-  }
-
-  if (sr.doLog() && appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) {
-    g_log << Logger::Warning << dc->d_mdp.d_qname << "|" << QType(dc->d_mdp.d_qtype) << appliedPolicy.getLogString() << endl;
-  }
-
-  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;
-  }
-
-  switch (appliedPolicy.d_kind) {
-
-  case DNSFilterEngine::PolicyKind::NoAction:
-    return PolicyResult::NoAction;
-
-  case DNSFilterEngine::PolicyKind::Drop:
-    tcpGuard.setDropOnIdle();
-    ++g_stats.policyDrops;
-    return PolicyResult::Drop;
-
-  case DNSFilterEngine::PolicyKind::NXDOMAIN:
-    ret.clear();
-    res = RCode::NXDomain;
-    return PolicyResult::HaveAnswer;
-
-  case DNSFilterEngine::PolicyKind::NODATA:
-    ret.clear();
-    res = RCode::NoError;
-    return PolicyResult::HaveAnswer;
-
-  case DNSFilterEngine::PolicyKind::Truncate:
-    if (!dc->d_tcp) {
-      ret.clear();
-      res = RCode::NoError;
-      pw.getHeader()->tc = 1;
-      return PolicyResult::HaveAnswer;
-    }
-    return PolicyResult::NoAction;
-
-  case DNSFilterEngine::PolicyKind::Custom:
-    res = RCode::NoError;
-    {
-      auto spoofed = appliedPolicy.getCustomRecords(dc->d_mdp.d_qname, dc->d_mdp.d_qtype);
-      for (auto& dr : spoofed) {
-        ret.push_back(dr);
-        try {
-          handleRPZCustom(dr, QType(dc->d_mdp.d_qtype), sr, res, ret);
-        }
-        catch (const ImmediateServFailException& e) {
-          if (g_logCommonErrors) {
-            g_log << Logger::Notice << "Sending SERVFAIL to " << dc->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '" << dc->d_mdp.d_qname << "' because: " << e.reason << endl;
-          }
-          res = RCode::ServFail;
-          break;
-        }
-        catch (const PolicyHitException& e) {
-          if (g_logCommonErrors) {
-            g_log << Logger::Notice << "Sending SERVFAIL to " << dc->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '" << dc->d_mdp.d_qname << "' because another RPZ policy was hit" << endl;
-          }
-          res = RCode::ServFail;
-          break;
-        }
-      }
-
-      return PolicyResult::HaveAnswer;
-    }
-  }
-
-  return PolicyResult::NoAction;
-}
-
-#ifdef NOD_ENABLED
-static bool nodCheckNewDomain(const shared_ptr<Logr::Logger>& nodlogger, const DNSName& dname)
-{
-  bool ret = false;
-  // First check the (sub)domain isn't ignored for NOD purposes
-  if (!g_nodDomainWL.check(dname)) {
-    // Now check the NODDB (note this is probabilistic so can have FNs/FPs)
-    if (t_nodDBp && t_nodDBp->isNewDomain(dname)) {
-      if (g_nodLog) {
-        // This should probably log to a dedicated log file
-        SLOG(g_log << Logger::Notice << "Newly observed domain nod=" << dname << endl,
-             nodlogger->info(Logr::Notice, "New domain observed"));
-      }
-      ret = true;
-    }
-  }
-  return ret;
-}
-
-static void sendNODLookup(const shared_ptr<Logr::Logger>& nodlogger, const DNSName& dname)
-{
-  if (!(g_nodLookupDomain.isRoot())) {
-    // Send a DNS A query to <domain>.g_nodLookupDomain
-    DNSName qname;
-    try {
-      qname = dname + g_nodLookupDomain;
-    }
-    catch (const std::range_error& e) {
-      nodlogger->v(10)->error(Logr::Error, "DNSName too long", "Unable to send NOD lookup");
-      ++g_stats.nodLookupsDroppedOversize;
-      return;
-    }
-    nodlogger->v(10)->info(Logr::Debug, "Sending NOD lookup", "nodqname", Logging::Loggable(qname));
-    vector<DNSRecord> dummy;
-    directResolve(qname, QType::A, QClass::IN, dummy, nullptr, false);
-  }
-}
-
-static bool udrCheckUniqueDNSRecord(const shared_ptr<Logr::Logger>& nodlogger, const DNSName& dname, uint16_t qtype, const DNSRecord& record)
-{
-  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.d_content->getZoneRepresentation();
-    if (t_udrDBp && t_udrDBp->isUniqueResponse(ss.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.d_content->getZoneRepresentation() << endl,
-             nodlogger->info(Logr::Debug, "New response observed",
-                             "qtype", Logging::Loggable(qtype),
-                             "rrtype", Logging::Loggable(QType(record.d_type)),
-                             "rrname", Logging::Loggable(record.d_name),
-                             "rrcontent", Logging::Loggable(record.d_content->getZoneRepresentation())););
-      }
-      ret = true;
-    }
-  }
-  return ret;
-}
-#endif /* NOD_ENABLED */
-
-static bool answerIsNOData(uint16_t requestedType, int rcode, const std::vector<DNSRecord>& records);
-
-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);
-      if (rec) {
-        target = rec->getTarget();
-        break;
-      }
-    }
-  }
-
-  if (target.empty()) {
-    return rcode;
-  }
-
-  rcode = directResolve(target, qtype, QClass::IN, resolved, t_pdl);
-
-  if (g_dns64Prefix && qtype == QType::AAAA && answerIsNOData(qtype, rcode, resolved)) {
-    rcode = getFakeAAAARecords(target, *g_dns64Prefix, resolved);
-  }
-
-  for (DNSRecord& rr : resolved) {
-    if (rr.d_place == DNSResourceRecord::ANSWER) {
-      ret.push_back(std::move(rr));
-    }
-  }
-  return rcode;
-}
-
-int getFakeAAAARecords(const DNSName& qname, ComboAddress prefix, vector<DNSRecord>& ret)
-{
-  /* we pass a separate vector of records because we will be resolving the initial qname
-     again, possibly encountering the same CNAME(s), and we don't want to trigger the CNAME
-     loop detection. */
-  vector<DNSRecord> newRecords;
-  int rcode = directResolve(qname, QType::A, QClass::IN, newRecords, t_pdl);
-
-  ret.reserve(ret.size() + newRecords.size());
-  for (auto& record : newRecords) {
-    ret.push_back(std::move(record));
-  }
-
-  // Remove double CNAME records
-  std::set<DNSName> seenCNAMEs;
-  ret.erase(std::remove_if(
-              ret.begin(),
-              ret.end(),
-              [&seenCNAMEs](DNSRecord& rr) {
-                if (rr.d_type == QType::CNAME) {
-                  auto target = getRR<CNAMERecordContent>(rr);
-                  if (target == nullptr) {
-                    return false;
-                  }
-                  if (seenCNAMEs.count(target->getTarget()) > 0) {
-                    // We've had this CNAME before, remove it
-                    return true;
-                  }
-                  seenCNAMEs.insert(target->getTarget());
-                }
-                return false;
-              }),
-            ret.end());
-
-  bool seenA = false;
-  for (DNSRecord& rr : ret) {
-    if (rr.d_type == QType::A && rr.d_place == DNSResourceRecord::ANSWER) {
-      if (auto rec = getRR<ARecordContent>(rr)) {
-        ComboAddress ipv4(rec->getCA());
-        memcpy(&prefix.sin6.sin6_addr.s6_addr[12], &ipv4.sin4.sin_addr.s_addr, sizeof(ipv4.sin4.sin_addr.s_addr));
-        rr.d_content = std::make_shared<AAAARecordContent>(prefix);
-        rr.d_type = QType::AAAA;
-      }
-      seenA = true;
-    }
-  }
-
-  if (seenA) {
-    // We've seen an A in the ANSWER section, so there is no need to keep any
-    // SOA in the AUTHORITY section as this is not a NODATA response.
-    ret.erase(std::remove_if(
-                ret.begin(),
-                ret.end(),
-                [](DNSRecord& rr) {
-                  return (rr.d_type == QType::SOA && rr.d_place == DNSResourceRecord::AUTHORITY);
-                }),
-              ret.end());
-  }
-  g_stats.dns64prefixanswers++;
-  return rcode;
-}
-
-int getFakePTRRecords(const DNSName& qname, vector<DNSRecord>& ret)
-{
-  /* qname has a reverse ordered IPv6 address, need to extract the underlying IPv4 address from it
-     and turn it into an IPv4 in-addr.arpa query */
-  ret.clear();
-  vector<string> parts = qname.getRawLabels();
-
-  if (parts.size() < 8) {
-    return -1;
-  }
-
-  string newquery;
-  for (int n = 0; n < 4; ++n) {
-    newquery += std::to_string(stoll(parts[n * 2], 0, 16) + 16 * stoll(parts[n * 2 + 1], 0, 16));
-    newquery.append(1, '.');
-  }
-  newquery += "in-addr.arpa.";
-
-  DNSRecord rr;
-  rr.d_name = qname;
-  rr.d_type = QType::CNAME;
-  rr.d_content = std::make_shared<CNAMERecordContent>(newquery);
-  ret.push_back(rr);
-
-  int rcode = directResolve(DNSName(newquery), QType::PTR, QClass::IN, ret, t_pdl);
-
-  g_stats.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;
-}
-
-bool isAllowNotifyForZone(DNSName qname)
-{
-  if (t_allowNotifyFor->empty()) {
-    return false;
-  }
-
-  notifyset_t::const_iterator ret;
-  do {
-    ret = t_allowNotifyFor->find(qname);
-    if (ret != t_allowNotifyFor->end())
-      return true;
-  } while (qname.chopOff());
-  return false;
-}
-
-void startDoResolve(void* p)
-{
-  auto dc = std::unique_ptr<DNSComboWriter>(reinterpret_cast<DNSComboWriter*>(p));
-  try {
-    if (t_queryring)
-      t_queryring->push_back({dc->d_mdp.d_qname, dc->d_mdp.d_qtype});
-
-    uint16_t maxanswersize = dc->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 haveEDNS = false;
-    bool paddingAllowed = false;
-    bool addPaddingToResponse = false;
-#ifdef NOD_ENABLED
-    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));
-    }
-#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)) {
-      haveEDNS = true;
-      if (edo.d_version != 0) {
-        ednsExtRCode = ERCode::BADVERS;
-      }
-
-      if (!dc->d_tcp) {
-        /* rfc6891 6.2.3:
-           "Values lower than 512 MUST be treated as equal to 512."
-        */
-        maxanswersize = min(static_cast<uint16_t>(edo.d_packetsize >= 512 ? edo.d_packetsize : 512), g_udpTruncationThreshold);
-      }
-      ednsOpts = edo.d_options;
-      maxanswersize -= 11; // EDNS header size
-
-      if (!dc->d_responsePaddingDisabled && g_paddingFrom.match(dc->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);
-        }
-        else if (o.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);
-            variableAnswer = true; // Can't packetcache an answer with NSID
-            maxanswersize -= EDNSOptionCodeSize + EDNSOptionLengthSize + mode_server_id.size();
-          }
-        }
-        else if (paddingAllowed && !addPaddingToResponse && g_paddingMode == PaddingMode::PaddedQueries && o.first == EDNSOptionCode::PADDING) {
-          addPaddingToResponse = true;
-        }
-      }
-    }
-
-    /* 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;
-    }
-
-    /* perhaps there was no EDNS or no ECS but by now we looked */
-    dc->d_ecsParsed = true;
-    vector<DNSRecord> ret;
-    vector<uint8_t> packet;
-
-    auto luaconfsLocal = g_luaconfs.getLocal();
-    // Used to tell syncres later on if we should apply NSDNAME and NSIP RPZ triggers for this query
-    bool wantsRPZ(true);
-    RecursorPacketCache::OptPBData pbDataForCache;
-    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.setServerIdentity(SyncRes::s_serverID);
-
-      // RRSets added below
-    }
-
-#ifdef HAVE_FSTRM
-    checkFrameStreamExport(luaconfsLocal);
-#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);
-
-    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;
-
-    /* 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;
-
-    SyncRes sr(dc->d_now);
-    sr.d_eventTrace = std::move(dc->d_eventTrace);
-    sr.setId(MT->getTid());
-
-    bool DNSSECOK = false;
-    if (dc->d_luaContext) {
-      sr.setLuaEngine(dc->d_luaContext);
-    }
-    if (g_dnssecmode != DNSSECMode::Off) {
-      sr.setDoDNSSEC(true);
-
-      // Does the requestor want DNSSEC records?
-      if (edo.d_extFlags & EDNSOpts::DNSSECOK) {
-        DNSSECOK = true;
-        g_stats.dnssecQueries++;
-      }
-      if (dc->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. */
-        ++g_stats.dnssecCheckDisabledQueries;
-      }
-      if (dc->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
-           indicate that it understands the AD bit without also requesting
-           DNSSEC data via the DO bit. */
-        ++g_stats.dnssecAuthenticDataQueries;
-      }
-    }
-    else {
-      // Ignore the client-set CD flag
-      pw.getHeader()->cd = 0;
-    }
-    sr.setDNSSECValidationRequested(g_dnssecmode == DNSSECMode::ValidateAll || g_dnssecmode == DNSSECMode::ValidateForLog || ((dc->d_mdp.d_header.ad || DNSSECOK) && g_dnssecmode == DNSSECMode::Process));
-
-    sr.setInitialRequestId(dc->d_uuid);
-    sr.setOutgoingProtobufServers(t_outgoingProtobufServers);
-#ifdef HAVE_FSTRM
-    sr.setFrameStreamServers(t_frameStreamServers);
-#endif
-    sr.setQuerySource(dc->d_source, g_useIncomingECS && !dc->d_ednssubnet.source.empty() ? boost::optional<const EDNSSubnetOpts&>(dc->d_ednssubnet) : boost::none);
-    sr.setQueryReceivedOverTCP(dc->d_tcp);
-
-    bool tracedQuery = false; // we could consider letting Lua know about this too
-    bool shouldNotValidate = false;
-
-    /* preresolve expects res (dq.rcode) to be set to RCode::NoError by default */
-    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);
-    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;
-
-    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;
-      res = 0;
-      variableAnswer = true;
-      goto sendit;
-    }
-
-    if (t_traceRegex && t_traceRegex->match(dc->d_mdp.d_qname.toString())) {
-      sr.setLogMode(SyncRes::Store);
-      tracedQuery = true;
-    }
-
-    if (!g_quiet || tracedQuery) {
-      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 << endl;
-    }
-
-    if (!dc->d_mdp.d_header.rd) {
-      sr.setCacheOnly();
-    }
-
-    if (dc->d_luaContext) {
-      dc->d_luaContext->prerpz(dq, res, sr.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 we already have an answer generated from gettag_ffi, let's see if the filtering policies
-       should be applied to it */
-    if (dc->d_rcode != boost::none) {
-
-      bool policyOverride = false;
-      /* Unless we already matched on the client IP, time to check the qname.
-         We normally check it in beginResolve() but it will be bypassed since we already have an answer */
-      if (wantsRPZ && appliedPolicy.policyOverridesGettag()) {
-        if (appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) {
-          // Client IP already matched
-        }
-        else {
-          // no match on the client IP, check the qname
-          if (luaconfsLocal->dfe.getQueryPolicy(dc->d_mdp.d_qname, sr.d_discardedPolicies, appliedPolicy)) {
-            // got a match
-            mergePolicyTags(dc->d_policyTags, appliedPolicy.getTags());
-          }
-        }
-
-        if (appliedPolicy.wasHit()) {
-          policyOverride = true;
-        }
-      }
-
-      if (!policyOverride) {
-        /* No RPZ or gettag overrides it anyway */
-        ret = std::move(dc->d_records);
-        res = *dc->d_rcode;
-        if (res == RCode::NoError && dc->d_followCNAMERecords) {
-          res = followCNAMERecords(ret, QType(dc->d_mdp.d_qtype), res);
-        }
-        goto haveAnswer;
-      }
-    }
-
-    // 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 (!g_dns64PrefixReverse.empty() && dq.qtype == QType::PTR && dq.qname.isPartOf(g_dns64PrefixReverse)) {
-        res = getFakePTRRecords(dq.qname, ret);
-        goto haveAnswer;
-      }
-
-      sr.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)) {
-          /* reset to no match */
-          appliedPolicy = DNSFilterEngine::Policy();
-        }
-        else {
-          auto policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, tcpGuard);
-          if (policyResult == PolicyResult::HaveAnswer) {
-            if (g_dns64Prefix && dq.qtype == QType::AAAA && answerIsNOData(dc->d_mdp.d_qtype, res, ret)) {
-              res = getFakeAAAARecords(dq.qname, *g_dns64Prefix, ret);
-              shouldNotValidate = true;
-            }
-            goto haveAnswer;
-          }
-          else if (policyResult == PolicyResult::Drop) {
-            return;
-          }
-        }
-      }
-
-      // Query did not get handled for Client IP or QNAME Policy reasons, now actually go out to find an answer
-      try {
-        sr.d_appliedPolicy = appliedPolicy;
-        sr.d_policyTags = std::move(dc->d_policyTags);
-
-        if (!dc->d_routingTag.empty()) {
-          sr.d_routingTag = dc->d_routingTag;
-        }
-
-        ret.clear(); // policy might have filled it with custom records but we decided not to use them
-        res = sr.beginResolve(dc->d_mdp.d_qname, QType(dc->d_mdp.d_qtype), dc->d_mdp.d_qclass, ret);
-        shouldNotValidate = sr.wasOutOfBand();
-      }
-      catch (const ImmediateQueryDropException& e) {
-        // XXX We need to export a protobuf message (and do a NOD lookup) if requested!
-        g_stats.policyDrops++;
-        g_log << Logger::Debug << "Dropping query because of a filtering policy " << makeLoginfo(dc) << endl;
-        return;
-      }
-      catch (const ImmediateServFailException& e) {
-        if (g_logCommonErrors) {
-          g_log << Logger::Notice << "Sending SERVFAIL to " << dc->getRemote() << " during resolve of '" << dc->d_mdp.d_qname << "' because: " << e.reason << endl;
-        }
-        res = RCode::ServFail;
-      }
-      catch (const SendTruncatedAnswerException& e) {
-        ret.clear();
-        res = RCode::NoError;
-        pw.getHeader()->tc = 1;
-      }
-      catch (const PolicyHitException& e) {
-        res = -2;
-      }
-      dq.validationState = sr.getValidationState();
-      appliedPolicy = sr.d_appliedPolicy;
-      dc->d_policyTags = std::move(sr.d_policyTags);
-
-      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;
-      }
-
-      // During lookup, an NSDNAME or NSIP trigger was hit in RPZ
-      if (res == -2) { // XXX This block should be macro'd, it is repeated post-resolve.
-        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);
-        if (policyResult == PolicyResult::HaveAnswer) {
-          goto haveAnswer;
-        }
-        else if (policyResult == PolicyResult::Drop) {
-          return;
-        }
-      }
-
-      if (dc->d_luaContext || (g_dns64Prefix && dq.qtype == QType::AAAA && !vStateIsBogus(dq.validationState))) {
-        if (res == RCode::NoError) {
-          if (answerIsNOData(dc->d_mdp.d_qtype, res, ret)) {
-            if (dc->d_luaContext && dc->d_luaContext->nodata(dq, res, sr.d_eventTrace)) {
-              shouldNotValidate = true;
-              auto policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, tcpGuard);
-              if (policyResult == PolicyResult::HaveAnswer) {
-                goto haveAnswer;
-              }
-              else if (policyResult == PolicyResult::Drop) {
-                return;
-              }
-            }
-            else if (g_dns64Prefix && dq.qtype == QType::AAAA && !vStateIsBogus(dq.validationState)) {
-              res = getFakeAAAARecords(dq.qname, *g_dns64Prefix, ret);
-              shouldNotValidate = true;
-            }
-          }
-        }
-        else if (res == RCode::NXDomain && dc->d_luaContext && dc->d_luaContext->nxdomain(dq, res, sr.d_eventTrace)) {
-          shouldNotValidate = true;
-          auto policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, tcpGuard);
-          if (policyResult == PolicyResult::HaveAnswer) {
-            goto haveAnswer;
-          }
-          else if (policyResult == PolicyResult::Drop) {
-            return;
-          }
-        }
-
-        if (dc->d_luaContext) {
-          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) {
-              shouldNotValidate = true;
-              auto policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, tcpGuard);
-              // haveAnswer case redundant
-              if (policyResult == PolicyResult::Drop) {
-                return;
-              }
-            }
-          }
-          else if (dc->d_luaContext->postresolve(dq, res, sr.d_eventTrace)) {
-            shouldNotValidate = true;
-            auto policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, tcpGuard);
-            // haveAnswer case redundant
-            if (policyResult == PolicyResult::Drop) {
-              return;
-            }
-          }
-        }
-      }
-    }
-    else if (dc->d_luaContext) {
-      // preresolve returned true
-      shouldNotValidate = true;
-      auto policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, tcpGuard);
-      // haveAnswer case redundant
-      if (policyResult == PolicyResult::Drop) {
-        return;
-      }
-    }
-
-  haveAnswer:;
-    if (tracedQuery || res == -1 || res == RCode::ServFail || pw.getHeader()->rcode == RCode::ServFail) {
-      string trace(sr.getTrace());
-      if (!trace.empty()) {
-        vector<string> lines;
-        boost::split(lines, trace, boost::is_any_of("\n"));
-        for (const string& line : lines) {
-          if (!line.empty())
-            g_log << Logger::Warning << line << endl;
-        }
-      }
-    }
-
-    if (res == -1) {
-      pw.getHeader()->rcode = RCode::ServFail;
-      // no commit here, because no record
-      g_stats.servFails++;
-    }
-    else {
-      pw.getHeader()->rcode = res;
-
-      // Does the validation mode or query demand validation?
-      if (!shouldNotValidate && sr.isDNSSECValidationRequested()) {
-        try {
-          auto state = sr.getValidationState();
-
-          string x_marker;
-          if (sr.doLog() || vStateIsBogus(state)) {
-            auto xdnssec = g_xdnssec.getLocal();
-            if (xdnssec->check(dc->d_mdp.d_qname)) {
-              x_marker = " [in x-dnssec-names]";
-            }
-          }
-
-          if (state == vState::Secure) {
-            if (sr.doLog()) {
-              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;
-            }
-
-            // Is the query source interested in the value of the ad-bit?
-            if (dc->d_mdp.d_header.ad || DNSSECOK)
-              pw.getHeader()->ad = 1;
-          }
-          else if (state == vState::Insecure) {
-            if (sr.doLog()) {
-              g_log << Logger::Warning << "Answer to " << dc->d_mdp.d_qname << "|" << QType(dc->d_mdp.d_qtype) << x_marker << " for " << dc->getRemote() << " validates as Insecure" << endl;
-            }
-
-            pw.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) {
-              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;
-            }
-
-            // 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()) {
-                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;
-              }
-
-              pw.getHeader()->rcode = RCode::ServFail;
-              goto sendit;
-            }
-            else {
-              if (sr.doLog()) {
-                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;
-              }
-            }
-          }
-        }
-        catch (const ImmediateServFailException& e) {
-          if (g_logCommonErrors)
-            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;
-          pw.getHeader()->rcode = RCode::ServFail;
-          goto sendit;
-        }
-      }
-
-      if (ret.size()) {
-        pdns::orderAndShuffle(ret, false);
-        if (auto sl = luaconfsLocal->sortlist.getOrderCmp(dc->d_source)) {
-          stable_sort(ret.begin(), ret.end(), *sl);
-          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))))) {
-          continue;
-        }
-
-        if (!addRecordToPacket(pw, *i, minTTL, dc->d_ttlCap, maxanswersize)) {
-          needCommit = false;
-          break;
-        }
-        needCommit = true;
-
-        bool udr = false;
-#ifdef NOD_ENABLED
-        if (g_udrEnabled) {
-          udr = udrCheckUniqueDNSRecord(nodlogger, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, *i);
-          if (!hasUDR && udr)
-            hasUDR = true;
-        }
-#endif /* NOD ENABLED */
-
-        if (t_protobufServers) {
-          pbMessage.addRR(*i, luaconfsLocal->protobufExportConfig.exportTypes, udr);
-        }
-      }
-      if (needCommit)
-        pw.commit();
-    }
-  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 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())) {
-
-        maxanswersize -= EDNSOptionCodeSize + EDNSOptionLengthSize + ecsPayload.size();
-
-        returnedEdnsOptions.emplace_back(EDNSOptionCode::ECS, std::move(ecsPayload));
-      }
-    }
-
-    if (haveEDNS && addPaddingToResponse) {
-      size_t currentSize = pw.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);
-
-      if (currentSize < (maxSize - 4)) {
-        size_t remaining = maxSize - (currentSize + 4);
-        /* from rfc8647, "4.1.  Recommended Strategy: Block-Length Padding":
-           If a server receives a query that includes the EDNS(0) "Padding"
-           option, it MUST pad the corresponding response (see Section 4 of
-           RFC 7830) and SHOULD pad the corresponding response to a
-           multiple of 468 octets (see below).
-        */
-        const size_t blockSize = 468;
-        size_t modulo = (currentSize + 4) % blockSize;
-        size_t padSize = 0;
-        if (modulo > 0) {
-          padSize = std::min(blockSize - modulo, remaining);
-        }
-        returnedEdnsOptions.emplace_back(EDNSOptionCode::PADDING, makeEDNSPaddingOptString(padSize));
-      }
-    }
-
-    if (haveEDNS) {
-      auto state = sr.getValidationState();
-      if (dc->d_extendedErrorCode || (g_addExtendedResolutionDNSErrors && vStateIsBogus(state))) {
-        EDNSExtendedError::code code;
-        std::string extra;
-
-        if (dc->d_extendedErrorCode) {
-          code = static_cast<EDNSExtendedError::code>(*dc->d_extendedErrorCode);
-          extra = std::move(dc->d_extendedErrorExtra);
-        }
-        else {
-          switch (state) {
-          case vState::BogusNoValidDNSKEY:
-            code = EDNSExtendedError::code::DNSKEYMissing;
-            break;
-          case vState::BogusInvalidDenial:
-            code = EDNSExtendedError::code::NSECMissing;
-            break;
-          case vState::BogusUnableToGetDSs:
-            code = EDNSExtendedError::code::DNSSECBogus;
-            break;
-          case vState::BogusUnableToGetDNSKEYs:
-            code = EDNSExtendedError::code::DNSKEYMissing;
-            break;
-          case vState::BogusSelfSignedDS:
-            code = EDNSExtendedError::code::DNSSECBogus;
-            break;
-          case vState::BogusNoRRSIG:
-            code = EDNSExtendedError::code::RRSIGsMissing;
-            break;
-          case vState::BogusNoValidRRSIG:
-            code = EDNSExtendedError::code::DNSSECBogus;
-            break;
-          case vState::BogusMissingNegativeIndication:
-            code = EDNSExtendedError::code::NSECMissing;
-            break;
-          case vState::BogusSignatureNotYetValid:
-            code = EDNSExtendedError::code::SignatureNotYetValid;
-            break;
-          case vState::BogusSignatureExpired:
-            code = EDNSExtendedError::code::SignatureExpired;
-            break;
-          case vState::BogusUnsupportedDNSKEYAlgo:
-            code = EDNSExtendedError::code::UnsupportedDNSKEYAlgorithm;
-            break;
-          case vState::BogusUnsupportedDSDigestType:
-            code = EDNSExtendedError::code::UnsupportedDSDigestType;
-            break;
-          case vState::BogusNoZoneKeyBitSet:
-            code = EDNSExtendedError::code::NoZoneKeyBitSet;
-            break;
-          case vState::BogusRevokedDNSKEY:
-            code = EDNSExtendedError::code::DNSSECBogus;
-            break;
-          case vState::BogusInvalidDNSKEYProtocol:
-            code = EDNSExtendedError::code::DNSSECBogus;
-            break;
-          default:
-            throw std::runtime_error("Bogus validation state not handled: " + vStateToString(state));
-          }
-        }
-
-        EDNSExtendedError eee;
-        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())) {
-          returnedEdnsOptions.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(eee));
-        }
-      }
-
-      /* 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, ednsExtRCode, DNSSECOK ? EDNSOpts::DNSSECOK : 0, returnedEdnsOptions);
-      pw.commit();
-    }
-
-    g_rs.submitResponse(dc->d_mdp.d_qtype, packet.size(), pw.getHeader()->rcode, !dc->d_tcp);
-    updateResponseStats(res, dc->d_source, packet.size(), &dc->d_mdp.d_qname, dc->d_mdp.d_qtype);
-#ifdef NOD_ENABLED
-    bool nod = false;
-    if (g_nodEnabled) {
-      if (nodCheckNewDomain(nodlogger, dc->d_mdp.d_qname)) {
-        nod = true;
-      }
-    }
-#endif /* NOD_ENABLED */
-
-    if (variableAnswer || sr.wasVariable()) {
-      g_stats.variableResponses++;
-    }
-
-    if (t_protobufServers && !(luaconfsLocal->protobufExportConfig.taggedOnly && appliedPolicy.getName().empty() && dc->d_policyTags.empty())) {
-      // Start constructing embedded DNSResponse object
-      pbMessage.setResponseCode(pw.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.setAppliedPolicyKind(appliedPolicy.d_kind);
-      }
-      pbMessage.addPolicyTags(dc->d_policyTags);
-      pbMessage.setInBytes(packet.size());
-      pbMessage.setValidationState(sr.getValidationState());
-
-      // 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()});
-#ifdef NOD_ENABLED
-      // if (g_udrEnabled) ??
-      pbMessage.clearUDR(pbDataForCache->d_response);
-#endif
-    }
-
-    if (!SyncRes::s_nopacketcache && !variableAnswer && !sr.wasVariable()) {
-      const auto& hdr = pw.getHeader();
-      if ((hdr->rcode != RCode::NoError && hdr->rcode != RCode::NXDomain) || (hdr->ancount == 0 && hdr->nscount == 0)) {
-        minTTL = min(minTTL, SyncRes::s_packetcacheservfailttl);
-      }
-      minTTL = min(minTTL, SyncRes::s_packetcachettl);
-      t_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()),
-                                          g_now.tv_sec,
-                                          minTTL,
-                                          dq.validationState,
-                                          std::move(pbDataForCache), dc->d_tcp);
-    }
-    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) {
-        g_log << Logger::Warning << "Sending UDP reply to client " << dc->getRemote() << " failed with: "
-              << strerror(sendErr) << endl;
-      }
-    }
-    else {
-      bool hadError = sendResponseOverTCP(dc, packet);
-      finishTCPReply(dc, hadError, true);
-      tcpGuard.setHandled();
-    }
-
-    sr.d_eventTrace.add(RecEventTrace::AnswerSent);
-
-    // Now do the per query changing part ot the protobuf message
-    if (t_protobufServers && !(luaconfsLocal->protobufExportConfig.taggedOnly && appliedPolicy.getName().empty() && dc->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);
-      }
-      else {
-        pbMessage.setQueryTime(dc->d_now.tv_sec, dc->d_now.tv_usec);
-      }
-      pbMessage.setMessageIdentity(dc->d_uuid);
-      pbMessage.setSocketFamily(dc->d_source.sin4.sin_family);
-      pbMessage.setSocketProtocol(dc->d_tcp ? pdns::ProtoZero::Message::TransportProtocol::TCP : pdns::ProtoZero::Message::TransportProtocol::UDP);
-      Netmask requestorNM(dc->d_source, dc->d_source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
-      ComboAddress requestor = requestorNM.getMaskedNetwork();
-      pbMessage.setFrom(requestor);
-      pbMessage.setTo(dc->d_destination);
-      pbMessage.setId(dc->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.setFromPort(dc->d_source.getPort());
-      pbMessage.setToPort(dc->d_destination.getPort());
-
-      for (const auto& m : dq.meta) {
-        pbMessage.setMeta(m.first, m.second.stringVal, m.second.intVal);
-      }
-#ifdef NOD_ENABLED
-      if (g_nodEnabled) {
-        if (nod) {
-          pbMessage.setNewlyObservedDomain(true);
-          pbMessage.addPolicyTag(g_nod_pbtag);
-        }
-        if (hasUDR) {
-          pbMessage.addPolicyTag(g_udr_pbtag);
-        }
-      }
-#endif /* NOD_ENABLED */
-      if (sr.d_eventTrace.enabled() && SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_pb) {
-        pbMessage.addEvents(sr.d_eventTrace);
-      }
-      if (dc->d_logResponse) {
-        protobufLogResponse(pbMessage);
-      }
-    }
-
-    if (sr.d_eventTrace.enabled() && SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_log) {
-      g_log << Logger::Info << sr.d_eventTrace.toString() << endl;
-    }
-
-    // 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);
-    if (!g_quiet) {
-      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;
-
-      if (!shouldNotValidate && sr.isDNSSECValidationRequested()) {
-        g_log << ", dnssec=" << sr.getValidationState();
-      }
-      g_log << endl;
-    }
-
-    if (dc->d_mdp.d_header.opcode == Opcode::Query) {
-      if (sr.d_outqueries || sr.d_authzonequeries) {
-        g_recCache->cacheMisses++;
-      }
-      else {
-        g_recCache->cacheHits++;
-      }
-    }
-
-    g_stats.answers(spentUsec);
-    g_stats.cumulativeAnswers(spentUsec);
-
-    double newLat = spentUsec;
-    newLat = min(newLat, g_networkTimeoutMsec * 1000.0); // outliers of several minutes exist..
-    g_stats.avgLatencyUsec = (1.0 - 1.0 / g_latencyStatSize) * g_stats.avgLatencyUsec + 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;
-      g_stats.ourtime(ourtime);
-      newLat = ourtime; // usec
-      g_stats.avgLatencyOursUsec = (1.0 - 1.0 / g_latencyStatSize) * g_stats.avgLatencyOursUsec + newLat / g_latencyStatSize;
-    }
-
-#ifdef NOD_ENABLED
-    if (nod) {
-      sendNODLookup(nodlogger, dc->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) {
-    g_log << Logger::Error << "startDoResolve problem " << makeLoginfo(dc) << ": " << ae.reason << endl;
-  }
-  catch (const MOADNSException& mde) {
-    g_log << Logger::Error << "DNS parser error " << makeLoginfo(dc) << ": " << dc->d_mdp.d_qname << ", " << mde.what() << endl;
-  }
-  catch (const std::exception& e) {
-    g_log << Logger::Error << "STL error " << makeLoginfo(dc) << ": " << e.what();
-
-    // Luawrapper nests the exception from Lua, so we unnest it here
-    try {
-      std::rethrow_if_nested(e);
-    }
-    catch (const std::exception& ne) {
-      g_log << ". Extra info: " << ne.what();
-    }
-    catch (...) {
-    }
-
-    g_log << endl;
-  }
-  catch (...) {
-    g_log << Logger::Error << "Any other exception in a resolver context " << makeLoginfo(dc) << endl;
-  }
-
-  runTaskOnce(g_logCommonErrors);
-
-  g_stats.maxMThreadStackUsage = max(MT->getMaxStackUsage(), g_stats.maxMThreadStackUsage.load());
-}
-
-void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass,
-                       bool& foundECS, EDNSSubnetOpts* ednssubnet, EDNSOptionViewMap* options,
-                       bool& foundXPF, ComboAddress* xpfSource, ComboAddress* xpfDest)
-{
-  const bool lookForXPF = xpfSource != nullptr && g_xpfRRCode != 0;
-  const bool lookForECS = ednssubnet != nullptr;
-  const dnsheader_aligned dnshead(question.data());
-  const dnsheader* dh = dnshead.get();
-  size_t questionLen = question.length();
-  unsigned int consumed = 0;
-  *dnsname = DNSName(question.c_str(), 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);
-
-  for (uint16_t arpos = 0; arpos < arcount && questionLen > (pos + headerSize) && ((lookForECS && !foundECS) || (lookForXPF && !foundXPF)); arpos++) {
-    if (question.at(pos) != 0) {
-      /* not an OPT or a XPF, bye. */
-      return;
-    }
-
-    pos += 1;
-    const dnsrecordheader* drh = reinterpret_cast<const dnsrecordheader*>(&question.at(pos));
-    pos += sizeof(dnsrecordheader);
-
-    if (pos >= questionLen) {
-      return;
-    }
-
-    /* OPT root label (1) followed by type (2) */
-    if (lookForECS && ntohs(drh->d_type) == QType::OPT) {
-      if (!options) {
-        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);
-        if (res == 0 && ecsLen > 4) {
-          EDNSSubnetOpts eso;
-          if (getEDNSSubnetOptsFromString(&question.at(pos - sizeof(drh->d_clen) + ecsStartPosition + 4), ecsLen - 4, &eso)) {
-            *ednssubnet = eso;
-            foundECS = true;
-          }
-        }
-      }
-      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);
-        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) {
-            EDNSSubnetOpts eso;
-            if (getEDNSSubnetOptsFromString(it->second.values.at(0).content, it->second.values.at(0).size, &eso)) {
-              *ednssubnet = eso;
-              foundECS = true;
-            }
-          }
-        }
-      }
-    }
-    else if (lookForXPF && ntohs(drh->d_type) == g_xpfRRCode && ntohs(drh->d_class) == QClass::IN && drh->d_ttl == 0) {
-      if ((questionLen - pos) < ntohs(drh->d_clen)) {
-        return;
-      }
-
-      foundXPF = parseXPFPayload(reinterpret_cast<const char*>(&question.at(pos)), ntohs(drh->d_clen), *xpfSource, xpfDest);
-    }
-
-    pos += ntohs(drh->d_clen);
-  }
-}
-
-bool checkForCacheHit(bool qnameParsed, unsigned int tag, const string& data,
-                      DNSName& qname, uint16_t& qtype, uint16_t& qclass,
-                      const struct timeval& now,
-                      string& response, uint32_t& qhash,
-                      RecursorPacketCache::OptPBData& pbData, bool tcp, const ComboAddress& source)
-{
-  bool cacheHit = false;
-  uint32_t age;
-  vState valState;
-
-  if (qnameParsed) {
-    cacheHit = !SyncRes::s_nopacketcache && t_packetCache->getResponsePacket(tag, data, qname, qtype, qclass, now.tv_sec, &response, &age, &valState, &qhash, &pbData, tcp);
-  }
-  else {
-    cacheHit = !SyncRes::s_nopacketcache && t_packetCache->getResponsePacket(tag, data, qname, &qtype, &qclass, now.tv_sec, &response, &age, &valState, &qhash, &pbData, tcp);
-  }
-
-  if (cacheHit) {
-    if (vStateIsBogus(valState)) {
-      if (t_bogusremotes) {
-        t_bogusremotes->push_back(source);
-      }
-      if (t_bogusqueryring) {
-        t_bogusqueryring->push_back({qname, qtype});
-      }
-    }
-
-    g_stats.packetCacheHits++;
-    SyncRes::s_queries++;
-    ageDNSPacket(response, age);
-    if (response.length() >= sizeof(struct dnsheader)) {
-      const struct dnsheader* dh = reinterpret_cast<const dnsheader*>(response.data());
-      updateResponseStats(dh->rcode, source, response.length(), 0, 0);
-    }
-    g_stats.avgLatencyUsec = (1.0 - 1.0 / g_latencyStatSize) * g_stats.avgLatencyUsec + 0.0; // we assume 0 usec
-    g_stats.avgLatencyOursUsec = (1.0 - 1.0 / g_latencyStatSize) * g_stats.avgLatencyOursUsec + 0.0; // we assume 0 usec
-#if 0
-    // XXX changes behaviour compared to old code!
-    g_stats.answers(0);
-    g_stats.ourtime(0);
-#endif
-  }
-
-  return cacheHit;
-}
-
-static void* pleaseWipeCaches(const DNSName& canon, bool subtree, uint16_t qtype)
-{
-  auto res = wipeCaches(canon, subtree, qtype);
-  g_log << Logger::Info << "Wiped caches for " << canon << ": " << res.record_count << " records; " << res.negative_record_count << " negative records; " << res.packet_count << " packets" << endl;
-  return nullptr;
-}
-
-void requestWipeCaches(const DNSName& canon)
-{
-  // send a message to the handler thread asking it
-  // to wipe all of the caches
-  ThreadMSG* tmsg = new ThreadMSG();
-  tmsg->func = [=] { return pleaseWipeCaches(canon, true, 0xffff); };
-  tmsg->wantAnswer = false;
-  if (write(RecThreadInfo::info(0).pipes.writeToThread, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) {
-    delete tmsg;
-
-    unixDie("write to thread pipe returned wrong size or error");
-  }
-}
-
-bool expectProxyProtocol(const ComboAddress& from)
-{
-  return g_proxyProtocolACL.match(from);
-}
-
-static string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fromaddr, const ComboAddress& destaddr, ComboAddress source, ComboAddress destination, struct timeval tv, int fd, std::vector<ProxyProtocolValue>& proxyProtocolValues, RecEventTrace& eventTrace)
-{
-  ++(RecThreadInfo::self().numberOfDistributedQueries);
-  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 (delta > 1000.0) {
-      g_stats.tooOldDrops++;
-      return nullptr;
-    }
-  }
-
-  ++g_stats.qcounter;
-  if (fromaddr.sin4.sin_family == AF_INET6)
-    g_stats.ipv6qcounter++;
-
-  string response;
-  const dnsheader_aligned headerdata(question.data());
-  const dnsheader* dh = headerdata.get();
-  unsigned int ctag = 0;
-  uint32_t qhash = 0;
-  bool needECS = false;
-  bool needXPF = g_XPFAcl.match(fromaddr);
-  std::unordered_set<std::string> policyTags;
-  std::map<std::string, RecursorLua4::MetaValue> meta;
-  LuaContext::LuaObject data;
-  string requestorId;
-  string deviceId;
-  string deviceName;
-  string routingTag;
-  bool logQuery = false;
-  bool logResponse = false;
-  boost::uuids::uuid uniqueId;
-  auto luaconfsLocal = g_luaconfs.getLocal();
-  if (checkProtobufExport(luaconfsLocal)) {
-    uniqueId = getUniqueID();
-    needECS = true;
-  }
-  else if (checkOutgoingProtobufExport(luaconfsLocal)) {
-    uniqueId = getUniqueID();
-  }
-  logQuery = t_protobufServers && luaconfsLocal->protobufExportConfig.logQueries;
-  logResponse = t_protobufServers && luaconfsLocal->protobufExportConfig.logResponses;
-#ifdef HAVE_FSTRM
-  checkFrameStreamExport(luaconfsLocal);
-#endif
-  EDNSSubnetOpts ednssubnet;
-  bool ecsFound = false;
-  bool ecsParsed = false;
-  std::vector<DNSRecord> records;
-  std::string extendedErrorExtra;
-  boost::optional<int> rcode = boost::none;
-  boost::optional<uint16_t> extendedErrorCode{boost::none};
-  uint32_t ttlCap = std::numeric_limits<uint32_t>::max();
-  bool variable = false;
-  bool followCNAMEs = false;
-  bool responsePaddingDisabled = false;
-  DNSName qname;
-  try {
-    uint16_t qtype = 0;
-    uint16_t qclass = 0;
-    bool qnameParsed = false;
-#ifdef MALLOC_TRACE
-    /*
-    static uint64_t last=0;
-    if(!last)
-      g_mtracer->clearAllocators();
-    cout<<g_mtracer->getAllocs()-last<<" "<<g_mtracer->getNumOut()<<" -- BEGIN TRACE"<<endl;
-    last=g_mtracer->getAllocs();
-    cout<<g_mtracer->topAllocatorsString()<<endl;
-    g_mtracer->clearAllocators();
-    */
-#endif
-
-    // We do not have a SyncRes specific Lua context at this point yet, so ok to use t_pdl
-    if (needECS || needXPF || (t_pdl && (t_pdl->d_gettag || t_pdl->d_gettag_ffi)) || dh->opcode == Opcode::Notify) {
-      try {
-        EDNSOptionViewMap ednsOptions;
-        bool xpfFound = false;
-
-        ecsFound = false;
-
-        getQNameAndSubnet(question, &qname, &qtype, &qclass,
-                          ecsFound, &ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr,
-                          xpfFound, needXPF ? &source : nullptr, needXPF ? &destination : nullptr);
-
-        qnameParsed = true;
-        ecsParsed = true;
-
-        if (t_pdl) {
-          try {
-            if (t_pdl->d_gettag_ffi) {
-              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) {
-              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);
-            }
-          }
-          catch (const std::exception& e) {
-            if (g_logCommonErrors) {
-              g_log << Logger::Warning << "Error parsing a query packet qname='" << qname << "' for tag determination, setting tag=0: " << e.what() << endl;
-            }
-          }
-        }
-      }
-      catch (const std::exception& e) {
-        if (g_logCommonErrors) {
-          g_log << Logger::Warning << "Error parsing a query packet for tag determination, setting tag=0: " << e.what() << endl;
-        }
-      }
-    }
-
-    RecursorPacketCache::OptPBData pbData{boost::none};
-    if (t_protobufServers) {
-      if (logQuery && !(luaconfsLocal->protobufExportConfig.taggedOnly && policyTags.empty())) {
-        protobufLogQuery(luaconfsLocal, uniqueId, source, destination, ednssubnet.source, false, dh->id, question.size(), qname, qtype, qclass, policyTags, requestorId, deviceId, deviceName, meta);
-      }
-    }
-
-    if (ctag == 0 && !responsePaddingDisabled && g_paddingFrom.match(fromaddr)) {
-      ctag = g_paddingTag;
-    }
-
-    if (dh->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. */
-      eventTrace.add(RecEventTrace::PCacheCheck);
-      bool cacheHit = checkForCacheHit(qnameParsed, ctag, question, qname, qtype, qclass, g_now, response, qhash, pbData, false, source);
-      eventTrace.add(RecEventTrace::PCacheCheck, cacheHit, false);
-      if (cacheHit) {
-        if (!g_quiet) {
-          g_log << Logger::Notice << RecThreadInfo::id() << " question answered from packet cache tag=" << ctag << " from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << endl;
-        }
-        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)) {
-          addCMsgSrcAddr(&msgh, &cbuf, &destaddr, 0);
-        }
-        int sendErr = sendOnNBSocket(fd, &msgh);
-        eventTrace.add(RecEventTrace::AnswerSent);
-
-        if (t_protobufServers && logResponse && !(luaconfsLocal->protobufExportConfig.taggedOnly && pbData && !pbData->d_tagged)) {
-          protobufLogResponse(dh, luaconfsLocal, pbData, tv, false, source, destination, ednssubnet, uniqueId, requestorId, deviceId, deviceName, meta, eventTrace);
-        }
-
-        if (eventTrace.enabled() && SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_log) {
-          g_log << Logger::Info << eventTrace.toString() << endl;
-        }
-        if (sendErr && g_logCommonErrors) {
-          g_log << Logger::Warning << "Sending UDP reply to client " << source.toStringWithPort()
-                << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << " failed with: "
-                << strerror(sendErr) << endl;
-        }
-        struct timeval now;
-        Utility::gettimeofday(&now, nullptr);
-        uint64_t spentUsec = uSec(now - tv);
-        g_stats.cumulativeAnswers(spentUsec);
-        return 0;
-      }
-    }
-  }
-  catch (const std::exception& e) {
-    if (g_logCommonErrors) {
-      g_log << Logger::Error << "Error processing or aging answer packet: " << e.what() << endl;
-    }
-    return 0;
-  }
-
-  if (t_pdl) {
-    bool ipf = t_pdl->ipfilter(source, destination, *dh, eventTrace);
-    if (ipf) {
-      if (!g_quiet) {
-        g_log << Logger::Notice << RecThreadInfo::id() << " [" << MT->getTid() << "/" << MT->numProcesses() << "] DROPPED question from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << " based on policy" << endl;
-      }
-      g_stats.policyDrops++;
-      return 0;
-    }
-  }
-
-  if (dh->opcode == Opcode::Notify) {
-    if (!isAllowNotifyForZone(qname)) {
-      if (!g_quiet) {
-        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;
-      }
-
-      g_stats.zoneDisallowedNotify++;
-      return 0;
-    }
-
-    if (!g_quiet) {
-      g_log << Logger::Notice << RecThreadInfo::id() << " got NOTIFY for " << qname.toLogString() << " from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << endl;
-    }
-
-    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
-    variable = true;
-  }
-
-  if (MT->numProcesses() > g_maxMThreads) {
-    if (!g_quiet)
-      g_log << Logger::Notice << RecThreadInfo::id() << " [" << MT->getTid() << "/" << MT->numProcesses() << "] DROPPED question from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << ", over capacity" << endl;
-
-    g_stats.overCapacityDrops++;
-    return 0;
-  }
-
-  auto dc = std::make_unique<DNSComboWriter>(question, g_now, std::move(policyTags), t_pdl, std::move(data), std::move(records));
-
-  if (SyncRes::isUnsupported(dc->d_mdp.d_qtype)) {
-    g_stats.ignoredCount++;
-    if (!g_quiet) {
-      g_log << Logger::Notice << RecThreadInfo::id() << " Unsupported qtype " << dc->d_mdp.d_qtype << " from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << endl;
-    }
-
-    return 0;
-  }
-
-  dc->setSocket(fd);
-  dc->d_tag = ctag;
-  dc->d_qhash = qhash;
-  dc->setRemote(fromaddr);
-  dc->setSource(source);
-  dc->setLocal(destaddr);
-  dc->setDestination(destination);
-  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;
-  if (t_protobufServers || t_outgoingProtobufServers) {
-    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
-
-  return 0;
-}
-
-static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& var)
-{
-  ssize_t len;
-  static const size_t maxIncomingQuerySize = g_proxyProtocolACL.empty() ? 512 : (512 + g_proxyProtocolMaximumSize);
-  static thread_local std::string data;
-  ComboAddress fromaddr;
-  ComboAddress source;
-  ComboAddress destination;
-  struct msghdr msgh;
-  struct iovec iov;
-  cmsgbuf_aligned cbuf;
-  bool firstQuery = true;
-  std::vector<ProxyProtocolValue> proxyProtocolValues;
-  RecEventTrace eventTrace;
-
-  for (size_t queriesCounter = 0; queriesCounter < g_maxUDPQueriesPerRound; queriesCounter++) {
-    bool proxyProto = false;
-    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);
-
-    if ((len = recvmsg(fd, &msgh, 0)) >= 0) {
-      eventTrace.clear();
-      eventTrace.setEnabled(SyncRes::s_event_trace_enabled);
-      eventTrace.add(RecEventTrace::ReqRecv);
-
-      firstQuery = false;
-
-      if (msgh.msg_flags & MSG_TRUNC) {
-        g_stats.truncatedDrops++;
-        if (!g_quiet) {
-          g_log << Logger::Error << "Ignoring truncated query from " << fromaddr.toString() << endl;
-        }
-        return;
-      }
-
-      data.resize(static_cast<size_t>(len));
-
-      if (expectProxyProtocol(fromaddr)) {
-        bool tcp;
-        ssize_t used = parseProxyHeader(data, proxyProto, source, destination, tcp, proxyProtocolValues);
-        if (used <= 0) {
-          ++g_stats.proxyProtocolInvalidCount;
-          if (!g_quiet) {
-            g_log << Logger::Error << "Ignoring invalid proxy protocol (" << std::to_string(len) << ", " << std::to_string(used) << ") query from " << fromaddr.toStringWithPort() << endl;
-          }
-          return;
-        }
-        else if (static_cast<size_t>(used) > g_proxyProtocolMaximumSize) {
-          if (g_quiet) {
-            g_log << Logger::Error << "Proxy protocol header in UDP packet from " << fromaddr.toStringWithPort() << " is larger than proxy-protocol-maximum-size (" << used << "), dropping" << endl;
-          }
-          ++g_stats.proxyProtocolInvalidCount;
-          return;
-        }
-
-        data.erase(0, used);
-      }
-      else if (len > 512) {
-        /* we only allow UDP packets larger than 512 for those with a proxy protocol header */
-        g_stats.truncatedDrops++;
-        if (!g_quiet) {
-          g_log << Logger::Error << "Ignoring truncated query from " << fromaddr.toStringWithPort() << endl;
-        }
-        return;
-      }
-
-      if (data.size() < sizeof(dnsheader)) {
-        g_stats.ignoredCount++;
-        if (!g_quiet) {
-          g_log << Logger::Error << "Ignoring too-short (" << std::to_string(data.size()) << ") query from " << fromaddr.toString() << endl;
-        }
-        return;
-      }
-
-      if (!proxyProto) {
-        source = fromaddr;
-      }
-
-      if (t_remotes) {
-        t_remotes->push_back(fromaddr);
-      }
-
-      if (t_allowFrom && !t_allowFrom->match(&source)) {
-        if (!g_quiet) {
-          g_log << Logger::Error << "[" << MT->getTid() << "] dropping UDP query from " << source.toString() << ", address not matched by allow-from" << endl;
-        }
-
-        g_stats.unauthorizedUDP++;
-        return;
-      }
-
-      BOOST_STATIC_ASSERT(offsetof(sockaddr_in, sin_port) == offsetof(sockaddr_in6, sin6_port));
-      if (!fromaddr.sin4.sin_port) { // also works for IPv6
-        if (!g_quiet) {
-          g_log << Logger::Error << "[" << MT->getTid() << "] dropping UDP query from " << fromaddr.toStringWithPort() << ", can't deal with port 0" << endl;
-        }
-
-        g_stats.clientParseError++; // not quite the best place to put it, but needs to go somewhere
-        return;
-      }
-
-      try {
-        const dnsheader_aligned headerdata(data.data());
-        const dnsheader* dh = headerdata.get();
-
-        if (dh->qr) {
-          g_stats.ignoredCount++;
-          if (g_logCommonErrors) {
-            g_log << Logger::Error << "Ignoring answer from " << fromaddr.toString() << " on server socket!" << endl;
-          }
-        }
-        else if (dh->opcode != Opcode::Query && dh->opcode != Opcode::Notify) {
-          g_stats.ignoredCount++;
-          if (g_logCommonErrors) {
-            g_log << Logger::Error << "Ignoring unsupported opcode " << Opcode::to_s(dh->opcode) << " from " << fromaddr.toString() << " on server socket!" << endl;
-          }
-        }
-        else if (dh->qdcount == 0) {
-          g_stats.emptyQueriesCount++;
-          if (g_logCommonErrors) {
-            g_log << Logger::Error << "Ignoring empty (qdcount == 0) query from " << fromaddr.toString() << " on server socket!" << endl;
-          }
-        }
-        else {
-          if (dh->opcode == Opcode::Notify) {
-            if (!t_allowNotifyFrom || !t_allowNotifyFrom->match(&source)) {
-              if (!g_quiet) {
-                g_log << Logger::Error << "[" << MT->getTid() << "] dropping UDP NOTIFY from " << source.toString() << ", address not matched by allow-notify-from" << endl;
-              }
-
-              g_stats.sourceDisallowedNotify++;
-              return;
-            }
-          }
-
-          struct timeval tv = {0, 0};
-          HarvestTimestamp(&msgh, &tv);
-          ComboAddress dest;
-          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)) {
-            // but.. need to get port too
-            if (loc) {
-              dest.sin4.sin_port = loc->sin4.sin_port;
-            }
-          }
-          else {
-            if (loc) {
-              dest = *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
-            }
-          }
-          if (!proxyProto) {
-            destination = dest;
-          }
-
-          if (RecThreadInfo::weDistributeQueries()) {
-            std::string localdata = data;
-            distributeAsyncFunction(data, [localdata, fromaddr, dest, source, destination, tv, fd, proxyProtocolValues, eventTrace]() mutable {
-              return doProcessUDPQuestion(localdata, fromaddr, dest, source, destination, tv, fd, proxyProtocolValues, eventTrace);
-            });
-          }
-          else {
-            doProcessUDPQuestion(data, fromaddr, dest, source, destination, tv, fd, proxyProtocolValues, eventTrace);
-          }
-        }
-      }
-      catch (const MOADNSException& mde) {
-        g_stats.clientParseError++;
-        if (g_logCommonErrors) {
-          g_log << Logger::Error << "Unable to parse packet from remote UDP client " << fromaddr.toString() << ": " << mde.what() << endl;
-        }
-      }
-      catch (const std::runtime_error& e) {
-        g_stats.clientParseError++;
-        if (g_logCommonErrors) {
-          g_log << Logger::Error << "Unable to parse packet from remote UDP client " << fromaddr.toString() << ": " << e.what() << endl;
-        }
-      }
-    }
-    else {
-      // cerr<<t_id<<" had error: "<<stringerror()<<endl;
-      if (firstQuery && errno == EAGAIN) {
-        g_stats.noPacketError++;
-      }
-
-      break;
-    }
-  }
-}
-
-void makeUDPServerSockets(deferredAdd_t& deferredAdds)
-{
-  int one = 1;
-  vector<string> locals;
-  stringtok(locals, ::arg()["local-address"], " ,");
-
-  if (locals.empty())
-    throw PDNSException("No local address specified");
-
-  for (vector<string>::const_iterator i = locals.begin(); i != locals.end(); ++i) {
-    ServiceTuple st;
-    st.port = ::arg().asNum("local-port");
-    parseService(*i, st);
-
-    ComboAddress sin;
-
-    sin.reset();
-    sin.sin4.sin_family = AF_INET;
-    if (!IpToU32(st.host.c_str(), (uint32_t*)&sin.sin4.sin_addr.s_addr)) {
-      sin.sin6.sin6_family = AF_INET6;
-      if (makeIPv6sockaddr(st.host, &sin.sin6) < 0)
-        throw PDNSException("Unable to resolve local address for UDP server on '" + st.host + "'");
-    }
-
-    int fd = socket(sin.sin4.sin_family, SOCK_DGRAM, 0);
-    if (fd < 0) {
-      throw PDNSException("Making a UDP server socket for resolver: " + stringerror());
-    }
-    if (!setSocketTimestamps(fd))
-      g_log << Logger::Warning << "Unable to enable timestamp reporting for socket" << endl;
-
-    if (IsAnyAddress(sin)) {
-      if (sin.sin4.sin_family == AF_INET)
-        if (!setsockopt(fd, IPPROTO_IP, GEN_IP_PKTINFO, &one, sizeof(one))) // linux supports this, so why not - might fail on other systems
-          g_fromtosockets.insert(fd);
-#ifdef IPV6_RECVPKTINFO
-      if (sin.sin4.sin_family == AF_INET6)
-        if (!setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)))
-          g_fromtosockets.insert(fd);
-#endif
-      if (sin.sin6.sin6_family == AF_INET6 && setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)) < 0) {
-        int err = errno;
-        g_log << Logger::Error << "Failed to set IPv6 socket to IPv6 only, continuing anyhow: " << strerror(err) << endl;
-      }
-    }
-    if (::arg().mustDo("non-local-bind"))
-      Utility::setBindAny(AF_INET6, fd);
-
-    setCloseOnExec(fd);
-
-    try {
-      setSocketReceiveBuffer(fd, 250000);
-    }
-    catch (const std::exception& e) {
-      g_log << Logger::Error << e.what() << endl;
-    }
-    sin.sin4.sin_port = htons(st.port);
-
-    if (g_reusePort) {
-#if defined(SO_REUSEPORT_LB)
-      try {
-        SSetsockopt(fd, SOL_SOCKET, SO_REUSEPORT_LB, 1);
-      }
-      catch (const std::exception& e) {
-        throw PDNSException(std::string("SO_REUSEPORT_LB: ") + e.what());
-      }
-#elif defined(SO_REUSEPORT)
-      try {
-        SSetsockopt(fd, SOL_SOCKET, SO_REUSEPORT, 1);
-      }
-      catch (const std::exception& e) {
-        throw PDNSException(std::string("SO_REUSEPORT: ") + e.what());
-      }
-#endif
-    }
-
-    try {
-      setSocketIgnorePMTU(fd, sin.sin4.sin_family);
-    }
-    catch (const std::exception& e) {
-      g_log << Logger::Warning << "Failed to set IP_MTU_DISCOVER on UDP server socket: " << e.what() << endl;
-    }
-
-    socklen_t socklen = sin.getSocklen();
-    if (::bind(fd, (struct sockaddr*)&sin, socklen) < 0)
-      throw PDNSException("Resolver binding to server socket on port " + std::to_string(st.port) + " for " + st.host + ": " + stringerror());
-
-    setNonBlocking(fd);
-
-    deferredAdds.emplace_back(fd, handleNewUDPQuestion);
-    g_listenSocketsAddresses[fd] = sin; // this is written to only from the startup thread, not from the workers
-    if (sin.sin4.sin_family == AF_INET)
-      g_log << Logger::Info << "Listening for UDP queries on " << sin.toString() << ":" << st.port << endl;
-    else
-      g_log << Logger::Info << "Listening for UDP queries on [" << sin.toString() << "]:" << st.port << endl;
-  }
-}
-
-static bool trySendingQueryToWorker(unsigned int target, ThreadMSG* tmsg)
-{
-  auto& targetInfo = RecThreadInfo::info(target);
-  if (!targetInfo.isWorker()) {
-    g_log << Logger::Error << "distributeAsyncFunction() tried to assign a query to a non-worker thread" << endl;
-    _exit(1);
-  }
-
-  const auto& tps = targetInfo.pipes;
-
-  ssize_t written = write(tps.writeQueriesToThread, &tmsg, sizeof(tmsg));
-  if (written > 0) {
-    if (static_cast<size_t>(written) != sizeof(tmsg)) {
-      delete tmsg;
-      unixDie("write to thread pipe returned wrong size or error");
-    }
-  }
-  else {
-    int error = errno;
-    if (error == EAGAIN || error == EWOULDBLOCK) {
-      return false;
-    }
-    else {
-      delete tmsg;
-      unixDie("write to thread pipe returned wrong size or error:" + std::to_string(error));
-    }
-  }
-
-  return true;
-}
-
-static unsigned int getWorkerLoad(size_t workerIdx)
-{
-  const auto mt = RecThreadInfo::info(RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + workerIdx).mt;
-  if (mt != nullptr) {
-    return mt->numProcesses();
-  }
-  return 0;
-}
-
-static unsigned int selectWorker(unsigned int hash)
-{
-  if (g_balancingFactor == 0) {
-    return RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + (hash % RecThreadInfo::numWorkers());
-  }
-
-  /* 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++) {
-    load[idx] = getWorkerLoad(idx);
-    currentLoad += load[idx];
-  }
-
-  double targetLoad = (currentLoad / RecThreadInfo::numWorkers()) * g_balancingFactor;
-
-  unsigned int worker = hash % RecThreadInfo::numWorkers();
-  /* at least one server has to be at or below the average load */
-  if (load[worker] > targetLoad) {
-    ++g_stats.rebalancedQueries;
-    do {
-      worker = (worker + 1) % RecThreadInfo::numWorkers();
-    } while (load[worker] > targetLoad);
-  }
-
-  return RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + worker;
-}
-
-// This function is only called by the distributor threads, when pdns-distributes-queries is set
-void distributeAsyncFunction(const string& packet, const pipefunc_t& func)
-{
-  if (!RecThreadInfo::self().isDistributor()) {
-    g_log << Logger::Error << "distributeAsyncFunction() has been called by a worker (" << RecThreadInfo::id() << ")" << endl;
-    _exit(1);
-  }
-
-  bool ok;
-  unsigned int hash = hashQuestion(reinterpret_cast<const uint8_t*>(packet.data()), packet.length(), g_disthashseed, ok);
-  if (!ok) {
-    // hashQuestion does detect invalid names, so we might as well punt here instead of in the worker thread
-    g_stats.ignoredCount++;
-    throw MOADNSException("too-short (" + std::to_string(packet.length()) + ") or invalid name");
-  }
-  unsigned int target = selectWorker(hash);
-
-  ThreadMSG* tmsg = new ThreadMSG();
-  tmsg->func = func;
-  tmsg->wantAnswer = false;
-
-  if (!trySendingQueryToWorker(target, tmsg)) {
-    /* if this function failed but did not raise an exception, it means that the pipe
-       was full, let's try another one */
-    unsigned int newTarget = 0;
-    do {
-      newTarget = RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + dns_random(RecThreadInfo::numWorkers());
-    } while (newTarget == target);
-
-    if (!trySendingQueryToWorker(newTarget, tmsg)) {
-      g_stats.queryPipeFullDrops++;
-      delete tmsg;
-    }
-  }
-}
-
-// resend event to everybody chained onto it
-static void doResends(MT_t::waiters_t::iterator& iter, const std::shared_ptr<PacketID>& resend, const PacketBuffer& content)
-{
-  // We close the chain for new entries, since they won't be processed anyway
-  iter->key->closed = true;
-
-  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);
-    g_stats.chainResends++;
-  }
-}
-
-static void handleUDPServerResponse(int fd, FDMultiplexer::funcparam_t& var)
-{
-  std::shared_ptr<PacketID> pid = boost::any_cast<std::shared_ptr<PacketID>>(var);
-  ssize_t len;
-  PacketBuffer packet;
-  packet.resize(g_outgoingEDNSBufsize);
-  ComboAddress fromaddr;
-  socklen_t addrlen = sizeof(fromaddr);
-
-  len = recvfrom(fd, &packet.at(0), packet.size(), 0, (sockaddr*)&fromaddr, &addrlen);
-
-  if (len < (ssize_t)sizeof(dnsheader)) {
-    if (len < 0)
-      ; //      cerr<<"Error on fd "<<fd<<": "<<stringerror()<<"\n";
-    else {
-      g_stats.serverParseError++;
-      if (g_logCommonErrors)
-        g_log << Logger::Error << "Unable to parse packet from remote UDP server " << fromaddr.toString() << ": packet smaller than DNS header" << endl;
-    }
-
-    t_udpclientsocks->returnSocket(fd);
-    PacketBuffer empty;
-
-    MT_t::waiters_t::iterator iter = MT->d_waiters.find(pid);
-    if (iter != MT->d_waiters.end())
-      doResends(iter, pid, empty);
-
-    MT->sendEvent(pid, &empty); // this denotes error (does lookup again.. at least L1 will be hot)
-    return;
-  }
-
-  packet.resize(len);
-  dnsheader dh;
-  memcpy(&dh, &packet.at(0), sizeof(dh));
-
-  auto pident = std::make_shared<PacketID>();
-  pident->remote = fromaddr;
-  pident->id = dh.id;
-  pident->fd = fd;
-
-  if (!dh.qr && g_logCommonErrors) {
-    g_log << Logger::Notice << "Not taking data from question on outgoing socket from " << fromaddr.toStringWithPort() << endl;
-  }
-
-  if (!dh.qdcount || // UPC, Nominum, very old BIND on FormErr, NSD
-      !dh.qr) { // one weird server
-    pident->domain.clear();
-    pident->type = 0;
-  }
-  else {
-    try {
-      if (len > 12)
-        pident->domain = DNSName(reinterpret_cast<const char*>(packet.data()), len, 12, false, &pident->type); // don't copy this from above - we need to do the actual read
-    }
-    catch (std::exception& e) {
-      g_stats.serverParseError++; // won't be fed to lwres.cc, so we have to increment
-      g_log << Logger::Warning << "Error in packet from remote nameserver " << fromaddr.toStringWithPort() << ": " << e.what() << endl;
-      return;
-    }
-  }
-
-  MT_t::waiters_t::iterator iter = MT->d_waiters.find(pident);
-  if (iter != MT->d_waiters.end()) {
-    doResends(iter, pident, packet);
-  }
-
-retryWithName:
-
-  if (!MT->sendEvent(pident, &packet)) {
-    /* 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) {
-        /* 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++;
-      }
-
-      // 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) {
-        // 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
-      }
-    }
-    g_stats.unexpectedCount++; // if we made it here, it really is an unexpected answer
-    if (g_logCommonErrors) {
-      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;
-    }
-  }
-  else if (fd >= 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);
-  }
-}
index 532cd52ff37d8d3e56ee73d7c412b407324525e7..15d241682e78651bd8c9d70beafcb3526551981e 100644 (file)
@@ -1,4 +1,4 @@
-
+#include <boost/smart_ptr/make_shared_array.hpp>
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 #include "statbag.hh"
 #include "base32.hh"
 #include "base64.hh"
+#include "dns.hh"
 
 #include <boost/program_options.hpp>
 #include <boost/assign/std/vector.hpp>
 #include <boost/assign/list_of.hpp>
+#include "json11.hpp"
 #include "tsigutils.hh"
 #include "dnsbackend.hh"
 #include "ueberbackend.hh"
@@ -31,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
@@ -50,7 +53,7 @@ uint16_t g_maxNSEC3Iterations{0};
 namespace po = boost::program_options;
 po::variables_map g_vm;
 
-string s_programname="pdns";
+string g_programname="pdns";
 
 namespace {
   bool g_verbose;
@@ -88,10 +91,10 @@ static void loadMainConfig(const std::string& configdir)
     exit(0);
   }
 
-  if(::arg()["config-name"]!="")
-    s_programname+="-"+::arg()["config-name"];
+  if(!::arg()["config-name"].empty())
+    g_programname+="-"+::arg()["config-name"];
 
-  string configname=::arg()["config-dir"]+"/"+s_programname+".conf";
+  string configname=::arg()["config-dir"]+"/"+g_programname+".conf";
   cleanSlashes(configname);
 
   ::arg().set("resolver","Use this resolver for ALIAS and the internal stub resolver")="no";
@@ -155,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) {
@@ -217,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++;
@@ -260,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++;
     }
@@ -278,14 +279,14 @@ static int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone, con
   catch (const PDNSException& e) {
     cout << "[Error] SOA lookup failed for zone '" << zone << "': " << e.reason << endl;
     numerrors++;
-    if (!sd.db) {
+    if (sd.db == nullptr) {
       return 1;
     }
   }
   catch (const std::exception& e) {
     cout << "[Error] SOA lookup failed for zone '" << zone << "': " << e.what() << endl;
     numerrors++;
-    if (!sd.db) {
+    if (sd.db == nullptr) {
       return 1;
     }
   }
@@ -298,7 +299,7 @@ static int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone, con
   bool isSecure=dk.isSecuredZone(zone);
   bool presigned=dk.isPresigned(zone);
   vector<string> checkKeyErrors;
-  bool validKeys=dk.checkKeys(zone, &checkKeyErrors);
+  bool validKeys=dk.checkKeys(zone, checkKeyErrors);
 
   if (haveNSEC3) {
     if(isSecure && zone.wirelength() > 222) {
@@ -360,7 +361,7 @@ static int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone, con
   pair<map<string, unsigned int>::iterator,bool> ret;
 
   vector<DNSResourceRecord> records;
-  if(!suppliedrecords) {
+  if(suppliedrecords == nullptr) {
     DNSResourceRecord drr;
     sd.db->list(zone, sd.domain_id, g_verbose);
     while(sd.db->get(drr)) {
@@ -409,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) {
@@ -445,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()) {
@@ -484,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:
@@ -495,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;
       }
@@ -651,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++;
@@ -676,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;
@@ -689,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++;
@@ -775,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;
@@ -800,7 +789,7 @@ static int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone, con
   for( const auto &rr : records ) {
     ok = ( rr.auth == 1 );
     ds_ns = false;
-    done = (suppliedrecords || !sd.db->doesDNSSEC());
+    done = (suppliedrecords != nullptr || !sd.db->doesDNSSEC());
     for( const auto &qname : checkOcclusion ) {
       if( qname.second == QType::NS ) {
         if( qname.first == rr.qname ) {
@@ -826,29 +815,27 @@ static int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone, con
       cout << "[Warning] DS record without a delegation '" << rr.qname<<"'." << endl;
       numwarnings++;
     }
-    if( ! ok && ! suppliedrecords ) {
+    if( ! ok && suppliedrecords == nullptr ) {
       cout << "[Error] Following record is auth=" << rr.auth << ", run pdnsutil rectify-zone?: " << rr.qname << " IN " << rr.qtype.toString() << " " << rr.content << endl;
       numerrors++;
     }
   }
 
   std::map<std::string, std::vector<std::string>> metadatas;
-  if (!B.getAllDomainMetadata(zone, metadatas)) {
-    cout << "[Error] Unable to retrieve metadata for zone " << zone << endl;
-    numerrors++;
-  }
-
-  for (const auto &metaData : metadatas) {
-    set<string> seen;
-    set<string> messaged;
-
-    for (const auto &value : metaData.second) {
-      if (seen.count(value) == 0) {
-        seen.insert(value);
-      } else if (messaged.count(value) <= 0) {
-        cout << "[Error] Found duplicate metadata key value pair for zone " << zone << " with key '" << metaData.first << "' and value '" << value << "'" << endl;
-        numerrors++;
-        messaged.insert(value);
+  if (B.getAllDomainMetadata(zone, metadatas)) {
+    for (const auto& metaData : metadatas) {
+      set<string> seen;
+      set<string> messaged;
+
+      for (const auto& value : metaData.second) {
+        if (seen.count(value) == 0) {
+          seen.insert(value);
+        }
+        else if (messaged.count(value) <= 0) {
+          cout << "[Error] Found duplicate metadata key value pair for zone " << zone << " with key '" << metaData.first << "' and value '" << value << "'" << endl;
+          numerrors++;
+          messaged.insert(value);
+        }
       }
     }
   }
@@ -875,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++;
     }
@@ -892,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)
@@ -925,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;
@@ -933,7 +921,7 @@ static int increaseSerial(const DNSName& zone, DNSSECKeeper &dk)
 
   if (sd.db->doesDNSSEC()) {
     NSEC3PARAMRecordContent ns3pr;
-    bool narrow;
+    bool narrow = false;
     bool haveNSEC3=dk.getNSEC3PARAM(zone, &ns3pr, &narrow);
 
     DNSName ordername;
@@ -1014,7 +1002,7 @@ static void listKey(DomainInfo const &di, DNSSECKeeper& dk, bool printHeader = t
       cout<<key.first.getKey()->getBits()<<string(spacelen, ' ');
     }
 
-    string algname = DNSSECKeeper::algorithm2name(key.first.d_algorithm);
+    string algname = DNSSECKeeper::algorithm2name(key.first.getAlgorithm());
     spacelen = (algname.length() >= 16) ? 1 : 16 - algname.length();
     cout<<algname<<string(spacelen, ' ');
 
@@ -1121,7 +1109,7 @@ static int read1char(){
     return c;
 }
 
-static int clearZone(DNSSECKeeper& dk, const DNSName &zone) {
+static int clearZone(const DNSName &zone) {
   UeberBackend B;
   DomainInfo di;
 
@@ -1137,7 +1125,34 @@ static int clearZone(DNSSECKeeper& dk, const DNSName &zone) {
   return EXIT_SUCCESS;
 }
 
-static int editZone(const DNSName &zone) {
+class PDNSColors
+{
+public:
+  PDNSColors(bool nocolors)
+    : d_colors(!nocolors && isatty(STDOUT_FILENO) && getenv("NO_COLORS") == nullptr)
+  {
+  }
+  [[nodiscard]] string red() const
+  {
+    return d_colors ? "\x1b[31m" : "";
+  }
+  [[nodiscard]] string green() const
+  {
+    return d_colors ? "\x1b[32m" : "";
+  }
+  [[nodiscard]] string bold() const
+  {
+    return d_colors ? "\x1b[1m" : "";
+  }
+  [[nodiscard]] string rst() const
+  {
+    return d_colors ? "\x1b[0m" : "";
+  }
+private:
+  bool d_colors;
+};
+
+static int editZone(const DNSName &zone, const PDNSColors& col) {
   UeberBackend B;
   DomainInfo di;
   DNSSECKeeper dk(&B);
@@ -1146,6 +1161,13 @@ static int editZone(const DNSName &zone) {
     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);
@@ -1182,7 +1204,7 @@ static int editZone(const DNSName &zone) {
     sort(pre.begin(), pre.end(), DNSRecord::prettyCompare);
     for(const auto& dr : pre) {
       ostringstream os;
-      os<<dr.d_name<<"\t"<<dr.d_ttl<<"\tIN\t"<<DNSRecordContent::NumberToType(dr.d_type)<<"\t"<<dr.d_content->getZoneRepresentation(true)<<endl;
+      os<<dr.d_name<<"\t"<<dr.d_ttl<<"\tIN\t"<<DNSRecordContent::NumberToType(dr.d_type)<<"\t"<<dr.getContent()->getZoneRepresentation(true)<<endl;
       if(write(tmpfd, os.str().c_str(), os.str().length()) < 0)
         unixDie("Writing zone to temporary file");
     }
@@ -1235,19 +1257,20 @@ static int editZone(const DNSName &zone) {
   }
   if(checkZone(dk, B, zone, &checkrr)) {
   reAsk:;
-    cerr<<"\x1b[31;1mThere was a problem with your zone\x1b[0m\nOptions are: (e)dit your changes, (r)etry with original zone, (a)pply change anyhow, (q)uit: "<<endl;
+    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;
+    }
   }
 
 
@@ -1257,7 +1280,7 @@ static int editZone(const DNSName &zone) {
   set_difference(pre.cbegin(), pre.cend(), post.cbegin(), post.cend(), back_inserter(diff), DNSRecord::prettyCompare);
   for(const auto& d : diff) {
     ostringstream str;
-    str<<"\033[0;31m-"<< d.d_name <<" "<<d.d_ttl<<" IN "<<DNSRecordContent::NumberToType(d.d_type)<<" "<<d.d_content->getZoneRepresentation(true)<<"\033[0m"<<endl;
+    str << col.red() << "-" << d.d_name << " " << d.d_ttl << " IN " << DNSRecordContent::NumberToType(d.d_type) << " " <<d.getContent()->getZoneRepresentation(true) << col.rst() <<endl;
     changed[{d.d_name,d.d_type}] += str.str();
 
   }
@@ -1266,25 +1289,25 @@ static int editZone(const DNSName &zone) {
   for(const auto& d : diff) {
     ostringstream str;
 
-    str<<"\033[0;32m+"<< d.d_name <<" "<<d.d_ttl<<" IN "<<DNSRecordContent::NumberToType(d.d_type)<<" "<<d.d_content->getZoneRepresentation(true)<<"\033[0m"<<endl;
+    str<<col.green() << "+" << d.d_name << " " << d.d_ttl << " IN " <<DNSRecordContent::NumberToType(d.d_type) << " " << d.getContent()->getZoneRepresentation(true) << col.rst() <<endl;
     changed[{d.d_name,d.d_type}]+=str.str();
   }
   cout<<"Detected the following changes:"<<endl;
   for(const auto& c : changed) {
     cout<<c.second;
   }
-  if (changed.size() > 0) {
+  if (!changed.empty()) {
     if (changed.find({zone, QType::SOA}) == changed.end()) {
      reAsk3:;
       cout<<endl<<"You have not updated the SOA record! Would you like to increase-serial?"<<endl;
-      cout<<"(y)es - increase serial, (n)o - leave SOA record as is, (e)dit your changes, (q)uit:"<<endl;
+      cout<<"(y)es - increase serial, (n)o - leave SOA record as is, (e)dit your changes, (q)uit: "<<std::flush;
       int c = read1char();
       switch(c) {
         case 'y':
           {
             DNSRecord oldSoaDR = grouped[{zone, QType::SOA}].at(0); // there should be only one SOA record, so we can use .at(0);
             ostringstream str;
-            str<<"\033[0;31m-"<< oldSoaDR.d_name <<" "<<oldSoaDR.d_ttl<<" IN "<<DNSRecordContent::NumberToType(oldSoaDR.d_type)<<" "<<oldSoaDR.d_content->getZoneRepresentation(true)<<"\033[0m"<<endl;
+            str<< col.red() << "-" << oldSoaDR.d_name << " " << oldSoaDR.d_ttl << " IN " << DNSRecordContent::NumberToType(oldSoaDR.d_type) << " " <<oldSoaDR.getContent()->getZoneRepresentation(true) << col.rst() <<endl;
 
             SOAData sd;
             B.getSOAUncached(zone, sd);
@@ -1296,7 +1319,7 @@ static int editZone(const DNSName &zone) {
             DNSResourceRecord rr;
             makeIncreasedSOARecord(sd, "SOA-EDIT-INCREASE", soaEditKind, rr);
             DNSRecord dr(rr);
-            str<<"\033[0;32m+"<< dr.d_name <<" "<<dr.d_ttl<<" IN "<<DNSRecordContent::NumberToType(dr.d_type)<<" "<<dr.d_content->getZoneRepresentation(true)<<"\033[0m"<<endl;
+            str << col.green() << "+" << dr.d_name << " " << dr.d_ttl<< " IN " <<DNSRecordContent::NumberToType(dr.d_type) << " " <<dr.getContent()->getZoneRepresentation(true) << col.rst() <<endl;
 
             changed[{dr.d_name, dr.d_type}]+=str.str();
             grouped[{dr.d_name, dr.d_type}].at(0) = dr;
@@ -1318,7 +1341,7 @@ static int editZone(const DNSName &zone) {
     cout<<endl<<"No changes to apply."<<endl;
     return(EXIT_SUCCESS);
   }
-  cout<<endl<<"(a)pply these changes, (e)dit again, (r)etry with original zone, (q)uit: ";
+  cout<<endl<<"(a)pply these changes, (e)dit again, (r)etry with original zone, (q)uit: "<<std::flush;
   int c=read1char();
   post.clear();
   cerr<<'\n';
@@ -1363,7 +1386,7 @@ static int xcryptIP(const std::string& cmd, const std::string& ip, const std::st
 #endif /* HAVE_IPCIPHER */
 
 static int zonemdVerifyFile(const DNSName& zone, const string& fname) {
-  ZoneParserTNG zpt(fname, zone);
+  ZoneParserTNG zpt(fname, zone, "", true);
   zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
 
   bool validationDone, validationOK;
@@ -1435,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;
@@ -1506,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));
@@ -1514,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;
@@ -1527,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));
@@ -1535,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) {
@@ -1623,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);
   }
@@ -1643,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;
@@ -1709,15 +1734,19 @@ static int deleteRRSet(const std::string& zone_, const std::string& name_, const
 static int listAllZones(const string &type="") {
 
   int kindFilter = -1;
-  if (type.size()) {
+  if (!type.empty()) {
     if (toUpper(type) == "PRIMARY" || toUpper(type) == "MASTER")
       kindFilter = 0;
     else if (toUpper(type) == "SECONDARY" || toUpper(type) == "SLAVE")
       kindFilter = 1;
     else if (toUpper(type) == "NATIVE")
       kindFilter = 2;
+    else if (toUpper(type) == "PRODUCER")
+      kindFilter = 3;
+    else if (toUpper(type) == "CONSUMER")
+      kindFilter = 4;
     else {
-      cerr << "Syntax: pdnsutil list-all-zones [primary|secondary|native]" << endl;
+      cerr << "Syntax: pdnsutil list-all-zones [primary|secondary|native|producer|consumer]" << endl;
       return 1;
     }
   }
@@ -1745,6 +1774,47 @@ static int listAllZones(const string &type="") {
   return 0;
 }
 
+static int listMemberZones(const string& catalog)
+{
+
+  UeberBackend B("default");
+
+  DNSName catz(catalog);
+  DomainInfo di;
+  if (!B.getDomainInfo(catz, di)) {
+    cerr << "Zone '" << catz << "' not found" << endl;
+    return EXIT_FAILURE;
+  }
+  if (!di.isCatalogType()) {
+    cerr << "Zone '" << catz << "' is not a catalog zone" << endl;
+    return EXIT_FAILURE;
+  }
+
+  CatalogInfo::CatalogType type;
+  if (di.kind == DomainInfo::Producer) {
+    type = CatalogInfo::Producer;
+  }
+  else {
+    type = CatalogInfo::Consumer;
+  }
+
+  vector<CatalogInfo> members;
+  if (!di.backend->getCatalogMembers(catz, members, type)) {
+    cerr << "Backend does not support catalog zones" << endl;
+    return EXIT_FAILURE;
+  }
+
+  for (const auto& ci : members) {
+    cout << ci.d_zone << endl;
+  }
+
+  if (g_verbose) {
+    cout << "All zonecount: " << members.size() << endl;
+  }
+
+  return EXIT_SUCCESS;
+}
+
 static bool testAlgorithm(int algo)
 {
   return DNSCryptoKeyEngine::testOne(algo);
@@ -1755,7 +1825,7 @@ static bool testAlgorithms()
   return DNSCryptoKeyEngine::testAll();
 }
 
-static void testSpeed(DNSSECKeeper& dk, const DNSName& zone, const string& remote, int cores)
+static void testSpeed(const DNSName& zone, const string& /* remote */, int cores)
 {
   DNSResourceRecord rr;
   rr.qname=DNSName("blah")+zone;
@@ -1766,12 +1836,12 @@ static void testSpeed(DNSSECKeeper& dk, const DNSName& zone, const string& remot
 
   UeberBackend db("key-only");
 
-  if ( ! db.backends.size() )
+  if ( db.backends.empty() )
   {
     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;
@@ -1780,7 +1850,7 @@ static void testSpeed(DNSSECKeeper& dk, const DNSName& zone, const string& remot
   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;
@@ -1816,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));
     }
   }
 
@@ -1851,13 +1921,84 @@ static bool disableDNSSECOnZone(DNSSECKeeper& dk, const DNSName& zone)
   }
 
   string error, info;
-  bool ret = dk.unSecureZone(zone, error, info);
+  bool ret = dk.unSecureZone(zone, error);
   if (!ret) {
     cerr << error << endl;
   }
   return ret;
 }
 
+static int setZoneOptionsJson(const DNSName& zone, const string& options)
+{
+  UeberBackend B("default");
+  DomainInfo di;
+
+  if (!B.getDomainInfo(zone, di)) {
+    cerr << "No such zone " << zone << " in the database" << endl;
+    return EXIT_FAILURE;
+  }
+  if (!di.backend->setOptions(zone, options)) {
+    cerr << "Could not find backend willing to accept new zone configuration" << endl;
+    return EXIT_FAILURE;
+  }
+  return EXIT_SUCCESS;
+}
+
+static int setZoneOption(const DNSName& zone, const string& type, const string& option, const set<string>& values)
+{
+  UeberBackend B("default");
+  DomainInfo di;
+  CatalogInfo ci;
+
+  if (!B.getDomainInfo(zone, di)) {
+    cerr << "No such zone " << zone << " in the database" << endl;
+    return EXIT_FAILURE;
+  }
+
+  CatalogInfo::CatalogType ctype;
+  if (type == "producer") {
+    ctype = CatalogInfo::CatalogType::Producer;
+  }
+  else {
+    ctype = CatalogInfo::CatalogType::Consumer;
+  }
+
+  ci.fromJson(di.options, ctype);
+
+  if (option == "coo") {
+    ci.d_coo = (!values.empty() ? DNSName(*values.begin()) : DNSName());
+  }
+  else if (option == "unique") {
+    ci.d_unique = (!values.empty() ? DNSName(*values.begin()) : DNSName());
+  }
+  else if (option == "group") {
+    ci.d_group = values;
+  }
+
+  if (!di.backend->setOptions(zone, ci.toJson())) {
+    cerr << "Could not find backend willing to accept new zone configuration" << endl;
+    return EXIT_FAILURE;
+  }
+
+  return EXIT_SUCCESS;
+}
+
+static int setZoneCatalog(const DNSName& zone, const DNSName& catalog)
+{
+  UeberBackend B("default");
+  DomainInfo di;
+
+  if (!B.getDomainInfo(zone, di)) {
+    cerr << "No such zone " << zone << " in the database" << endl;
+    return EXIT_FAILURE;
+  }
+  if (!di.backend->setCatalog(zone, catalog)) {
+    cerr << "Could not find backend willing to accept new zone configuration" << endl;
+    return EXIT_FAILURE;
+  }
+  return EXIT_SUCCESS;
+}
+
 static int setZoneAccount(const DNSName& zone, const string &account)
 {
   UeberBackend B("default");
@@ -1905,7 +2046,7 @@ static bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = fals
   }
   if (!exportDS) {
     cout<<"This is a "<<DomainInfo::getKindString(di.kind)<<" zone"<<endl;
-    if(di.kind == DomainInfo::Master) {
+    if (di.isPrimaryType()) {
       cout<<"Last SOA serial number we notified: "<<di.notified_serial<<" ";
       SOAData sd;
       if(B.getSOAUncached(zone, sd)) {
@@ -1916,9 +2057,9 @@ static bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = fals
         cout<<sd.serial<<" (serial in the database)"<<endl;
       }
     }
-    else if(di.kind == DomainInfo::Slave) {
-      cout << "Primar" << addS(di.masters, "y", "ies") << ": ";
-      for(const auto& m : di.masters)
+    else if (di.isSecondaryType()) {
+      cout << "Primar" << addS(di.primaries, "y", "ies") << ": ";
+      for (const auto& m : di.primaries)
         cout<<m.toStringWithPort()<<" ";
       cout<<endl;
       struct tm tm;
@@ -1951,7 +2092,7 @@ static bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = fals
   }
 
   NSEC3PARAMRecordContent ns3pr;
-  bool narrow;
+  bool narrow = false;
   bool haveNSEC3=dk.getNSEC3PARAM(zone, &ns3pr, &narrow);
 
   DNSSECKeeper::keyset_t keyset=dk.getKeys(zone);
@@ -1959,12 +2100,12 @@ static bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = fals
   if (!exportDS) {
     std::vector<std::string> meta;
 
-    if (B.getDomainMetadata(zone, "TSIG-ALLOW-AXFR", meta) && meta.size() > 0) {
+    if (B.getDomainMetadata(zone, "TSIG-ALLOW-AXFR", meta) && !meta.empty()) {
       cout << "Zone has following allowed TSIG key(s): " << boost::join(meta, ",") << endl;
     }
 
     meta.clear();
-    if (B.getDomainMetadata(zone, "AXFR-MASTER-TSIG", meta) && meta.size() > 0) {
+    if (B.getDomainMetadata(zone, "AXFR-MASTER-TSIG", meta) && !meta.empty()) {
       cout << "Zone uses following TSIG key(s): " << boost::join(meta, ",") << endl;
     }
 
@@ -2060,7 +2201,7 @@ static bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = fals
     }
 
     for(const DNSSECKeeper::keyset_t::value_type& value :  keyset) {
-      string algname = DNSSECKeeper::algorithm2name(value.first.d_algorithm);
+      string algname = DNSSECKeeper::algorithm2name(value.first.getAlgorithm());
       if (!exportDS) {
         cout<<"ID = "<<value.second.id<<" ("<<DNSSECKeeper::keyTypeToString(value.second.keyType)<<")";
       }
@@ -2069,9 +2210,9 @@ static bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = fals
         continue;
       }
       if (!exportDS) {
-        cout<<", flags = "<<std::to_string(value.first.d_flags);
+        cout<<", flags = "<<std::to_string(value.first.getFlags());
         cout<<", tag = "<<value.first.getDNSKEY().getTag();
-        cout<<", algo = "<<(int)value.first.d_algorithm<<", bits = "<<value.first.getKey()->getBits()<<"\t"<<((int)value.second.active == 1 ? "  A" : "Ina")<<"ctive\t"<<(value.second.published ? " Published" : " Unpublished")<<"  ( " + algname + " ) "<<endl;
+        cout<<", algo = "<<(int)value.first.getAlgorithm()<<", bits = "<<value.first.getKey()->getBits()<<"\t"<<((int)value.second.active == 1 ? "  A" : "Ina")<<"ctive\t"<<(value.second.published ? " Published" : " Unpublished")<<"  ( " + algname + " ) "<<endl;
       }
 
       if (!exportDS) {
@@ -2101,27 +2242,33 @@ static bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = fals
       }
     }
   }
+  if (!di.options.empty()) {
+    cout << "Options:" << endl;
+    cout << di.options << endl;
+  }
+  if (!di.catalog.empty()) {
+    cout << "Catalog: " << endl;
+    cout << di.catalog << endl;
+  }
   return true;
 }
 
 static bool secureZone(DNSSECKeeper& dk, const DNSName& zone)
 {
-  // parse attribute
-  int k_size;
-  int z_size;
   // temp var for addKey
-  int64_t id;
+  int64_t id{-1};
 
+  // parse attribute
   string k_algo = ::arg()["default-ksk-algorithm"];
-  k_size = ::arg().asNum("default-ksk-size");
+  int k_size = ::arg().asNum("default-ksk-size");
   string z_algo = ::arg()["default-zsk-algorithm"];
-  z_size = ::arg().asNum("default-zsk-size");
+  int z_size = ::arg().asNum("default-zsk-size");
 
   if (k_size < 0) {
      throw runtime_error("KSK key size must be equal to or greater than 0");
   }
 
-  if (k_algo == "" && z_algo == "") {
+  if (k_algo.empty() && z_algo.empty()) {
      throw runtime_error("Zero algorithms given for KSK+ZSK in total");
   }
 
@@ -2136,24 +2283,24 @@ static bool secureZone(DNSSECKeeper& dk, const DNSName& zone)
 
   DomainInfo di;
   UeberBackend B("default");
-  if(!B.getDomainInfo(zone, di, false) || !di.backend) { // di.backend and B are mostly identical
+  // di.backend and B are mostly identical
+  if(!B.getDomainInfo(zone, di, false) || di.backend == nullptr) {
     cerr<<"Can't find a zone called '"<<zone<<"'"<<endl;
     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;
   }
 
-  if (k_algo != "") { // Add a KSK
+  if (!k_algo.empty()) { // Add a KSK
     if (k_size)
       cout << "Securing zone with key size " << k_size << endl;
     else
       cout << "Securing zone with default key size" << endl;
 
-    cout << "Adding "<<(z_algo == "" ? "CSK (257)" : "KSK")<<" with algorithm " << k_algo << endl;
+    cout << "Adding " << (z_algo.empty() ? "CSK (257)" : "KSK") << " with algorithm " << k_algo << endl;
 
     int k_real_algo = DNSSECKeeper::shorthand2algorithm(k_algo);
 
@@ -2166,8 +2313,8 @@ static bool secureZone(DNSSECKeeper& dk, const DNSName& zone)
     }
   }
 
-  if (z_algo != "") {
-    cout << "Adding "<<(k_algo == "" ? "CSK (256)" : "ZSK")<<" with algorithm " << z_algo << endl;
+  if (!z_algo.empty()) {
+    cout << "Adding " << (k_algo.empty() ? "CSK (256)" : "ZSK") << " with algorithm " << z_algo << endl;
 
     int z_real_algo = DNSSECKeeper::shorthand2algorithm(z_algo);
 
@@ -2204,13 +2351,14 @@ 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;
-  if(!B.getDomainInfo(zone, di) || !di.backend) { // di.backend and B are mostly identical
+  // di.backend and B are mostly identical
+  if(!B.getDomainInfo(zone, di) || di.backend == nullptr) {
     cout << "Can't find zone we just created, aborting" << endl;
     return EXIT_FAILURE;
   }
@@ -2347,6 +2495,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
 {
@@ -2358,6 +2507,7 @@ try
     ("force", "force an action")
     ("config-name", po::value<string>()->default_value(""), "virtual configuration name")
     ("config-dir", po::value<string>()->default_value(SYSCONFDIR), "location of pdns.conf")
+    ("no-colors", "do not use colors in output")
     ("commands", po::value<vector<string> >());
 
   po::positional_options_description p;
@@ -2381,7 +2531,7 @@ try
     cout << "Usage: \npdnsutil [options] <command> [params ..]\n"
          << endl;
     cout << "Commands:" << endl;
-    cout << "activate-tsig-key ZONE NAME {primary|secondary}" << endl;
+    cout << "activate-tsig-key ZONE NAME {primary|secondary|producer|consumer}" << endl;
     cout << "                                   Enable TSIG authenticated AXFR using the key NAME for ZONE" << endl;
     cout << "activate-zone-key ZONE KEY-ID      Activate the key with key id KEY-ID in ZONE" << endl;
     cout << "add-record ZONE NAME TYPE [ttl] content" << endl;
@@ -2451,8 +2601,9 @@ try
     cout << "list-keys [ZONE]                   List DNSSEC keys for ZONE. When ZONE is unset, display all keys for all active zones" << endl;
     cout << "                                   --verbose or -v will also include the keys for disabled or empty zones" << endl;
     cout << "list-zone ZONE                     List zone contents" << endl;
-    cout << "list-all-zones [primary|secondary|native]" << endl;
+    cout << "list-all-zones [primary|secondary|native|producer|consumer]" << endl;
     cout << "                                   List all active zone names. --verbose or -v will also include disabled or empty zones" << endl;
+    cout << "list-member-zones CATALOG          List all members of catalog zone CATALOG" << endl;
 
     cout << "list-tsig-keys                     List all TSIG keys" << endl;
     cout << "publish-zone-key ZONE KEY-ID       Publish the zone key with key id KEY-ID in ZONE" << endl;
@@ -2463,7 +2614,13 @@ try
     cout << "       content [content..]" << endl;
     cout << "secure-all-zones [increase-serial] Secure all zones without keys" << endl;
     cout << "secure-zone ZONE [ZONE ..]         Add DNSSEC to zone ZONE" << endl;
-    cout << "set-kind ZONE KIND                 Change the kind of ZONE to KIND (primary, secondary, native)" << endl;
+    cout << "set-kind ZONE KIND                 Change the kind of ZONE to KIND (primary, secondary, native, producer, consumer)" << endl;
+    cout << "set-options-json ZONE JSON         Change the options of ZONE to JSON" << endl;
+    cout << "set-option ZONE                    Set or remove an option for ZONE Providing an empty value removes an option" << endl;
+    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. 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;
@@ -2484,6 +2641,7 @@ try
     cout << "test-schema ZONE                   Test DB schema - will create ZONE" << endl;
     cout << "raw-lua-from-content TYPE CONTENT  Display record contents in a form suitable for dnsdist's `SpoofRawAction`" << endl;
     cout << "zonemd-verify-file ZONE FILE       Validate ZONEMD for ZONE" << endl;
+    cout << "lmdb-get-backend-version           Get schema version supported by backend" << endl;
     cout << desc << endl;
 
     return 0;
@@ -2491,6 +2649,10 @@ try
 
   loadMainConfig(g_vm["config-dir"].as<string>());
 
+  if (cmds.at(0) == "lmdb-get-backend-version") {
+    cout << "5" << endl; // FIXME this should reuse the constant from lmdbbackend but that is currently a #define in a .cc
+    return 0;
+  }
   if (cmds.at(0) == "test-algorithm") {
     if(cmds.size() != 2) {
       cerr << "Syntax: pdnsutil test-algorithm algonum"<<endl;
@@ -2584,7 +2746,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;
@@ -2668,13 +2830,20 @@ try
   }
   else if (cmds.at(0) == "list-all-zones") {
     if (cmds.size() > 2) {
-      cerr << "Syntax: pdnsutil list-all-zones [primary|secondary|native]" << endl;
+      cerr << "Syntax: pdnsutil list-all-zones [primary|secondary|native|producer|consumer]" << endl;
       return 0;
     }
     if (cmds.size() == 2)
       return listAllZones(cmds.at(1));
     return listAllZones();
   }
+  else if (cmds.at(0) == "list-member-zones") {
+    if (cmds.size() != 2) {
+      cerr << "Syntax: pdnsutil list-member-zones CATALOG" << endl;
+      return 0;
+    }
+    return listMemberZones(cmds.at(1));
+  }
   else if (cmds.at(0) == "test-zone") {
     cerr << "Did you mean check-zone?"<<endl;
     return 0;
@@ -2698,7 +2867,7 @@ try
       cerr << "Syntax: pdnsutil test-speed numcores [signing-server]"<<endl;
       return 0;
     }
-    testSpeed(dk, DNSName(cmds.at(1)), (cmds.size() > 3) ? cmds.at(3) : "", pdns::checked_stoi<int>(cmds.at(2)));
+    testSpeed(DNSName(cmds.at(1)), (cmds.size() > 3) ? cmds.at(3) : "", pdns::checked_stoi<int>(cmds.at(2)));
   }
   else if (cmds.at(0) == "verify-crypto") {
     if(cmds.size() != 2) {
@@ -2890,7 +3059,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;
@@ -2934,19 +3103,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) {
@@ -2955,12 +3124,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) {
@@ -3004,17 +3173,18 @@ try
     if (cmds.at(1) == ".")
       cmds.at(1).clear();
 
-    return editZone(DNSName(cmds.at(1)));
+    PDNSColors col(g_vm.count("no-colors"));
+    return editZone(DNSName(cmds.at(1)), col);
   }
   else if (cmds.at(0) == "clear-zone") {
     if(cmds.size() != 2) {
-      cerr<<"Syntax: pdnsutil edit-zone ZONE"<<endl;
+      cerr<<"Syntax: pdnsutil clear-zone ZONE"<<endl;
       return 0;
     }
     if (cmds.at(1) == ".")
       cmds.at(1).clear();
 
-    return clearZone(dk, DNSName(cmds.at(1)));
+    return clearZone(DNSName(cmds.at(1)));
   }
   else if (cmds.at(0) == "list-keys") {
     if(cmds.size() > 2) {
@@ -3110,6 +3280,59 @@ try
     auto kind = DomainInfo::stringToKind(cmds.at(2));
     return setZoneKind(zone, kind);
   }
+  else if (cmds.at(0) == "set-options-json") {
+    if (cmds.size() != 3) {
+      cerr << "Syntax: pdnsutil set-options ZONE VALUE" << endl;
+      return EXIT_FAILURE;
+    }
+
+    // Verify json
+    if (!cmds.at(2).empty()) {
+      std::string err;
+      json11::Json doc = json11::Json::parse(cmds.at(2), err);
+      if (doc.is_null()) {
+        cerr << "Parsing of JSON document failed:" << err << endl;
+        return EXIT_FAILURE;
+      }
+    }
+
+    DNSName zone(cmds.at(1));
+
+    return setZoneOptionsJson(zone, cmds.at(2));
+  }
+  else if (cmds.at(0) == "set-option") {
+    if (cmds.size() < 5 || (cmds.size() > 5 && (cmds.at(3) != "group"))) {
+      cerr << "Syntax: pdnsutil set-option ZONE [producer|consumer] [coo|unique|group] VALUE [VALUE ...]1" << endl;
+      return EXIT_FAILURE;
+    }
+
+    if ((cmds.at(2) != "producer" && cmds.at(2) != "consumer") || (cmds.at(3) != "coo" && cmds.at(3) != "unique" && cmds.at(3) != "group")) {
+      cerr << "Syntax: pdnsutil set-option ZONE [producer|consumer] [coo|unique|group] VALUE [VALUE ...]" << endl;
+      return EXIT_FAILURE;
+    }
+
+    DNSName zone(cmds.at(1));
+    set<string> values;
+    for (unsigned int n = 4; n < cmds.size(); ++n) {
+      if (!cmds.at(n).empty()) {
+        values.insert(cmds.at(n));
+      }
+    }
+
+    return setZoneOption(zone, cmds.at(2), cmds.at(3), values);
+  }
+  else if (cmds.at(0) == "set-catalog") {
+    if (cmds.size() != 3) {
+      cerr << "Syntax: pdnsutil set-catalog ZONE CATALOG" << endl;
+      return 0;
+    }
+    DNSName zone(cmds.at(1));
+    DNSName catalog; // Create an empty DNSName()
+    if (!cmds.at(2).empty()) {
+      catalog = DNSName(cmds.at(2));
+    }
+    return setZoneCatalog(zone, catalog);
+  }
   else if (cmds.at(0) == "set-account") {
     if(cmds.size() != 3) {
       cerr<<"Syntax: pdnsutil set-account ZONE ACCOUNT"<<endl;
@@ -3240,7 +3463,7 @@ try
     DNSName zone(cmds.at(1));
     DNSName record(cmds.at(2));
     NSEC3PARAMRecordContent ns3pr;
-    bool narrow;
+    bool narrow = false;
     if(!dk.getNSEC3PARAM(zone, &ns3pr, &narrow)) {
       cerr<<"The '"<<zone<<"' zone does not use NSEC3"<<endl;
       return 0;
@@ -3284,7 +3507,7 @@ try
     string zone = cmds.at(1);
     auto id = pdns::checked_stoi<unsigned int>(cmds.at(2));
     DNSSECPrivateKey dpk = dk.getKeyById(DNSName(zone), id);
-    dpk.getKey()->convertToPEM(*stdout);
+    dpk.getKey()->convertToPEMFile(*stdout);
   }
   else if (cmds.at(0) == "increase-serial") {
     if (cmds.size() < 2) {
@@ -3311,28 +3534,29 @@ try
     }
 
     DNSKEYRecordContent drc;
-    shared_ptr<DNSCryptoKeyEngine> key{DNSCryptoKeyEngine::makeFromPEMFile(drc, filename, *fp, algorithm)};
+    shared_ptr<DNSCryptoKeyEngine> key{DNSCryptoKeyEngine::makeFromPEMFile(drc, algorithm, *fp, filename)};
     if (!key) {
       cerr << "Could not convert key from PEM to internal format" << endl;
       return 1;
     }
 
     DNSSECPrivateKey dpk;
-    dpk.setKey(key);
 
-    pdns::checked_stoi_into(dpk.d_algorithm, cmds.at(3));
-    if (dpk.d_algorithm == DNSSECKeeper::RSASHA1NSEC3SHA1) {
-      dpk.d_algorithm = DNSSECKeeper::RSASHA1;
+    uint8_t algo = 0;
+    pdns::checked_stoi_into(algo, cmds.at(3));
+    if (algo == DNSSECKeeper::RSASHA1NSEC3SHA1) {
+      algo = DNSSECKeeper::RSASHA1;
     }
 
-    cerr << (int)dpk.d_algorithm << endl;
+    cerr << std::to_string(algo) << endl;
 
+    uint16_t flags = 0;
     if (cmds.size() > 4) {
       if (pdns_iequals(cmds.at(4), "ZSK")) {
-        dpk.d_flags = 256;
+        flags = 256;
       }
       else if (pdns_iequals(cmds.at(4), "KSK")) {
-        dpk.d_flags = 257;
+        flags = 257;
       }
       else {
         cerr << "Unknown key flag '" << cmds.at(4) << "'" << endl;
@@ -3340,10 +3564,11 @@ try
       }
     }
     else {
-      dpk.d_flags = 257; // ksk
+      flags = 257; // ksk
     }
+    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;
@@ -3366,24 +3591,18 @@ try
     }
     string zone = cmds.at(1);
     string fname = cmds.at(2);
-    DNSSECPrivateKey dpk;
     DNSKEYRecordContent drc;
     shared_ptr<DNSCryptoKeyEngine> key(DNSCryptoKeyEngine::makeFromISCFile(drc, fname.c_str()));
-    dpk.setKey(key);
-    dpk.d_algorithm = drc.d_algorithm;
-
-    if(dpk.d_algorithm == DNSSECKeeper::RSASHA1NSEC3SHA1)
-      dpk.d_algorithm = DNSSECKeeper::RSASHA1;
 
-    dpk.d_flags = 257;
+    uint16_t flags = 257;
     bool active=true;
     bool published=true;
 
     for(unsigned int n = 3; n < cmds.size(); ++n) {
       if (pdns_iequals(cmds.at(n), "ZSK"))
-        dpk.d_flags = 256;
+        flags = 256;
       else if (pdns_iequals(cmds.at(n), "KSK"))
-        dpk.d_flags = 257;
+        flags = 257;
       else if (pdns_iequals(cmds.at(n), "active"))
         active = true;
       else if (pdns_iequals(cmds.at(n), "passive") || pdns_iequals(cmds.at(n), "inactive")) // passive eventually needs to be removed
@@ -3397,7 +3616,15 @@ try
         return 1;
       }
     }
-    int64_t id;
+
+    DNSSECPrivateKey dpk;
+    uint8_t algo = key->getAlgorithm();
+    if (algo == DNSSECKeeper::RSASHA1NSEC3SHA1) {
+      algo = DNSSECKeeper::RSASHA1;
+    }
+    dpk.setKey(key, flags, algo);
+
+    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;
@@ -3457,7 +3684,6 @@ try
     if(bits)
       cerr<<"Requesting specific key size of "<<bits<<" bits"<<endl;
 
-    DNSSECPrivateKey dspk;
     shared_ptr<DNSCryptoKeyEngine> dpk(DNSCryptoKeyEngine::make(algorithm));
     if(!bits) {
       if(algorithm <= 10)
@@ -3475,12 +3701,11 @@ try
       }
     }
     dpk->create(bits);
-    dspk.setKey(dpk);
-    dspk.d_algorithm = algorithm;
-    dspk.d_flags = keyOrZone ? 257 : 256;
+    DNSSECPrivateKey dspk;
+    dspk.setKey(dpk, keyOrZone ? 257 : 256, algorithm);
 
     // print key to stdout
-    cout << "Flags: " << dspk.d_flags << endl <<
+    cout << "Flags: " << dspk.getFlags() << endl <<
              dspk.getKey()->convertToISC() << endl;
   }
   else if (cmds.at(0) == "generate-tsig-key") {
@@ -3562,12 +3787,12 @@ try
     }
     DNSName zname(cmds.at(1));
     string name = cmds.at(2);
-    if (cmds.at(3) == "primary" || 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) == "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" << endl;
+      cerr << "Invalid parameter '" << cmds.at(3) << "', expected primary or secondary type" << endl;
       return 1;
     }
     UeberBackend B("default");
@@ -3602,17 +3827,17 @@ try
   else if (cmds.at(0) == "deactivate-tsig-key") {
     string metaKey;
     if (cmds.size() < 4) {
-      cerr << "Syntax: " << cmds.at(0) << " ZONE NAME {primary|secondary}" << endl;
+      cerr << "Syntax: " << cmds.at(0) << " ZONE NAME {primary|secondary|producer|consumer}" << endl;
       return 0;
     }
     DNSName zname(cmds.at(1));
     string name = cmds.at(2);
-    if (cmds.at(3) == "primary" || 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) == "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" << endl;
+      cerr << "Invalid parameter '" << cmds.at(3) << "', expected primary or secondary type" << endl;
       return 1;
     }
 
@@ -3684,8 +3909,8 @@ try
     }
     DNSName zone(cmds.at(1));
     string kind = cmds.at(2);
-    static vector<string> multiMetaWhitelist = {"ALLOW-AXFR-FROM", "ALLOW-DNSUPDATE-FROM",
-      "ALSO-NOTIFY", "TSIG-ALLOW-AXFR", "TSIG-ALLOW-DNSUPDATE",
+    const static std::array<string, 7> multiMetaWhitelist = {"ALLOW-AXFR-FROM", "ALLOW-DNSUPDATE-FROM",
+      "ALSO-NOTIFY", "TSIG-ALLOW-AXFR", "TSIG-ALLOW-DNSUPDATE", "GSS-ALLOW-AXFR-PRINCIPAL",
       "PUBLISH-CDS"};
     bool clobber = true;
     if (cmds.at(0) == "add-meta") {
@@ -3729,7 +3954,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);
@@ -3751,20 +3975,19 @@ try
         "PubLabel: " << pub_label << std::endl;
 
       DNSKEYRecordContent drc;
-      DNSSECPrivateKey dpk;
-      dpk.d_flags = (keyOrZone ? 257 : 256);
 
       shared_ptr<DNSCryptoKeyEngine> dke(DNSCryptoKeyEngine::makeFromISCString(drc, iscString.str()));
       if(!dke->checkKey()) {
         cerr << "Invalid DNS Private Key in engine " << module << " slot " << slot << std::endl;
         return 1;
       }
-      dpk.setKey(dke);
+      DNSSECPrivateKey dpk;
+      dpk.setKey(dke, keyOrZone ? 257 : 256);
 
       // 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...
@@ -3848,24 +4071,32 @@ 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;
+    }
+
+    if (cmds.at(1) == cmds.at(2)) {
+      cerr << "Error: b2b-migrate OLD NEW: OLD cannot be the same as NEW" << endl;
       return 1;
     }
 
-    DNSBackend *src,*tgt;
-    src = tgt = nullptr;
+    unique_ptr<DNSBackend> src{nullptr};
+    unique_ptr<DNSBackend> tgt{nullptr};
 
-    for(DNSBackend *b : BackendMakers().all()) {
-      if (b->getPrefix() == cmds.at(1))
-        src = b;
-      if (b->getPrefix() == cmds.at(2))
-        tgt = b;
+    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) {
+
+    if (src == nullptr) {
       cerr << "Unknown source backend '" << cmds.at(1) << "'" << endl;
       return 1;
     }
-    if (!tgt) {
+    if (tgt == nullptr) {
       cerr << "Unknown target backend '" << cmds.at(2) << "'" << endl;
       return 1;
     }
@@ -3875,7 +4106,7 @@ try
     vector<DomainInfo> domains;
 
     tgt->getAllDomains(&domains, false, true);
-    if (domains.size()>0)
+    if (!domains.empty())
       throw PDNSException("Target backend has zone(s), please clean it first");
 
     src->getAllDomains(&domains, false, true);
@@ -3886,7 +4117,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");
@@ -3906,7 +4138,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++;
         }
       }
@@ -3956,22 +4190,22 @@ try
       return 1;
     }
 
-    DNSBackend *db;
-    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) {
+    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 2ea2f68944206889d487acf7118f18220ab7046e..dc53f2930752dfaa64615aa95c99716849897e2a 100644 (file)
@@ -1,3 +1,4 @@
+#include <openssl/evp.h>
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -18,6 +19,7 @@
 #include <openssl/ec.h>
 #endif
 
+#include "misc.hh"
 #include "pkcs11signers.hh"
 /* TODO
 
@@ -42,8 +44,8 @@ in it. you need to use softhsm tools to manage this all.
 static CK_FUNCTION_LIST** p11_modules;
 #endif
 
-#define ECDSA256_PARAMS "\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07"
-#define ECDSA384_PARAMS "\x06\x05\x2b\x81\x04\x00\x22"
+static constexpr const char* ECDSA256_PARAMS{"\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07"};
+static constexpr const char* ECDSA384_PARAMS{"\x06\x05\x2b\x81\x04\x00\x22"};
 
 // map for signing algorithms
 static std::map<unsigned int,CK_MECHANISM_TYPE> dnssec2smech = boost::assign::map_list_of
@@ -71,14 +73,14 @@ static std::map<unsigned int,CK_MECHANISM_TYPE> dnssec2cmech = boost::assign::ma
 (13, CKM_ECDSA_KEY_PAIR_GEN)
 (14, CKM_ECDSA_KEY_PAIR_GEN);
 
-typedef enum { Attribute_Byte, Attribute_Long, Attribute_String } CkaValueType;
+using CkaValueType = enum { Attribute_Byte, Attribute_Long, Attribute_String };
 
 // Attribute handling
 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;
@@ -112,19 +114,19 @@ public:
     setLong(value);
   }
 
-  CkaValueType valueType() const {
+  [[nodiscard]] CkaValueType valueType() const {
     return ckType;
   }
 
-  const std::string &str() const {
+  [[nodiscard]] const std::string &str() const {
     return ckString;
   };
 
-  unsigned char byte() const {
+  [[nodiscard]] unsigned char byte() const {
     return ckByte;
   }
 
-  unsigned long ulong() const {
+  [[nodiscard]] unsigned long ulong() const {
     return ckLong;
   }
 
@@ -211,11 +213,12 @@ public:
 
 class Pkcs11Slot {
   private:
-    bool d_logged_in;
+    bool d_logged_in{};
     CK_FUNCTION_LIST* d_functions; // module functions
     CK_SESSION_HANDLE d_session;
     CK_SLOT_ID d_slot;
-    CK_RV d_err;
+    CK_RV d_err{};
+    std::string d_pin;
 
     void logError(const std::string& operation) const {
       if (d_err) {
@@ -225,12 +228,12 @@ class Pkcs11Slot {
     }
 
   public:
-  Pkcs11Slot(CK_FUNCTION_LIST* functions, const CK_SLOT_ID& slot) :
-    d_logged_in(false),
-    d_functions(functions),
-    d_slot(slot),
-    d_err(0)
-  {
+    Pkcs11Slot(CK_FUNCTION_LIST* functions, const CK_SLOT_ID& slot) :
+      d_logged_in(false),
+      d_functions(functions),
+      d_slot(slot),
+      d_err(0)
+    {
       CK_TOKEN_INFO tokenInfo;
 
       if ((d_err = d_functions->C_OpenSession(this->d_slot, CKF_SERIAL_SESSION|CKF_RW_SESSION, 0, 0, &(this->d_session)))) {
@@ -246,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; }
@@ -276,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;
@@ -299,24 +309,43 @@ class Pkcs11Token {
       }
     }
 
-    unsigned int ecparam2bits(const std::string& obj) const {
+    [[nodiscard]] unsigned int ecparam2bits(const std::string& obj) const {
       // if we can use some library to parse the EC parameters, better use it.
       // otherwise fall back to using hardcoded primev256 and secp384r1
 #ifdef HAVE_LIBCRYPTO_ECDSA
+#if OPENSSL_VERSION_MAJOR >= 3
+      using Key = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>;
+#else
+      using Key = std::unique_ptr<EC_KEY, decltype(&EC_KEY_free)>;
+      using BigNum = std::unique_ptr<BIGNUM, decltype(&BN_clear_free)>;
+#endif
+
       unsigned int bits = 0;
-      const unsigned char *in = reinterpret_cast<const unsigned char*>(obj.c_str());
-      auto order = std::unique_ptr<BIGNUM, void(*)(BIGNUM*)>(BN_new(), BN_clear_free);
-      auto tempKey = d2i_ECParameters(nullptr, &in, obj.size());
-      if (tempKey != nullptr) {
-        auto key = std::unique_ptr<EC_KEY, void(*)(EC_KEY*)>(tempKey, EC_KEY_free);
-        tempKey = nullptr;
-        if (EC_GROUP_get_order(EC_KEY_get0_group(key.get()), order.get(), nullptr) == 1) {
-          bits = BN_num_bits(order.get());
-        }
+
+      // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+      const auto* objCStr = reinterpret_cast<const unsigned char*>(obj.c_str());
+#if OPENSSL_VERSION_MAJOR >= 3
+      auto key = Key(d2i_KeyParams(EVP_PKEY_EC, nullptr, &objCStr, static_cast<long>(obj.size())), EVP_PKEY_free);
+#else
+      auto key = Key(d2i_ECParameters(nullptr, &objCStr, static_cast<long>(obj.size())), EC_KEY_free);
+#endif
+      if (key == nullptr) {
+        throw pdns::OpenSSL::error("PKCS11", "Cannot parse EC parameters from DER");
+      }
+
+#if OPENSSL_VERSION_MAJOR >= 3
+      bits = EVP_PKEY_get_bits(key.get());
+#else
+      const auto* group = EC_KEY_get0_group(key.get());
+      auto order = BigNum(BN_new(), BN_clear_free);
+      if (EC_GROUP_get_order(group, order.get(), nullptr) == 1) {
+        bits = BN_num_bits(order.get());
       }
+#endif
 
-      if (bits == 0)
+      if (bits == 0) {
         throw PDNSException("Unsupported EC key");
+      }
 
       return bits;
 #else
@@ -350,61 +379,64 @@ 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 PCKS#11 private key "<<d_label<<std::endl;;
+        g_log<<Logger::Warning<<"Cannot load PKCS#11 private key "<<d_label<<std::endl;;
         return;
       }
       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 PCKS#11 public key "<<d_pub_label<<std::endl;
+        g_log<<Logger::Warning<<"Cannot load PKCS#11 public key "<<d_pub_label<<std::endl;
         return;
       }
       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();
             d_exponent = attr[1].str();
             d_bits = attr[2].ulong();
           } else {
-            throw PDNSException("Cannot load attributes for PCKS#11 public key " + d_pub_label);
+            throw PDNSException("Cannot load attributes for PKCS#11 public key " + d_pub_label);
           }
         } 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);
             if (attr[1].str().length() != (d_bits*2/8 + 3)) throw PDNSException("EC Point data invalid");
             d_ec_point = attr[1].str().substr(3);
           } else {
-            throw PDNSException("Cannot load attributes for PCKS#11 public key " + d_pub_label);
+            throw PDNSException("Cannot load attributes for PKCS#11 public key " + d_pub_label);
           }
         } else {
-          throw PDNSException("Cannot determine type for PCKS#11 public key " + d_pub_label);
+          throw PDNSException("Cannot determine type for PKCS#11 public key " + d_pub_label);
         }
       } else {
-        throw PDNSException("Cannot load attributes for PCKS#11 public key " + d_pub_label);
+        throw PDNSException("Cannot load attributes for PKCS#11 public key " + d_pub_label);
       }
 
       d_loaded = true;
@@ -416,7 +448,7 @@ class Pkcs11Token {
 
       size_t k;
       auto pubAttr = std::make_unique<CK_ATTRIBUTE[]>(pubAttributes.size());
-      auto privAttr = std::make_unique<CK_ATTRIBUTE[]>(pubAttributes.size());
+      auto privAttr = std::make_unique<CK_ATTRIBUTE[]>(privAttributes.size());
 
       k = 0;
       for(P11KitAttribute& attribute :  pubAttributes) {
@@ -444,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) {
@@ -461,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;
@@ -493,25 +534,6 @@ class Pkcs11Token {
       return d_err;
     }
 
-    int DigestKey(std::string& result) {
-      auto slot = d_slot->lock();
-      CK_MECHANISM mech;
-      mech.mechanism = CKM_SHA_1;
-
-      DigestInit(*slot, &mech);
-
-      if (d_key_type == CKK_RSA) {
-        DigestUpdate(*slot, d_modulus);
-        DigestUpdate(*slot, d_exponent);
-      } else if (d_key_type == CKK_EC || d_key_type == CKK_ECDSA) {
-        DigestUpdate(*slot, d_ec_point);
-      }
-
-      DigestFinal(*slot, result);
-
-      return d_err;
-    }
-
     int DigestFinal(Pkcs11Slot& slot, std::string& result) {
       CK_BYTE buffer[1024] = {0};
       CK_ULONG buflen = sizeof buffer; // should be enough for most digests
@@ -758,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)
 {
@@ -769,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) {
@@ -887,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);
     }
     };
   };
@@ -924,19 +945,6 @@ bool PKCS11DNSCryptoKeyEngine::verify(const std::string& msg, const std::string&
   }
 };
 
-std::string PKCS11DNSCryptoKeyEngine::getPubKeyHash() const {
-  // find us a public key
-  std::shared_ptr<Pkcs11Token> d_slot;
-  d_slot = Pkcs11Token::GetToken(d_module, d_slot_id, d_label, d_pub_label);
-  if (d_slot->LoggedIn() == false)
-    if (d_slot->Login(d_pin) == false)
-      throw PDNSException("Not logged in to token");
-
-  std::string result;
-  if (d_slot->DigestKey(result) == 0) return result;
-  throw PDNSException("Could not digest key (maybe it's missing?)");
-};
-
 std::string PKCS11DNSCryptoKeyEngine::getPublicKeyString() const {
   std::string result("");
   std::shared_ptr<Pkcs11Token> d_slot;
index 831493deb29e975f808bf2a78af1954a6d618417..36cef9f54d09d878fb8d86590cdb151f4632d1f7 100644 (file)
@@ -32,12 +32,8 @@ class PKCS11DNSCryptoKeyEngine : public DNSCryptoKeyEngine
 
   public:
     PKCS11DNSCryptoKeyEngine(unsigned int algorithm);
-    ~PKCS11DNSCryptoKeyEngine();
+    ~PKCS11DNSCryptoKeyEngine() override;
 
-    bool operator<(const PKCS11DNSCryptoKeyEngine& rhs) const
-    {
-      return false;
-    }
     PKCS11DNSCryptoKeyEngine(const PKCS11DNSCryptoKeyEngine& orig);
 
     string getName() const override { return "P11 Kit PKCS#11"; };
@@ -52,14 +48,12 @@ class PKCS11DNSCryptoKeyEngine : public DNSCryptoKeyEngine
 
     bool verify(const std::string& msg, const std::string& signature) const override;
 
-    std::string getPubKeyHash() const override;
-
     std::string getPublicKeyString() const override;
     int getBits() const override;
 
     void fromISCMap(DNSKEYRecordContent& drc, stormap_t& stormap) override;
 
-    void fromPublicKeyString(const std::string& content) override { throw "Unimplemented"; };
+    void fromPublicKeyString(const std::string& /* content */) override { throw "Unimplemented"; };
 
     static std::unique_ptr<DNSCryptoKeyEngine> maker(unsigned int algorithm);
 };
index 05ab28bf88c2eff389b3676843461a4116bb0635..936c0c5088003b1d8e58ed23b0fb255c6277e49b 100644 (file)
@@ -9,12 +9,12 @@
 #include "misc.hh"
 #include "namespaces.hh"
 
-FDMultiplexer* FDMultiplexer::getMultiplexerSilent()
+FDMultiplexer* FDMultiplexer::getMultiplexerSilent(unsigned int maxEventsHint)
 {
   FDMultiplexer* ret = nullptr;
   for (const auto& i : FDMultiplexer::getMultiplexerMap()) {
     try {
-      ret = i.second();
+      ret = i.second(std::min(maxEventsHint, FDMultiplexer::s_maxevents));
       return ret;
     }
     catch (const FDMultiplexerException& fe) {
@@ -28,11 +28,8 @@ FDMultiplexer* FDMultiplexer::getMultiplexerSilent()
 class PollFDMultiplexer : public FDMultiplexer
 {
 public:
-  PollFDMultiplexer()
+  PollFDMultiplexer(unsigned int /* maxEventsHint */)
   {}
-  ~PollFDMultiplexer()
-  {
-  }
 
   int run(struct timeval* tv, int timeout = 500) override;
   void getAvailableFDs(std::vector<int>& fds, int timeout) override;
@@ -50,9 +47,9 @@ private:
   vector<struct pollfd> preparePollFD() const;
 };
 
-static FDMultiplexer* make()
+static FDMultiplexer* make(unsigned int maxEventsHint)
 {
-  return new PollFDMultiplexer();
+  return new PollFDMultiplexer(maxEventsHint);
 }
 
 static struct RegisterOurselves
index f5e151043cee874ca8b427014bbfc86bbde61668..86bd56304177e5cf4894ee70957602de752111dc 100644 (file)
@@ -17,7 +17,7 @@
 class PortsFDMultiplexer : public FDMultiplexer
 {
 public:
-  PortsFDMultiplexer();
+  PortsFDMultiplexer(unsigned int maxEventsHint);
   ~PortsFDMultiplexer()
   {
     close(d_portfd);
@@ -37,12 +37,11 @@ public:
 private:
   int d_portfd;
   std::vector<port_event_t> d_pevents;
-  static int s_maxevents; // not a hard maximum
 };
 
-static FDMultiplexer* makePorts()
+static FDMultiplexer* makePorts(unsigned int maxEventsHint)
 {
-  return new PortsFDMultiplexer();
+  return new PortsFDMultiplexer(maxEventsHint);
 }
 
 static struct PortsRegisterOurselves
@@ -53,10 +52,8 @@ static struct PortsRegisterOurselves
   }
 } doItPorts;
 
-int PortsFDMultiplexer::s_maxevents = 1024;
-
-PortsFDMultiplexer::PortsFDMultiplexer() :
-  d_pevents(s_maxevents)
+PortsFDMultiplexer::PortsFDMultiplexer(unsigned int maxEventsHint) :
+  d_pevents(maxEventsHint)
 {
   d_portfd = port_create(); // not hard max
   if (d_portfd < 0) {
@@ -97,7 +94,7 @@ void PortsFDMultiplexer::getAvailableFDs(std::vector<int>& fds, int timeout)
   timeoutspec.tv_sec = timeout / 1000;
   timeoutspec.tv_nsec = (timeout % 1000) * 1000000;
   unsigned int numevents = 1;
-  int ret = port_getn(d_portfd, d_pevents.data(), min(PORT_MAX_LIST, s_maxevents), &numevents, &timeoutspec);
+  int ret = port_getn(d_portfd, d_pevents.data(), min(PORT_MAX_LIST, static_cast<int>(d_pevents.size())), &numevents, timeout != -1 ? &timeoutspec : nullptr);
 
   /* port_getn has an unusual API - (ret == -1, errno == ETIME) can
      mean partial success; you must check (*numevents) in this case
@@ -158,7 +155,7 @@ int PortsFDMultiplexer::run(struct timeval* now, int timeout)
   timeoutspec.tv_sec = timeout / 1000;
   timeoutspec.tv_nsec = (timeout % 1000) * 1000000;
   unsigned int numevents = 1;
-  int ret = port_getn(d_portfd, d_pevents.data(), min(PORT_MAX_LIST, s_maxevents), &numevents, &timeoutspec);
+  int ret = port_getn(d_portfd, d_pevents.data(), min(PORT_MAX_LIST, static_cast<int>(d_pevents.size())), &numevents, timeout != -1 ? &timeoutspec : nullptr);
 
   /* port_getn has an unusual API - (ret == -1, errno == ETIME) can
      mean partial success; you must check (*numevents) in this case
index 218852cbc99130181234a946145ef995fd67c108..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;
@@ -95,7 +95,7 @@ void pdns::ProtoZero::Message::addRRsFromPacket(const char* packet, const size_t
     return;
   }
 
-  PacketReader pr(pdns_string_view(packet, len));
+  PacketReader pr(std::string_view(packet, len));
 
   size_t idx = 0;
   DNSName rrname;
index 4b3438d85bb0fdbf0ffb7dac0903b613cbe73993..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());
@@ -108,6 +114,7 @@ namespace pdns {
 
       void setTime(time_t sec, uint32_t usec)
       {
+        // coverity[store_truncates_time_t]
         add_uint32(d_message, Field::timeSec, sec);
         add_uint32(d_message, Field::timeUsec, usec);
       }
index b7c7270e79bab9c81e74cf483e5f3b139e08eb1e..9bf48adcc343a17e2def24b92b42cc3bba3f21d1 100644 (file)
 #define PROXYMAGIC "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
 #define PROXYMAGICLEN sizeof(PROXYMAGIC)-1
 
-static string proxymagic(PROXYMAGIC, PROXYMAGICLEN);
+static const string proxymagic(PROXYMAGIC, PROXYMAGICLEN);
 
-static std::string makeSimpleHeader(uint8_t command, uint8_t protocol, uint16_t contentLen)
+static void makeSimpleHeader(uint8_t command, uint8_t protocol, uint16_t contentLen, size_t additionalSize, std::string& out)
 {
-  std::string ret;
   const uint8_t versioncommand = (0x20 | command);
+  const size_t totalSize = proxymagic.size() + sizeof(versioncommand) + sizeof(protocol) + sizeof(contentLen) + additionalSize;
+  if (out.capacity() < totalSize) {
+    out.reserve(totalSize);
+  }
 
-  ret.reserve(proxymagic.size() + sizeof(versioncommand) + sizeof(protocol) + sizeof(contentLen) + contentLen);
-
-  ret.append(proxymagic);
-
-  ret.append(reinterpret_cast<const char*>(&versioncommand), sizeof(versioncommand));
-  ret.append(reinterpret_cast<const char*>(&protocol), sizeof(protocol));
+  out.append(proxymagic);
 
-  ret.append(reinterpret_cast<const char*>(&contentLen), sizeof(contentLen));
+  out.append(reinterpret_cast<const char*>(&versioncommand), sizeof(versioncommand));
+  out.append(reinterpret_cast<const char*>(&protocol), sizeof(protocol));
 
-  return ret;
+  out.append(reinterpret_cast<const char*>(&contentLen), sizeof(contentLen));
 }
 
 std::string makeLocalProxyHeader()
 {
-  return makeSimpleHeader(0x00, 0, 0);
+  std::string out;
+  makeSimpleHeader(0x00, 0, 0, 0, out);
+  return out;
 }
 
 std::string makeProxyHeader(bool tcp, const ComboAddress& source, const ComboAddress& destination, const std::vector<ProxyProtocolValue>& values)
@@ -74,13 +75,15 @@ std::string makeProxyHeader(bool tcp, const ComboAddress& source, const ComboAdd
     }
   }
 
-  size_t total = (addrSize * 2) + sizeof(sourcePort) + sizeof(destinationPort) + valuesSize;
-  if (total > std::numeric_limits<uint16_t>::max()) {
-    throw std::runtime_error("The size of a proxy protocol header is limited to " + std::to_string(std::numeric_limits<uint16_t>::max()) + ", trying to send one of size " + std::to_string(total));
+  /* size of the data that will come _after_ the minimal proxy protocol header */
+  size_t additionalDataSize = (addrSize * 2) + sizeof(sourcePort) + sizeof(destinationPort) + valuesSize;
+  if (additionalDataSize > std::numeric_limits<uint16_t>::max()) {
+    throw std::runtime_error("The size of a proxy protocol header is limited to " + std::to_string(std::numeric_limits<uint16_t>::max()) + ", trying to send one of size " + std::to_string(additionalDataSize));
   }
 
-  const uint16_t contentlen = htons(static_cast<uint16_t>(total));
-  std::string ret = makeSimpleHeader(command, protocol, contentlen);
+  const uint16_t contentlen = htons(static_cast<uint16_t>(additionalDataSize));
+  std::string ret;
+  makeSimpleHeader(command, protocol, contentlen, additionalDataSize, ret);
 
   // We already established source and destination sin_family equivalence
   if (source.isIPv4()) {
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 53d05c0edbe9ad30e45530864f264b468bc6ff4b..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;
 }
 
@@ -135,7 +132,7 @@ const string QType::toString() const
   if (name != numbers.cend()) {
     return name->second;
   }
-  return "TYPE" + itoa(code);
+  return "TYPE" + std::to_string(code);
 }
 
 uint16_t QType::chartocode(const char *p)
index 795b295c23711f64f68cb3b098c5436a4b911462..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);
@@ -133,6 +144,10 @@ public:
 #endif
   };
 
+  const static uint16_t rfc6895MetaLowerBound = 128;
+  const static uint16_t rfc6895MetaUpperBound = 254; // Note 255: ANY is not included
+  const static uint16_t rfc6895Reserved = 65535;
+
   const static map<const string, uint16_t> names;
   const static map<uint16_t, const string> numbers;
 
@@ -150,6 +165,11 @@ namespace std {
   };
 }
 
+inline std::ostream& operator<<(std::ostream& stream, const QType& qtype)
+{
+  return stream << qtype.toString();
+}
+
 // Used by e.g. boost multi-index
 inline size_t hash_value(const QType qtype) {
   return qtype.getCode();
index 9204773d379a51de05a65ce16af24797bf73ca6d..6a4569100cd003e7a499b62065fa3a3047d4e62f 100644 (file)
@@ -23,7 +23,6 @@
 #include "config.h"
 #endif
 #include "rcpgenerator.hh"
-#include "ascii.hh"
 #include "dnsparser.hh"
 #include "misc.hh"
 #include "utility.hh"
@@ -124,6 +123,7 @@ void RecordTextReader::xfrTime(uint32_t &val)
 
   tm.tm_year-=1900;
   tm.tm_mon-=1;
+  // coverity[store_truncates_time_t]
   val=(uint32_t)Utility::timegm(&tm);
 }
 
@@ -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)
@@ -568,7 +574,7 @@ void RecordTextWriter::xfrBase32HexBlob(const string& val)
 }
 
 
-void RecordTextReader::xfrText(string& val, bool multi, bool lenField)
+void RecordTextReader::xfrText(string& val, bool multi, bool /* lenField */)
 {
   val.clear();
   val.reserve(d_end - d_pos);
@@ -607,7 +613,7 @@ void RecordTextReader::xfrText(string& val, bool multi, bool lenField)
   }
 }
 
-void RecordTextReader::xfrUnquotedText(string& val, bool lenField)
+void RecordTextReader::xfrUnquotedText(string& val, bool /* lenField */)
 {
   val.clear();
   val.reserve(d_end - d_pos);
@@ -741,7 +747,7 @@ void RecordTextWriter::xfrIP6(const std::string& val)
   d_string += std::string(addrbuf);
 }
 
-void RecordTextWriter::xfrCAWithoutPort(uint8_t version, ComboAddress &val)
+void RecordTextWriter::xfrCAWithoutPort(uint8_t /* version */, ComboAddress &val)
 {
   string ip = val.toString();
 
@@ -781,7 +787,7 @@ void RecordTextWriter::xfr8BitInt(const uint8_t& val)
 }
 
 // should not mess with the escapes
-void RecordTextWriter::xfrName(const DNSName& val, bool, bool noDot)
+void RecordTextWriter::xfrName(const DNSName& val, bool /* unused */, bool /* noDot */)
 {
   if(!d_string.empty())
     d_string.append(1,' ');
@@ -941,7 +947,7 @@ void RecordTextWriter::xfrSvcParamKeyVals(const set<SvcParam>& val) {
   }
 }
 
-void RecordTextWriter::xfrText(const string& val, bool multi, bool lenField)
+void RecordTextWriter::xfrText(const string& val, bool /* multi */, bool /* lenField */)
 {
   if(!d_string.empty())
     d_string.append(1,' ');
@@ -949,7 +955,7 @@ void RecordTextWriter::xfrText(const string& val, bool multi, bool lenField)
   d_string.append(val);
 }
 
-void RecordTextWriter::xfrUnquotedText(const string& val, bool lenField)
+void RecordTextWriter::xfrUnquotedText(const string& val, bool /* lenField */)
 {
   if(!d_string.empty())
     d_string.append(1,' ');
diff --git a/pdns/rec-carbon.cc b/pdns/rec-carbon.cc
deleted file mode 100644 (file)
index a8e67a6..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-#include "mtasker.hh"
-#include "syncres.hh"
-#include "rec_channel.hh"
-#include "iputils.hh"
-#include "logger.hh"
-#include "arguments.hh"
-#include "lock.hh"
-
-GlobalStateHolder<CarbonConfig> g_carbonConfig;
-
-void doCarbonDump(void*)
-{
-  try {
-    static thread_local auto configHolder = g_carbonConfig.getLocal();
-
-    auto config = *configHolder;
-    if (config.servers.empty()) {
-      return;
-    }
-
-    if (config.namespace_name.empty()) {
-      config.namespace_name = "pdns";
-    }
-
-    if (config.hostname.empty()) {
-      try {
-        config.hostname = getCarbonHostName();
-      }
-      catch (const std::exception& e) {
-        throw std::runtime_error(std::string("The 'carbon-ourname' setting has not been set and we are unable to determine the system's hostname: ") + e.what());
-      }
-    }
-    if (config.instance_name.empty()) {
-      config.instance_name = "recursor";
-    }
-
-    registerAllStats();
-    PacketBuffer msg;
-    for (const auto& carbonServer : config.servers) {
-      ComboAddress remote(carbonServer, 2003);
-      Socket s(remote.sin4.sin_family, SOCK_STREAM);
-      s.setNonBlocking();
-      std::shared_ptr<TLSCtx> tlsCtx{nullptr};
-      const struct timeval timeout
-      {
-        g_networkTimeoutMsec / 1000, static_cast<suseconds_t>(g_networkTimeoutMsec) % 1000 * 1000
-      };
-      auto handler = std::make_shared<TCPIOHandler>("", false, s.releaseHandle(), timeout, tlsCtx, time(nullptr));
-      handler->tryConnect(SyncRes::s_tcp_fast_open_connect, remote); // we do the connect so the first attempt happens while we gather stats
-
-      if (msg.empty()) {
-        auto all = getAllStatsMap(StatComponent::Carbon);
-
-        ostringstream str;
-        time_t now = time(0);
-
-        for (const auto& val : all) {
-          str << config.namespace_name << '.' << config.hostname << '.' << config.instance_name << '.' << val.first << ' ' << val.second.d_value << ' ' << now << "\r\n";
-        }
-        const string& x = str.str();
-        msg.insert(msg.end(), x.cbegin(), x.cend());
-      }
-
-      auto ret = asendtcp(msg, handler); // this will actually do the right thing waiting on the connect
-      if (ret == LWResult::Result::Timeout) {
-        g_log << Logger::Warning << "Timeout connecting/writing carbon data to " << remote.toStringWithPort() << endl;
-      }
-      else if (ret != LWResult::Result::Success) {
-        g_log << Logger::Warning << "Error writing carbon data to " << remote.toStringWithPort() << ": " << stringerror() << endl;
-      }
-      handler->close();
-    }
-  }
-  catch (const PDNSException& e) {
-    g_log << Logger::Error << "Error in carbon thread: " << e.reason << endl;
-  }
-  catch (const std::exception& e) {
-    g_log << Logger::Error << "Error in carbon thread: " << e.what() << endl;
-  }
-  catch (...) {
-    g_log << Logger::Error << "Unknown error in carbon thread" << endl;
-  }
-}
diff --git a/pdns/rec-lua-conf.cc b/pdns/rec-lua-conf.cc
deleted file mode 100644 (file)
index 8f730e4..0000000
+++ /dev/null
@@ -1,767 +0,0 @@
-#include "config.h"
-#include "ext/luawrapper/include/LuaContext.hpp"
-
-#include <fstream>
-#include <thread>
-#include "namespaces.hh"
-#include "logger.hh"
-#include "lua-base4.hh"
-#include "rec-lua-conf.hh"
-#include "sortlist.hh"
-#include "filterpo.hh"
-#include "syncres.hh"
-#include "rpzloader.hh"
-#include "base64.hh"
-#include "remote_logger.hh"
-#include "validate.hh"
-#include "validate-recursor.hh"
-#include "root-dnssec.hh"
-
-GlobalStateHolder<LuaConfigItems> g_luaconfs;
-
-/* SO HOW DOES THIS WORK! AND PLEASE PAY ATTENTION!
-   This function can be called at any time. It is expected to overwrite all the contents
-   of LuaConfigItems, which is held in a GlobalStateHolder for RCU properties.
-
-   This function can be called again at a later date, so you must make sure that anything you
-   allow to be configured from here lives in g_luaconfs AND NOWHERE ELSE.
-
-   If someone loads an empty Lua file, the default LuaConfigItems struct MUST MAKE SENSE.
-
-   To make this easy on you, here is a LuaConfigItems constructor where you
-   can set sane defaults:
-*/
-
-LuaConfigItems::LuaConfigItems()
-{
-  DNSName root("."); // don't use g_rootdnsname here, it might not exist yet
-  for (const auto& dsRecord : rootDSs) {
-    auto ds = std::dynamic_pointer_cast<DSRecordContent>(DSRecordContent::make(dsRecord));
-    dsAnchors[root].insert(*ds);
-  }
-}
-
-/* DID YOU READ THE STORY ABOVE? */
-
-template <typename C>
-typename C::value_type::second_type constGet(const C& c, const std::string& name)
-{
-  auto iter = c.find(name);
-  if (iter == c.end())
-    return 0;
-  return iter->second;
-}
-
-typedef std::unordered_map<std::string, boost::variant<bool, uint32_t, std::string, std::vector<std::pair<int, std::string>>>> rpzOptions_t;
-
-static void parseRPZParameters(rpzOptions_t& have, std::shared_ptr<DNSFilterEngine::Zone>& zone, std::string& polName, boost::optional<DNSFilterEngine::Policy>& defpol, bool& defpolOverrideLocal, uint32_t& maxTTL)
-{
-  if (have.count("policyName")) {
-    polName = boost::get<std::string>(have["policyName"]);
-  }
-  if (have.count("defpol")) {
-    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 (have.count("defttl"))
-        defpol->d_ttl = static_cast<int32_t>(boost::get<uint32_t>(have["defttl"]));
-      else
-        defpol->d_ttl = -1; // get it from the zone
-    }
-
-    if (have.count("defpolOverrideLocalData")) {
-      defpolOverrideLocal = boost::get<bool>(have["defpolOverrideLocalData"]);
-    }
-  }
-  if (have.count("maxTTL")) {
-    maxTTL = boost::get<uint32_t>(have["maxTTL"]);
-  }
-  if (have.count("zoneSizeHint")) {
-    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"]);
-    std::unordered_set<std::string> tags;
-    for (const auto& tag : tagsTable) {
-      tags.insert(tag.second);
-    }
-    zone->setTags(std::move(tags));
-  }
-  if (have.count("overridesGettag")) {
-    zone->setPolicyOverridesGettag(boost::get<bool>(have["overridesGettag"]));
-  }
-  if (have.count("extendedErrorCode")) {
-    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")) {
-      zone->setExtendedErrorExtra(boost::get<std::string>(have["extendedErrorExtra"]));
-    }
-  }
-}
-
-typedef std::unordered_map<std::string, boost::variant<bool, uint64_t, std::string, std::vector<std::pair<int, std::string>>>> protobufOptions_t;
-
-static void parseProtobufOptions(boost::optional<protobufOptions_t> vars, ProtobufExportConfig& config)
-{
-  if (!vars) {
-    return;
-  }
-
-  if (vars->count("timeout")) {
-    config.timeout = boost::get<uint64_t>((*vars)["timeout"]);
-  }
-
-  if (vars->count("maxQueuedEntries")) {
-    config.maxQueuedEntries = boost::get<uint64_t>((*vars)["maxQueuedEntries"]);
-  }
-
-  if (vars->count("reconnectWaitTime")) {
-    config.reconnectWaitTime = boost::get<uint64_t>((*vars)["reconnectWaitTime"]);
-  }
-
-  if (vars->count("asyncConnect")) {
-    config.asyncConnect = boost::get<bool>((*vars)["asyncConnect"]);
-  }
-
-  if (vars->count("taggedOnly")) {
-    config.taggedOnly = boost::get<bool>((*vars)["taggedOnly"]);
-  }
-
-  if (vars->count("logQueries")) {
-    config.logQueries = boost::get<bool>((*vars)["logQueries"]);
-  }
-
-  if (vars->count("logResponses")) {
-    config.logResponses = boost::get<bool>((*vars)["logResponses"]);
-  }
-
-  if (vars->count("exportTypes")) {
-    config.exportTypes.clear();
-
-    auto types = boost::get<std::vector<std::pair<int, std::string>>>((*vars)["exportTypes"]);
-    for (const auto& pair : types) {
-      const auto& type = pair.second;
-
-      QType qtype;
-      try {
-        qtype = std::stoul(type);
-      }
-      catch (const std::exception& ex) {
-        qtype = QType::chartocode(type.c_str());
-        if (qtype == 0) {
-          throw std::runtime_error("Unknown QType '" + type + "' in protobuf's export types");
-        }
-      }
-      config.exportTypes.insert(qtype);
-    }
-  }
-}
-
-#ifdef HAVE_FSTRM
-typedef std::unordered_map<std::string, boost::variant<bool, uint64_t, std::string, std::vector<std::pair<int, std::string>>>> frameStreamOptions_t;
-
-static void parseFrameStreamOptions(boost::optional<frameStreamOptions_t> vars, FrameStreamExportConfig& config)
-{
-  if (!vars) {
-    return;
-  }
-
-  if (vars->count("logQueries")) {
-    config.logQueries = boost::get<bool>((*vars)["logQueries"]);
-  }
-  if (vars->count("logResponses")) {
-    config.logResponses = boost::get<bool>((*vars)["logResponses"]);
-  }
-
-  if (vars->count("bufferHint")) {
-    config.bufferHint = boost::get<uint64_t>((*vars)["bufferHint"]);
-  }
-  if (vars->count("flushTimeout")) {
-    config.flushTimeout = boost::get<uint64_t>((*vars)["flushTimeout"]);
-  }
-  if (vars->count("inputQueueSize")) {
-    config.inputQueueSize = boost::get<uint64_t>((*vars)["inputQueueSize"]);
-  }
-  if (vars->count("outputQueueSize")) {
-    config.outputQueueSize = boost::get<uint64_t>((*vars)["outputQueueSize"]);
-  }
-  if (vars->count("queueNotifyThreshold")) {
-    config.queueNotifyThreshold = boost::get<uint64_t>((*vars)["queueNotifyThreshold"]);
-  }
-  if (vars->count("reopenInterval")) {
-    config.reopenInterval = boost::get<uint64_t>((*vars)["reopenInterval"]);
-  }
-}
-#endif /* HAVE_FSTRM */
-
-static void rpzPrimary(LuaConfigItems& lci, luaConfigDelayedThreads& delayedThreads, const boost::variant<string, std::vector<std::pair<int, string>>>& primaries_, const string& zoneName, boost::optional<rpzOptions_t> options)
-{
-  boost::optional<DNSFilterEngine::Policy> defpol;
-  bool defpolOverrideLocal = true;
-  std::shared_ptr<DNSFilterEngine::Zone> zone = std::make_shared<DNSFilterEngine::Zone>();
-  TSIGTriplet tt;
-  uint32_t refresh = 0;
-  size_t maxReceivedXFRMBytes = 0;
-  uint16_t axfrTimeout = 20;
-  uint32_t maxTTL = std::numeric_limits<uint32_t>::max();
-  ComboAddress localAddress;
-  std::vector<ComboAddress> primaries;
-  if (primaries_.type() == typeid(string)) {
-    primaries.push_back(ComboAddress(boost::get<std::string>(primaries_), 53));
-  }
-  else {
-    for (const auto& primary : boost::get<std::vector<std::pair<int, std::string>>>(primaries_)) {
-      primaries.push_back(ComboAddress(primary.second, 53));
-    }
-  }
-
-  size_t zoneIdx;
-  std::string dumpFile;
-  std::shared_ptr<SOARecordContent> sr = nullptr;
-
-  try {
-    std::string seedFile;
-    std::string polName(zoneName);
-
-    if (options) {
-      auto& have = *options;
-      parseRPZParameters(have, zone, polName, defpol, defpolOverrideLocal, maxTTL);
-
-      if (have.count("tsigname")) {
-        tt.name = DNSName(toLower(boost::get<string>(have["tsigname"])));
-        tt.algo = DNSName(toLower(boost::get<string>(have["tsigalgo"])));
-        if (B64Decode(boost::get<string>(have["tsigsecret"]), tt.secret))
-          throw std::runtime_error("TSIG secret is not valid Base-64 encoded");
-      }
-
-      if (have.count("refresh")) {
-        refresh = boost::get<uint32_t>(have["refresh"]);
-        if (refresh == 0) {
-          g_log << Logger::Warning << "rpzPrimary refresh value of 0 ignored" << endl;
-        }
-      }
-
-      if (have.count("maxReceivedMBytes")) {
-        maxReceivedXFRMBytes = static_cast<size_t>(boost::get<uint32_t>(have["maxReceivedMBytes"]));
-      }
-
-      if (have.count("localAddress")) {
-        localAddress = ComboAddress(boost::get<string>(have["localAddress"]));
-      }
-
-      if (have.count("axfrTimeout")) {
-        axfrTimeout = static_cast<uint16_t>(boost::get<uint32_t>(have["axfrTimeout"]));
-      }
-
-      if (have.count("seedFile")) {
-        seedFile = boost::get<std::string>(have["seedFile"]);
-      }
-
-      if (have.count("dumpFile")) {
-        dumpFile = boost::get<std::string>(have["dumpFile"]);
-      }
-    }
-
-    if (localAddress != ComboAddress()) {
-      // We were passed a localAddress, check if its AF matches the primaries'
-      for (const auto& primary : primaries) {
-        if (localAddress.sin4.sin_family != primary.sin4.sin_family) {
-          throw PDNSException("Primary address(" + primary.toString() + ") is not of the same Address Family as the local address (" + localAddress.toString() + ").");
-        }
-      }
-    }
-
-    DNSName domain(zoneName);
-    zone->setDomain(domain);
-    zone->setName(polName);
-    zoneIdx = lci.dfe.addZone(zone);
-
-    if (!seedFile.empty()) {
-      g_log << Logger::Info << "Pre-loading RPZ zone " << zoneName << " from seed file '" << seedFile << "'" << endl;
-      try {
-        sr = loadRPZFromFile(seedFile, zone, defpol, defpolOverrideLocal, maxTTL);
-
-        if (zone->getDomain() != domain) {
-          throw PDNSException("The RPZ zone " + zoneName + " loaded from the seed file (" + zone->getDomain().toString() + ") does not match the one passed in parameter (" + domain.toString() + ")");
-        }
-
-        if (sr == nullptr) {
-          throw PDNSException("The RPZ zone " + zoneName + " loaded from the seed file (" + zone->getDomain().toString() + ") has no SOA record");
-        }
-      }
-      catch (const PDNSException& e) {
-        g_log << Logger::Warning << "Unable to pre-load RPZ zone " << zoneName << " from seed file '" << seedFile << "': " << e.reason << endl;
-        zone->clear();
-      }
-      catch (const std::exception& e) {
-        g_log << Logger::Warning << "Unable to pre-load RPZ zone " << zoneName << " from seed file '" << seedFile << "': " << e.what() << endl;
-        zone->clear();
-      }
-    }
-  }
-  catch (const std::exception& e) {
-    g_log << Logger::Error << "Problem configuring 'rpzPrimary': " << e.what() << endl;
-    exit(1); // FIXME proper exit code?
-  }
-  catch (const PDNSException& e) {
-    g_log << Logger::Error << "Problem configuring 'rpzPrimary': " << e.reason << endl;
-    exit(1); // FIXME proper exit code?
-  }
-
-  delayedThreads.rpzPrimaryThreads.push_back(std::make_tuple(primaries, defpol, defpolOverrideLocal, maxTTL, zoneIdx, tt, maxReceivedXFRMBytes, localAddress, axfrTimeout, refresh, sr, dumpFile));
-}
-
-// A wrapper class that loads the standard Lua defintions into the context, so that we can use things like pdns.A
-class RecLuaConfigContext : public BaseLua4
-{
-public:
-  RecLuaConfigContext()
-  {
-    prepareContext();
-  }
-  void postPrepareContext() override
-  {
-    // clang-format off
-    d_pd.push_back({"AdditionalMode", in_t{
-          {"Ignore", static_cast<int>(AdditionalMode::Ignore)},
-          {"CacheOnly", static_cast<int>(AdditionalMode::CacheOnly)},
-          {"CacheOnlyRequireAuth", static_cast<int>(AdditionalMode::CacheOnlyRequireAuth)},
-          {"ResolveImmediately", static_cast<int>(AdditionalMode::ResolveImmediately)},
-          {"ResolveDeferred", static_cast<int>(AdditionalMode::ResolveDeferred)}
-        }});
-  }
-  void postLoad() override
-  {
-  }
-  LuaContext* operator->()
-  {
-    return d_lw.get();
-  }
-};
-
-void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& delayedThreads)
-{
-  LuaConfigItems lci;
-
-  RecLuaConfigContext Lua;
-
-  if (fname.empty())
-    return;
-  ifstream ifs(fname);
-  if (!ifs)
-    throw PDNSException("Cannot open file '" + fname + "': " + stringerror());
-
-  auto luaconfsLocal = g_luaconfs.getLocal();
-  lci.generation = luaconfsLocal->generation + 1;
-
-  Lua->writeFunction("clearSortlist", [&lci]() { lci.sortlist.clear(); });
-
-  /* we can get: "1.2.3.4"
-                 {"1.2.3.4", "4.5.6.7"}
-                 {"1.2.3.4", {"4.5.6.7", "8.9.10.11"}}
-  */
-
-  map<string, DNSFilterEngine::PolicyKind> pmap{
-    {"NoAction", DNSFilterEngine::PolicyKind::NoAction},
-    {"Drop", DNSFilterEngine::PolicyKind::Drop},
-    {"NXDOMAIN", DNSFilterEngine::PolicyKind::NXDOMAIN},
-    {"NODATA", DNSFilterEngine::PolicyKind::NODATA},
-    {"Truncate", DNSFilterEngine::PolicyKind::Truncate},
-    {"Custom", DNSFilterEngine::PolicyKind::Custom}};
-  Lua->writeVariable("Policy", pmap);
-
-  Lua->writeFunction("rpzFile", [&lci](const string& filename, boost::optional<rpzOptions_t> options) {
-    try {
-      boost::optional<DNSFilterEngine::Policy> defpol;
-      bool defpolOverrideLocal = true;
-      std::string polName("rpzFile");
-      std::shared_ptr<DNSFilterEngine::Zone> zone = std::make_shared<DNSFilterEngine::Zone>();
-      uint32_t maxTTL = std::numeric_limits<uint32_t>::max();
-      if (options) {
-        auto& have = *options;
-        parseRPZParameters(have, zone, polName, defpol, defpolOverrideLocal, maxTTL);
-      }
-      g_log << Logger::Warning << "Loading RPZ from file '" << filename << "'" << endl;
-      zone->setName(polName);
-      loadRPZFromFile(filename, zone, defpol, defpolOverrideLocal, maxTTL);
-      lci.dfe.addZone(zone);
-      g_log << Logger::Warning << "Done loading RPZ from file '" << filename << "'" << endl;
-    }
-    catch (const std::exception& e) {
-      g_log << Logger::Error << "Unable to load RPZ zone from '" << filename << "': " << e.what() << endl;
-    }
-  });
-
-  Lua->writeFunction("rpzMaster", [&lci, &delayedThreads](const boost::variant<string, std::vector<std::pair<int, string>>>& primaries_, const string& zoneName, boost::optional<rpzOptions_t> options) {
-    g_log << Logger::Warning << "'rpzMaster' is deprecated and will be removed in a future release, use 'rpzPrimary' instead" << endl;
-    rpzPrimary(lci, delayedThreads, primaries_, zoneName, options);
-  });
-  Lua->writeFunction("rpzPrimary", [&lci, &delayedThreads](const boost::variant<string, std::vector<std::pair<int, string>>>& primaries_, const string& zoneName, boost::optional<rpzOptions_t> options) {
-    rpzPrimary(lci, delayedThreads, primaries_, zoneName, options);
-  });
-
-  typedef std::unordered_map<std::string, boost::variant<uint32_t, std::string>> zoneToCacheOptions_t;
-
-  Lua->writeFunction("zoneToCache", [&lci](const string& zoneName, const string& method, const boost::variant<string, std::vector<std::pair<int, string>>>& srcs, boost::optional<zoneToCacheOptions_t> options) {
-    try {
-      RecZoneToCache::Config conf;
-      DNSName validZoneName(zoneName);
-      conf.d_zone = zoneName;
-      const set<string> methods = {"axfr", "url", "file"};
-      if (methods.count(method) == 0) {
-        throw std::runtime_error("unknwon method '" + method + "'");
-      }
-      conf.d_method = method;
-      if (srcs.type() == typeid(std::string)) {
-        conf.d_sources.push_back(boost::get<std::string>(srcs));
-      }
-      else {
-        for (const auto& src : boost::get<std::vector<std::pair<int, std::string>>>(srcs)) {
-          conf.d_sources.push_back(src.second);
-        }
-      }
-      if (conf.d_sources.size() == 0) {
-        throw std::runtime_error("at least one source required");
-      }
-      if (options) {
-        auto& have = *options;
-        if (have.count("timeout")) {
-          conf.d_timeout = boost::get<uint32_t>(have.at("timeout"));
-        }
-        if (have.count("tsigname")) {
-          conf.d_tt.name = DNSName(toLower(boost::get<string>(have.at("tsigname"))));
-          conf.d_tt.algo = DNSName(toLower(boost::get<string>(have.at("tsigalgo"))));
-          if (B64Decode(boost::get<string>(have.at("tsigsecret")), conf.d_tt.secret)) {
-            throw std::runtime_error("TSIG secret is not valid Base-64 encoded");
-          }
-        }
-        if (have.count("maxReceivedMBytes")) {
-          conf.d_maxReceivedBytes = static_cast<size_t>(boost::get<uint32_t>(have.at("maxReceivedMBytes")));
-          conf.d_maxReceivedBytes *= 1024 * 1024;
-        }
-        if (have.count("localAddress")) {
-          conf.d_local = ComboAddress(boost::get<string>(have.at("localAddress")));
-        }
-        if (have.count("refreshPeriod")) {
-          conf.d_refreshPeriod = boost::get<uint32_t>(have.at("refreshPeriod"));
-        }
-        if (have.count("retryOnErrorPeriod")) {
-          conf.d_retryOnError = boost::get<uint32_t>(have.at("retryOnErrorPeriod"));
-        }
-        const map<string, pdns::ZoneMD::Config> nameToVal = {
-          {"ignore", pdns::ZoneMD::Config::Ignore},
-          {"validate", pdns::ZoneMD::Config::Validate},
-          {"require", pdns::ZoneMD::Config::Require},
-        };
-        if (have.count("zonemd")) {
-          string zonemdValidation = boost::get<string>(have.at("zonemd"));
-          auto it = nameToVal.find(zonemdValidation);
-          if (it == nameToVal.end()) {
-            throw std::runtime_error(zonemdValidation + " is not a valid value for `zonemd`");
-          }
-          else {
-            conf.d_zonemd = it->second;
-          }
-        }
-        if (have.count("dnssec")) {
-          string dnssec = boost::get<string>(have.at("dnssec"));
-          auto it = nameToVal.find(dnssec);
-          if (it == nameToVal.end()) {
-            throw std::runtime_error(dnssec + " is not a valid value for `dnssec`");
-          }
-          else {
-            conf.d_dnssec = it->second;
-          }
-        }
-      }
-
-      lci.ztcConfigs[validZoneName] = conf;
-    }
-    catch (const std::exception& e) {
-      g_log << Logger::Error << "Problem configuring zoneToCache for zone '" << zoneName << "': " << e.what() << endl;
-    }
-  });
-
-  typedef vector<pair<int, boost::variant<string, vector<pair<int, string>>>>> argvec_t;
-  Lua->writeFunction("addSortList",
-                     [&lci](const std::string& formask_,
-                            const boost::variant<string, argvec_t>& masks,
-                            boost::optional<int> order_) {
-                       try {
-                         Netmask formask(formask_);
-                         int order = order_ ? (*order_) : lci.sortlist.getMaxOrder(formask) + 1;
-                         if (auto str = boost::get<string>(&masks))
-                           lci.sortlist.addEntry(formask, Netmask(*str), order);
-                         else {
-
-                           auto vec = boost::get<argvec_t>(&masks);
-                           for (const auto& e : *vec) {
-                             if (auto s = boost::get<string>(&e.second)) {
-                               lci.sortlist.addEntry(formask, Netmask(*s), order);
-                             }
-                             else {
-                               const auto& v = boost::get<vector<pair<int, string>>>(e.second);
-                               for (const auto& entry : v)
-                                 lci.sortlist.addEntry(formask, Netmask(entry.second), order);
-                             }
-                             ++order;
-                           }
-                         }
-                       }
-                       catch (std::exception& e) {
-                         g_log << Logger::Error << "Error in addSortList: " << e.what() << endl;
-                       }
-                     });
-
-  Lua->writeFunction("addTA", [&lci](const std::string& who, const std::string& what) {
-    warnIfDNSSECDisabled("Warning: adding Trust Anchor for DNSSEC (addTA), but dnssec is set to 'off'!");
-    DNSName zone(who);
-    auto ds = std::dynamic_pointer_cast<DSRecordContent>(DSRecordContent::make(what));
-    lci.dsAnchors[zone].insert(*ds);
-  });
-
-  Lua->writeFunction("clearTA", [&lci](boost::optional<string> who) {
-    warnIfDNSSECDisabled("Warning: removing Trust Anchor for DNSSEC (clearTA), but dnssec is set to 'off'!");
-    if (who)
-      lci.dsAnchors.erase(DNSName(*who));
-    else
-      lci.dsAnchors.clear();
-  });
-
-  /* Remove in 4.3 */
-  Lua->writeFunction("addDS", [&lci](const std::string& who, const std::string& what) {
-    warnIfDNSSECDisabled("Warning: adding Trust Anchor for DNSSEC (addDS), but dnssec is set to 'off'!");
-    g_log << Logger::Warning << "addDS is deprecated and will be removed in the future, switch to addTA" << endl;
-    DNSName zone(who);
-    auto ds = std::dynamic_pointer_cast<DSRecordContent>(DSRecordContent::make(what));
-    lci.dsAnchors[zone].insert(*ds);
-  });
-
-  /* Remove in 4.3 */
-  Lua->writeFunction("clearDS", [&lci](boost::optional<string> who) {
-    g_log << Logger::Warning << "clearDS is deprecated and will be removed in the future, switch to clearTA" << endl;
-    warnIfDNSSECDisabled("Warning: removing Trust Anchor for DNSSEC (clearDS), but dnssec is set to 'off'!");
-    if (who)
-      lci.dsAnchors.erase(DNSName(*who));
-    else
-      lci.dsAnchors.clear();
-  });
-
-  Lua->writeFunction("addNTA", [&lci](const std::string& who, const boost::optional<std::string> why) {
-    warnIfDNSSECDisabled("Warning: adding Negative Trust Anchor for DNSSEC (addNTA), but dnssec is set to 'off'!");
-    if (why)
-      lci.negAnchors[DNSName(who)] = static_cast<string>(*why);
-    else
-      lci.negAnchors[DNSName(who)] = "";
-  });
-
-  Lua->writeFunction("clearNTA", [&lci](boost::optional<string> who) {
-    warnIfDNSSECDisabled("Warning: removing Negative Trust Anchor for DNSSEC (clearNTA), but dnssec is set to 'off'!");
-    if (who)
-      lci.negAnchors.erase(DNSName(*who));
-    else
-      lci.negAnchors.clear();
-  });
-
-  Lua->writeFunction("readTrustAnchorsFromFile", [&lci](const std::string& fnamearg, const boost::optional<uint32_t> interval) {
-    uint32_t realInterval = 24;
-    if (interval) {
-      realInterval = static_cast<uint32_t>(*interval);
-    }
-    warnIfDNSSECDisabled("Warning: reading Trust Anchors from file (readTrustAnchorsFromFile), but dnssec is set to 'off'!");
-    lci.trustAnchorFileInfo.fname = fnamearg;
-    lci.trustAnchorFileInfo.interval = realInterval;
-    updateTrustAnchorsFromFile(fnamearg, lci.dsAnchors);
-  });
-
-  Lua->writeFunction("setProtobufMasks", [&lci](const uint8_t maskV4, uint8_t maskV6) {
-    lci.protobufMaskV4 = maskV4;
-    lci.protobufMaskV6 = maskV6;
-  });
-
-  Lua->writeFunction("protobufServer", [&lci](boost::variant<const std::string, const std::unordered_map<int, std::string>> servers, boost::optional<protobufOptions_t> vars) {
-    if (!lci.protobufExportConfig.enabled) {
-
-      lci.protobufExportConfig.enabled = true;
-
-      try {
-        if (servers.type() == typeid(std::string)) {
-          auto server = boost::get<const std::string>(servers);
-
-          lci.protobufExportConfig.servers.emplace_back(server);
-        }
-        else {
-          auto serversMap = boost::get<const std::unordered_map<int, std::string>>(servers);
-          for (const auto& serverPair : serversMap) {
-            lci.protobufExportConfig.servers.emplace_back(serverPair.second);
-          }
-        }
-
-        parseProtobufOptions(vars, lci.protobufExportConfig);
-      }
-      catch (std::exception& e) {
-        g_log << Logger::Error << "Error while adding protobuf logger: " << e.what() << endl;
-      }
-      catch (PDNSException& e) {
-        g_log << Logger::Error << "Error while adding protobuf logger: " << e.reason << endl;
-      }
-    }
-    else {
-      g_log << Logger::Error << "Only one protobufServer() directive can be configured, we already have " << lci.protobufExportConfig.servers.at(0).toString() << endl;
-    }
-  });
-
-  Lua->writeFunction("outgoingProtobufServer", [&lci](boost::variant<const std::string, const std::unordered_map<int, std::string>> servers, boost::optional<protobufOptions_t> vars) {
-    if (!lci.outgoingProtobufExportConfig.enabled) {
-
-      lci.outgoingProtobufExportConfig.enabled = true;
-
-      try {
-        if (servers.type() == typeid(std::string)) {
-          auto server = boost::get<const std::string>(servers);
-
-          lci.outgoingProtobufExportConfig.servers.emplace_back(server);
-        }
-        else {
-          auto serversMap = boost::get<const std::unordered_map<int, std::string>>(servers);
-          for (const auto& serverPair : serversMap) {
-            lci.outgoingProtobufExportConfig.servers.emplace_back(serverPair.second);
-          }
-        }
-
-        parseProtobufOptions(vars, lci.outgoingProtobufExportConfig);
-      }
-      catch (std::exception& e) {
-        g_log << Logger::Error << "Error while starting outgoing protobuf logger: " << e.what() << endl;
-      }
-      catch (PDNSException& e) {
-        g_log << Logger::Error << "Error while starting outgoing protobuf logger: " << e.reason << endl;
-      }
-    }
-    else {
-      g_log << Logger::Error << "Only one outgoingProtobufServer() directive can be configured, we already have " << lci.outgoingProtobufExportConfig.servers.at(0).toString() << endl;
-    }
-  });
-
-#ifdef HAVE_FSTRM
-  Lua->writeFunction("dnstapFrameStreamServer", [&lci](boost::variant<const std::string, const std::unordered_map<int, std::string>> servers, boost::optional<frameStreamOptions_t> vars) {
-    if (!lci.frameStreamExportConfig.enabled) {
-
-      lci.frameStreamExportConfig.enabled = true;
-
-      try {
-        if (servers.type() == typeid(std::string)) {
-          auto server = boost::get<const std::string>(servers);
-          if (!boost::starts_with(server, "/")) {
-            ComboAddress parsecheck(server);
-          }
-          lci.frameStreamExportConfig.servers.emplace_back(server);
-        }
-        else {
-          auto serversMap = boost::get<const std::unordered_map<int, std::string>>(servers);
-          for (const auto& serverPair : serversMap) {
-            lci.frameStreamExportConfig.servers.emplace_back(serverPair.second);
-          }
-        }
-
-        parseFrameStreamOptions(vars, lci.frameStreamExportConfig);
-      }
-      catch (std::exception& e) {
-        g_log << Logger::Error << "Error reading config for dnstap framestream logger: " << e.what() << endl;
-      }
-      catch (PDNSException& e) {
-        g_log << Logger::Error << "Error reading config for dnstap framestream logger: " << e.reason << endl;
-      }
-    }
-    else {
-      g_log << Logger::Error << "Only one dnstapFrameStreamServer() directive can be configured, we already have " << lci.frameStreamExportConfig.servers.at(0) << endl;
-    }
-  });
-#endif /* HAVE_FSTRM */
-
-  Lua->writeFunction("addAllowedAdditionalQType", [&lci](int qtype, std::unordered_map<int, int> targetqtypes, boost::optional<std::map<std::string, int>> options) {
-    switch (qtype) {
-    case QType::MX:
-    case QType::SRV:
-    case QType::SVCB:
-    case QType::HTTPS:
-    case QType::NAPTR:
-      break;
-    default:
-      g_log << Logger::Error << "addAllowedAdditionalQType does not support " << QType(qtype).toString() << endl;
-      return;
-    }
-
-    std::set<QType> targets;
-    for (const auto& t : targetqtypes) {
-      targets.emplace(QType(t.second));
-    }
-
-    AdditionalMode mode = AdditionalMode::CacheOnlyRequireAuth; // Always cheap and should be safe
-
-    if (options) {
-      if (const auto it = options->find("mode"); it != options->end()) {
-        mode = static_cast<AdditionalMode>(it->second);
-        if (mode > AdditionalMode::ResolveDeferred) {
-          g_log << Logger::Error << "addAllowedAdditionalQType: unknown mode " << it->second << endl;
-        }
-      }
-    }
-    lci.allowAdditionalQTypes.insert_or_assign(qtype, pair(targets, mode));
-  });
-
-  try {
-    Lua->executeCode(ifs);
-    g_luaconfs.setState(std::move(lci));
-  }
-  catch (const LuaContext::ExecutionErrorException& e) {
-    g_log << Logger::Error << "Unable to load Lua script from '" + fname + "': ";
-    try {
-      std::rethrow_if_nested(e);
-    }
-    catch (const std::exception& exp) {
-      // exp is the exception that was thrown from inside the lambda
-      g_log << exp.what() << std::endl;
-    }
-    catch (const PDNSException& exp) {
-      // exp is the exception that was thrown from inside the lambda
-      g_log << exp.reason << std::endl;
-    }
-    throw;
-  }
-  catch (std::exception& err) {
-    g_log << Logger::Error << "Unable to load Lua script from '" + fname + "': " << err.what() << endl;
-    throw;
-  }
-}
-
-void startLuaConfigDelayedThreads(const luaConfigDelayedThreads& delayedThreads, uint64_t generation)
-{
-  for (const auto& rpzPrimary : delayedThreads.rpzPrimaryThreads) {
-    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();
-    }
-    catch (const std::exception& e) {
-      g_log << Logger::Error << "Problem starting RPZIXFRTracker thread: " << e.what() << endl;
-      exit(1);
-    }
-    catch (const PDNSException& e) {
-      g_log << Logger::Error << "Problem starting RPZIXFRTracker thread: " << e.reason << endl;
-      exit(1);
-    }
-  }
-}
diff --git a/pdns/rec-lua-conf.hh b/pdns/rec-lua-conf.hh
deleted file mode 100644 (file)
index a88ae91..0000000
+++ /dev/null
@@ -1,105 +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 <set>
-
-#include "sholder.hh"
-#include "sortlist.hh"
-#include "filterpo.hh"
-#include "validate.hh"
-#include "rec-zonetocache.hh"
-
-struct ProtobufExportConfig
-{
-  std::set<uint16_t> exportTypes = {QType::A, QType::AAAA, QType::CNAME};
-  std::vector<ComboAddress> servers;
-  uint64_t maxQueuedEntries{100};
-  uint16_t timeout{2};
-  uint16_t reconnectWaitTime{1};
-  bool asyncConnect{false};
-  bool enabled{false};
-  bool logQueries{true};
-  bool logResponses{true};
-  bool taggedOnly{false};
-};
-
-struct FrameStreamExportConfig
-{
-  std::vector<string> servers;
-  bool enabled{false};
-  bool logQueries{true};
-  bool logResponses{true};
-  unsigned bufferHint{0};
-  unsigned flushTimeout{0};
-  unsigned inputQueueSize{0};
-  unsigned outputQueueSize{0};
-  unsigned queueNotifyThreshold{0};
-  unsigned reopenInterval{0};
-};
-
-struct TrustAnchorFileInfo
-{
-  uint32_t interval{24};
-  std::string fname;
-};
-
-enum class AdditionalMode : uint8_t
-{
-  Ignore,
-  CacheOnly,
-  CacheOnlyRequireAuth,
-  ResolveImmediately,
-  ResolveDeferred
-};
-
-class LuaConfigItems
-{
-public:
-  LuaConfigItems();
-  SortList sortlist;
-  DNSFilterEngine dfe;
-  TrustAnchorFileInfo trustAnchorFileInfo; // Used to update the Trust Anchors from file periodically
-  map<DNSName, dsmap_t> dsAnchors;
-  map<DNSName, std::string> negAnchors;
-  map<DNSName, RecZoneToCache::Config> ztcConfigs;
-  std::map<QType, std::pair<std::set<QType>, AdditionalMode>> allowAdditionalQTypes;
-  ProtobufExportConfig protobufExportConfig;
-  ProtobufExportConfig outgoingProtobufExportConfig;
-  FrameStreamExportConfig frameStreamExportConfig;
-  /* we need to increment this every time the configuration
-     is reloaded, so we know if we need to reload the protobuf
-     remote loggers */
-  uint64_t generation{0};
-  uint8_t protobufMaskV4{32};
-  uint8_t protobufMaskV6{128};
-};
-
-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<SOARecordContent>, std::string>> rpzPrimaryThreads;
-};
-
-void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& delayedThreads);
-void startLuaConfigDelayedThreads(const luaConfigDelayedThreads& delayedThreads, uint64_t generation);
diff --git a/pdns/rec-snmp.cc b/pdns/rec-snmp.cc
deleted file mode 100644 (file)
index 0f76cad..0000000
+++ /dev/null
@@ -1,385 +0,0 @@
-
-#include <unordered_map>
-
-#include "rec-snmp.hh"
-#include "rec_channel.hh"
-
-#include "logger.hh"
-
-#ifdef HAVE_NET_SNMP
-
-#define RECURSOR_OID 1, 3, 6, 1, 4, 1, 43315, 2
-#define RECURSOR_STATS_OID RECURSOR_OID, 1
-#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};
-#ifdef __linux__
-static const oid udpRecvbufErrorsOID[] = {RECURSOR_STATS_OID, 66};
-static const oid udpSndbufErrorsOID[] = {RECURSOR_STATS_OID, 67};
-static const oid udpNoportErrorsOID[] = {RECURSOR_STATS_OID, 68};
-static const oid udpinErrorsOID[] = {RECURSOR_STATS_OID, 69};
-#endif /* __linux__ */
-static const oid ednsPingMatchesOID[] = {RECURSOR_STATS_OID, 70};
-static const oid ednsPingMismatchesOID[] = {RECURSOR_STATS_OID, 71};
-static const oid dnssecQueriesOID[] = {RECURSOR_STATS_OID, 72};
-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};
-#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};
-#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 std::unordered_map<oid, std::string> 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(questionsOID) + 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;
-  }
-
-  auto value = getStatByName(it->second);
-  if (value) {
-    return RecursorSNMPAgent::setCounter64Value(requests, *value);
-  }
-  else {
-    return RecursorSNMPAgent::setCounter64Value(requests, 0);
-  }
-}
-
-static int handleDisabledCounter64Stats(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(questionsOID) + 1) {
-    return SNMP_ERR_GENERR;
-  }
-
-  return RecursorSNMPAgent::setCounter64Value(requests, 0);
-}
-
-static void registerCounter64Stat(const std::string& name, const oid statOID[], size_t statOIDLength)
-{
-  if (statOIDLength != OID_LENGTH(questionsOID)) {
-    g_log << Logger::Error << "Invalid OID for SNMP Counter64 statistic " << name << endl;
-    return;
-  }
-
-  if (s_statsMap.find(statOID[statOIDLength - 1]) != s_statsMap.end()) {
-    g_log << Logger::Error << "OID for SNMP Counter64 statistic " << name << " has already been registered" << endl;
-    return;
-  }
-
-  s_statsMap[statOID[statOIDLength - 1]] = name.c_str();
-  netsnmp_register_scalar(netsnmp_create_handler_registration(name.c_str(),
-                                                              isStatDisabled(StatComponent::SNMP, name) ? handleDisabledCounter64Stats : handleCounter64Stats,
-                                                              statOID,
-                                                              statOIDLength,
-                                                              HANDLER_CAN_RONLY));
-}
-
-#endif /* HAVE_NET_SNMP */
-
-std::shared_ptr<RecursorSNMPAgent> g_snmpAgent{nullptr};
-
-bool RecursorSNMPAgent::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);
-#endif /* HAVE_NET_SNMP */
-  return true;
-}
-
-RecursorSNMPAgent::RecursorSNMPAgent(const std::string& name, const std::string& masterSocket) :
-  SNMPAgent(name, masterSocket)
-{
-#ifdef HAVE_NET_SNMP
-  /* This is done so that the statistics maps are
-     initialized. */
-  registerAllStats();
-
-  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));
-#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));
-#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));
-#endif /* HAVE_NET_SNMP */
-}
diff --git a/pdns/rec_channel.cc b/pdns/rec_channel.cc
deleted file mode 100644 (file)
index 7cc46b8..0000000
+++ /dev/null
@@ -1,220 +0,0 @@
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-#include "rec_channel.hh"
-#include "utility.hh"
-#include <sys/socket.h>
-#include <cerrno>
-#include "misc.hh"
-#include <string.h>
-#include <cstdlib>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <iostream>
-#include <limits.h>
-
-#include "pdnsexception.hh"
-
-#include "namespaces.hh"
-
-std::atomic<bool> RecursorControlChannel::stop = false;
-
-RecursorControlChannel::RecursorControlChannel()
-{
-  d_fd = -1;
-  *d_local.sun_path = 0;
-  d_local.sun_family = 0;
-}
-
-RecursorControlChannel::~RecursorControlChannel()
-{
-  if (d_fd > 0)
-    close(d_fd);
-  if (*d_local.sun_path)
-    unlink(d_local.sun_path);
-}
-
-int RecursorControlChannel::listen(const string& fname)
-{
-  d_fd = socket(AF_UNIX, SOCK_STREAM, 0);
-  setCloseOnExec(d_fd);
-
-  if (d_fd < 0)
-    throw PDNSException("Creating UNIX domain socket: " + stringerror());
-
-  int tmp = 1;
-  if (setsockopt(d_fd, SOL_SOCKET, SO_REUSEADDR, (char*)&tmp, sizeof tmp) < 0)
-    throw PDNSException("Setsockopt failed: " + stringerror());
-
-  int err = unlink(fname.c_str());
-  if (err < 0 && errno != ENOENT)
-    throw PDNSException("Can't remove (previous) controlsocket '" + fname + "': " + stringerror() + " (try --socket-dir)");
-
-  if (makeUNsockaddr(fname, &d_local))
-    throw PDNSException("Unable to bind to controlsocket, path '" + fname + "' is not a valid UNIX socket path.");
-
-  if (bind(d_fd, (sockaddr*)&d_local, sizeof(d_local)) < 0)
-    throw PDNSException("Unable to bind to controlsocket '" + fname + "': " + stringerror());
-  if (::listen(d_fd, 0) == -1) {
-    throw PDNSException("Unable to listen on controlsocket '" + fname + "': " + stringerror());
-  }
-  return d_fd;
-}
-
-void RecursorControlChannel::connect(const string& path, const string& fname)
-{
-  struct sockaddr_un remote;
-
-  d_fd = socket(AF_UNIX, SOCK_STREAM, 0);
-  setCloseOnExec(d_fd);
-
-  if (d_fd < 0)
-    throw PDNSException("Creating UNIX domain socket: " + stringerror());
-
-  try {
-    int tmp = 1;
-    if (setsockopt(d_fd, SOL_SOCKET, SO_REUSEADDR, (char*)&tmp, sizeof tmp) < 0)
-      throw PDNSException("Setsockopt failed: " + stringerror());
-
-    string remotename = path + "/" + fname;
-    if (makeUNsockaddr(remotename, &remote))
-      throw PDNSException("Unable to connect to controlsocket, path '" + remotename + "' is not a valid UNIX socket path.");
-
-    if (::connect(d_fd, (sockaddr*)&remote, sizeof(remote)) < 0) {
-      if (*d_local.sun_path)
-        unlink(d_local.sun_path);
-      throw PDNSException("Unable to connect to remote '" + string(remote.sun_path) + "': " + stringerror());
-    }
-  }
-  catch (...) {
-    close(d_fd);
-    d_fd = -1;
-    d_local.sun_path[0] = 0;
-    throw;
-  }
-}
-
-static void sendfd(int s, int fd)
-{
-  struct msghdr msg;
-  struct cmsghdr* cmsg;
-  union
-  {
-    struct cmsghdr hdr;
-    unsigned char buf[CMSG_SPACE(sizeof(int))];
-  } cmsgbuf;
-  struct iovec io_vector[1];
-  char ch = 'X';
-
-  io_vector[0].iov_base = &ch;
-  io_vector[0].iov_len = 1;
-
-  memset(&msg, 0, sizeof(msg));
-  msg.msg_control = &cmsgbuf.buf;
-  msg.msg_controllen = sizeof(cmsgbuf.buf);
-  msg.msg_iov = io_vector;
-  msg.msg_iovlen = 1;
-
-  cmsg = CMSG_FIRSTHDR(&msg);
-  cmsg->cmsg_len = CMSG_LEN(sizeof(int));
-  cmsg->cmsg_level = SOL_SOCKET;
-  cmsg->cmsg_type = SCM_RIGHTS;
-  *(int*)CMSG_DATA(cmsg) = fd;
-
-  if (sendmsg(s, &msg, 0) == -1) {
-    throw PDNSException("Unable to send fd message over control channel: " + stringerror());
-  }
-}
-
-void RecursorControlChannel::send(int fd, const Answer& msg, unsigned int timeout, int fd_to_pass)
-{
-  int ret = waitForRWData(fd, false, timeout, 0);
-  if (ret == 0) {
-    throw PDNSException("Timeout sending message over control channel");
-  }
-  else if (ret < 0) {
-    throw PDNSException("Error sending message over control channel:" + stringerror());
-  }
-
-  if (::send(fd, &msg.d_ret, sizeof(msg.d_ret), 0) < 0) {
-    throw PDNSException("Unable to send return code over control channel: " + stringerror());
-  }
-  size_t len = msg.d_str.length();
-  if (::send(fd, &len, sizeof(len), 0) < 0) {
-    throw PDNSException("Unable to send length over control channel: " + stringerror());
-  }
-  if (::send(fd, msg.d_str.c_str(), len, 0) != static_cast<ssize_t>(len)) {
-    throw PDNSException("Unable to send message over control channel: " + stringerror());
-  }
-
-  if (fd_to_pass != -1) {
-    sendfd(fd, fd_to_pass);
-  }
-}
-
-static void waitForRead(int fd, unsigned int timeout, time_t start)
-{
-  time_t elapsed = time(nullptr) - start;
-  if (elapsed >= timeout) {
-    throw PDNSException("Timeout waiting for control channel data");
-  }
-  int ret = waitForData(fd, timeout - elapsed, 0);
-  if (ret == 0) {
-    throw PDNSException("Timeout waiting for control channel data");
-  }
-}
-
-static size_t getArgMax()
-{
-#if defined(ARG_MAX)
-  return ARG_MAX;
-#endif
-
-#if defined(_SC_ARG_MAX)
-  auto tmp = sysconf(_SC_ARG_MAX);
-  if (tmp != -1) {
-    return tmp;
-  }
-#endif
-  /* _POSIX_ARG_MAX */
-  return 4096;
-}
-
-RecursorControlChannel::Answer RecursorControlChannel::recv(int fd, unsigned int timeout)
-{
-  // timeout covers the operation of all read ops combined
-  const time_t start = time(nullptr);
-
-  waitForRead(fd, timeout, start);
-  int err;
-  if (::recv(fd, &err, sizeof(err), 0) != sizeof(err)) {
-    throw PDNSException("Unable to receive return status over control channel: " + stringerror());
-  }
-
-  waitForRead(fd, timeout, start);
-  size_t len;
-  if (::recv(fd, &len, sizeof(len), 0) != sizeof(len)) {
-    throw PDNSException("Unable to receive length over control channel: " + stringerror());
-  }
-
-  if (len > getArgMax()) {
-    throw PDNSException("Length of control channel message too large");
-  }
-
-  string str;
-  str.reserve(len);
-  while (str.length() < len) {
-    char buffer[1024];
-    waitForRead(fd, timeout, start);
-    size_t toRead = std::min(len - str.length(), sizeof(buffer));
-    ssize_t recvd = ::recv(fd, buffer, toRead, 0);
-    if (recvd <= 0) {
-      // EOF means we have a length error
-      throw PDNSException("Unable to receive message over control channel: " + stringerror());
-    }
-    str.append(buffer, recvd);
-  }
-
-  return {err, str};
-}
diff --git a/pdns/rec_channel.hh b/pdns/rec_channel.hh
deleted file mode 100644 (file)
index 0ad0545..0000000
+++ /dev/null
@@ -1,147 +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 <map>
-#include <optional>
-#include <vector>
-#include <inttypes.h>
-#include <sys/un.h>
-#include <signal.h>
-#include <pthread.h>
-#include "iputils.hh"
-#include "dnsname.hh"
-#include "sholder.hh"
-#include <atomic>
-
-extern GlobalStateHolder<SuffixMatchNode> g_dontThrottleNames;
-extern GlobalStateHolder<NetmaskGroup> g_dontThrottleNetmasks;
-
-/** this class is used both to send and answer channel commands to the PowerDNS Recursor */
-class RecursorControlChannel
-{
-public:
-  RecursorControlChannel();
-
-  ~RecursorControlChannel();
-
-  int listen(const std::string& filename);
-  void connect(const std::string& path, const std::string& filename);
-
-  uint64_t getStat(const std::string& name);
-
-  struct Answer
-  {
-    Answer& operator+=(const Answer& rhs)
-    {
-      if (d_ret == 0 && rhs.d_ret != 0) {
-        d_ret = rhs.d_ret;
-      }
-      d_str += rhs.d_str;
-      return *this;
-    }
-    int d_ret{0};
-    std::string d_str;
-  };
-
-  void send(int remote, const Answer&, unsigned int timeout = 5, int fd_to_pass = -1);
-  RecursorControlChannel::Answer recv(int fd, unsigned int timeout = 5);
-
-  int d_fd;
-  static std::atomic<bool> stop;
-
-private:
-  struct sockaddr_un d_local;
-};
-
-class RecursorControlParser
-{
-public:
-  RecursorControlParser()
-  {
-  }
-  static void nop(void) {}
-  typedef void func_t(void);
-
-  RecursorControlChannel::Answer getAnswer(int s, const std::string& question, func_t** func);
-};
-
-enum class StatComponent
-{
-  API,
-  Carbon,
-  RecControl,
-  SNMP
-};
-
-struct StatsMapEntry
-{
-  std::string d_prometheusName;
-  std::string d_value;
-};
-
-class PrefixDashNumberCompare
-{
-private:
-  static std::pair<std::string, std::string> prefixAndTrailingNum(const std::string& a);
-
-public:
-  bool operator()(const std::string& a, const std::string& b) const;
-};
-
-typedef std::map<std::string, StatsMapEntry, PrefixDashNumberCompare> StatsMap;
-
-StatsMap getAllStatsMap(StatComponent component);
-
-struct CarbonConfig
-{
-  std::string hostname;
-  std::string instance_name;
-  std::string namespace_name;
-  std::vector<std::string> servers;
-};
-
-extern GlobalStateHolder<CarbonConfig> g_carbonConfig;
-
-std::vector<std::pair<DNSName, uint16_t>>* pleaseGetQueryRing();
-std::vector<std::pair<DNSName, uint16_t>>* pleaseGetServfailQueryRing();
-std::vector<std::pair<DNSName, uint16_t>>* pleaseGetBogusQueryRing();
-std::vector<ComboAddress>* pleaseGetRemotes();
-std::vector<ComboAddress>* pleaseGetServfailRemotes();
-std::vector<ComboAddress>* pleaseGetBogusRemotes();
-std::vector<ComboAddress>* pleaseGetLargeAnswerRemotes();
-std::vector<ComboAddress>* pleaseGetTimeouts();
-DNSName getRegisteredName(const DNSName& dom);
-std::atomic<unsigned long>* getDynMetric(const std::string& str, const std::string& prometheusName);
-std::optional<uint64_t> getStatByName(const std::string& name);
-bool isStatDisabled(StatComponent component, const std::string& name);
-void disableStat(StatComponent component, const string& name);
-void disableStats(StatComponent component, const string& stats);
-
-void registerAllStats();
-
-void doExitGeneric(bool nicely);
-void doExit();
-void doExitNicely();
-RecursorControlChannel::Answer doQueueReloadLuaScript(vector<string>::const_iterator begin, vector<string>::const_iterator end);
diff --git a/pdns/rec_channel_rec.cc b/pdns/rec_channel_rec.cc
deleted file mode 100644 (file)
index 8ed79f3..0000000
+++ /dev/null
@@ -1,2162 +0,0 @@
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-#include "utility.hh"
-#include "rec_channel.hh"
-
-#include <vector>
-#ifdef MALLOC_TRACE
-#include "malloctrace.hh"
-#endif
-#include "misc.hh"
-#include "recursor_cache.hh"
-#include "syncres.hh"
-#include "negcache.hh"
-#include <boost/format.hpp>
-#include <boost/algorithm/string.hpp>
-
-#include "version.hh"
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include "logger.hh"
-#include "dnsparser.hh"
-#include "arguments.hh"
-#include <sys/resource.h>
-#include <sys/time.h>
-#include "lock.hh"
-#include "responsestats.hh"
-#include "rec-lua-conf.hh"
-
-#include "aggressive_nsec.hh"
-#include "validate-recursor.hh"
-#include "filterpo.hh"
-
-#include "secpoll-recursor.hh"
-#include "pubsuffix.hh"
-#include "namespaces.hh"
-#include "rec-taskqueue.hh"
-#include "rec-tcpout.hh"
-#include "rec-main.hh"
-
-std::pair<std::string, std::string> PrefixDashNumberCompare::prefixAndTrailingNum(const std::string& a)
-{
-  auto i = a.length();
-  if (i == 0) {
-    return {a, ""};
-  }
-  --i;
-  if (!std::isdigit(a[i])) {
-    return {a, ""};
-  }
-  while (i > 0) {
-    if (!std::isdigit(a[i])) {
-      break;
-    }
-    --i;
-  }
-  return {a.substr(0, i + 1), a.substr(i + 1, a.size() - i - 1)};
-}
-
-bool PrefixDashNumberCompare::operator()(const std::string& a, const std::string& b) const
-{
-  auto [aprefix, anum] = prefixAndTrailingNum(a);
-  auto [bprefix, bnum] = prefixAndTrailingNum(b);
-
-  if (aprefix != bprefix || anum.length() == 0 || bnum.length() == 0) {
-    return a < b;
-  }
-  auto aa = std::stoull(anum);
-  auto bb = std::stoull(bnum);
-  return aa < bb;
-}
-
-static map<string, const uint32_t*> d_get32bitpointers;
-static map<string, const pdns::stat_t*> d_getatomics;
-static map<string, std::function<uint64_t()>> d_get64bitmembers;
-static map<string, std::function<StatsMap()>> d_getmultimembers;
-
-struct dynmetrics
-{
-  std::atomic<unsigned long>* d_ptr;
-  std::string d_prometheusName;
-};
-
-static LockGuarded<map<string, dynmetrics>> d_dynmetrics;
-
-static std::map<StatComponent, std::set<std::string>> s_disabledStats;
-
-bool isStatDisabled(StatComponent component, const string& name)
-{
-  return s_disabledStats[component].count(name) != 0;
-}
-
-void disableStat(StatComponent component, const string& name)
-{
-  s_disabledStats[component].insert(name);
-}
-
-void disableStats(StatComponent component, const string& stats)
-{
-  std::vector<std::string> disabledStats;
-  stringtok(disabledStats, stats, ", ");
-  auto& map = s_disabledStats[component];
-  for (const auto& st : disabledStats) {
-    map.insert(st);
-  }
-}
-
-static void addGetStat(const string& name, const uint32_t* place)
-{
-  d_get32bitpointers[name] = place;
-}
-
-static void addGetStat(const string& name, const pdns::stat_t* place)
-{
-  d_getatomics[name] = place;
-}
-
-static void addGetStat(const string& name, std::function<uint64_t()> f)
-{
-  d_get64bitmembers[name] = f;
-}
-
-static void addGetStat(const string& name, std::function<StatsMap()> f)
-{
-  d_getmultimembers[name] = f;
-}
-
-static std::string getPrometheusName(const std::string& arg)
-{
-  std::string name = arg;
-  std::replace_if(
-    name.begin(), name.end(), [](char c) { return !isalnum(static_cast<unsigned char>(c)); }, '_');
-  return "pdns_recursor_" + name;
-}
-
-std::atomic<unsigned long>* getDynMetric(const std::string& str, const std::string& prometheusName)
-{
-  auto dm = d_dynmetrics.lock();
-  auto f = dm->find(str);
-  if (f != dm->end()) {
-    return f->second.d_ptr;
-  }
-
-  std::string name(str);
-  if (!prometheusName.empty()) {
-    name = prometheusName;
-  }
-  else {
-    name = getPrometheusName(name);
-  }
-
-  auto ret = dynmetrics{new std::atomic<unsigned long>(), name};
-  (*dm)[str] = ret;
-  return ret.d_ptr;
-}
-
-static std::optional<uint64_t> get(const string& name)
-{
-  std::optional<uint64_t> ret;
-
-  if (d_get32bitpointers.count(name))
-    return *d_get32bitpointers.find(name)->second;
-  if (d_getatomics.count(name))
-    return d_getatomics.find(name)->second->load();
-  if (d_get64bitmembers.count(name))
-    return d_get64bitmembers.find(name)->second();
-
-  {
-    auto dm = d_dynmetrics.lock();
-    auto f = rplookup(*dm, name);
-    if (f) {
-      return f->d_ptr->load();
-    }
-  }
-
-  for (const auto& themultimember : d_getmultimembers) {
-    const auto items = themultimember.second();
-    const auto item = items.find(name);
-    if (item != items.end()) {
-      return std::stoull(item->second.d_value);
-    }
-  }
-
-  return ret;
-}
-
-std::optional<uint64_t> getStatByName(const std::string& name)
-{
-  return get(name);
-}
-
-StatsMap getAllStatsMap(StatComponent component)
-{
-  StatsMap ret;
-  const auto& disabledlistMap = s_disabledStats.at(component);
-
-  for (const auto& the32bits : d_get32bitpointers) {
-    if (disabledlistMap.count(the32bits.first) == 0) {
-      ret.emplace(the32bits.first, StatsMapEntry{getPrometheusName(the32bits.first), std::to_string(*the32bits.second)});
-    }
-  }
-  for (const auto& atomic : d_getatomics) {
-    if (disabledlistMap.count(atomic.first) == 0) {
-      ret.emplace(atomic.first, StatsMapEntry{getPrometheusName(atomic.first), std::to_string(atomic.second->load())});
-    }
-  }
-
-  for (const auto& the64bitmembers : d_get64bitmembers) {
-    if (disabledlistMap.count(the64bitmembers.first) == 0) {
-      ret.emplace(the64bitmembers.first, StatsMapEntry{getPrometheusName(the64bitmembers.first), std::to_string(the64bitmembers.second())});
-    }
-  }
-
-  for (const auto& themultimember : d_getmultimembers) {
-    if (disabledlistMap.count(themultimember.first) == 0) {
-      ret.merge(themultimember.second());
-    }
-  }
-
-  {
-    for (const auto& a : *(d_dynmetrics.lock())) {
-      if (disabledlistMap.count(a.first) == 0) {
-        ret.emplace(a.first, StatsMapEntry{a.second.d_prometheusName, std::to_string(*a.second.d_ptr)});
-      }
-    }
-  }
-
-  return ret;
-}
-
-static string getAllStats()
-{
-  auto varmap = getAllStatsMap(StatComponent::RecControl);
-  string ret;
-  for (const auto& tup : varmap) {
-    ret += tup.first + "\t" + tup.second.d_value + "\n";
-  }
-  return ret;
-}
-
-template <typename T>
-static string doGet(T begin, T end)
-{
-  string ret;
-
-  for (T i = begin; i != end; ++i) {
-    std::optional<uint64_t> num = get(*i);
-    if (num)
-      ret += std::to_string(*num) + "\n";
-    else
-      ret += "UNKNOWN\n";
-  }
-  return ret;
-}
-
-template <typename T>
-string static doGetParameter(T begin, T end)
-{
-  string ret;
-  string parm;
-  using boost::replace_all;
-  for (T i = begin; i != end; ++i) {
-    if (::arg().parmIsset(*i)) {
-      parm = ::arg()[*i];
-      replace_all(parm, "\\", "\\\\");
-      replace_all(parm, "\"", "\\\"");
-      replace_all(parm, "\n", "\\n");
-      ret += *i + "=\"" + parm + "\"\n";
-    }
-    else
-      ret += *i + " not known\n";
-  }
-  return ret;
-}
-
-/* Read an (open) fd from the control channel */
-static FDWrapper
-getfd(int s)
-{
-  int fd = -1;
-  struct msghdr msg;
-  struct cmsghdr* cmsg;
-  union
-  {
-    struct cmsghdr hdr;
-    unsigned char buf[CMSG_SPACE(sizeof(int))];
-  } cmsgbuf;
-  struct iovec io_vector[1];
-  char ch;
-
-  io_vector[0].iov_base = &ch;
-  io_vector[0].iov_len = 1;
-
-  memset(&msg, 0, sizeof(msg));
-  msg.msg_control = &cmsgbuf.buf;
-  msg.msg_controllen = sizeof(cmsgbuf.buf);
-  msg.msg_iov = io_vector;
-  msg.msg_iovlen = 1;
-
-  if (recvmsg(s, &msg, 0) == -1) {
-    throw PDNSException("recvmsg");
-  }
-  if ((msg.msg_flags & MSG_TRUNC) || (msg.msg_flags & MSG_CTRUNC)) {
-    throw PDNSException("control message truncated");
-  }
-  for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
-       cmsg = CMSG_NXTHDR(&msg, cmsg)) {
-    if (cmsg->cmsg_len == CMSG_LEN(sizeof(int)) && cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
-      fd = *(int*)CMSG_DATA(cmsg);
-      break;
-    }
-  }
-  return FDWrapper(fd);
-}
-
-static uint64_t dumpNegCache(int fd)
-{
-  int newfd = dup(fd);
-  if (newfd == -1) {
-    return 0;
-  }
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
-  if (!fp) {
-    return 0;
-  }
-  fprintf(fp.get(), "; negcache dump follows\n;\n");
-
-  struct timeval now;
-  Utility::gettimeofday(&now, nullptr);
-  return g_negCache->dumpToFile(fp.get(), now);
-}
-
-static uint64_t dumpAggressiveNSECCache(int fd)
-{
-  if (!g_aggressiveNSECCache) {
-    return 0;
-  }
-
-  int newfd = dup(fd);
-  if (newfd == -1) {
-    return 0;
-  }
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
-  if (!fp) {
-    return 0;
-  }
-  fprintf(fp.get(), "; aggressive NSEC cache dump follows\n;\n");
-
-  struct timeval now;
-  Utility::gettimeofday(&now, nullptr);
-  return g_aggressiveNSECCache->dumpToFile(fp, now);
-}
-
-static uint64_t* pleaseDump(int fd)
-{
-  return new uint64_t(t_packetCache->doDump(fd));
-}
-
-static uint64_t* pleaseDumpEDNSMap(int fd)
-{
-  return new uint64_t(SyncRes::doEDNSDump(fd));
-}
-
-static uint64_t* pleaseDumpNSSpeeds(int fd)
-{
-  return new uint64_t(SyncRes::doDumpNSSpeeds(fd));
-}
-
-static uint64_t* pleaseDumpThrottleMap(int fd)
-{
-  return new uint64_t(SyncRes::doDumpThrottleMap(fd));
-}
-
-static uint64_t* pleaseDumpFailedServers(int fd)
-{
-  return new uint64_t(SyncRes::doDumpFailedServers(fd));
-}
-
-static uint64_t* pleaseDumpNonResolvingNS(int fd)
-{
-  return new uint64_t(SyncRes::doDumpNonResolvingNS(fd));
-}
-
-// Generic dump to file command
-static RecursorControlChannel::Answer doDumpToFile(int s, uint64_t* (*function)(int s), const string& name, bool threads = true)
-{
-  auto fdw = getfd(s);
-
-  if (fdw < 0) {
-    return {1, name + ": error opening dump file for writing: " + stringerror() + "\n"};
-  }
-
-  uint64_t total = 0;
-  try {
-    if (threads) {
-      int fd = fdw;
-      total = broadcastAccFunction<uint64_t>([function, fd] { return function(fd); });
-    }
-    else {
-      auto ret = function(fdw);
-      total = *ret;
-      delete ret;
-    }
-  }
-  catch (std::exception& e) {
-    return {1, name + ": error dumping data: " + string(e.what()) + "\n"};
-  }
-  catch (PDNSException& e) {
-    return {1, name + ": error dumping data: " + e.reason + "\n"};
-  }
-
-  return {0, name + ": dumped " + std::to_string(total) + " records\n"};
-}
-
-// Does not follow the generic dump to file pattern, has a more complex lambda
-static RecursorControlChannel::Answer doDumpCache(int s)
-{
-  auto fdw = getfd(s);
-
-  if (fdw < 0) {
-    return {1, "Error opening dump file for writing: " + stringerror() + "\n"};
-  }
-  uint64_t total = 0;
-  try {
-    int fd = fdw;
-    total = g_recCache->doDump(fd) + dumpNegCache(fd) + broadcastAccFunction<uint64_t>([fd] { return pleaseDump(fd); }) + dumpAggressiveNSECCache(fd);
-  }
-  catch (...) {
-  }
-
-  return {0, "dumped " + std::to_string(total) + " records\n"};
-}
-
-// Does not follow the generic dump to file pattern, has an argument
-template <typename T>
-static RecursorControlChannel::Answer doDumpRPZ(int s, T begin, T end)
-{
-  auto fdw = getfd(s);
-
-  if (fdw < 0) {
-    return {1, "Error opening dump file for writing: " + stringerror() + "\n"};
-  }
-
-  T i = begin;
-
-  if (i == end) {
-    return {1, "No zone name specified\n"};
-  }
-  string zoneName = *i;
-
-  auto luaconf = g_luaconfs.getLocal();
-  const auto zone = luaconf->dfe.getZone(zoneName);
-  if (!zone) {
-    return {1, "No RPZ zone named " + zoneName + "\n"};
-  }
-
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(fdw, "w"), fclose);
-  if (!fp) {
-    int err = errno;
-    return {1, "converting file descriptor: " + stringerror(err) + "\n"};
-  }
-
-  zone->dump(fp.get());
-
-  return {0, "done\n"};
-}
-
-template <typename T>
-static string doWipeCache(T begin, T end, uint16_t qtype)
-{
-  vector<pair<DNSName, bool>> toWipe;
-  for (T i = begin; i != end; ++i) {
-    DNSName canon;
-    bool subtree = false;
-
-    try {
-      if (boost::ends_with(*i, "$")) {
-        canon = DNSName(i->substr(0, i->size() - 1));
-        subtree = true;
-      }
-      else {
-        canon = DNSName(*i);
-      }
-    }
-    catch (std::exception& e) {
-      return "Error: " + std::string(e.what()) + ", nothing wiped\n";
-    }
-    toWipe.emplace_back(canon, subtree);
-  }
-
-  int count = 0, pcount = 0, countNeg = 0;
-  for (auto wipe : toWipe) {
-    try {
-      auto res = wipeCaches(wipe.first, wipe.second, qtype);
-      count += res.record_count;
-      pcount += res.packet_count;
-      countNeg += res.negative_record_count;
-    }
-    catch (const std::exception& e) {
-      g_log << Logger::Warning << ", failed: " << e.what() << endl;
-    }
-  }
-
-  return "wiped " + std::to_string(count) + " records, " + std::to_string(countNeg) + " negative records, " + std::to_string(pcount) + " packets\n";
-}
-
-template <typename T>
-static string doSetCarbonServer(T begin, T end)
-{
-  auto config = g_carbonConfig.getCopy();
-  if (begin == end) {
-    config.servers.clear();
-    g_carbonConfig.setState(std::move(config));
-    return "cleared carbon-server setting\n";
-  }
-
-  string ret;
-  stringtok(config.servers, *begin, ", ");
-  ret = "set carbon-server to '" + *begin + "'\n";
-
-  ++begin;
-  if (begin != end) {
-    config.hostname = *begin;
-    ret += "set carbon-ourname to '" + *begin + "'\n";
-  }
-  else {
-    g_carbonConfig.setState(std::move(config));
-    return ret;
-  }
-
-  ++begin;
-  if (begin != end) {
-    config.namespace_name = *begin;
-    ret += "set carbon-namespace to '" + *begin + "'\n";
-  }
-  else {
-    g_carbonConfig.setState(std::move(config));
-    return ret;
-  }
-
-  ++begin;
-  if (begin != end) {
-    config.instance_name = *begin;
-    ret += "set carbon-instance to '" + *begin + "'\n";
-  }
-
-  g_carbonConfig.setState(std::move(config));
-  return ret;
-}
-
-template <typename T>
-static string doSetDnssecLogBogus(T begin, T end)
-{
-  if (checkDNSSECDisabled())
-    return "DNSSEC is disabled in the configuration, not changing the Bogus logging setting\n";
-
-  if (begin == end)
-    return "No DNSSEC Bogus logging setting specified\n";
-
-  if (pdns_iequals(*begin, "on") || pdns_iequals(*begin, "yes")) {
-    if (!g_dnssecLogBogus) {
-      g_log << Logger::Warning << "Enabling DNSSEC Bogus logging, requested via control channel" << endl;
-      g_dnssecLogBogus = true;
-      return "DNSSEC Bogus logging enabled\n";
-    }
-    return "DNSSEC Bogus logging was already enabled\n";
-  }
-
-  if (pdns_iequals(*begin, "off") || pdns_iequals(*begin, "no")) {
-    if (g_dnssecLogBogus) {
-      g_log << Logger::Warning << "Disabling DNSSEC Bogus logging, requested via control channel" << endl;
-      g_dnssecLogBogus = false;
-      return "DNSSEC Bogus logging disabled\n";
-    }
-    return "DNSSEC Bogus logging was already disabled\n";
-  }
-
-  return "Unknown DNSSEC Bogus setting: '" + *begin + "'\n";
-}
-
-template <typename T>
-static string doAddNTA(T begin, T end)
-{
-  if (checkDNSSECDisabled())
-    return "DNSSEC is disabled in the configuration, not adding a Negative Trust Anchor\n";
-
-  if (begin == end)
-    return "No NTA specified, doing nothing\n";
-
-  DNSName who;
-  try {
-    who = DNSName(*begin);
-  }
-  catch (std::exception& e) {
-    string ret("Can't add Negative Trust Anchor: ");
-    ret += e.what();
-    ret += "\n";
-    return ret;
-  }
-  begin++;
-
-  string why("");
-  while (begin != end) {
-    why += *begin;
-    begin++;
-    if (begin != end)
-      why += " ";
-  }
-  g_log << Logger::Warning << "Adding Negative Trust Anchor for " << who << " with reason '" << why << "', requested via control channel" << endl;
-  g_luaconfs.modify([who, why](LuaConfigItems& lci) {
-    lci.negAnchors[who] = why;
-  });
-  try {
-    wipeCaches(who, true, 0xffff);
-  }
-  catch (std::exception& e) {
-    g_log << Logger::Warning << ", failed: " << e.what() << endl;
-    return "Unable to clear caches while adding Negative Trust Anchor for " + who.toStringRootDot() + ": " + e.what() + "\n";
-  }
-  return "Added Negative Trust Anchor for " + who.toLogString() + " with reason '" + why + "'\n";
-}
-
-template <typename T>
-static string doClearNTA(T begin, T end)
-{
-  if (checkDNSSECDisabled())
-    return "DNSSEC is disabled in the configuration, not removing a Negative Trust Anchor\n";
-
-  if (begin == end)
-    return "No Negative Trust Anchor specified, doing nothing.\n";
-
-  if (begin + 1 == end && *begin == "*") {
-    g_log << Logger::Warning << "Clearing all Negative Trust Anchors, requested via control channel" << endl;
-    g_luaconfs.modify([](LuaConfigItems& lci) {
-      lci.negAnchors.clear();
-    });
-    return "Cleared all Negative Trust Anchors.\n";
-  }
-
-  vector<DNSName> toRemove;
-  DNSName who;
-  while (begin != end) {
-    if (*begin == "*")
-      return "Don't mix all Negative Trust Anchor removal with multiple Negative Trust Anchor removal. Nothing removed\n";
-    try {
-      who = DNSName(*begin);
-    }
-    catch (std::exception& e) {
-      string ret("Error: ");
-      ret += e.what();
-      ret += ". No Negative Anchors removed\n";
-      return ret;
-    }
-    toRemove.push_back(who);
-    begin++;
-  }
-
-  string removed("");
-  bool first(true);
-  try {
-    for (auto const& entry : toRemove) {
-      g_log << Logger::Warning << "Clearing Negative Trust Anchor for " << entry << ", requested via control channel" << endl;
-      g_luaconfs.modify([entry](LuaConfigItems& lci) {
-        lci.negAnchors.erase(entry);
-      });
-      wipeCaches(entry, true, 0xffff);
-      if (!first) {
-        first = false;
-        removed += ",";
-      }
-      removed += " " + entry.toStringRootDot();
-    }
-  }
-  catch (std::exception& e) {
-    g_log << Logger::Warning << ", failed: " << e.what() << endl;
-    return "Unable to clear caches while clearing Negative Trust Anchor for " + who.toStringRootDot() + ": " + e.what() + "\n";
-  }
-
-  return "Removed Negative Trust Anchors for " + removed + "\n";
-}
-
-static string getNTAs()
-{
-  if (checkDNSSECDisabled())
-    return "DNSSEC is disabled in the configuration\n";
-
-  string ret("Configured Negative Trust Anchors:\n");
-  auto luaconf = g_luaconfs.getLocal();
-  for (auto negAnchor : luaconf->negAnchors)
-    ret += negAnchor.first.toLogString() + "\t" + negAnchor.second + "\n";
-  return ret;
-}
-
-template <typename T>
-static string doAddTA(T begin, T end)
-{
-  if (checkDNSSECDisabled())
-    return "DNSSEC is disabled in the configuration, not adding a Trust Anchor\n";
-
-  if (begin == end)
-    return "No TA specified, doing nothing\n";
-
-  DNSName who;
-  try {
-    who = DNSName(*begin);
-  }
-  catch (std::exception& e) {
-    string ret("Can't add Trust Anchor: ");
-    ret += e.what();
-    ret += "\n";
-    return ret;
-  }
-  begin++;
-
-  string what("");
-  while (begin != end) {
-    what += *begin + " ";
-    begin++;
-  }
-
-  try {
-    g_log << Logger::Warning << "Adding Trust Anchor for " << who << " with data '" << what << "', requested via control channel";
-    g_luaconfs.modify([who, what](LuaConfigItems& lci) {
-      auto ds = std::dynamic_pointer_cast<DSRecordContent>(DSRecordContent::make(what));
-      lci.dsAnchors[who].insert(*ds);
-    });
-    wipeCaches(who, true, 0xffff);
-    g_log << Logger::Warning << endl;
-    return "Added Trust Anchor for " + who.toStringRootDot() + " with data " + what + "\n";
-  }
-  catch (std::exception& e) {
-    g_log << Logger::Warning << ", failed: " << e.what() << endl;
-    return "Unable to add Trust Anchor for " + who.toStringRootDot() + ": " + e.what() + "\n";
-  }
-}
-
-template <typename T>
-static string doClearTA(T begin, T end)
-{
-  if (checkDNSSECDisabled())
-    return "DNSSEC is disabled in the configuration, not removing a Trust Anchor\n";
-
-  if (begin == end)
-    return "No Trust Anchor to clear\n";
-
-  vector<DNSName> toRemove;
-  DNSName who;
-  while (begin != end) {
-    try {
-      who = DNSName(*begin);
-    }
-    catch (std::exception& e) {
-      string ret("Error: ");
-      ret += e.what();
-      ret += ". No Anchors removed\n";
-      return ret;
-    }
-    if (who.isRoot())
-      return "Refusing to remove root Trust Anchor, no Anchors removed\n";
-    toRemove.push_back(who);
-    begin++;
-  }
-
-  string removed("");
-  bool first(true);
-  try {
-    for (auto const& entry : toRemove) {
-      g_log << Logger::Warning << "Removing Trust Anchor for " << entry << ", requested via control channel" << endl;
-      g_luaconfs.modify([entry](LuaConfigItems& lci) {
-        lci.dsAnchors.erase(entry);
-      });
-      wipeCaches(entry, true, 0xffff);
-      if (!first) {
-        first = false;
-        removed += ",";
-      }
-      removed += " " + entry.toStringRootDot();
-    }
-  }
-  catch (std::exception& e) {
-    g_log << Logger::Warning << ", failed: " << e.what() << endl;
-    return "Unable to clear caches while clearing Trust Anchor for " + who.toStringRootDot() + ": " + e.what() + "\n";
-  }
-
-  return "Removed Trust Anchor(s) for" + removed + "\n";
-}
-
-static string getTAs()
-{
-  if (checkDNSSECDisabled())
-    return "DNSSEC is disabled in the configuration\n";
-
-  string ret("Configured Trust Anchors:\n");
-  auto luaconf = g_luaconfs.getLocal();
-  for (auto anchor : luaconf->dsAnchors) {
-    ret += anchor.first.toLogString() + "\n";
-    for (auto e : anchor.second) {
-      ret += "\t\t" + e.getZoneRepresentation() + "\n";
-    }
-  }
-
-  return ret;
-}
-
-template <typename T>
-static string setMinimumTTL(T begin, T end)
-{
-  if (end - begin != 1)
-    return "Need to supply new minimum TTL number\n";
-  try {
-    pdns::checked_stoi_into(SyncRes::s_minimumTTL, *begin);
-    return "New minimum TTL: " + std::to_string(SyncRes::s_minimumTTL) + "\n";
-  }
-  catch (const std::exception& e) {
-    return "Error parsing the new minimum TTL number: " + std::string(e.what()) + "\n";
-  }
-}
-
-template <typename T>
-static string setMinimumECSTTL(T begin, T end)
-{
-  if (end - begin != 1)
-    return "Need to supply new ECS minimum TTL number\n";
-  try {
-    pdns::checked_stoi_into(SyncRes::s_minimumECSTTL, *begin);
-    return "New minimum ECS TTL: " + std::to_string(SyncRes::s_minimumECSTTL) + "\n";
-  }
-  catch (const std::exception& e) {
-    return "Error parsing the new ECS minimum TTL number: " + std::string(e.what()) + "\n";
-  }
-}
-
-template <typename T>
-static string setMaxCacheEntries(T begin, T end)
-{
-  if (end - begin != 1)
-    return "Need to supply new cache size\n";
-  try {
-    g_maxCacheEntries = pdns::checked_stoi<uint32_t>(*begin);
-    return "New max cache entries: " + std::to_string(g_maxCacheEntries) + "\n";
-  }
-  catch (const std::exception& e) {
-    return "Error parsing the new cache size: " + std::string(e.what()) + "\n";
-  }
-}
-
-template <typename T>
-static string setMaxPacketCacheEntries(T begin, T end)
-{
-  if (end - begin != 1)
-    return "Need to supply new packet cache size\n";
-  try {
-    g_maxPacketCacheEntries = pdns::checked_stoi<uint32_t>(*begin);
-    return "New max packetcache entries: " + std::to_string(g_maxPacketCacheEntries) + "\n";
-  }
-  catch (const std::exception& e) {
-    return "Error parsing the new packet cache size: " + std::string(e.what()) + "\n";
-  }
-}
-
-static uint64_t getSysTimeMsec()
-{
-  struct rusage ru;
-  getrusage(RUSAGE_SELF, &ru);
-  return (ru.ru_stime.tv_sec * 1000ULL + ru.ru_stime.tv_usec / 1000);
-}
-
-static uint64_t getUserTimeMsec()
-{
-  struct rusage ru;
-  getrusage(RUSAGE_SELF, &ru);
-  return (ru.ru_utime.tv_sec * 1000ULL + ru.ru_utime.tv_usec / 1000);
-}
-
-/* This is a pretty weird set of functions. To get per-thread cpu usage numbers,
-   we have to ask a thread over a pipe. We could do so surgically, so if you want to know about
-   thread 3, we pick pipe 3, but we lack that infrastructure.
-
-   We can however ask "execute this function on all threads and add up the results".
-   This is what the first function does using a custom object ThreadTimes, which if you add
-   to each other keeps filling the first one with CPU usage numbers
-*/
-
-static ThreadTimes* pleaseGetThreadCPUMsec()
-{
-  uint64_t ret = 0;
-#ifdef RUSAGE_THREAD
-  struct rusage ru;
-  getrusage(RUSAGE_THREAD, &ru);
-  ret = (ru.ru_utime.tv_sec * 1000ULL + ru.ru_utime.tv_usec / 1000);
-  ret += (ru.ru_stime.tv_sec * 1000ULL + ru.ru_stime.tv_usec / 1000);
-#endif
-  return new ThreadTimes{ret, vector<uint64_t>()};
-}
-
-/* Next up, when you want msec data for a specific thread, we check
-   if we recently executed pleaseGetThreadCPUMsec. If we didn't we do so
-   now and consult all threads.
-
-   We then answer you from the (re)fresh(ed) ThreadTimes.
-*/
-static uint64_t doGetThreadCPUMsec(int n)
-{
-  static std::mutex s_mut;
-  static time_t last = 0;
-  static ThreadTimes tt;
-
-  std::lock_guard<std::mutex> l(s_mut);
-  if (last != time(nullptr)) {
-    tt = broadcastAccFunction<ThreadTimes>(pleaseGetThreadCPUMsec);
-    last = time(nullptr);
-  }
-
-  return tt.times.at(n);
-}
-
-static uint64_t calculateUptime()
-{
-  return time(nullptr) - g_stats.startupTime;
-}
-
-static string* pleaseGetCurrentQueries()
-{
-  ostringstream ostr;
-  struct timeval now;
-  gettimeofday(&now, 0);
-
-  ostr << getMT()->d_waiters.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) {
-    const std::shared_ptr<PacketID>& pident = mthread.key;
-    const double spent = g_networkTimeoutMsec - (DiffTime(now, mthread.ttd) * 1000);
-    ostr << (fmt
-             % pident->domain.toLogString() /* ?? */ % DNSRecordContent::NumberToType(pident->type)
-             % pident->remote.toString() % (pident->tcpsock ? 'Y' : 'n')
-             % (pident->fd == -1 ? 'Y' : 'n')
-             % (spent > 0 ? spent : '0'));
-    ++n;
-    if (n >= 100)
-      break;
-  }
-  ostr << " - done\n";
-  return new string(ostr.str());
-}
-
-static string doCurrentQueries()
-{
-  return broadcastAccFunction<string>(pleaseGetCurrentQueries);
-}
-
-uint64_t* pleaseGetThrottleSize()
-{
-  return new uint64_t(SyncRes::getThrottledServersSize());
-}
-
-static uint64_t getThrottleSize()
-{
-  return broadcastAccFunction<uint64_t>(pleaseGetThrottleSize);
-}
-
-static uint64_t getNegCacheSize()
-{
-  return g_negCache->size();
-}
-
-uint64_t* pleaseGetNsSpeedsSize()
-{
-  return new uint64_t(SyncRes::getNSSpeedsSize());
-}
-
-static uint64_t getNsSpeedsSize()
-{
-  return broadcastAccFunction<uint64_t>(pleaseGetNsSpeedsSize);
-}
-
-uint64_t* pleaseGetEDNSStatusesSize()
-{
-  return new uint64_t(SyncRes::getEDNSStatusesSize());
-}
-
-uint64_t* pleaseGetConcurrentQueries()
-{
-  return new uint64_t(getMT() ? getMT()->numProcesses() : 0);
-}
-
-static uint64_t getConcurrentQueries()
-{
-  return broadcastAccFunction<uint64_t>(pleaseGetConcurrentQueries);
-}
-
-static uint64_t doGetCacheSize()
-{
-  return g_recCache->size();
-}
-
-static uint64_t doGetCacheBytes()
-{
-  return g_recCache->bytes();
-}
-
-static uint64_t doGetCacheHits()
-{
-  return g_recCache->cacheHits;
-}
-
-static uint64_t doGetCacheMisses()
-{
-  return g_recCache->cacheMisses;
-}
-
-uint64_t* pleaseGetPacketCacheSize()
-{
-  return new uint64_t(t_packetCache ? t_packetCache->size() : 0);
-}
-
-static uint64_t* pleaseGetPacketCacheBytes()
-{
-  return new uint64_t(t_packetCache ? t_packetCache->bytes() : 0);
-}
-
-static uint64_t doGetPacketCacheSize()
-{
-  return broadcastAccFunction<uint64_t>(pleaseGetPacketCacheSize);
-}
-
-static uint64_t doGetPacketCacheBytes()
-{
-  return broadcastAccFunction<uint64_t>(pleaseGetPacketCacheBytes);
-}
-
-uint64_t* pleaseGetPacketCacheHits()
-{
-  return new uint64_t(t_packetCache ? t_packetCache->d_hits : 0);
-}
-
-static uint64_t doGetPacketCacheHits()
-{
-  return broadcastAccFunction<uint64_t>(pleaseGetPacketCacheHits);
-}
-
-static uint64_t* pleaseGetPacketCacheMisses()
-{
-  return new uint64_t(t_packetCache ? t_packetCache->d_misses : 0);
-}
-
-static uint64_t doGetPacketCacheMisses()
-{
-  return broadcastAccFunction<uint64_t>(pleaseGetPacketCacheMisses);
-}
-
-static uint64_t doGetMallocated()
-{
-  // this turned out to be broken
-  /*  struct mallinfo mi = mallinfo();
-  return mi.uordblks; */
-  return 0;
-}
-
-static StatsMap toStatsMap(const string& name, const pdns::AtomicHistogram& histogram)
-{
-  const auto& data = histogram.getCumulativeBuckets();
-  const string pbasename = getPrometheusName(name);
-  StatsMap entries;
-  char buf[32];
-
-  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)});
-  }
-
-  snprintf(buf, sizeof(buf), "%g", histogram.getSum() / 1e6);
-  entries.emplace(name + "sum", StatsMapEntry{pbasename + "seconds_sum", buf});
-  entries.emplace(name + "count", StatsMapEntry{pbasename + "seconds_count", std::to_string(data.back().d_count)});
-
-  return entries;
-}
-
-static StatsMap toStatsMap(const string& name, const pdns::AtomicHistogram& histogram4, const pdns::AtomicHistogram& histogram6)
-{
-  const string pbasename = getPrometheusName(name);
-  StatsMap entries;
-  char buf[32];
-  std::string pname;
-
-  const auto& data4 = histogram4.getCumulativeBuckets();
-  for (const auto& bucket : data4) {
-    snprintf(buf, sizeof(buf), "%g", bucket.d_boundary / 1e6);
-    pname = pbasename + "seconds_bucket{ipversion=\"v4\",le=\"" + (bucket.d_boundary == std::numeric_limits<uint64_t>::max() ? "+Inf" : buf) + "\"}";
-    entries.emplace(bucket.d_name + "4", StatsMapEntry{pname, std::to_string(bucket.d_count)});
-  }
-  snprintf(buf, sizeof(buf), "%g", histogram4.getSum() / 1e6);
-  entries.emplace(name + "sum4", StatsMapEntry{pbasename + "seconds_sum{ipversion=\"v4\"}", buf});
-  entries.emplace(name + "count4", StatsMapEntry{pbasename + "seconds_count{ipversion=\"v4\"}", std::to_string(data4.back().d_count)});
-
-  const auto& data6 = histogram6.getCumulativeBuckets();
-  for (const auto& bucket : data6) {
-    snprintf(buf, sizeof(buf), "%g", bucket.d_boundary / 1e6);
-    pname = pbasename + "seconds_bucket{ipversion=\"v6\",le=\"" + (bucket.d_boundary == std::numeric_limits<uint64_t>::max() ? "+Inf" : buf) + "\"}";
-    entries.emplace(bucket.d_name + "6", StatsMapEntry{pname, std::to_string(bucket.d_count)});
-  }
-  snprintf(buf, sizeof(buf), "%g", histogram6.getSum() / 1e6);
-  entries.emplace(name + "sum6", StatsMapEntry{pbasename + "seconds_sum{ipversion=\"v6\"}", buf});
-  entries.emplace(name + "count6", StatsMapEntry{pbasename + "seconds_count{ipversion=\"v6\"}", std::to_string(data6.back().d_count)});
-
-  return entries;
-}
-
-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) {
-    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)});
-  }
-  return entries;
-}
-
-static StatsMap toRPZStatsMap(const string& name, LockGuarded<std::unordered_map<std::string, pdns::stat_t>>& map)
-{
-  const string pbasename = getPrometheusName(name);
-  StatsMap entries;
-
-  uint64_t total = 0;
-  for (const auto& entry : *map.lock()) {
-    auto& key = entry.first;
-    auto count = entry.second.load();
-    std::string sname, pname;
-    if (key.empty()) {
-      sname = name + "-filter";
-      pname = pbasename + "{type=\"filter\"}";
-    }
-    else {
-      sname = name + "-rpz-" + key;
-      pname = pbasename + "{type=\"rpz\",policyname=\"" + key + "\"}";
-    }
-    entries.emplace(sname, StatsMapEntry{pname, std::to_string(count)});
-    total += count;
-  }
-  entries.emplace(name, StatsMapEntry{pbasename, std::to_string(total)});
-  return entries;
-}
-
-static void registerAllStats1()
-{
-  addGetStat("questions", &g_stats.qcounter);
-  addGetStat("ipv6-questions", &g_stats.ipv6qcounter);
-  addGetStat("tcp-questions", &g_stats.tcpqcounter);
-
-  addGetStat("cache-hits", doGetCacheHits);
-  addGetStat("cache-misses", doGetCacheMisses);
-  addGetStat("cache-entries", doGetCacheSize);
-  addGetStat("max-cache-entries", []() { return g_maxCacheEntries.load(); });
-  addGetStat("max-packetcache-entries", []() { return g_maxPacketCacheEntries.load(); });
-  addGetStat("cache-bytes", doGetCacheBytes);
-  addGetStat("record-cache-contended", []() { return g_recCache->stats().first; });
-  addGetStat("record-cache-acquired", []() { return g_recCache->stats().second; });
-
-  addGetStat("packetcache-hits", doGetPacketCacheHits);
-  addGetStat("packetcache-misses", doGetPacketCacheMisses);
-  addGetStat("packetcache-entries", doGetPacketCacheSize);
-  addGetStat("packetcache-bytes", doGetPacketCacheBytes);
-
-  addGetStat("aggressive-nsec-cache-entries", []() { return g_aggressiveNSECCache ? g_aggressiveNSECCache->getEntriesCount() : 0; });
-  addGetStat("aggressive-nsec-cache-nsec-hits", []() { return g_aggressiveNSECCache ? g_aggressiveNSECCache->getNSECHits() : 0; });
-  addGetStat("aggressive-nsec-cache-nsec3-hits", []() { return g_aggressiveNSECCache ? g_aggressiveNSECCache->getNSEC3Hits() : 0; });
-  addGetStat("aggressive-nsec-cache-nsec-wc-hits", []() { return g_aggressiveNSECCache ? g_aggressiveNSECCache->getNSECWildcardHits() : 0; });
-  addGetStat("aggressive-nsec-cache-nsec3-wc-hits", []() { return g_aggressiveNSECCache ? g_aggressiveNSECCache->getNSEC3WildcardHits() : 0; });
-
-  addGetStat("malloc-bytes", doGetMallocated);
-
-  addGetStat("servfail-answers", &g_stats.servFails);
-  addGetStat("nxdomain-answers", &g_stats.nxDomains);
-  addGetStat("noerror-answers", &g_stats.noErrors);
-
-  addGetStat("unauthorized-udp", &g_stats.unauthorizedUDP);
-  addGetStat("unauthorized-tcp", &g_stats.unauthorizedTCP);
-  addGetStat("source-disallowed-notify", &g_stats.sourceDisallowedNotify);
-  addGetStat("zone-disallowed-notify", &g_stats.zoneDisallowedNotify);
-  addGetStat("tcp-client-overflow", &g_stats.tcpClientOverflow);
-
-  addGetStat("client-parse-errors", &g_stats.clientParseError);
-  addGetStat("server-parse-errors", &g_stats.serverParseError);
-  addGetStat("too-old-drops", &g_stats.tooOldDrops);
-  addGetStat("truncated-drops", &g_stats.truncatedDrops);
-  addGetStat("query-pipe-full-drops", &g_stats.queryPipeFullDrops);
-
-  addGetStat("answers0-1", []() { return g_stats.answers.getCount(0); });
-  addGetStat("answers1-10", []() { return g_stats.answers.getCount(1); });
-  addGetStat("answers10-100", []() { return g_stats.answers.getCount(2); });
-  addGetStat("answers100-1000", []() { return g_stats.answers.getCount(3); });
-  addGetStat("answers-slow", []() { return g_stats.answers.getCount(4); });
-
-  addGetStat("x-ourtime0-1", []() { return g_stats.ourtime.getCount(0); });
-  addGetStat("x-ourtime1-2", []() { return g_stats.ourtime.getCount(1); });
-  addGetStat("x-ourtime2-4", []() { return g_stats.ourtime.getCount(2); });
-  addGetStat("x-ourtime4-8", []() { return g_stats.ourtime.getCount(3); });
-  addGetStat("x-ourtime8-16", []() { return g_stats.ourtime.getCount(4); });
-  addGetStat("x-ourtime16-32", []() { return g_stats.ourtime.getCount(5); });
-  addGetStat("x-ourtime-slow", []() { return g_stats.ourtime.getCount(6); });
-
-  addGetStat("auth4-answers0-1", []() { return g_stats.auth4Answers.getCount(0); });
-  addGetStat("auth4-answers1-10", []() { return g_stats.auth4Answers.getCount(1); });
-  addGetStat("auth4-answers10-100", []() { return g_stats.auth4Answers.getCount(2); });
-  addGetStat("auth4-answers100-1000", []() { return g_stats.auth4Answers.getCount(3); });
-  addGetStat("auth4-answers-slow", []() { return g_stats.auth4Answers.getCount(4); });
-
-  addGetStat("auth6-answers0-1", []() { return g_stats.auth6Answers.getCount(0); });
-  addGetStat("auth6-answers1-10", []() { return g_stats.auth6Answers.getCount(1); });
-  addGetStat("auth6-answers10-100", []() { return g_stats.auth6Answers.getCount(2); });
-  addGetStat("auth6-answers100-1000", []() { return g_stats.auth6Answers.getCount(3); });
-  addGetStat("auth6-answers-slow", []() { return g_stats.auth6Answers.getCount(4); });
-
-  addGetStat("qa-latency", []() { return round(g_stats.avgLatencyUsec.load()); });
-  addGetStat("x-our-latency", []() { return round(g_stats.avgLatencyOursUsec.load()); });
-  addGetStat("unexpected-packets", &g_stats.unexpectedCount);
-  addGetStat("case-mismatches", &g_stats.caseMismatchCount);
-  addGetStat("spoof-prevents", &g_stats.spoofCount);
-
-  addGetStat("nsset-invalidations", &g_stats.nsSetInvalidations);
-
-  addGetStat("resource-limits", &g_stats.resourceLimits);
-  addGetStat("over-capacity-drops", &g_stats.overCapacityDrops);
-  addGetStat("policy-drops", &g_stats.policyDrops);
-  addGetStat("no-packet-error", &g_stats.noPacketError);
-  addGetStat("ignored-packets", &g_stats.ignoredCount);
-  addGetStat("empty-queries", &g_stats.emptyQueriesCount);
-  addGetStat("max-mthread-stack", &g_stats.maxMThreadStackUsage);
-
-  addGetStat("negcache-entries", getNegCacheSize);
-  addGetStat("throttle-entries", getThrottleSize);
-
-  addGetStat("nsspeeds-entries", getNsSpeedsSize);
-  addGetStat("failed-host-entries", SyncRes::getFailedServersSize);
-  addGetStat("non-resolving-nameserver-entries", SyncRes::getNonResolvingNSSize);
-
-  addGetStat("concurrent-queries", getConcurrentQueries);
-  addGetStat("security-status", &g_security_status);
-  addGetStat("outgoing-timeouts", &SyncRes::s_outgoingtimeouts);
-  addGetStat("outgoing4-timeouts", &SyncRes::s_outgoing4timeouts);
-  addGetStat("outgoing6-timeouts", &SyncRes::s_outgoing6timeouts);
-  addGetStat("auth-zone-queries", &SyncRes::s_authzonequeries);
-  addGetStat("tcp-outqueries", &SyncRes::s_tcpoutqueries);
-  addGetStat("dot-outqueries", &SyncRes::s_dotoutqueries);
-  addGetStat("all-outqueries", &SyncRes::s_outqueries);
-  addGetStat("ipv6-outqueries", &g_stats.ipv6queries);
-  addGetStat("throttled-outqueries", &SyncRes::s_throttledqueries);
-  addGetStat("dont-outqueries", &SyncRes::s_dontqueries);
-  addGetStat("qname-min-fallback-success", &SyncRes::s_qnameminfallbacksuccess);
-  addGetStat("throttled-out", &SyncRes::s_throttledqueries);
-  addGetStat("unreachables", &SyncRes::s_unreachables);
-  addGetStat("ecs-queries", &SyncRes::s_ecsqueries);
-  addGetStat("ecs-responses", &SyncRes::s_ecsresponses);
-  addGetStat("chain-resends", &g_stats.chainResends);
-  addGetStat("tcp-clients", [] { return TCPConnection::getCurrentConnections(); });
-
-#ifdef __linux__
-  addGetStat("udp-recvbuf-errors", [] { return udpErrorStats("udp-recvbuf-errors"); });
-  addGetStat("udp-sndbuf-errors", [] { return udpErrorStats("udp-sndbuf-errors"); });
-  addGetStat("udp-noport-errors", [] { return udpErrorStats("udp-noport-errors"); });
-  addGetStat("udp-in-errors", [] { return udpErrorStats("udp-in-errors"); });
-  addGetStat("udp-in-csum-errors", [] { return udpErrorStats("udp-in-csum-errors"); });
-  addGetStat("udp6-recvbuf-errors", [] { return udp6ErrorStats("udp6-recvbuf-errors"); });
-  addGetStat("udp6-sndbuf-errors", [] { return udp6ErrorStats("udp6-sndbuf-errors"); });
-  addGetStat("udp6-noport-errors", [] { return udp6ErrorStats("udp6-noport-errors"); });
-  addGetStat("udp6-in-errors", [] { return udp6ErrorStats("udp6-in-errors"); });
-  addGetStat("udp6-in-csum-errors", [] { return udp6ErrorStats("udp6-in-csum-errors"); });
-#endif
-
-  addGetStat("edns-ping-matches", &g_stats.ednsPingMatches);
-  addGetStat("edns-ping-mismatches", &g_stats.ednsPingMismatches);
-  addGetStat("dnssec-queries", &g_stats.dnssecQueries);
-
-  addGetStat("dnssec-authentic-data-queries", &g_stats.dnssecAuthenticDataQueries);
-  addGetStat("dnssec-check-disabled-queries", &g_stats.dnssecCheckDisabledQueries);
-
-  addGetStat("variable-responses", &g_stats.variableResponses);
-
-  addGetStat("noping-outqueries", &g_stats.noPingOutQueries);
-  addGetStat("noedns-outqueries", &g_stats.noEdnsOutQueries);
-
-  addGetStat("uptime", calculateUptime);
-  addGetStat("real-memory-usage", [] { return getRealMemoryUsage(string()); });
-  addGetStat("special-memory-usage", [] { return getSpecialMemoryUsage(string()); });
-  addGetStat("fd-usage", [] { return getOpenFileDescriptors(string()); });
-
-  //  addGetStat("query-rate", getQueryRate);
-  addGetStat("user-msec", getUserTimeMsec);
-  addGetStat("sys-msec", getSysTimeMsec);
-
-#ifdef __linux__
-  addGetStat("cpu-iowait", [] { return getCPUIOWait(string()); });
-  addGetStat("cpu-steal", [] { return getCPUSteal(string()); });
-#endif
-
-  addGetStat("cpu-msec", []() { return toCPUStatsMap("cpu-msec"); });
-
-#ifdef MALLOC_TRACE
-  addGetStat("memory-allocs", [] { return g_mtracer->getAllocs(string()); });
-  addGetStat("memory-alloc-flux", [] { return g_mtracer->getAllocFlux(string()); });
-  addGetStat("memory-allocated", [] { return g_mtracer->getTotAllocated(string()); });
-#endif
-
-  addGetStat("dnssec-validations", &g_stats.dnssecValidations);
-  addGetStat("dnssec-result-insecure", &g_stats.dnssecResults[vState::Insecure]);
-  addGetStat("dnssec-result-secure", &g_stats.dnssecResults[vState::Secure]);
-  addGetStat("dnssec-result-bogus", []() {
-    std::set<vState> const bogusStates = {vState::BogusNoValidDNSKEY, vState::BogusInvalidDenial, vState::BogusUnableToGetDSs, vState::BogusUnableToGetDNSKEYs, vState::BogusSelfSignedDS, vState::BogusNoRRSIG, vState::BogusNoValidRRSIG, vState::BogusMissingNegativeIndication, vState::BogusSignatureNotYetValid, vState::BogusSignatureExpired, vState::BogusUnsupportedDNSKEYAlgo, vState::BogusUnsupportedDSDigestType, vState::BogusNoZoneKeyBitSet, vState::BogusRevokedDNSKEY, vState::BogusInvalidDNSKEYProtocol};
-    uint64_t total = 0;
-    for (const auto& state : bogusStates) {
-      total += g_stats.dnssecResults[state];
-    }
-    return total;
-  });
-
-  addGetStat("dnssec-result-bogus-no-valid-dnskey", &g_stats.dnssecResults[vState::BogusNoValidDNSKEY]);
-  addGetStat("dnssec-result-bogus-invalid-denial", &g_stats.dnssecResults[vState::BogusInvalidDenial]);
-  addGetStat("dnssec-result-bogus-unable-to-get-dss", &g_stats.dnssecResults[vState::BogusUnableToGetDSs]);
-  addGetStat("dnssec-result-bogus-unable-to-get-dnskeys", &g_stats.dnssecResults[vState::BogusUnableToGetDNSKEYs]);
-  addGetStat("dnssec-result-bogus-self-signed-ds", &g_stats.dnssecResults[vState::BogusSelfSignedDS]);
-  addGetStat("dnssec-result-bogus-no-rrsig", &g_stats.dnssecResults[vState::BogusNoRRSIG]);
-  addGetStat("dnssec-result-bogus-no-valid-rrsig", &g_stats.dnssecResults[vState::BogusNoValidRRSIG]);
-  addGetStat("dnssec-result-bogus-missing-negative-indication", &g_stats.dnssecResults[vState::BogusMissingNegativeIndication]);
-  addGetStat("dnssec-result-bogus-signature-not-yet-valid", &g_stats.dnssecResults[vState::BogusSignatureNotYetValid]);
-  addGetStat("dnssec-result-bogus-signature-expired", &g_stats.dnssecResults[vState::BogusSignatureExpired]);
-  addGetStat("dnssec-result-bogus-unsupported-dnskey-algo", &g_stats.dnssecResults[vState::BogusUnsupportedDNSKEYAlgo]);
-  addGetStat("dnssec-result-bogus-unsupported-ds-digest-type", &g_stats.dnssecResults[vState::BogusUnsupportedDSDigestType]);
-  addGetStat("dnssec-result-bogus-no-zone-key-bit-set", &g_stats.dnssecResults[vState::BogusNoZoneKeyBitSet]);
-  addGetStat("dnssec-result-bogus-revoked-dnskey", &g_stats.dnssecResults[vState::BogusRevokedDNSKEY]);
-  addGetStat("dnssec-result-bogus-invalid-dnskey-protocol", &g_stats.dnssecResults[vState::BogusInvalidDNSKEYProtocol]);
-  addGetStat("dnssec-result-indeterminate", &g_stats.dnssecResults[vState::Indeterminate]);
-  addGetStat("dnssec-result-nta", &g_stats.dnssecResults[vState::NTA]);
-
-  if (::arg()["x-dnssec-names"].length() > 0) {
-    addGetStat("x-dnssec-result-bogus", []() {
-      std::set<vState> const bogusStates = {vState::BogusNoValidDNSKEY, vState::BogusInvalidDenial, vState::BogusUnableToGetDSs, vState::BogusUnableToGetDNSKEYs, vState::BogusSelfSignedDS, vState::BogusNoRRSIG, vState::BogusNoValidRRSIG, vState::BogusMissingNegativeIndication, vState::BogusSignatureNotYetValid, vState::BogusSignatureExpired, vState::BogusUnsupportedDNSKEYAlgo, vState::BogusUnsupportedDSDigestType, vState::BogusNoZoneKeyBitSet, vState::BogusRevokedDNSKEY, vState::BogusInvalidDNSKEYProtocol};
-      uint64_t total = 0;
-      for (const auto& state : bogusStates) {
-        total += g_stats.xdnssecResults[state];
-      }
-      return total;
-    });
-    addGetStat("x-dnssec-result-bogus-no-valid-dnskey", &g_stats.xdnssecResults[vState::BogusNoValidDNSKEY]);
-    addGetStat("x-dnssec-result-bogus-invalid-denial", &g_stats.xdnssecResults[vState::BogusInvalidDenial]);
-    addGetStat("x-dnssec-result-bogus-unable-to-get-dss", &g_stats.xdnssecResults[vState::BogusUnableToGetDSs]);
-    addGetStat("x-dnssec-result-bogus-unable-to-get-dnskeys", &g_stats.xdnssecResults[vState::BogusUnableToGetDNSKEYs]);
-    addGetStat("x-dnssec-result-bogus-self-signed-ds", &g_stats.xdnssecResults[vState::BogusSelfSignedDS]);
-    addGetStat("x-dnssec-result-bogus-no-rrsig", &g_stats.xdnssecResults[vState::BogusNoRRSIG]);
-    addGetStat("x-dnssec-result-bogus-no-valid-rrsig", &g_stats.xdnssecResults[vState::BogusNoValidRRSIG]);
-    addGetStat("x-dnssec-result-bogus-missing-negative-indication", &g_stats.xdnssecResults[vState::BogusMissingNegativeIndication]);
-    addGetStat("x-dnssec-result-bogus-signature-not-yet-valid", &g_stats.xdnssecResults[vState::BogusSignatureNotYetValid]);
-    addGetStat("x-dnssec-result-bogus-signature-expired", &g_stats.xdnssecResults[vState::BogusSignatureExpired]);
-    addGetStat("x-dnssec-result-bogus-unsupported-dnskey-algo", &g_stats.xdnssecResults[vState::BogusUnsupportedDNSKEYAlgo]);
-    addGetStat("x-dnssec-result-bogus-unsupported-ds-digest-type", &g_stats.xdnssecResults[vState::BogusUnsupportedDSDigestType]);
-    addGetStat("x-dnssec-result-bogus-no-zone-key-bit-set", &g_stats.xdnssecResults[vState::BogusNoZoneKeyBitSet]);
-    addGetStat("x-dnssec-result-bogus-revoked-dnskey", &g_stats.xdnssecResults[vState::BogusRevokedDNSKEY]);
-    addGetStat("x-dnssec-result-bogus-invalid-dnskey-protocol", &g_stats.xdnssecResults[vState::BogusInvalidDNSKEYProtocol]);
-    addGetStat("x-dnssec-result-indeterminate", &g_stats.xdnssecResults[vState::Indeterminate]);
-    addGetStat("x-dnssec-result-nta", &g_stats.xdnssecResults[vState::NTA]);
-    addGetStat("x-dnssec-result-insecure", &g_stats.xdnssecResults[vState::Insecure]);
-    addGetStat("x-dnssec-result-secure", &g_stats.xdnssecResults[vState::Secure]);
-  }
-
-  addGetStat("policy-result-noaction", &g_stats.policyResults[DNSFilterEngine::PolicyKind::NoAction]);
-  addGetStat("policy-result-drop", &g_stats.policyResults[DNSFilterEngine::PolicyKind::Drop]);
-  addGetStat("policy-result-nxdomain", &g_stats.policyResults[DNSFilterEngine::PolicyKind::NXDOMAIN]);
-  addGetStat("policy-result-nodata", &g_stats.policyResults[DNSFilterEngine::PolicyKind::NODATA]);
-  addGetStat("policy-result-truncate", &g_stats.policyResults[DNSFilterEngine::PolicyKind::Truncate]);
-  addGetStat("policy-result-custom", &g_stats.policyResults[DNSFilterEngine::PolicyKind::Custom]);
-
-  addGetStat("rebalanced-queries", &g_stats.rebalancedQueries);
-
-  addGetStat("proxy-protocol-invalid", &g_stats.proxyProtocolInvalidCount);
-
-  addGetStat("nod-lookups-dropped-oversize", &g_stats.nodLookupsDroppedOversize);
-
-  addGetStat("taskqueue-pushed", []() { return getTaskPushes(); });
-  addGetStat("taskqueue-expired", []() { return getTaskExpired(); });
-  addGetStat("taskqueue-size", []() { return getTaskSize(); });
-
-  addGetStat("dns64-prefix-answers", &g_stats.dns64prefixanswers);
-
-  addGetStat("almost-expired-pushed", []() { return getAlmostExpiredTasksPushed(); });
-  addGetStat("almost-expired-run", []() { return getAlmostExpiredTasksRun(); });
-  addGetStat("almost-expired-exceptions", []() { return getAlmostExpiredTaskExceptions(); });
-
-  addGetStat("idle-tcpout-connections", getCurrentIdleTCPConnections);
-
-  /* make sure that the ECS stats are properly initialized */
-  SyncRes::clearECSStats();
-  for (size_t idx = 0; idx < SyncRes::s_ecsResponsesBySubnetSize4.size(); idx++) {
-    const std::string name = "ecs-v4-response-bits-" + std::to_string(idx + 1);
-    addGetStat(name, &(SyncRes::s_ecsResponsesBySubnetSize4.at(idx)));
-  }
-  for (size_t idx = 0; idx < SyncRes::s_ecsResponsesBySubnetSize6.size(); idx++) {
-    const std::string name = "ecs-v6-response-bits-" + std::to_string(idx + 1);
-    addGetStat(name, &(SyncRes::s_ecsResponsesBySubnetSize6.at(idx)));
-  }
-
-  addGetStat("cumul-clientanswers", []() {
-    return toStatsMap(g_stats.cumulativeAnswers.getName(), g_stats.cumulativeAnswers);
-  });
-  addGetStat("cumul-authanswers", []() {
-    return toStatsMap(g_stats.cumulativeAuth4Answers.getName(), g_stats.cumulativeAuth4Answers, g_stats.cumulativeAuth6Answers);
-  });
-  addGetStat("policy-hits", []() {
-    return toRPZStatsMap("policy-hits", g_stats.policyHits);
-  });
-}
-
-void registerAllStats()
-{
-  static std::once_flag s_once;
-  std::call_once(s_once, []() {
-    try {
-      registerAllStats1();
-    }
-    catch (...) {
-      g_log << Logger::Critical << "Could not add stat entries" << endl;
-      exit(1);
-    }
-  });
-}
-
-void doExitGeneric(bool nicely)
-{
-  g_log << Logger::Error << "Exiting on user request" << endl;
-  g_rcc.~RecursorControlChannel();
-
-  if (!g_pidfname.empty())
-    unlink(g_pidfname.c_str()); // we can at least try..
-  if (nicely) {
-    RecursorControlChannel::stop = true;
-  }
-  else {
-    _exit(1);
-  }
-}
-
-void doExit()
-{
-  doExitGeneric(false);
-}
-
-void doExitNicely()
-{
-  doExitGeneric(true);
-}
-
-vector<pair<DNSName, uint16_t>>* pleaseGetQueryRing()
-{
-  typedef pair<DNSName, uint16_t> query_t;
-  vector<query_t>* ret = new vector<query_t>();
-  if (!t_queryring)
-    return ret;
-  ret->reserve(t_queryring->size());
-
-  for (const query_t& q : *t_queryring) {
-    ret->push_back(q);
-  }
-  return ret;
-}
-vector<pair<DNSName, uint16_t>>* pleaseGetServfailQueryRing()
-{
-  typedef pair<DNSName, uint16_t> query_t;
-  vector<query_t>* ret = new vector<query_t>();
-  if (!t_servfailqueryring)
-    return ret;
-  ret->reserve(t_servfailqueryring->size());
-  for (const query_t& q : *t_servfailqueryring) {
-    ret->push_back(q);
-  }
-  return ret;
-}
-vector<pair<DNSName, uint16_t>>* pleaseGetBogusQueryRing()
-{
-  typedef pair<DNSName, uint16_t> query_t;
-  vector<query_t>* ret = new vector<query_t>();
-  if (!t_bogusqueryring)
-    return ret;
-  ret->reserve(t_bogusqueryring->size());
-  for (const query_t& q : *t_bogusqueryring) {
-    ret->push_back(q);
-  }
-  return ret;
-}
-
-typedef std::function<vector<ComboAddress>*()> pleaseremotefunc_t;
-typedef std::function<vector<pair<DNSName, uint16_t>>*()> pleasequeryfunc_t;
-
-vector<ComboAddress>* pleaseGetRemotes()
-{
-  vector<ComboAddress>* ret = new vector<ComboAddress>();
-  if (!t_remotes)
-    return ret;
-
-  ret->reserve(t_remotes->size());
-  for (const ComboAddress& ca : *t_remotes) {
-    ret->push_back(ca);
-  }
-  return ret;
-}
-
-vector<ComboAddress>* pleaseGetServfailRemotes()
-{
-  vector<ComboAddress>* ret = new vector<ComboAddress>();
-  if (!t_servfailremotes)
-    return ret;
-  ret->reserve(t_servfailremotes->size());
-  for (const ComboAddress& ca : *t_servfailremotes) {
-    ret->push_back(ca);
-  }
-  return ret;
-}
-
-vector<ComboAddress>* pleaseGetBogusRemotes()
-{
-  vector<ComboAddress>* ret = new vector<ComboAddress>();
-  if (!t_bogusremotes)
-    return ret;
-  ret->reserve(t_bogusremotes->size());
-  for (const ComboAddress& ca : *t_bogusremotes) {
-    ret->push_back(ca);
-  }
-  return ret;
-}
-
-vector<ComboAddress>* pleaseGetLargeAnswerRemotes()
-{
-  vector<ComboAddress>* ret = new vector<ComboAddress>();
-  if (!t_largeanswerremotes)
-    return ret;
-  ret->reserve(t_largeanswerremotes->size());
-  for (const ComboAddress& ca : *t_largeanswerremotes) {
-    ret->push_back(ca);
-  }
-  return ret;
-}
-
-vector<ComboAddress>* pleaseGetTimeouts()
-{
-  vector<ComboAddress>* ret = new vector<ComboAddress>();
-  if (!t_timeouts)
-    return ret;
-  ret->reserve(t_timeouts->size());
-  for (const ComboAddress& ca : *t_timeouts) {
-    ret->push_back(ca);
-  }
-  return ret;
-}
-
-static string doGenericTopRemotes(pleaseremotefunc_t func)
-{
-  typedef map<ComboAddress, int, ComboAddress::addressOnlyLessThan> counts_t;
-  counts_t counts;
-
-  vector<ComboAddress> remotes = broadcastAccFunction<vector<ComboAddress>>(func);
-
-  unsigned int total = 0;
-  for (const ComboAddress& ca : remotes) {
-    total++;
-    counts[ca]++;
-  }
-
-  typedef std::multimap<int, ComboAddress> rcounts_t;
-  rcounts_t rcounts;
-
-  for (auto&& c : counts)
-    rcounts.emplace(-c.second, c.first);
-
-  ostringstream ret;
-  ret << "Over last " << total << " entries:\n";
-  boost::format fmt("%.02f%%\t%s\n");
-  int limit = 0, accounted = 0;
-  if (total) {
-    for (rcounts_t::const_iterator i = rcounts.begin(); i != rcounts.end() && limit < 20; ++i, ++limit) {
-      ret << fmt % (-100.0 * i->first / total) % i->second.toString();
-      accounted += -i->first;
-    }
-    ret << '\n'
-        << fmt % (100.0 * (total - accounted) / total) % "rest";
-  }
-  return ret.str();
-}
-
-// XXX DNSName Pain - this function should benefit from native DNSName methods
-DNSName getRegisteredName(const DNSName& dom)
-{
-  auto parts = dom.getRawLabels();
-  if (parts.size() <= 2)
-    return dom;
-  reverse(parts.begin(), parts.end());
-  for (string& str : parts) {
-    str = toLower(str);
-  };
-
-  // uk co migweb
-  string last;
-  while (!parts.empty()) {
-    if (parts.size() == 1 || binary_search(g_pubs.begin(), g_pubs.end(), parts)) {
-
-      string ret = last;
-      if (!ret.empty())
-        ret += ".";
-
-      for (auto p = parts.crbegin(); p != parts.crend(); ++p) {
-        ret += (*p) + ".";
-      }
-      return DNSName(ret);
-    }
-
-    last = parts[parts.size() - 1];
-    parts.resize(parts.size() - 1);
-  }
-  return DNSName("??");
-}
-
-static DNSName nopFilter(const DNSName& name)
-{
-  return name;
-}
-
-static string doGenericTopQueries(pleasequeryfunc_t func, std::function<DNSName(const DNSName&)> filter = nopFilter)
-{
-  typedef pair<DNSName, uint16_t> query_t;
-  typedef map<query_t, int> counts_t;
-  counts_t counts;
-  vector<query_t> queries = broadcastAccFunction<vector<query_t>>(func);
-
-  unsigned int total = 0;
-  for (const query_t& q : queries) {
-    total++;
-    counts[pair(filter(q.first), q.second)]++;
-  }
-
-  typedef std::multimap<int, query_t> rcounts_t;
-  rcounts_t rcounts;
-
-  for (auto&& c : counts)
-    rcounts.emplace(-c.second, c.first);
-
-  ostringstream ret;
-  ret << "Over last " << total << " entries:\n";
-  boost::format fmt("%.02f%%\t%s\n");
-  int limit = 0, accounted = 0;
-  if (total) {
-    for (rcounts_t::const_iterator i = rcounts.begin(); i != rcounts.end() && limit < 20; ++i, ++limit) {
-      ret << fmt % (-100.0 * i->first / total) % (i->second.first.toLogString() + "|" + DNSRecordContent::NumberToType(i->second.second));
-      accounted += -i->first;
-    }
-    ret << '\n'
-        << fmt % (100.0 * (total - accounted) / total) % "rest";
-  }
-
-  return ret.str();
-}
-
-static string* nopFunction()
-{
-  return new string("pong " + RecThreadInfo::self().getName() + '\n');
-}
-
-static string getDontThrottleNames()
-{
-  auto dtn = g_dontThrottleNames.getLocal();
-  return dtn->toString() + "\n";
-}
-
-static string getDontThrottleNetmasks()
-{
-  auto dtn = g_dontThrottleNetmasks.getLocal();
-  return dtn->toString() + "\n";
-}
-
-template <typename T>
-static string addDontThrottleNames(T begin, T end)
-{
-  if (begin == end) {
-    return "No names specified, keeping existing list\n";
-  }
-  vector<DNSName> toAdd;
-  while (begin != end) {
-    try {
-      auto d = DNSName(*begin);
-      toAdd.push_back(d);
-    }
-    catch (const std::exception& e) {
-      return "Problem parsing '" + *begin + "': " + e.what() + ", nothing added\n";
-    }
-    begin++;
-  }
-
-  string ret = "Added";
-  auto dnt = g_dontThrottleNames.getCopy();
-  bool first = true;
-  for (auto const& d : toAdd) {
-    if (!first) {
-      ret += ",";
-    }
-    first = false;
-    ret += " " + d.toLogString();
-    dnt.add(d);
-  }
-
-  g_dontThrottleNames.setState(std::move(dnt));
-
-  ret += " to the list of nameservers that may not be throttled";
-  g_log << Logger::Info << ret << ", requested via control channel" << endl;
-  return ret + "\n";
-}
-
-template <typename T>
-static string addDontThrottleNetmasks(T begin, T end)
-{
-  if (begin == end) {
-    return "No netmasks specified, keeping existing list\n";
-  }
-  vector<Netmask> toAdd;
-  while (begin != end) {
-    try {
-      auto n = Netmask(*begin);
-      toAdd.push_back(n);
-    }
-    catch (const std::exception& e) {
-      return "Problem parsing '" + *begin + "': " + e.what() + ", nothing added\n";
-    }
-    catch (const PDNSException& e) {
-      return "Problem parsing '" + *begin + "': " + e.reason + ", nothing added\n";
-    }
-    begin++;
-  }
-
-  string ret = "Added";
-  auto dnt = g_dontThrottleNetmasks.getCopy();
-  bool first = true;
-  for (auto const& t : toAdd) {
-    if (!first) {
-      ret += ",";
-    }
-    first = false;
-    ret += " " + t.toString();
-    dnt.addMask(t);
-  }
-
-  g_dontThrottleNetmasks.setState(std::move(dnt));
-
-  ret += " to the list of nameserver netmasks that may not be throttled";
-  g_log << Logger::Info << ret << ", requested via control channel" << endl;
-  return ret + "\n";
-}
-
-template <typename T>
-static string clearDontThrottleNames(T begin, T end)
-{
-  if (begin == end)
-    return "No names specified, doing nothing.\n";
-
-  if (begin + 1 == end && *begin == "*") {
-    SuffixMatchNode smn;
-    g_dontThrottleNames.setState(std::move(smn));
-    string ret = "Cleared list of nameserver names that may not be throttled";
-    g_log << Logger::Warning << ret << ", requested via control channel" << endl;
-    return ret + "\n";
-  }
-
-  vector<DNSName> toRemove;
-  while (begin != end) {
-    try {
-      if (*begin == "*") {
-        return "Please don't mix '*' with other names, nothing removed\n";
-      }
-      toRemove.push_back(DNSName(*begin));
-    }
-    catch (const std::exception& e) {
-      return "Problem parsing '" + *begin + "': " + e.what() + ", nothing removed\n";
-    }
-    begin++;
-  }
-
-  string ret = "Removed";
-  bool first = true;
-  auto dnt = g_dontThrottleNames.getCopy();
-  for (const auto& name : toRemove) {
-    if (!first) {
-      ret += ",";
-    }
-    first = false;
-    ret += " " + name.toLogString();
-    dnt.remove(name);
-  }
-
-  g_dontThrottleNames.setState(std::move(dnt));
-
-  ret += " from the list of nameservers that may not be throttled";
-  g_log << Logger::Info << ret << ", requested via control channel" << endl;
-  return ret + "\n";
-}
-
-template <typename T>
-static string clearDontThrottleNetmasks(T begin, T end)
-{
-  if (begin == end)
-    return "No netmasks specified, doing nothing.\n";
-
-  if (begin + 1 == end && *begin == "*") {
-    auto nmg = g_dontThrottleNetmasks.getCopy();
-    nmg.clear();
-    g_dontThrottleNetmasks.setState(std::move(nmg));
-
-    string ret = "Cleared list of nameserver addresses that may not be throttled";
-    g_log << Logger::Warning << ret << ", requested via control channel" << endl;
-    return ret + "\n";
-  }
-
-  std::vector<Netmask> toRemove;
-  while (begin != end) {
-    try {
-      if (*begin == "*") {
-        return "Please don't mix '*' with other netmasks, nothing removed\n";
-      }
-      auto n = Netmask(*begin);
-      toRemove.push_back(n);
-    }
-    catch (const std::exception& e) {
-      return "Problem parsing '" + *begin + "': " + e.what() + ", nothing added\n";
-    }
-    catch (const PDNSException& e) {
-      return "Problem parsing '" + *begin + "': " + e.reason + ", nothing added\n";
-    }
-    begin++;
-  }
-
-  string ret = "Removed";
-  bool first = true;
-  auto dnt = g_dontThrottleNetmasks.getCopy();
-  for (const auto& mask : toRemove) {
-    if (!first) {
-      ret += ",";
-    }
-    first = false;
-    ret += " " + mask.toString();
-    dnt.deleteMask(mask);
-  }
-
-  g_dontThrottleNetmasks.setState(std::move(dnt));
-
-  ret += " from the list of nameservers that may not be throttled";
-  g_log << Logger::Info << ret << ", requested via control channel" << endl;
-  return ret + "\n";
-}
-
-template <typename T>
-static string setEventTracing(T begin, T end)
-{
-  if (begin == end) {
-    return "No event trace enabled value specified\n";
-  }
-  try {
-    pdns::checked_stoi_into(SyncRes::s_event_trace_enabled, *begin);
-    return "New event trace enabled value: " + std::to_string(SyncRes::s_event_trace_enabled) + "\n";
-  }
-  catch (const std::exception& e) {
-    return "Error parsing the new event trace enabled value: " + std::string(e.what()) + "\n";
-  }
-}
-
-RecursorControlChannel::Answer RecursorControlParser::getAnswer(int s, const string& question, RecursorControlParser::func_t** command)
-{
-  *command = nop;
-  vector<string> words;
-  stringtok(words, question);
-
-  if (words.empty())
-    return {1, "invalid command\n"};
-
-  string cmd = toLower(words[0]);
-  vector<string>::const_iterator begin = words.begin() + 1, 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-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-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-qtypelist                    get QType statistics\n"
-            "                                 notice: queries from cache aren't being counted yet\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]              emit resolution trace for matching queries (empty regex to clear trace)\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 == "get-all") {
-    return {0, getAllStats()};
-  }
-  if (cmd == "get") {
-    return {0, doGet(begin, end)};
-  }
-  if (cmd == "get-parameter") {
-    return {0, doGetParameter(begin, end)};
-  }
-  if (cmd == "quit") {
-    *command = &doExit;
-    return {0, "bye\n"};
-  }
-  if (cmd == "version") {
-    return {0, getPDNSVersion() + "\n"};
-  }
-  if (cmd == "quit-nicely") {
-    *command = &doExitNicely;
-    return {0, "bye nicely\n"};
-  }
-  if (cmd == "dump-cache") {
-    return doDumpCache(s);
-  }
-  if (cmd == "dump-ednsstatus" || cmd == "dump-edns") {
-    return doDumpToFile(s, pleaseDumpEDNSMap, cmd);
-  }
-  if (cmd == "dump-nsspeeds") {
-    return doDumpToFile(s, pleaseDumpNSSpeeds, cmd);
-  }
-  if (cmd == "dump-failedservers") {
-    return doDumpToFile(s, pleaseDumpFailedServers, cmd, false);
-  }
-  if (cmd == "dump-rpz") {
-    return doDumpRPZ(s, begin, end);
-  }
-  if (cmd == "dump-throttlemap") {
-    return doDumpToFile(s, pleaseDumpThrottleMap, cmd);
-  }
-  if (cmd == "dump-non-resolving") {
-    return doDumpToFile(s, pleaseDumpNonResolvingNS, cmd, false);
-  }
-  if (cmd == "wipe-cache" || cmd == "flushname") {
-    return {0, doWipeCache(begin, end, 0xffff)};
-  }
-  if (cmd == "wipe-cache-typed") {
-    if (begin == end) {
-      return {1, "Need a qtype\n"};
-    }
-    uint16_t qtype = QType::chartocode(begin->c_str());
-    if (qtype == 0) {
-      return {1, "Unknown qtype " + *begin + "\n"};
-    }
-    ++begin;
-    return {0, doWipeCache(begin, end, qtype)};
-  }
-  if (cmd == "reload-lua-script") {
-    return doQueueReloadLuaScript(begin, end);
-  }
-  if (cmd == "reload-lua-config") {
-    if (begin != end)
-      ::arg().set("lua-config-file") = *begin;
-
-    try {
-      luaConfigDelayedThreads delayedLuaThreads;
-      loadRecursorLuaConfig(::arg()["lua-config-file"], delayedLuaThreads);
-      startLuaConfigDelayedThreads(delayedLuaThreads, g_luaconfs.getCopy().generation);
-      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"};
-    }
-  }
-  if (cmd == "set-carbon-server") {
-    return {0, doSetCarbonServer(begin, end)};
-  }
-  if (cmd == "trace-regex") {
-    return {0, doTraceRegex(begin, end)};
-  }
-  if (cmd == "unload-lua-script") {
-    vector<string> empty;
-    empty.push_back(string());
-    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"};
-  }
-  if (cmd == "top-remotes") {
-    return {0, doGenericTopRemotes(pleaseGetRemotes)};
-  }
-  if (cmd == "top-queries") {
-    return {0, doGenericTopQueries(pleaseGetQueryRing)};
-  }
-  if (cmd == "top-pub-queries") {
-    return {0, doGenericTopQueries(pleaseGetQueryRing, getRegisteredName)};
-  }
-  if (cmd == "top-servfail-queries") {
-    return {0, doGenericTopQueries(pleaseGetServfailQueryRing)};
-  }
-  if (cmd == "top-pub-servfail-queries") {
-    return {0, doGenericTopQueries(pleaseGetServfailQueryRing, getRegisteredName)};
-  }
-  if (cmd == "top-bogus-queries") {
-    return {0, doGenericTopQueries(pleaseGetBogusQueryRing)};
-  }
-  if (cmd == "top-pub-bogus-queries") {
-    return {0, doGenericTopQueries(pleaseGetBogusQueryRing, getRegisteredName)};
-  }
-  if (cmd == "top-servfail-remotes") {
-    return {0, doGenericTopRemotes(pleaseGetServfailRemotes)};
-  }
-  if (cmd == "top-bogus-remotes") {
-    return {0, doGenericTopRemotes(pleaseGetBogusRemotes)};
-  }
-  if (cmd == "top-largeanswer-remotes") {
-    return {0, doGenericTopRemotes(pleaseGetLargeAnswerRemotes)};
-  }
-  if (cmd == "top-timeouts") {
-    return {0, doGenericTopRemotes(pleaseGetTimeouts)};
-  }
-  if (cmd == "current-queries") {
-    return {0, doCurrentQueries()};
-  }
-  if (cmd == "ping") {
-    return {0, broadcastAccFunction<string>(nopFunction)};
-  }
-  if (cmd == "reload-zones") {
-    if (!::arg()["chroot"].empty()) {
-      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()};
-  }
-  if (cmd == "set-ecs-minimum-ttl") {
-    return {0, setMinimumECSTTL(begin, end)};
-  }
-  if (cmd == "set-max-cache-entries") {
-    return {0, setMaxCacheEntries(begin, end)};
-  }
-  if (cmd == "set-max-packetcache-entries") {
-    return {0, setMaxPacketCacheEntries(begin, end)};
-  }
-  if (cmd == "set-minimum-ttl") {
-    return {0, setMinimumTTL(begin, end)};
-  }
-  if (cmd == "get-qtypelist") {
-    return {0, g_rs.getQTypeReport()};
-  }
-  if (cmd == "add-nta") {
-    return {0, doAddNTA(begin, end)};
-  }
-  if (cmd == "clear-nta") {
-    return {0, doClearNTA(begin, end)};
-  }
-  if (cmd == "get-ntas") {
-    return {0, getNTAs()};
-  }
-  if (cmd == "add-ta") {
-    return {0, doAddTA(begin, end)};
-  }
-  if (cmd == "clear-ta") {
-    return {0, doClearTA(begin, end)};
-  }
-  if (cmd == "get-tas") {
-    return {0, getTAs()};
-  }
-  if (cmd == "set-dnssec-log-bogus") {
-    return {0, doSetDnssecLogBogus(begin, end)};
-  }
-  if (cmd == "get-dont-throttle-names") {
-    return {0, getDontThrottleNames()};
-  }
-  if (cmd == "get-dont-throttle-netmasks") {
-    return {0, getDontThrottleNetmasks()};
-  }
-  if (cmd == "add-dont-throttle-names") {
-    return {0, addDontThrottleNames(begin, end)};
-  }
-  if (cmd == "add-dont-throttle-netmasks") {
-    return {0, addDontThrottleNetmasks(begin, end)};
-  }
-  if (cmd == "clear-dont-throttle-names") {
-    return {0, clearDontThrottleNames(begin, end)};
-  }
-  if (cmd == "clear-dont-throttle-netmasks") {
-    return {0, clearDontThrottleNetmasks(begin, end)};
-  }
-  if (cmd == "set-event-trace-enabled") {
-    return {0, setEventTracing(begin, end)};
-  }
-
-  return {1, "Unknown command '" + cmd + "', try 'help'\n"};
-}
diff --git a/pdns/rec_control.cc b/pdns/rec_control.cc
deleted file mode 100644 (file)
index fdb6817..0000000
+++ /dev/null
@@ -1,194 +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 <iostream>
-#include <fcntl.h>
-
-#include "pdnsexception.hh"
-#include "arguments.hh"
-#include "credentials.hh"
-#include "namespaces.hh"
-#include "rec_channel.hh"
-
-ArgvMap& arg()
-{
-  static ArgvMap arg;
-  return arg;
-}
-
-static void initArguments(int argc, char** argv)
-{
-  arg().set("config-dir", "Location of configuration directory (recursor.conf)") = SYSCONFDIR;
-
-  arg().set("socket-dir", string("Where the controlsocket will live, ") + LOCALSTATEDIR + "/pdns-recursor when unset and not chrooted") = "";
-  arg().set("chroot", "switch to chroot jail") = "";
-  arg().set("process", "When controlling multiple recursors, the target process number") = "";
-  arg().set("timeout", "Number of seconds to wait for the recursor to respond") = "5";
-  arg().set("config-name", "Name of this virtual configuration - will rename the binary image") = "";
-  arg().setCmd("help", "Provide this helpful message");
-  arg().setCmd("version", "Show the version of this program");
-
-  arg().laxParse(argc, argv);
-  if (arg().mustDo("help") || arg().getCommands().empty()) {
-    cout << "syntax: rec_control [options] command, options as below: " << endl
-         << endl;
-    cout << arg().helpstring(arg()["help"]) << endl;
-    cout << "In addition, 'rec_control help' can be used to retrieve a list\nof available commands from PowerDNS" << endl;
-    exit(arg().mustDo("help") ? 0 : 99);
-  }
-
-  if (arg().mustDo("version")) {
-    cout << "rec_control version " << VERSION << endl;
-    exit(0);
-  }
-
-  string configname = ::arg()["config-dir"] + "/recursor.conf";
-  if (::arg()["config-name"] != "")
-    configname = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"] + ".conf";
-
-  cleanSlashes(configname);
-
-  arg().laxFile(configname.c_str());
-
-  arg().laxParse(argc, argv); // make sure the commandline wins
-
-  if (::arg()["socket-dir"].empty()) {
-    if (::arg()["chroot"].empty())
-      ::arg().set("socket-dir") = std::string(LOCALSTATEDIR) + "/pdns-recursor";
-    else
-      ::arg().set("socket-dir") = ::arg()["chroot"] + "/";
-  }
-  else if (!::arg()["chroot"].empty()) {
-    ::arg().set("socket-dir") = ::arg()["chroot"] + "/" + ::arg()["socket-dir"];
-  }
-}
-
-int main(int argc, char** argv)
-{
-  const set<string> fileCommands = {
-    "dump-cache",
-    "dump-edns",
-    "dump-ednsstatus",
-    "dump-nsspeeds",
-    "dump-failedservers",
-    "dump-rpz",
-    "dump-throttlemap",
-    "dump-non-resolving"};
-  try {
-    initArguments(argc, argv);
-    string sockname = "pdns_recursor";
-
-    if (arg()["config-name"] != "")
-      sockname += "-" + arg()["config-name"];
-
-    if (!arg()["process"].empty())
-      sockname += "." + arg()["process"];
-
-    sockname.append(".controlsocket");
-
-    const vector<string>& commands = arg().getCommands();
-
-    if (commands.size() >= 1 && commands.at(0) == "hash-password") {
-      uint64_t workFactor = CredentialsHolder::s_defaultWorkFactor;
-      if (commands.size() > 1) {
-        try {
-          pdns::checked_stoi_into(workFactor, commands.at(1));
-        }
-        catch (const std::exception& e) {
-          cerr << "Unable to parse the supplied work factor: " << e.what() << endl;
-          return EXIT_FAILURE;
-        }
-      }
-
-      auto password = CredentialsHolder::readFromTerminal();
-
-      try {
-        cout << hashPassword(password.getString(), workFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize) << endl;
-        return EXIT_SUCCESS;
-      }
-      catch (const std::exception& e) {
-        cerr << "Error while hashing the supplied password: " << e.what() << endl;
-        return EXIT_FAILURE;
-      }
-    }
-
-    string command;
-    int fd = -1;
-    unsigned int i = 0;
-    while (i < commands.size()) {
-      if (i > 0) {
-        command += " ";
-      }
-      command += commands[i];
-      if (fileCommands.count(commands[i]) > 0) {
-        if (i + 1 < commands.size()) {
-          // dump-rpz is different, it also has a zonename as argument
-          if (commands[i] == "dump-rpz") {
-            if (i + 2 < commands.size()) {
-              ++i;
-              command += " " + commands[i]; // add rpzname and continue with filename
-            }
-            else {
-              throw PDNSException("Command needs a zone and file argument");
-            }
-          }
-          ++i;
-          if (commands[i] == "-") {
-            fd = STDOUT_FILENO;
-          }
-          else {
-            fd = open(commands[i].c_str(), O_CREAT | O_EXCL | O_WRONLY, 0660);
-          }
-          if (fd == -1) {
-            int err = errno;
-            throw PDNSException("Error opening dump file for writing: " + stringerror(err));
-          }
-        }
-        else {
-          throw PDNSException("Command needs a file argument");
-        }
-      }
-      ++i;
-    }
-
-    auto timeout = arg().asNum("timeout");
-    RecursorControlChannel rccS;
-    rccS.connect(arg()["socket-dir"], sockname);
-    rccS.send(rccS.d_fd, {0, command}, timeout, fd);
-
-    auto receive = rccS.recv(rccS.d_fd, timeout);
-    if (receive.d_ret != 0) {
-      cerr << receive.d_str;
-    }
-    else {
-      cout << receive.d_str;
-    }
-    return receive.d_ret;
-  }
-  catch (PDNSException& ae) {
-    cerr << "Fatal: " << ae.reason << "\n";
-    return 1;
-  }
-}
diff --git a/pdns/receiver.cc b/pdns/receiver.cc
deleted file mode 100644 (file)
index e5e6785..0000000
+++ /dev/null
@@ -1,697 +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 "packetcache.hh"
-
-#include <cstdio>
-#include <signal.h>
-#include <cstring>
-#include <cstdlib>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <iostream>
-#include <string>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <errno.h>
-#include <pthread.h>
-#include <unistd.h>
-#include <sys/mman.h>
-#include <fcntl.h>
-#include <fstream>
-#include <boost/algorithm/string.hpp>
-#ifdef HAVE_LIBSODIUM
-#include <sodium.h>
-#endif
-#include "opensslsigners.hh"
-
-#include "dns.hh"
-#include "dnsbackend.hh"
-#include "ueberbackend.hh"
-#include "dnspacket.hh"
-#include "nameserver.hh"
-#include "distributor.hh"
-#include "logger.hh"
-#include "arguments.hh"
-#include "packethandler.hh"
-#include "statbag.hh"
-#include "tcpreceiver.hh"
-#include "misc.hh"
-#include "dynlistener.hh"
-#include "dynhandler.hh"
-#include "communicator.hh"
-#include "dnsproxy.hh"
-#include "utility.hh"
-#include "common_startup.hh"
-#include "dnsrecords.hh"
-#include "version.hh"
-
-#ifdef HAVE_LUA_RECORDS
-#include "minicurl.hh"
-#endif /* HAVE_LUA_RECORDS */
-
-time_t s_starttime;
-
-string s_programname="pdns"; // used in packethandler.cc
-
-const char *funnytext=
-"*****************************************************************************\n"\
-"Ok, you just ran pdns_server through 'strings' hoping to find funny messages.\n"\
-"Well, you found one. \n"\
-"Two ions are flying through their particle accelerator, says the one to the\n"
-"other 'I think I've lost an electron!' \n"\
-"So the other one says, 'Are you sure?'. 'YEAH! I'M POSITIVE!'\n"\
-"                                            the pdns crew - pdns@powerdns.com\n"
-"*****************************************************************************\n";
-
-
-// start (sys)logging
-
-
-/**
-\file receiver.cc
-\brief The main loop of powerdns 
-
-This file is where it all happens - main is here, as are the two pivotal threads qthread() and athread()
-*/
-
-static void daemonize()
-{
-  if(fork())
-    exit(0); // bye bye
-  
-  setsid(); 
-
-  int i=open("/dev/null",O_RDWR); /* open stdin */
-  if(i < 0) 
-    g_log<<Logger::Critical<<"Unable to open /dev/null: "<<stringerror()<<endl;
-  else {
-    dup2(i,0); /* stdin */
-    dup2(i,1); /* stderr */
-    dup2(i,2); /* stderr */
-    close(i);
-  }
-}
-
-static int cpid;
-static void takedown(int i)
-{
-  if(cpid) {
-    g_log<<Logger::Error<<"Guardian is killed, taking down children with us"<<endl;
-    kill(cpid,SIGKILL);
-    exit(0);
-  }
-}
-
-static void writePid()
-{
-  if(!::arg().mustDo("write-pid"))
-    return;
-
-  string fname=::arg()["socket-dir"];
-  if (::arg()["socket-dir"].empty()) {
-    if (::arg()["chroot"].empty())
-      fname = std::string(LOCALSTATEDIR) + "/pdns";
-    else
-      fname = ::arg()["chroot"] + "/";
-  } else if (!::arg()["socket-dir"].empty() && !::arg()["chroot"].empty()) {
-    fname = ::arg()["chroot"] + ::arg()["socket-dir"];
-  }
-
-  fname += + "/" + s_programname + ".pid";
-  ofstream of(fname.c_str());
-  if(of)
-    of<<getpid()<<endl;
-  else
-    g_log<<Logger::Error<<"Writing pid for "<<getpid()<<" to "<<fname<<" failed: "<<stringerror()<<endl;
-}
-
-static int g_fd1[2], g_fd2[2];
-static FILE *g_fp;
-static std::mutex g_guardian_lock;
-
-// The next two methods are not in dynhandler.cc because they use a few items declared in this file.
-static string DLCycleHandler(const vector<string>&parts, pid_t ppid)
-{
-  kill(cpid, SIGKILL); // why?
-  kill(cpid, SIGKILL); // why?
-  sleep(1);
-  return "ok";
-}
-
-static string DLRestHandler(const vector<string>&parts, pid_t ppid)
-{
-  string line;
-  
-  for(vector<string>::const_iterator i=parts.begin();i!=parts.end();++i) {
-    if(i!=parts.begin())
-      line.append(1,' ');
-    line.append(*i);
-  }
-  line.append(1,'\n');
-  
-  std::lock_guard<std::mutex> l(g_guardian_lock);
-
-  try {
-    writen2(g_fd1[1],line.c_str(),line.size()+1);
-  }
-  catch(PDNSException &ae) {
-    return "Error communicating with instance: "+ae.reason;
-  }
-  char mesg[512];
-  string response;
-  while(fgets(mesg,sizeof(mesg),g_fp)) {
-    if(*mesg=='\0')
-      break;
-    response+=mesg;
-  }
-  boost::trim_right(response);
-  return response;
-}
-
-
-
-static int guardian(int argc, char **argv)
-{
-  if(isGuarded(argv))
-    return 0;
-
-  int infd=0, outfd=1;
-
-  DynListener dlg(s_programname);
-  dlg.registerFunc("QUIT",&DLQuitHandler, "quit daemon");
-  dlg.registerFunc("CYCLE",&DLCycleHandler, "restart instance");
-  dlg.registerFunc("PING",&DLPingHandler, "ping guardian");
-  dlg.registerFunc("STATUS",&DLStatusHandler, "get instance status from guardian");
-  dlg.registerRestFunc(&DLRestHandler);
-  dlg.go();
-  string progname=argv[0];
-
-  bool first=true;
-  cpid=0;
-
-  g_guardian_lock.lock();
-
-  for(;;) {
-    int pid;
-    setStatus("Launching child");
-    
-    if(pipe(g_fd1)<0 || pipe(g_fd2)<0) {
-      g_log<<Logger::Critical<<"Unable to open pipe for coprocess: "<<stringerror()<<endl;
-      exit(1);
-    }
-
-    if(!(g_fp=fdopen(g_fd2[0],"r"))) {
-      g_log<<Logger::Critical<<"Unable to associate a file pointer with pipe: "<<stringerror()<<endl;
-      exit(1);
-    }
-    setbuf(g_fp,nullptr); // no buffering please, confuses select
-
-    if(!(pid=fork())) { // child
-      signal(SIGTERM, SIG_DFL);
-
-      signal(SIGHUP, SIG_DFL);
-      signal(SIGUSR1, SIG_DFL);
-      signal(SIGUSR2, SIG_DFL);
-
-      char **const newargv=new char*[argc+2];
-      int n;
-
-      if(::arg()["config-name"]!="") {
-        progname+="-"+::arg()["config-name"];
-        g_log<<Logger::Error<<"Virtual configuration name: "<<::arg()["config-name"]<<endl;
-      }
-
-      newargv[0]=strdup(const_cast<char *>((progname+"-instance").c_str()));
-      for(n=1;n<argc;n++) {
-        newargv[n]=argv[n];
-      }
-      newargv[n]=nullptr;
-      
-      g_log<<Logger::Error<<"Guardian is launching an instance"<<endl;
-      close(g_fd1[1]);
-      fclose(g_fp); // this closes g_fd2[0] for us
-
-      if(g_fd1[0]!= infd) {
-        dup2(g_fd1[0], infd);
-        close(g_fd1[0]);
-      }
-
-      if(g_fd2[1]!= outfd) {
-        dup2(g_fd2[1], outfd);
-        close(g_fd2[1]);
-      }
-      if(execvp(argv[0], newargv)<0) {
-        g_log<<Logger::Error<<"Unable to execvp '"<<argv[0]<<"': "<<stringerror()<<endl;
-        char **p=newargv;
-        while(*p)
-          g_log<<Logger::Error<<*p++<<endl;
-
-        exit(1);
-      }
-      g_log<<Logger::Error<<"execvp returned!!"<<endl;
-      // never reached
-    }
-    else if(pid>0) { // parent
-      close(g_fd1[0]);
-      close(g_fd2[1]);
-
-      if(first) {
-        first=false;
-        signal(SIGTERM, takedown);
-
-        signal(SIGHUP, SIG_IGN);
-        signal(SIGUSR1, SIG_IGN);
-        signal(SIGUSR2, SIG_IGN);
-
-        writePid();
-      }
-      g_guardian_lock.unlock();
-      int status;
-      cpid=pid;
-      for(;;) {
-        int ret=waitpid(pid,&status,WNOHANG);
-
-        if(ret<0) {
-          g_log<<Logger::Error<<"In guardian loop, waitpid returned error: "<<stringerror()<<endl;
-          g_log<<Logger::Error<<"Dying"<<endl;
-          exit(1);
-        }
-        else if(ret) // something exited
-          break;
-        else { // child is alive
-          // execute some kind of ping here 
-          if(DLQuitPlease())
-            takedown(1); // needs a parameter..
-          setStatus("Child running on pid "+itoa(pid));
-          sleep(1);
-        }
-      }
-
-      g_guardian_lock.lock();
-      close(g_fd1[1]);
-      fclose(g_fp);
-      g_fp=nullptr;
-
-      if(WIFEXITED(status)) {
-        int ret=WEXITSTATUS(status);
-
-        if(ret==99) {
-          g_log<<Logger::Error<<"Child requested a stop, exiting"<<endl;
-          exit(1);
-        }
-        setStatus("Child died with code "+itoa(ret));
-        g_log<<Logger::Error<<"Our pdns instance exited with code "<<ret<<", respawning"<<endl;
-
-        sleep(1);
-        continue;
-      }
-      if(WIFSIGNALED(status)) {
-        int sig=WTERMSIG(status);
-        setStatus("Child died because of signal "+itoa(sig));
-        g_log<<Logger::Error<<"Our pdns instance ("<<pid<<") exited after signal "<<sig<<endl;
-#ifdef WCOREDUMP
-        if(WCOREDUMP(status)) 
-          g_log<<Logger::Error<<"Dumped core"<<endl;
-#endif
-
-        g_log<<Logger::Error<<"Respawning"<<endl;
-        sleep(1);
-        continue;
-      }
-      g_log<<Logger::Error<<"No clue what happened! Respawning"<<endl;
-    }
-    else {
-      g_log<<Logger::Error<<"Unable to fork: "<<stringerror()<<endl;
-      exit(1);
-    }
-  }
-}
-
-#if defined(__GLIBC__) && !defined(__UCLIBC__)
-#include <execinfo.h>
-static void tbhandler(int num)
-{
-  g_log<<Logger::Critical<<"Got a signal "<<num<<", attempting to print trace: "<<endl;
-  void *array[20]; //only care about last 17 functions (3 taken with tracing support)
-  size_t size;
-  char **strings;
-  size_t i;
-  
-  size = backtrace (array, 20);
-  strings = backtrace_symbols (array, size); //Need -rdynamic gcc (linker) flag for this to work
-  
-  for (i = 0; i < size; i++) //skip useless functions
-    g_log<<Logger::Error<<strings[i]<<endl;
-  
-  
-  signal(SIGABRT, SIG_DFL);
-  abort();//hopefully will give core
-
-}
-#endif
-
-//! The main function of pdns, the pdns process
-int main(int argc, char **argv)
-{
-  versionSetProduct(ProductAuthoritative);
-  reportAllTypes(); // init MOADNSParser
-
-  s_programname="pdns";
-  s_starttime=time(nullptr);
-
-#if defined(__GLIBC__) && !defined(__UCLIBC__)
-  signal(SIGSEGV,tbhandler);
-  signal(SIGFPE,tbhandler);
-  signal(SIGABRT,tbhandler);
-  signal(SIGILL,tbhandler);
-#endif
-
-  std::ios_base::sync_with_stdio(false);
-
-  g_log.toConsole(Logger::Warning);
-  try {
-    declareArguments();
-
-    ::arg().laxParse(argc,argv); // do a lax parse
-    
-    if(::arg().mustDo("version")) {
-      showProductVersion();
-      showBuildConfiguration();
-      exit(99);
-    }
-
-    if(::arg()["config-name"]!="") 
-      s_programname+="-"+::arg()["config-name"];
-    
-    g_log.setName(s_programname);
-    
-    string configname=::arg()["config-dir"]+"/"+s_programname+".conf";
-    cleanSlashes(configname);
-
-    if(::arg()["config"] != "default" && !::arg().mustDo("no-config")) // "config" == print a configuration file
-      ::arg().laxFile(configname.c_str());
-    
-    ::arg().laxParse(argc,argv); // reparse so the commandline still wins
-    if(!::arg()["logging-facility"].empty()) {
-      int val=logFacilityToLOG(::arg().asNum("logging-facility") );
-      if(val >= 0)
-        g_log.setFacility(val);
-      else
-        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"];
-
-    // 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"];
-
-    g_log.setLoglevel((Logger::Urgency)(::arg().asNum("loglevel")));
-    g_log.disableSyslog(::arg().mustDo("disable-syslog"));
-    g_log.setTimestamps(::arg().mustDo("log-timestamp"));
-    g_log.toConsole((Logger::Urgency)(::arg().asNum("loglevel")));  
-
-    if(::arg().mustDo("help") || ::arg().mustDo("config")) {
-      ::arg().set("daemon")="no";
-      ::arg().set("guardian")="no";
-    }
-
-    if(::arg().mustDo("guardian") && !isGuarded(argv)) {
-      if(::arg().mustDo("daemon")) {
-        g_log.toConsole(Logger::Critical);
-        daemonize();
-      }
-      guardian(argc, argv);  
-      // never get here, guardian will reinvoke process
-      cerr<<"Um, we did get here!"<<endl;
-    }
-
-    
-    // we really need to do work - either standalone or as an instance
-
-#if defined(__GLIBC__) && !defined(__UCLIBC__)
-    if(!::arg().mustDo("traceback-handler")) {
-      g_log<<Logger::Warning<<"Disabling traceback handler"<<endl;
-      signal(SIGSEGV,SIG_DFL);
-      signal(SIGFPE,SIG_DFL);
-      signal(SIGABRT,SIG_DFL);
-      signal(SIGILL,SIG_DFL);
-    }
-#endif
-
-#ifdef HAVE_LIBSODIUM
-      if (sodium_init() == -1) {
-        cerr<<"Unable to initialize sodium crypto library"<<endl;
-        exit(99);
-      }
-#endif
-
-    openssl_thread_setup();
-    openssl_seed();
-    /* setup rng */
-    dns_random_init();
-
-#ifdef HAVE_LUA_RECORDS
-    MiniCurl::init();
-#endif /* HAVE_LUA_RECORDS */
-
-    if(!::arg()["load-modules"].empty()) {
-      vector<string> modules;
-
-      stringtok(modules,::arg()["load-modules"], ", ");
-      if (!UeberBackend::loadModules(modules, ::arg()["module-dir"])) {
-        exit(1);
-      }
-    }
-
-    BackendMakers().launch(::arg()["launch"]); // vrooooom!
-
-    if(!::arg().getCommands().empty()) {
-      cerr<<"Fatal: non-option";
-      if (::arg().getCommands().size() > 1) {
-        cerr<<"s";
-      }
-      cerr<<" (";
-      bool first = true;
-      for (const auto& c : ::arg().getCommands()) {
-        if (!first) {
-          cerr<<", ";
-        }
-        first = false;
-        cerr<<c;
-      }
-      cerr<<") on the command line, perhaps a '--setting=123' statement missed the '='?"<<endl;
-      exit(99);
-    }
-    
-    if(::arg().mustDo("help")) {
-      cout<<"syntax:"<<endl<<endl;
-      cout<<::arg().helpstring(::arg()["help"])<<endl;
-      exit(0);
-    }
-    
-    if(::arg().mustDo("config")) {
-      string config = ::arg()["config"];
-      if (config == "default") {
-        cout<<::arg().configstring(false, true);
-      } else if (config == "diff") {
-          cout<<::arg().configstring(true, false);
-      } else if (config == "check") {
-        try {
-          if(!::arg().mustDo("no-config"))
-            ::arg().file(configname.c_str());
-          ::arg().parse(argc,argv);
-          exit(0);
-        }
-        catch(const ArgException &A) {
-          cerr<<"Fatal error: "<<A.reason<<endl;
-          exit(1);
-        }
-      } else {
-        cout<<::arg().configstring(true, true);
-      }
-      exit(0);
-    }
-
-    if(::arg().mustDo("list-modules")) {
-      auto modules = BackendMakers().getModules();
-      cout<<"Modules available:"<<endl;
-      for(const auto& m : modules)
-        cout<< m <<endl;
-
-      _exit(99);
-    }
-
-    if(!::arg().asNum("local-port")) {
-      g_log<<Logger::Error<<"Unable to launch, binding to no port or port 0 makes no sense"<<endl;
-      exit(99); // this isn't going to fix itself either
-    }
-    if(!BackendMakers().numLauncheable()) {
-      g_log<<Logger::Error<<"Unable to launch, no backends configured for querying"<<endl;
-      exit(99); // this isn't going to fix itself either
-    }    
-    if(::arg().mustDo("daemon")) {
-      g_log.toConsole(Logger::None);
-      if(!isGuarded(argv))
-        daemonize();
-    }
-
-    if(isGuarded(argv)) {
-      g_log<<Logger::Warning<<"This is a guarded instance of pdns"<<endl;
-      dl=make_unique<DynListener>(); // listens on stdin 
-    }
-    else {
-      g_log<<Logger::Warning<<"This is a standalone pdns"<<endl; 
-      
-      if(::arg().mustDo("control-console"))
-        dl=make_unique<DynListener>();
-      else
-        dl = std::make_unique<DynListener>(s_programname);
-
-      writePid();
-    }
-    DynListener::registerFunc("SHOW",&DLShowHandler, "show a specific statistic or * to get a list", "<statistic>");
-    DynListener::registerFunc("RPING",&DLPingHandler, "ping instance");
-    DynListener::registerFunc("QUIT",&DLRQuitHandler, "quit daemon");
-    DynListener::registerFunc("UPTIME",&DLUptimeHandler, "get instance uptime");
-    DynListener::registerFunc("NOTIFY-HOST", &DLNotifyHostHandler, "notify host for specific zone", "<zone> <host>");
-    DynListener::registerFunc("NOTIFY", &DLNotifyHandler, "queue a notification", "<zone>");
-    DynListener::registerFunc("RELOAD",&DLReloadHandler, "reload all zones");
-    DynListener::registerFunc("REDISCOVER",&DLRediscoverHandler, "discover any new zones");
-    DynListener::registerFunc("VERSION",&DLVersionHandler, "get instance version");
-    DynListener::registerFunc("PURGE",&DLPurgeHandler, "purge entries from packet cache", "[<record>]");
-    DynListener::registerFunc("CCOUNTS",&DLCCHandler, "get cache statistics");
-    DynListener::registerFunc("QTYPES", &DLQTypesHandler, "get QType statistics");
-    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("CURRENT-CONFIG",&DLCurrentConfigHandler, "retrieve the current configuration", "[diff]");
-    DynListener::registerFunc("LIST-ZONES", &DLListZones, "show list of zones", "[primary|secondary|native]");
-    DynListener::registerFunc("TOKEN-LOGIN", &DLTokenLogin, "Login to a PKCS#11 token", "<module> <slot> <pin>");
-    DynListener::registerFunc("XFR-QUEUE", &DLSuckRequests, "Get all requests for XFR in queue");
-
-    if(!::arg()["tcp-control-address"].empty()) {
-      DynListener* dlTCP=new DynListener(ComboAddress(::arg()["tcp-control-address"], ::arg().asNum("tcp-control-port")));
-      dlTCP->go();
-    }
-
-    // reparse, with error checking
-    if(!::arg().mustDo("no-config"))
-      ::arg().file(configname.c_str());
-    ::arg().parse(argc,argv);
-
-    if(::arg()["server-id"].empty()) {
-      char tmp[128];
-      if(gethostname(tmp, sizeof(tmp)-1) == 0) {
-        ::arg().set("server-id")=tmp;
-      } else {
-        g_log<<Logger::Warning<<"Unable to get the hostname, NSID and id.server values will be empty: "<<stringerror()<<endl;
-      }
-    }
-
-    UeberBackend::go();
-
-    g_zoneCache.setRefreshInterval(::arg().asNum("zone-cache-refresh-interval"));
-    {
-      UeberBackend B;
-      B.updateZoneCache();
-    }
-
-    N=std::make_shared<UDPNameserver>(); // this fails when we are not root, throws exception
-    g_udpReceivers.push_back(N);
-
-    size_t rthreads = ::arg().asNum("receiver-threads", 1);
-    if (rthreads > 1 && N->canReusePort()) {
-      g_udpReceivers.resize(rthreads);
-
-      for (size_t idx = 1; idx < rthreads; idx++) {
-        try {
-          g_udpReceivers[idx] = std::make_shared<UDPNameserver>(true);
-        }
-        catch(const PDNSException& e) {
-          g_log<<Logger::Error<<"Unable to reuse port, falling back to original bind"<<endl;
-          break;
-        }
-      }
-    }
-
-    TN = make_unique<TCPNameserver>();
-  }
-  catch(const ArgException &A) {
-    g_log<<Logger::Error<<"Fatal error: "<<A.reason<<endl;
-    exit(1);
-  }
-  
-  try {
-    declareStats();
-  }
-  catch(PDNSException &PE) {
-    g_log<<Logger::Error<<"Exiting because: "<<PE.reason<<endl;
-    exit(1);
-  }
-  S.blacklist("special-memory-usage");
-
-  DLOG(g_log<<Logger::Warning<<"Verbose logging in effect"<<endl);
-
-  showProductVersion();
-
-  try {
-    mainthread();
-  }
-  catch(PDNSException &AE) {
-    if(!::arg().mustDo("daemon"))
-      cerr<<"Exiting because: "<<AE.reason<<endl;
-    g_log<<Logger::Error<<"Exiting because: "<<AE.reason<<endl;
-  }      
-  catch(std::exception &e) {
-    if(!::arg().mustDo("daemon"))
-      cerr<<"Exiting because of STL error: "<<e.what()<<endl;
-    g_log<<Logger::Error<<"Exiting because of STL error: "<<e.what()<<endl;
-  }
-  catch(...) {
-    cerr<<"Uncaught exception of unknown type - sorry"<<endl;
-  }
-
-  exit(1);
-  
-}
-
-
diff --git a/pdns/recpacketcache.cc b/pdns/recpacketcache.cc
deleted file mode 100644 (file)
index 926bf80..0000000
+++ /dev/null
@@ -1,243 +0,0 @@
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-#include <iostream>
-#include <cinttypes>
-
-#include "recpacketcache.hh"
-#include "cachecleaner.hh"
-#include "dns.hh"
-#include "namespaces.hh"
-#include "rec-taskqueue.hh"
-
-RecursorPacketCache::RecursorPacketCache()
-{
-  d_hits = d_misses = 0;
-}
-
-unsigned int RecursorPacketCache::s_refresh_ttlperc{0};
-
-int RecursorPacketCache::doWipePacketCache(const DNSName& name, uint16_t qtype, bool subtree)
-{
-  int count = 0;
-  auto& idx = d_packetCache.get<NameTag>();
-  for (auto iter = idx.lower_bound(name); iter != idx.end();) {
-    if (subtree) {
-      if (!iter->d_name.isPartOf(name)) { // this is case insensitive
-        break;
-      }
-    }
-    else {
-      if (iter->d_name != name)
-        break;
-    }
-
-    if (qtype == 0xffff || iter->d_type == qtype) {
-      iter = idx.erase(iter);
-      count++;
-    }
-    else
-      ++iter;
-  }
-  return count;
-}
-
-bool RecursorPacketCache::qrMatch(const packetCache_t::index<HashTag>::type::iterator& iter, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass)
-{
-  // this ignores checking on the EDNS subnet flags!
-  if (qname != iter->d_name || iter->d_type != qtype || iter->d_class != qclass) {
-    return false;
-  }
-
-  static const std::unordered_set<uint16_t> optionsToSkip{EDNSOptionCode::COOKIE, EDNSOptionCode::ECS};
-  return queryMatches(iter->d_query, queryPacket, qname, optionsToSkip);
-}
-
-bool RecursorPacketCache::checkResponseMatches(std::pair<packetCache_t::index<HashTag>::type::iterator, packetCache_t::index<HashTag>::type::iterator> range, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now, std::string* responsePacket, uint32_t* age, vState* valState, OptPBData* pbdata)
-{
-  for (auto iter = range.first; iter != range.second; ++iter) {
-    // the possibility is VERY real that we get hits that are not right - birthday paradox
-    if (!qrMatch(iter, queryPacket, qname, qtype, qclass)) {
-      continue;
-    }
-
-    if (now < iter->d_ttd) { // it is right, it is fresh!
-      *age = static_cast<uint32_t>(now - iter->d_creation);
-      // we know ttl is > 0
-      uint32_t 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);
-        }
-      }
-      *responsePacket = iter->d_packet;
-      responsePacket->replace(0, 2, queryPacket.c_str(), 2);
-      *valState = iter->d_vstate;
-
-      const size_t wirelength = qname.wirelength();
-      if (responsePacket->size() > (sizeof(dnsheader) + wirelength)) {
-        responsePacket->replace(sizeof(dnsheader), wirelength, queryPacket, sizeof(dnsheader), wirelength);
-      }
-
-      d_hits++;
-      moveCacheItemToBack<SequencedTag>(d_packetCache, iter);
-
-      if (pbdata != nullptr) {
-        if (iter->d_pbdata) {
-          *pbdata = iter->d_pbdata;
-        }
-        else {
-          *pbdata = boost::none;
-        }
-      }
-
-      return true;
-    }
-    else {
-      moveCacheItemToFront<SequencedTag>(d_packetCache, iter);
-      d_misses++;
-      break;
-    }
-  }
-
-  return false;
-}
-
-bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string& queryPacket, time_t now,
-                                            std::string* responsePacket, uint32_t* age, uint32_t* qhash)
-{
-  DNSName qname;
-  uint16_t qtype, qclass;
-  vState valState;
-  return getResponsePacket(tag, queryPacket, qname, &qtype, &qclass, now, responsePacket, age, &valState, qhash, nullptr, false);
-}
-
-bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now,
-                                            std::string* responsePacket, uint32_t* age, uint32_t* qhash)
-{
-  vState valState;
-  return getResponsePacket(tag, queryPacket, qname, qtype, qclass, now, responsePacket, age, &valState, qhash, nullptr, false);
-}
-
-static const std::unordered_set<uint16_t> s_skipOptions = {EDNSOptionCode::ECS, EDNSOptionCode::COOKIE};
-
-bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now,
-                                            std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, OptPBData* pbdata, bool tcp)
-{
-  *qhash = canHashPacket(queryPacket, s_skipOptions);
-  const auto& idx = d_packetCache.get<HashTag>();
-  auto range = idx.equal_range(std::tie(tag, *qhash, tcp));
-
-  if (range.first == range.second) {
-    d_misses++;
-    return false;
-  }
-
-  return checkResponseMatches(range, queryPacket, qname, qtype, qclass, now, responsePacket, age, valState, pbdata);
-}
-
-bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string& queryPacket, DNSName& qname, uint16_t* qtype, uint16_t* qclass, time_t now,
-                                            std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, OptPBData* pbdata, bool tcp)
-{
-  *qhash = canHashPacket(queryPacket, s_skipOptions);
-  const auto& idx = d_packetCache.get<HashTag>();
-  auto range = idx.equal_range(std::tie(tag, *qhash, tcp));
-
-  if (range.first == range.second) {
-    d_misses++;
-    return false;
-  }
-
-  qname = DNSName(queryPacket.c_str(), queryPacket.length(), sizeof(dnsheader), false, qtype, qclass, 0);
-
-  return checkResponseMatches(range, queryPacket, qname, *qtype, *qclass, now, responsePacket, age, valState, pbdata);
-}
-
-void RecursorPacketCache::insertResponsePacket(unsigned int tag, uint32_t qhash, std::string&& query, const DNSName& qname, uint16_t qtype, uint16_t qclass, std::string&& responsePacket, time_t now, uint32_t ttl, const vState& valState, OptPBData&& pbdata, bool tcp)
-{
-  auto& idx = d_packetCache.get<HashTag>();
-  auto range = idx.equal_range(std::tie(tag, qhash, tcp));
-  auto iter = range.first;
-
-  for (; iter != range.second; ++iter) {
-    if (iter->d_type != qtype || iter->d_class != qclass || iter->d_name != qname) {
-      continue;
-    }
-
-    moveCacheItemToBack<SequencedTag>(d_packetCache, iter);
-    iter->d_packet = std::move(responsePacket);
-    iter->d_query = std::move(query);
-    iter->d_ttd = now + ttl;
-    iter->d_creation = now;
-    iter->d_vstate = valState;
-    iter->d_submitted = false;
-    if (pbdata) {
-      iter->d_pbdata = std::move(*pbdata);
-    }
-
-    break;
-  }
-
-  if (iter == range.second) { // nothing to refresh
-    struct Entry e(qname, std::move(responsePacket), std::move(query), tcp);
-    e.d_qhash = qhash;
-    e.d_type = qtype;
-    e.d_class = qclass;
-    e.d_ttd = now + ttl;
-    e.d_creation = now;
-    e.d_tag = tag;
-    e.d_vstate = valState;
-    if (pbdata) {
-      e.d_pbdata = std::move(*pbdata);
-    }
-
-    d_packetCache.insert(e);
-  }
-}
-
-uint64_t RecursorPacketCache::size()
-{
-  return d_packetCache.size();
-}
-
-uint64_t RecursorPacketCache::bytes()
-{
-  uint64_t sum = 0;
-  for (const auto& e : d_packetCache) {
-    sum += sizeof(e) + e.d_packet.length() + 4;
-  }
-  return sum;
-}
-
-void RecursorPacketCache::doPruneTo(size_t maxCached)
-{
-  pruneCollection<SequencedTag>(*this, d_packetCache, maxCached);
-}
-
-uint64_t RecursorPacketCache::doDump(int fd)
-{
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(dup(fd), "w"), fclose);
-  if (!fp) { // dup probably failed
-    return 0;
-  }
-
-  fprintf(fp.get(), "; main packet cache dump from thread follows\n;\n");
-
-  const auto& sidx = d_packetCache.get<SequencedTag>();
-  uint64_t count = 0;
-  time_t now = time(nullptr);
-
-  for (const auto& i : sidx) {
-    count++;
-    try {
-      fprintf(fp.get(), "%s %" PRId64 " %s  ; tag %d %s\n", i.d_name.toString().c_str(), static_cast<int64_t>(i.d_ttd - now), DNSRecordContent::NumberToType(i.d_type).c_str(), i.d_tag, i.d_tcp ? "tcp" : "udp");
-    }
-    catch (...) {
-      fprintf(fp.get(), "; error printing '%s'\n", i.d_name.empty() ? "EMPTY" : i.d_name.toString().c_str());
-    }
-  }
-  return count;
-}
diff --git a/pdns/recpacketcache.hh b/pdns/recpacketcache.hh
deleted file mode 100644 (file)
index d1084bb..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 <string>
-#include <inttypes.h>
-#include "dns.hh"
-#include "namespaces.hh"
-#include <iostream>
-#include <boost/multi_index_container.hpp>
-#include <boost/multi_index/ordered_index.hpp>
-#include <boost/multi_index/hashed_index.hpp>
-#include <boost/multi_index/sequenced_index.hpp>
-#include <boost/multi_index/key_extractors.hpp>
-#include <boost/optional.hpp>
-
-#include "packetcache.hh"
-#include "validate.hh"
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-using namespace ::boost::multi_index;
-
-//! Stores whole packets, ready for lobbing back at the client. Not threadsafe.
-/* Note: we store answers as value AND KEY, and with careful work, we make sure that
-   you can use a query as a key too. But query and answer must compare as identical! 
-   
-   This precludes doing anything smart with EDNS directly from the packet */
-class RecursorPacketCache : public PacketCache
-{
-public:
-  static unsigned int s_refresh_ttlperc;
-
-  struct PBData
-  {
-    std::string d_message;
-    std::string d_response;
-    bool d_tagged;
-  };
-  typedef boost::optional<PBData> OptPBData;
-
-  RecursorPacketCache();
-  bool getResponsePacket(unsigned int tag, const std::string& queryPacket, time_t now, std::string* responsePacket, uint32_t* age, uint32_t* qhash);
-  bool getResponsePacket(unsigned int tag, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now, std::string* responsePacket, uint32_t* age, uint32_t* qhash);
-  bool getResponsePacket(unsigned int tag, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now, std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, OptPBData* pbdata, bool tcp);
-  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 = 250000);
-  uint64_t doDump(int fd);
-  int doWipePacketCache(const DNSName& name, uint16_t qtype = 0xffff, bool subtree = false);
-
-  void prune();
-  uint64_t d_hits, d_misses;
-  uint64_t size();
-  uint64_t bytes();
-
-private:
-  struct HashTag
-  {
-  };
-  struct NameTag
-  {
-  };
-  struct Entry
-  {
-    Entry(const DNSName& qname, std::string&& packet, std::string&& query, bool tcp) :
-      d_name(qname), d_packet(std::move(packet)), d_query(std::move(query)), d_tcp(tcp)
-    {
-    }
-
-    DNSName d_name;
-    mutable std::string d_packet;
-    mutable std::string d_query;
-    mutable OptPBData d_pbdata;
-    mutable time_t d_ttd;
-    mutable time_t d_creation; // so we can 'age' our packets
-    uint32_t d_qhash;
-    uint32_t d_tag;
-    uint16_t d_type;
-    uint16_t d_class;
-    mutable vState d_vstate;
-    mutable bool d_submitted{false}; // whether this entry has been queued for refetch
-    bool d_tcp; // whether this entry was created from a TCP query
-    inline bool operator<(const struct Entry& rhs) const;
-
-    time_t getTTD() const
-    {
-      return d_ttd;
-    }
-
-    uint32_t getOrigTTL() const
-    {
-      return d_ttd - d_creation;
-    }
-  };
-
-  struct SequencedTag
-  {
-  };
-  typedef multi_index_container<
-    Entry,
-    indexed_by<
-      hashed_non_unique<tag<HashTag>,
-                        composite_key<Entry,
-                                      member<Entry, uint32_t, &Entry::d_tag>,
-                                      member<Entry, uint32_t, &Entry::d_qhash>,
-                                      member<Entry, bool, &Entry::d_tcp>>>,
-      sequenced<tag<SequencedTag>>,
-      ordered_non_unique<tag<NameTag>, member<Entry, DNSName, &Entry::d_name>, CanonDNSNameCompare>>>
-    packetCache_t;
-
-  packetCache_t d_packetCache;
-
-  static bool qrMatch(const packetCache_t::index<HashTag>::type::iterator& iter, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass);
-  bool checkResponseMatches(std::pair<packetCache_t::index<HashTag>::type::iterator, packetCache_t::index<HashTag>::type::iterator> range, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now, std::string* responsePacket, uint32_t* age, vState* valState, OptPBData* pbdata);
-};
diff --git a/pdns/recursor_cache.cc b/pdns/recursor_cache.cc
deleted file mode 100644 (file)
index 73c2d87..0000000
+++ /dev/null
@@ -1,702 +0,0 @@
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include <cinttypes>
-
-#include "recursor_cache.hh"
-#include "misc.hh"
-#include <iostream>
-#include "dnsrecords.hh"
-#include "arguments.hh"
-#include "syncres.hh"
-#include "recursor_cache.hh"
-#include "namespaces.hh"
-#include "cachecleaner.hh"
-#include "rec-taskqueue.hh"
-
-MemRecursorCache::MemRecursorCache(size_t mapsCount) :
-  d_maps(mapsCount)
-{
-}
-
-size_t MemRecursorCache::size() const
-{
-  size_t count = 0;
-  for (const auto& map : d_maps) {
-    count += map.d_entriesCount;
-  }
-  return count;
-}
-
-pair<uint64_t, uint64_t> MemRecursorCache::stats()
-{
-  uint64_t c = 0, a = 0;
-  for (auto& mc : d_maps) {
-    auto content = mc.lock();
-    c += content->d_contended_count;
-    a += content->d_acquired_count;
-  }
-  return pair<uint64_t, uint64_t>(c, a);
-}
-
-size_t MemRecursorCache::ecsIndexSize()
-{
-  // XXX!
-  size_t count = 0;
-  for (auto& mc : d_maps) {
-    auto content = mc.lock();
-    count += content->d_ecsIndex.size();
-  }
-  return count;
-}
-
-// this function is too slow to poll!
-size_t MemRecursorCache::bytes()
-{
-  size_t ret = 0;
-  for (auto& mc : d_maps) {
-    auto m = mc.lock();
-    for (const auto& i : m->d_map) {
-      ret += sizeof(struct CacheEntry);
-      ret += i.d_qname.toString().length();
-      for (const auto& record : i.d_records) {
-        ret += sizeof(record); // XXX WRONG we don't know the stored size!
-      }
-    }
-  }
-  return ret;
-}
-
-static void updateDNSSECValidationStateFromCache(boost::optional<vState>& state, const vState stateUpdate)
-{
-  // if there was no state it's easy */
-  if (state == boost::none) {
-    state = stateUpdate;
-    return;
-  }
-
-  if (stateUpdate == vState::TA) {
-    state = vState::Secure;
-  }
-  else if (stateUpdate == vState::NTA) {
-    state = vState::Insecure;
-  }
-  else if (vStateIsBogus(stateUpdate)) {
-    state = stateUpdate;
-  }
-  else if (stateUpdate == vState::Indeterminate) {
-    state = stateUpdate;
-  }
-  else if (stateUpdate == vState::Insecure) {
-    if (!vStateIsBogus(*state) && *state != vState::Indeterminate) {
-      state = stateUpdate;
-    }
-  }
-  else if (stateUpdate == vState::Secure) {
-    if (!vStateIsBogus(*state) && *state != vState::Indeterminate) {
-      state = stateUpdate;
-    }
-  }
-}
-
-time_t MemRecursorCache::handleHit(MapCombo::LockedContent& content, MemRecursorCache::OrderedTagIterator_t& entry, const DNSName& qname, uint32_t& origTTL, vector<DNSRecord>* res, vector<std::shared_ptr<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;
-  origTTL = entry->d_orig_ttl;
-
-  if (variable && (!entry->d_netmask.empty() || entry->d_rtag)) {
-    *variable = true;
-  }
-
-  if (res) {
-    res->reserve(res->size() + entry->d_records.size());
-
-    for (const auto& k : entry->d_records) {
-      DNSRecord dr;
-      dr.d_name = qname;
-      dr.d_type = entry->d_qtype;
-      dr.d_class = QClass::IN;
-      dr.d_content = k;
-      dr.d_ttl = static_cast<uint32_t>(entry->d_ttd); // XXX truncation
-      dr.d_place = DNSResourceRecord::ANSWER;
-      res->push_back(std::move(dr));
-    }
-  }
-
-  if (signatures) {
-    signatures->insert(signatures->end(), entry->d_signatures.begin(), entry->d_signatures.end());
-  }
-
-  if (authorityRecs) {
-    authorityRecs->insert(authorityRecs->end(), entry->d_authorityRecs.begin(), entry->d_authorityRecs.end());
-  }
-
-  updateDNSSECValidationStateFromCache(state, entry->d_state);
-
-  if (wasAuth) {
-    *wasAuth = *wasAuth && entry->d_auth;
-  }
-
-  if (fromAuthZone) {
-    *fromAuthZone = entry->d_authZone;
-  }
-
-  if (fromAuthIP) {
-    *fromAuthIP = entry->d_from;
-  }
-
-  moveCacheItemToBack<SequencedTag>(content.d_map, entry);
-
-  return ttd;
-}
-
-MemRecursorCache::cache_t::const_iterator MemRecursorCache::getEntryUsingECSIndex(MapCombo::LockedContent& map, time_t now, const DNSName& qname, const QType qtype, bool requireAuth, const ComboAddress& who)
-{
-  // MUTEX SHOULD BE ACQUIRED (as indicated by the reference to the content which is protected by a lock)
-  auto ecsIndexKey = std::tie(qname, qtype);
-  auto ecsIndex = map.d_ecsIndex.find(ecsIndexKey);
-  if (ecsIndex != map.d_ecsIndex.end() && !ecsIndex->isEmpty()) {
-    /* we have netmask-specific entries, let's see if we match one */
-    while (true) {
-      const Netmask best = ecsIndex->lookupBestMatch(who);
-      if (best.empty()) {
-        /* we have nothing more specific for you */
-        break;
-      }
-      auto key = std::make_tuple(qname, qtype, boost::none, best);
-      auto entry = map.d_map.find(key);
-      if (entry == map.d_map.end()) {
-        /* ecsIndex is not up-to-date */
-        ecsIndex->removeNetmask(best);
-        if (ecsIndex->isEmpty()) {
-          map.d_ecsIndex.erase(ecsIndex);
-          break;
-        }
-        continue;
-      }
-
-      if (entry->d_ttd > now) {
-        if (!requireAuth || entry->d_auth) {
-          return entry;
-        }
-        /* 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);
-        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 entry = map.d_map.find(key);
-  if (entry != map.d_map.end()) {
-    if (entry->d_ttd > now) {
-      if (!requireAuth || entry->d_auth) {
-        return entry;
-      }
-    }
-    else {
-      moveCacheItemToFront<SequencedTag>(map.d_map, entry);
-    }
-  }
-
-  /* nothing for you, sorry */
-  return map.d_map.end();
-}
-
-MemRecursorCache::Entries MemRecursorCache::getEntries(MapCombo::LockedContent& map, const DNSName& qname, const QType qt, const OptTag& rtag)
-{
-  // MUTEX SHOULD BE ACQUIRED
-  if (!map.d_cachecachevalid || map.d_cachedqname != qname || map.d_cachedrtag != rtag) {
-    map.d_cachedqname = qname;
-    map.d_cachedrtag = rtag;
-    const auto& idx = map.d_map.get<NameAndRTagOnlyHashedTag>();
-    map.d_cachecache = idx.equal_range(std::tie(qname, rtag));
-    map.d_cachecachevalid = true;
-  }
-  return map.d_cachecache;
-}
-
-bool MemRecursorCache::entryMatches(MemRecursorCache::OrderedTagIterator_t& entry, const QType qt, bool requireAuth, const ComboAddress& who)
-{
-  // This code assumes that if a routing tag is present, it matches
-  // MUTEX SHOULD BE ACQUIRED
-  if (requireAuth && !entry->d_auth)
-    return false;
-
-  bool match = (entry->d_qtype == qt || qt == QType::ANY || (qt == QType::ADDR && (entry->d_qtype == QType::A || entry->d_qtype == QType::AAAA)))
-    && (entry->d_netmask.empty() || entry->d_netmask.match(who));
-  return match;
-}
-
-// Fake a cache miss if more than refreshTTLPerc of the original TTL has passed
-time_t MemRecursorCache::fakeTTD(MemRecursorCache::OrderedTagIterator_t& entry, const DNSName& qname, QType qtype, time_t ret, time_t now, uint32_t origTTL, bool refresh)
-{
-  time_t ttl = ret - now;
-  if (ttl > 0 && SyncRes::s_refresh_ttlperc > 0) {
-    const uint32_t deadline = origTTL * SyncRes::s_refresh_ttlperc / 100;
-    const bool almostExpired = static_cast<uint32_t>(ttl) <= deadline;
-    if (almostExpired && qname != g_rootdnsname) {
-      if (refresh) {
-        return -1;
-      }
-      else {
-        if (!entry->d_submitted) {
-          if (qtype == QType::ADDR) {
-            pushAlmostExpiredTask(qname, QType::A, entry->d_ttd);
-            pushAlmostExpiredTask(qname, QType::AAAA, entry->d_ttd);
-          }
-          else {
-            pushAlmostExpiredTask(qname, qtype, entry->d_ttd);
-          }
-          entry->d_submitted = true;
-        }
-      }
-    }
-  }
-  return ttl;
-}
-// returns -1 for no hits
-time_t MemRecursorCache::get(time_t now, const DNSName& qname, const QType qt, bool requireAuth, vector<DNSRecord>* res, const ComboAddress& who, bool refresh, const OptTag& routingTag, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, vState* state, bool* wasAuth, DNSName* fromAuthZone, ComboAddress* fromAuthIP)
-{
-  boost::optional<vState> cachedState{boost::none};
-  uint32_t origTTL;
-
-  if (res) {
-    res->clear();
-  }
-  const uint16_t qtype = qt.getCode();
-  if (wasAuth) {
-    // 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;
-  }
-
-  auto& mc = getMap(qname);
-  auto map = mc.lock();
-
-  /* If we don't have any netmask-specific entries at all, let's just skip this
-     to be able to use the nice d_cachecache hack. */
-  if (qtype != QType::ANY && !map->d_ecsIndex.empty() && !routingTag) {
-    if (qtype == QType::ADDR) {
-      time_t ret = -1;
-
-      auto entryA = getEntryUsingECSIndex(*map, now, qname, QType::A, requireAuth, who);
-      if (entryA != map->d_map.end()) {
-        ret = handleHit(*map, entryA, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
-      }
-      auto entryAAAA = getEntryUsingECSIndex(*map, now, qname, QType::AAAA, requireAuth, who);
-      if (entryAAAA != map->d_map.end()) {
-        time_t ttdAAAA = handleHit(*map, entryAAAA, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
-        if (ret > 0) {
-          ret = std::min(ret, ttdAAAA);
-        }
-        else {
-          ret = ttdAAAA;
-        }
-      }
-
-      if (state && cachedState) {
-        *state = *cachedState;
-      }
-
-      return ret > 0 ? (ret - now) : ret;
-    }
-    else {
-      auto entry = getEntryUsingECSIndex(*map, now, qname, qtype, requireAuth, who);
-      if (entry != map->d_map.end()) {
-        time_t ret = handleHit(*map, 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);
-      }
-      return -1;
-    }
-  }
-
-  if (routingTag) {
-    auto entries = getEntries(*map, qname, qt, routingTag);
-    bool found = false;
-    time_t ttd;
-
-    if (entries.first != entries.second) {
-      OrderedTagIterator_t firstIndexIterator;
-      for (auto i = entries.first; i != entries.second; ++i) {
-        firstIndexIterator = map->d_map.project<OrderedTag>(i);
-
-        if (i->d_ttd <= now) {
-          moveCacheItemToFront<SequencedTag>(map->d_map, firstIndexIterator);
-          continue;
-        }
-
-        if (!entryMatches(firstIndexIterator, qtype, requireAuth, who)) {
-          continue;
-        }
-        found = true;
-        ttd = handleHit(*map, 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
-          break;
-        }
-      }
-      if (found) {
-        if (state && cachedState) {
-          *state = *cachedState;
-        }
-        return fakeTTD(firstIndexIterator, qname, qtype, ttd, now, origTTL, refresh);
-      }
-      else {
-        return -1;
-      }
-    }
-  }
-  // Try (again) without tag
-  auto entries = getEntries(*map, qname, qt, boost::none);
-
-  if (entries.first != entries.second) {
-    OrderedTagIterator_t firstIndexIterator;
-    bool found = false;
-    time_t ttd;
-
-    for (auto i = entries.first; i != entries.second; ++i) {
-      firstIndexIterator = map->d_map.project<OrderedTag>(i);
-
-      if (i->d_ttd <= now) {
-        moveCacheItemToFront<SequencedTag>(map->d_map, firstIndexIterator);
-        continue;
-      }
-
-      if (!entryMatches(firstIndexIterator, qtype, requireAuth, who)) {
-        continue;
-      }
-
-      found = true;
-      ttd = handleHit(*map, 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
-        break;
-      }
-    }
-    if (found) {
-      if (state && cachedState) {
-        *state = *cachedState;
-      }
-      return fakeTTD(firstIndexIterator, qname, qtype, ttd, now, origTTL, refresh);
-    }
-  }
-  return -1;
-}
-
-void MemRecursorCache::replace(time_t now, const DNSName& qname, const QType qt, const vector<DNSRecord>& content, const vector<shared_ptr<RRSIGRecordContent>>& signatures, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs, bool auth, const DNSName& authZone, boost::optional<Netmask> ednsmask, const OptTag& routingTag, vState state, boost::optional<ComboAddress> from)
-{
-  auto& mc = getMap(qname);
-  auto map = mc.lock();
-
-  map->d_cachecachevalid = false;
-  if (ednsmask) {
-    ednsmask = ednsmask->getNormalized();
-  }
-
-  // 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());
-  bool isNew = false;
-  cache_t::iterator stored = map->d_map.find(key);
-  if (stored == map->d_map.end()) {
-    stored = map->d_map.insert(CacheEntry(key, auth)).first;
-    ++mc.d_entriesCount;
-    isNew = true;
-  }
-
-  /* if we are inserting a new entry or updating an expired one (in which case the
-     ECS index might have been removed but the entry still exists because it has not
-     been garbage collected yet) we might need to update the ECS index.
-     Otherwise it should already be indexed and we don't need to update it.
-  */
-  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 ecsIndex = map->d_ecsIndex.find(ecsIndexKey);
-      if (ecsIndex == map->d_ecsIndex.end()) {
-        ecsIndex = map->d_ecsIndex.insert(ECSIndexEntry(qname, qt.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();
-
-  if (!auth && ce.d_auth) { // unauth data came in, we have some auth data, but is it fresh?
-    if (ce.d_ttd > now) { // we still have valid data, ignore unauth data
-      return;
-    }
-    else {
-      ce.d_auth = false; // new data won't be auth
-    }
-  }
-
-  if (auth) {
-    /* we don't want to keep a non-auth entry while we have an auth one */
-    if (vStateIsBogus(state) && (!vStateIsBogus(ce.d_state) && ce.d_state != vState::Indeterminate) && ce.d_ttd > now) {
-      /* the new entry is Bogus, the existing one is not and is still valid, let's keep the existing one */
-      return;
-    }
-  }
-
-  ce.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()) {
-    //    cerr<<"\tLimiting TTL of auth->auth NS set replace to "<<ce.d_ttd<<endl;
-    maxTTD = ce.d_ttd;
-  }
-
-  if (auth) {
-    ce.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;
-  if (from) {
-    ce.d_from = *from;
-  }
-  else {
-    ce.d_from = ComboAddress();
-  }
-
-  for (const auto& i : content) {
-    /* Yes, we have altered the d_ttl value by adding time(nullptr) to it
-       prior to calling this function, so the TTL actually holds a TTD. */
-    ce.d_ttd = min(maxTTD, static_cast<time_t>(i.d_ttl)); // XXX this does weird things if TTLs differ in the set
-    ce.d_orig_ttl = ce.d_ttd - now;
-    ce.d_records.push_back(i.d_content);
-  }
-
-  if (!isNew) {
-    moveCacheItemToBack<SequencedTag>(map->d_map, stored);
-  }
-  ce.d_submitted = false;
-  map->d_map.replace(stored, ce);
-}
-
-size_t MemRecursorCache::doWipeCache(const DNSName& name, bool sub, const QType qtype)
-{
-  size_t count = 0;
-
-  if (!sub) {
-    auto& mc = getMap(name);
-    auto map = mc.lock();
-    map->d_cachecachevalid = false;
-    auto& idx = map->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);
-        count++;
-        --mc.d_entriesCount;
-      }
-      else {
-        ++i;
-      }
-    }
-
-    if (qtype == 0xffff) {
-      auto& ecsIdx = map->d_ecsIndex.get<OrderedTag>();
-      auto ecsIndexRange = ecsIdx.equal_range(name);
-      ecsIdx.erase(ecsIndexRange.first, ecsIndexRange.second);
-    }
-    else {
-      auto& ecsIdx = map->d_ecsIndex.get<HashedTag>();
-      auto ecsIndexRange = ecsIdx.equal_range(std::tie(name, qtype));
-      ecsIdx.erase(ecsIndexRange.first, ecsIndexRange.second);
-    }
-  }
-  else {
-    for (auto& mc : d_maps) {
-      auto map = mc.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))
-          break;
-        if (i->d_qtype == qtype || qtype == 0xffff) {
-          count++;
-          i = idx.erase(i);
-          --mc.d_entriesCount;
-        }
-        else {
-          ++i;
-        }
-      }
-      auto& ecsIdx = map->d_ecsIndex.get<OrderedTag>();
-      for (auto i = ecsIdx.lower_bound(name); i != ecsIdx.end();) {
-        if (!i->d_qname.isPartOf(name))
-          break;
-        if (i->d_qtype == qtype || qtype == 0xffff) {
-          i = ecsIdx.erase(i);
-        }
-        else {
-          ++i;
-        }
-      }
-    }
-  }
-  return count;
-}
-
-// Name should be doLimitTime or so
-bool MemRecursorCache::doAgeCache(time_t now, const DNSName& name, const QType qtype, uint32_t newTTL)
-{
-  auto& mc = getMap(name);
-  auto map = mc.lock();
-  cache_t::iterator iter = map->d_map.find(std::tie(name, qtype));
-  if (iter == map->d_map.end()) {
-    return false;
-  }
-
-  CacheEntry ce = *iter;
-  if (ce.d_ttd < now)
-    return false; // would be dead anyhow
-
-  uint32_t maxTTL = static_cast<uint32_t>(ce.d_ttd - now);
-  if (maxTTL > newTTL) {
-    map->d_cachecachevalid = false;
-
-    time_t newTTD = now + newTTL;
-
-    if (ce.d_ttd > newTTD) {
-      ce.d_ttd = newTTD;
-      map->d_map.replace(iter, ce);
-    }
-    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)
-{
-  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());
-  }
-  if (qtype == QType::ADDR) {
-    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();
-
-  bool updated = false;
-  if (!map->d_ecsIndex.empty() && !routingTag) {
-    auto entry = getEntryUsingECSIndex(*map, now, qname, qtype, requireAuth, who);
-    if (entry == map->d_map.end()) {
-      return false;
-    }
-
-    entry->d_state = newState;
-    if (capTTD) {
-      entry->d_ttd = std::min(entry->d_ttd, *capTTD);
-    }
-    return true;
-  }
-
-  auto entries = getEntries(*map, qname, qt, routingTag);
-
-  for (auto i = entries.first; i != entries.second; ++i) {
-    auto firstIndexIterator = map->d_map.project<OrderedTag>(i);
-
-    if (!entryMatches(firstIndexIterator, qtype, requireAuth, who)) {
-      continue;
-    }
-
-    i->d_state = newState;
-    if (capTTD) {
-      i->d_ttd = std::min(i->d_ttd, *capTTD);
-    }
-    updated = true;
-
-    break;
-  }
-
-  return updated;
-}
-
-uint64_t MemRecursorCache::doDump(int fd)
-{
-  int newfd = dup(fd);
-  if (newfd == -1) {
-    return 0;
-  }
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
-  if (!fp) { // dup probably failed
-    close(newfd);
-    return 0;
-  }
-
-  fprintf(fp.get(), "; main record cache dump follows\n;\n");
-  uint64_t count = 0;
-
-  for (auto& mc : d_maps) {
-    auto map = mc.lock();
-    const auto& sidx = map->d_map.get<SequencedTag>();
-
-    time_t now = time(nullptr);
-    for (const auto& i : sidx) {
-      for (const auto& j : i.d_records) {
-        count++;
-        try {
-          fprintf(fp.get(), "%s %" PRIu32 " %" PRId64 " IN %s %s ; (%s) auth=%i zone=%s from=%s %s %s\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());
-        }
-        catch (...) {
-          fprintf(fp.get(), "; error printing '%s'\n", i.d_qname.empty() ? "EMPTY" : i.d_qname.toString().c_str());
-        }
-      }
-      for (const auto& sig : i.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());
-        }
-        catch (...) {
-          fprintf(fp.get(), "; error printing '%s'\n", i.d_qname.empty() ? "EMPTY" : i.d_qname.toString().c_str());
-        }
-      }
-    }
-  }
-  return count;
-}
-
-void MemRecursorCache::doPrune(size_t keep)
-{
-  //size_t maxCached = d_maxEntries;
-  size_t cacheSize = size();
-  pruneMutexCollectionsVector<SequencedTag>(*this, d_maps, keep, cacheSize);
-}
-
-namespace boost
-{
-size_t hash_value(const MemRecursorCache::OptTag& o)
-{
-  return o ? hash_value(o.get()) : 0xcafebaaf;
-}
-}
diff --git a/pdns/recursor_cache.hh b/pdns/recursor_cache.hh
deleted file mode 100644 (file)
index 56c6991..0000000
+++ /dev/null
@@ -1,277 +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 <set>
-#include "dns.hh"
-#include "qtype.hh"
-#include "misc.hh"
-#include "dnsname.hh"
-#include <iostream>
-#include "dnsrecords.hh"
-#include <boost/utility.hpp>
-#include <boost/multi_index_container.hpp>
-#include <boost/multi_index/ordered_index.hpp>
-#include <boost/multi_index/hashed_index.hpp>
-#include <boost/multi_index/key_extractors.hpp>
-#include <boost/multi_index/sequenced_index.hpp>
-#include <boost/version.hpp>
-#include "iputils.hh"
-#include "lock.hh"
-#include "stat_t.hh"
-#include "validate.hh"
-#undef max
-
-#include "namespaces.hh"
-using namespace ::boost::multi_index;
-
-class MemRecursorCache : public boost::noncopyable //  : public RecursorCache
-{
-public:
-  MemRecursorCache(size_t mapsCount = 1024);
-
-  size_t size() const;
-  size_t bytes();
-  pair<uint64_t, uint64_t> stats();
-  size_t ecsIndexSize();
-
-  typedef boost::optional<std::string> OptTag;
-
-  time_t get(time_t, const DNSName& qname, const QType qt, bool requireAuth, vector<DNSRecord>* res, const ComboAddress& who, bool refresh = false, const OptTag& routingTag = boost::none, vector<std::shared_ptr<RRSIGRecordContent>>* signatures = nullptr, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs = nullptr, bool* variable = nullptr, vState* state = nullptr, bool* wasAuth = nullptr, DNSName* fromAuthZone = nullptr, ComboAddress* fromAuthIP = nullptr);
-
-  void replace(time_t, const DNSName& qname, const QType qt, const vector<DNSRecord>& content, const vector<shared_ptr<RRSIGRecordContent>>& signatures, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs, bool auth, const DNSName& authZone, boost::optional<Netmask> ednsmask = boost::none, const OptTag& routingTag = boost::none, vState state = vState::Indeterminate, boost::optional<ComboAddress> from = boost::none);
-
-  void doPrune(size_t keep);
-  uint64_t doDump(int fd);
-
-  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);
-
-  pdns::stat_t cacheHits{0}, cacheMisses{0};
-
-private:
-  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_qtype(std::get<1>(key)), d_auth(auth), d_submitted(false)
-    {
-    }
-
-    typedef vector<std::shared_ptr<DNSRecordContent>> records_t;
-    time_t getTTD() const
-    {
-      return d_ttd;
-    }
-
-    records_t d_records;
-    std::vector<std::shared_ptr<RRSIGRecordContent>> d_signatures;
-    std::vector<std::shared_ptr<DNSRecord>> d_authorityRecs;
-    DNSName d_qname;
-    DNSName d_authZone;
-    ComboAddress d_from;
-    Netmask d_netmask;
-    OptTag d_rtag;
-    mutable vState d_state;
-    mutable time_t d_ttd;
-    uint32_t d_orig_ttl;
-    QType d_qtype;
-    bool d_auth;
-    mutable bool d_submitted; // whether this entry has been queued for refetch
-  };
-
-  /* The ECS Index (d_ecsIndex) keeps track of whether there is any ECS-specific
-     entry for a given (qname,qtype) entry in the cache (d_map), and if so
-     provides a NetmaskTree of those ECS entries.
-     This allows figuring out quickly if we should look for an entry
-     specific to the requestor IP, and if so which entry is the most
-     specific one.
-     Keeping the entries in the regular cache is currently necessary
-     because of the way we manage expired entries (moving them to the
-     front of the expunge queue to be deleted at a regular interval).
-  */
-  class ECSIndexEntry
-  {
-  public:
-    ECSIndexEntry(const DNSName& qname, QType qtype) :
-      d_nmt(), d_qname(qname), d_qtype(qtype)
-    {
-    }
-
-    Netmask lookupBestMatch(const ComboAddress& addr) const
-    {
-      const auto best = d_nmt.lookup(addr);
-      if (best != nullptr) {
-        return best->first;
-      }
-
-      return Netmask();
-    }
-
-    void addMask(const Netmask& nm) const
-    {
-      d_nmt.insert(nm).second = true;
-    }
-
-    void removeNetmask(const Netmask& nm) const
-    {
-      d_nmt.erase(nm);
-    }
-
-    bool isEmpty() const
-    {
-      return d_nmt.empty();
-    }
-
-    mutable NetmaskTree<bool> d_nmt;
-    DNSName d_qname;
-    QType d_qtype;
-  };
-
-  struct HashedTag
-  {
-  };
-  struct SequencedTag
-  {
-  };
-  struct NameAndRTagOnlyHashedTag
-  {
-  };
-  struct OrderedTag
-  {
-  };
-
-  typedef multi_index_container<
-    CacheEntry,
-    indexed_by<
-      ordered_unique<tag<OrderedTag>,
-                     composite_key<
-                       CacheEntry,
-                       member<CacheEntry, DNSName, &CacheEntry::d_qname>,
-                       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>>>,
-      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;
-
-  typedef MemRecursorCache::cache_t::index<MemRecursorCache::OrderedTag>::type::iterator OrderedTagIterator_t;
-  typedef MemRecursorCache::cache_t::index<MemRecursorCache::NameAndRTagOnlyHashedTag>::type::iterator NameAndRTagOnlyHashedTagIterator_t;
-
-  typedef multi_index_container<
-    ECSIndexEntry,
-    indexed_by<
-      hashed_unique<tag<HashedTag>,
-                    composite_key<
-                      ECSIndexEntry,
-                      member<ECSIndexEntry, DNSName, &ECSIndexEntry::d_qname>,
-                      member<ECSIndexEntry, QType, &ECSIndexEntry::d_qtype>>>,
-      ordered_unique<tag<OrderedTag>,
-                     composite_key<
-                       ECSIndexEntry,
-                       member<ECSIndexEntry, DNSName, &ECSIndexEntry::d_qname>,
-                       member<ECSIndexEntry, QType, &ECSIndexEntry::d_qtype>>,
-                     composite_key_compare<CanonDNSNameCompare, std::less<QType>>>>>
-    ecsIndex_t;
-
-  typedef std::pair<NameAndRTagOnlyHashedTagIterator_t, NameAndRTagOnlyHashedTagIterator_t> Entries;
-
-  struct MapCombo
-  {
-    MapCombo() {}
-    MapCombo(const MapCombo&) = delete;
-    MapCombo& operator=(const MapCombo&) = delete;
-    struct LockedContent
-    {
-      cache_t d_map;
-      ecsIndex_t d_ecsIndex;
-      DNSName d_cachedqname;
-      OptTag d_cachedrtag;
-      Entries d_cachecache;
-      uint64_t d_contended_count{0};
-      uint64_t d_acquired_count{0};
-      bool d_cachecachevalid{false};
-
-      void invalidate()
-      {
-        d_cachecachevalid = false;
-      }
-    };
-
-    pdns::stat_t d_entriesCount{0};
-
-    LockGuardedTryHolder<LockedContent> lock()
-    {
-      auto locked = d_content.try_lock();
-      if (!locked.owns_lock()) {
-        locked.lock();
-        ++locked->d_contended_count;
-      }
-      ++locked->d_acquired_count;
-      return locked;
-    }
-
-  private:
-    LockGuarded<LockedContent> d_content;
-  };
-
-  vector<MapCombo> d_maps;
-  MapCombo& getMap(const DNSName& qname)
-  {
-    return d_maps.at(qname.hash() % d_maps.size());
-  }
-
-  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);
-
-  time_t handleHit(MapCombo::LockedContent& content, OrderedTagIterator_t& entry, const DNSName& qname, uint32_t& origTTL, vector<DNSRecord>* res, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, boost::optional<vState>& state, bool* wasAuth, DNSName* authZone, ComboAddress* fromAuthIP);
-
-public:
-  void preRemoval(MapCombo::LockedContent& map, const CacheEntry& entry)
-  {
-    if (entry.d_netmask.empty()) {
-      return;
-    }
-
-    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);
-      }
-    }
-  }
-};
-
-namespace boost
-{
-size_t hash_value(const MemRecursorCache::OptTag& rtag);
-}
index 02e5539c7df159d492119c8f2967af27d88eb1d9..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
@@ -55,3 +56,5 @@ PowerDNS-Recursor.pdf
 /*.pb.cc
 /*.pb.h
 /.cache
+/.clang-tidy
+/recursor.yml
index 9217e88cfe3436d1cecea7aad86298bd32087e1c..2819adac31c780275586b445e09754b7f7648a8d 100644 (file)
@@ -1,11 +1,19 @@
-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)
 
+if LIBDECAF
+AM_CPPFLAGS += $(LIBDECAF_CFLAGS)
+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
@@ -15,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 += \
@@ -35,12 +41,14 @@ BUILT_SOURCES=htmlfiles.h \
        dnslabeltext.cc
 
 CLEANFILES = htmlfiles.h \
-       recursor.conf-dist
+       recursor.conf-dist recursor.yml-dist
 
-htmlfiles.h: html/*
-       ./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)
@@ -59,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 \
@@ -86,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:
@@ -97,17 +104,20 @@ endif
 pdns_recursor_SOURCES = \
        aggressive_nsec.cc aggressive_nsec.hh \
        arguments.cc \
-       ascii.hh \
+       auth-catalogzone.hh \
        axfr-retriever.hh axfr-retriever.cc \
        base32.cc base32.hh \
        base64.cc base64.hh \
+       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 \
@@ -125,6 +135,7 @@ pdns_recursor_SOURCES = \
        filterpo.cc filterpo.hh \
        fstrm_logger.cc fstrm_logger.hh \
        gettime.cc gettime.hh \
+       gss_context.cc gss_context.hh \
        histogram.hh \
        iputils.hh iputils.cc \
        ixfr.cc ixfr.hh \
@@ -163,25 +174,28 @@ pdns_recursor_SOURCES = \
        rec-lua-conf.hh rec-lua-conf.cc \
        rec-main.hh rec-main.cc \
        rec-protozero.cc rec-protozero.hh \
+       rec-responsestats.hh rec-responsestats.cc \
        rec-snmp.hh rec-snmp.cc \
        rec-taskqueue.cc rec-taskqueue.hh \
+       rec-tcounters.cc rec-tcounters.hh \
        rec-tcp.cc \
-        rec-tcpout.cc rec-tcpout.hh \
-        rec-zonetocache.cc rec-zonetocache.hh \
+       rec-tcpout.cc rec-tcpout.hh \
+       rec-zonetocache.cc rec-zonetocache.hh \
        rec_channel.cc rec_channel.hh rec_metrics.hh \
        rec_channel_rec.cc \
        recpacketcache.cc recpacketcache.hh \
        recursor_cache.cc recursor_cache.hh \
+       reczones-helpers.cc reczones-helpers.hh \
        reczones.cc \
        remote_logger.cc remote_logger.hh \
        resolve-context.hh \
        resolver.hh resolver.cc \
-       responsestats.hh responsestats.cc \
        root-addresses.hh \
        root-dnssec.hh \
        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 \
@@ -194,6 +208,7 @@ pdns_recursor_SOURCES = \
        svc-records.cc svc-records.hh \
        syncres.cc syncres.hh \
        taskqueue.cc taskqueue.hh \
+       tcounters.hh \
        tcpiohandler.cc tcpiohandler.hh \
        threadname.hh threadname.cc \
        tsigverifier.cc tsigverifier.hh \
@@ -206,13 +221,15 @@ pdns_recursor_SOURCES = \
        webserver.cc webserver.hh \
        ws-api.cc ws-api.hh \
        ws-recursor.cc ws-recursor.hh \
-       xpf.cc xpf.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
@@ -227,9 +244,9 @@ pdns_recursor_LDADD = \
        $(RT_LIBS) \
        $(BOOST_SYSTEM_LIBS) \
        $(PROBDS_LIBS) \
-       $(LIBCAP_LIBS)
-
-rec_control_LDADD = $(LIBCRYPTO_LIBS)
+       $(LIBCAP_LIBS) \
+       $(ARC4RANDOM_LIBS) \
+       $(RUST_LIBS)
 
 pdns_recursor_LDFLAGS = $(AM_LDFLAGS) \
        $(LIBCRYPTO_LDFLAGS) $(BOOST_CONTEXT_LDFLAGS) \
@@ -243,6 +260,11 @@ pdns_recursor_LDFLAGS += \
        $(BOOST_FILESYSTEM_LDFLAGS)
 endif
 
+rec_control_LDADD = $(LIBCRYPTO_LIBS) $(ARC4RANDOM_LIBS) $(RUST_LIBS)
+
+rec_control_LDFLAGS = $(AM_LDFLAGS) \
+       $(LIBCRYPTO_LDFLAGS)
+
 testrunner_SOURCES = \
        aggressive_nsec.cc aggressive_nsec.hh \
        arguments.cc \
@@ -252,7 +274,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 \
@@ -267,6 +289,7 @@ testrunner_SOURCES = \
        ednssubnet.cc ednssubnet.hh \
        filterpo.cc filterpo.hh \
        gettime.cc gettime.hh \
+       gss_context.cc gss_context.hh \
        iputils.cc iputils.hh \
        ixfr.cc ixfr.hh \
        logger.cc logger.hh \
@@ -283,15 +306,18 @@ testrunner_SOURCES = \
        query-local-address.hh query-local-address.cc \
        rcpgenerator.cc \
        rec-eventtrace.cc rec-eventtrace.hh \
+       rec-responsestats.hh rec-responsestats.cc \
        rec-taskqueue.cc rec-taskqueue.hh \
+       rec-tcounters.cc rec-tcounters.hh \
        rec-zonetocache.cc rec-zonetocache.hh \
        recpacketcache.cc recpacketcache.hh \
        recursor_cache.cc recursor_cache.hh \
+       reczones-helpers.cc reczones-helpers.hh \
        resolver.hh resolver.cc \
-       responsestats.cc \
        root-dnssec.hh \
        rpzloader.cc rpzloader.hh \
        secpoll.cc \
+       settings/cxxsupport.cc \
        sholder.hh \
        sillyrecords.cc \
        sstuff.hh \
@@ -323,11 +349,14 @@ testrunner_SOURCES = \
        test-packetcache_hh.cc \
        test-rcpgenerator_cc.cc \
        test-rec-taskqueue.cc \
+       test-rec-tcounters_cc.cc \
        test-rec-zonetocache.cc \
        test-recpacketcache_cc.cc \
        test-recursorcache_cc.cc \
+       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 \
@@ -342,17 +371,18 @@ testrunner_SOURCES = \
        test-syncres_cc8.cc \
        test-syncres_cc9.cc \
        test-tsig.cc \
-       test-xpf_cc.cc \
        testrunner.cc \
        threadname.hh threadname.cc \
        tsigverifier.cc tsigverifier.hh \
        unix_utility.cc \
        validate-recursor.cc validate-recursor.hh \
        validate.cc validate.hh \
-       xpf.cc xpf.hh \
        zonemd.cc zonemd.hh \
        zoneparser-tng.cc zoneparser-tng.hh
 
+nodist_testrunner_SOURCES = \
+       settings/cxxsettings-generated.cc
+
 testrunner_LDFLAGS = \
        $(AM_LDFLAGS) \
        $(BOOST_CONTEXT_LDFLAGS) \
@@ -367,7 +397,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 \
@@ -474,12 +506,17 @@ rec_control_SOURCES = \
        dnslabeltext.cc \
        dnsname.hh dnsname.cc \
        logger.cc \
+       logging.cc \
        misc.cc \
        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
 
@@ -490,13 +527,16 @@ $(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 > $@
+       $(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 \
@@ -507,13 +547,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
@@ -610,6 +650,15 @@ endif
 if !HAVE_SYSTEMD_SYSTEM_CALL_FILTER
        $(AM_V_GEN)perl -ni -e 'print unless /^SystemCallFilter/' $@
 endif
+if !HAVE_SYSTEMD_PROTECT_PROC
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectProc/' $@
+endif
+if !HAVE_SYSTEMD_PRIVATE_IPC
+       $(AM_V_GEN)perl -ni -e 'print unless /^PrivateIPC/' $@
+endif
+if !HAVE_SYSTEMD_REMOVE_IPC
+       $(AM_V_GEN)perl -ni -e 'print unless /^RemoveIPC/' $@
+endif
 
 pdns-recursor@.service: pdns-recursor.service
        $(AM_V_GEN)sed -e 's!/pdns_recursor!& --config-name=%i!' \
index 199b2de8af21766515ffea94effbc0b71827b6e6..ebdc05cff4b7b0d200e9ea8cceb7a0f3cb6e5f47 100644 (file)
@@ -1,89 +1,10 @@
 PowerDNS Recursor
------------------
-For full details, please read https://doc.powerdns.com/md/recursor/
-
-Here follow some brief notes that may be useful to get you going.
-
-Compiling
----------
-Starting with version 4.0.0, the PowerDNS recursor uses autotools and compiling
-[from the tarball](https://downloads.powerdns.com/releases/) can be as simple as
-
-```sh
-./configure
-make
-```
-
-As for dependencies, Boost (http://boost.org/), OpenSSL (https://openssl.org/),
-and Lua (https://www.lua.org/) are required.
-
-On most modern UNIX distributions, you can simply install 'boost' or
-'boost-dev' or 'boost-devel'. Otherwise, just download boost, and point the
-compiler at the right directory using CPPFLAGS.
-
-On Debian and Ubuntu, the following will get you the dependencies:
-
-```sh
-apt-get install libboost-dev libboost-filesystem-dev libboost-serialization-dev \
-  libboost-system-dev libboost-thread-dev libboost-context-dev libboost-test-dev \
-  libssl-dev libboost-test-dev g++ make pkg-config libluajit-5.1-dev
-```
-
-Compiling from git checkout
-===========================
-Source code is available on GitHub:
-
-```sh
-git clone https://github.com/PowerDNS/pdns.git
-```
-
-This repository contains the sources for the PowerDNS Recursor, the PowerDNS
-Authoritative Server, and dnsdist (a powerful DNS loadbalancer). The sources for
-the recursor are located in the `pdns/recursordist` subdirectory of the repository.
-
-To compile from a git checkout, install the dependencies above plus ragel, automake, autoconf, libtool, virtualenv and curl.
-Then run
-
-```sh
-cd pdns/pdns/recursordist/
-autoreconf -vi
-./configure
-make
-```
-
-macOS Notes
------------
-
-If you want to compile yourself, the dependencies can be installed using
-Homebrew. You need to tell configure where to find OpenSSL, too.
-
-```sh
-brew install boost lua pkg-config ragel openssl
-./configure --with-lua PKG_CONFIG_PATH=/usr/local/opt/openssl/lib/pkgconfig
-make -j4
-```
-
-Lua scripting
--------------
-To benefit from Lua scripting, as described on https://doc.powerdns.com/md/recursor/scripting/
-Install Lua and development headers. PowerDNS supports Lua 5.1, 5.2, 5.3 and LuaJIT.
-On Debian/Ubuntu, install e.g. `liblua5.2-dev` to use Lua 5.2.
-
-The configure script will automatically detect the Lua version. If more than one
-version of Lua is installed, the `--with-lua` configure flag can be set to the
-desired version. e.g.:
-
-```sh
-./configure --with-lua=lua51
-```
-
-(On older versions of Debian/Ubuntu, you'll need to pass `--with-lua=lua5.1` instead.)
+=================
+For full details on building PowerDNS Recursor, please read https://docs.powerdns.com/recursor/appendices/compiling.html
 
 Documentation
 =============
-After compiling, run `pdns\_recursor --config` to view the configuration options
-and a short description. The full documentation is online at
-https://doc.powerdns.com/recursor/
+The full documentation is online at https://doc.powerdns.com/recursor/
 
 Reporting bugs
 ==============
@@ -93,7 +14,7 @@ reported.
 
 License
 =======
-PowerDNS is copyright © 2001-2019 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 2def016acb27c6c41ad553916138a276119cc269..f80eab30c0dffcc45ba9be152e47fc77ae6780b3 100644 (file)
@@ -48,6 +48,18 @@ rec MODULE-IDENTITY
     REVISION "202201310000Z"
     DESCRIPTION "Added non-resolving NS name metric."
 
+    REVISION "202208220000Z"
+    DESCRIPTION "Added internal maintenance metrics."
+
+    REVISION "202209120000Z"
+    DESCRIPTION "Added metrics for answers from auths by rcode"
+
+    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 }
@@ -691,7 +703,7 @@ dnssecValidations OBJECT-TYPE
     MAX-ACCESS read-only
     STATUS current
     DESCRIPTION
-        "Number of DNSSEC validations"
+        "Number of responses sent, packet-cache hits excluded, for which a DNSSEC validation was requested by either the client or the configuration"
     ::= { stats 80 }
 
 dnssecResultInsecure OBJECT-TYPE
@@ -699,7 +711,7 @@ dnssecResultInsecure OBJECT-TYPE
     MAX-ACCESS read-only
     STATUS current
     DESCRIPTION
-        "Number of DNSSEC insecure results"
+        "Number of responses sent, excluding packet-cache hits, that were in the DNSSEC insecure state"
     ::= { stats 81 }
 
 dnssecResultSecure OBJECT-TYPE
@@ -707,7 +719,7 @@ dnssecResultSecure OBJECT-TYPE
     MAX-ACCESS read-only
     STATUS current
     DESCRIPTION
-        "Number of DNSSEC secure results"
+        "Number of responses sent, excluding packet-cache hits, that were in the DNSSEC secure state"
     ::= { stats 82 }
 
 dnssecResultBogus OBJECT-TYPE
@@ -715,7 +727,7 @@ dnssecResultBogus OBJECT-TYPE
     MAX-ACCESS read-only
     STATUS current
     DESCRIPTION
-        "Number of DNSSEC bogus results"
+        "Number of responses sent, excluding packet-cache hits, that were in the DNSSEC bogus state"
     ::= { stats 83 }
 
 dnssecResultIndeterminate OBJECT-TYPE
@@ -723,7 +735,7 @@ dnssecResultIndeterminate OBJECT-TYPE
     MAX-ACCESS read-only
     STATUS current
     DESCRIPTION
-        "Number of DNSSEC indeterminate results"
+        "Number of responses sent, excluding packet-cache hits, that were in the DNSSEC indeterminate state"
     ::= { stats 84 }
 
 dnssecResultNta OBJECT-TYPE
@@ -731,7 +743,7 @@ dnssecResultNta OBJECT-TYPE
     MAX-ACCESS read-only
     STATUS current
     DESCRIPTION
-        "Number of DNSSEC NTA results"
+        "Number of responses sent, excluding packet-cache hits, that were in the DNSSEC NTA state"
     ::= { stats 85 }
 
 policyResultNoaction OBJECT-TYPE
@@ -1062,6 +1074,182 @@ nonResolvingNameserverEntries OBJECT-TYPE
         "Number of entries in the non-resolving NS name cache"
     ::= { stats 126 }
 
+maintenanceUSec OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Time spent doing internal maintenance, including Lua maintenance"
+    ::= { stats 127 }
+
+maintenanceCount OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of times internal maintenance has been called, including Lua maintenance"
+    ::= { stats 128 }
+
+authrcode0Count OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of rcode 0 (noerror) answers received"
+    ::= { stats 129 }
+
+authrcode1Count OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of rcode 1 (formerr) answers received"
+    ::= { stats 130 }
+
+authrcode2Count OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of rcode 2 (servfail) answers received"
+    ::= { stats 131 }
+
+authrcode3Count OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of rcode 3 (nxdomain) answers received"
+    ::= { stats 132 }
+
+authrcode4Count OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of rcode 4 (notimp) answers received"
+    ::= { stats 133 }
+
+authrcode5Count OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of rcode 5 (refused) answers received"
+    ::= { stats 134 }
+
+authrcode6Count OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of rcode 6 (yxdomain) answers received"
+    ::= { stats 135 }
+
+authrcode7Count OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of rcode 7 (yxrrset) answers received"
+    ::= { stats 136 }
+
+authrcode8Count OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of rcode 8 (nxrrset) answers received"
+    ::= { stats 137 }
+
+authrcode9Count OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of rcode 9 (notauth) answers received"
+    ::= { stats 138 }
+
+authrcode10Count OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of rcode 10 answers received"
+    ::= { stats 139 }
+
+authrcode11Count OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of rcode 11 answers received"
+    ::= { stats 140 }
+
+authrcode12Count OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of rcode 12 answers received"
+    ::= { stats 141 }
+
+authrcode13Count OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of rcode 13 answers received"
+    ::= { stats 142 }
+
+authrcode14Count OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of rcode 14 answers received"
+    ::= { stats 143 }
+
+authrcode15Count OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of rcode 15 answers received"
+    ::= { stats 144 }
+
+packetCacheContended OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of contended packet cache lock acquisitions"
+    ::= { stats 145 }
+
+packetCacheAcquired OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "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
 ---
@@ -1235,7 +1423,29 @@ recGroup OBJECT-GROUP
         udp6InCsumErrors,
         sourceDisallowedNotify,
         zoneDisallowedNotify,
-        nonResolvingNameserverEntries
+        nonResolvingNameserverEntries,
+        maintenanceUSec,
+        maintenanceCalls,
+        authrcode0Count,
+        authrcode1Count,
+        authrcode2Count,
+        authrcode3Count,
+        authrcode4Count,
+        authrcode5Count,
+        authrcode6Count,
+        authrcode7Count,
+        authrcode8Count,
+        authrcode9Count,
+        authrcode10Count,
+        authrcode11Count,
+        authrcode12Count,
+        authrcode13Count,
+        authrcode14Count,
+        authrcode15Count,
+        packetCacheContended,
+        packetCacheAcquired,
+        nodEvents,
+        udrEvents
     }
     STATUS current
     DESCRIPTION "Objects conformance group for PowerDNS Recursor"
index 2f332b639542ca62a11ecce15cfb93d1923064ff..9cd729a0b807673773249247ff20fdd81775902d 100644 (file)
@@ -20,6 +20,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #include <cinttypes>
+#include <climits>
 
 #include "aggressive_nsec.hh"
 #include "cachecleaner.hh"
@@ -28,6 +29,8 @@
 #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 */
 extern std::unique_ptr<MemRecursorCache> g_recCache;
@@ -124,83 +127,79 @@ 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);
     }
   }
 }
 
-static bool isMinimallyCoveringNSEC(const DNSName& owner, const std::shared_ptr<NSECRecordContent>& nsec)
+static bool isMinimallyCoveringNSEC(const DNSName& owner, const std::shared_ptr<const NSECRecordContent>& nsec)
 {
   /* this test only covers Cloudflare's ones (https://blog.cloudflare.com/black-lies/),
      we might need to cover more cases described in rfc4470 as well, but the name generation algorithm
@@ -226,25 +225,55 @@ static bool isMinimallyCoveringNSEC(const DNSName& owner, const std::shared_ptr<
   return true;
 }
 
-// This function name is somewhat misleading. It only returns true if the nextHash is ownerHash+2, as is common
-// in minimally covering NXDOMAINs (i.e. the NSEC3 covers hash[deniedname]-1 .. hash[deniedname]+2.
-// Minimally covering NSEC3s for NODATA tend to be ownerHash+1, because they need to prove the name, so they
-// can tell us what types are in the bitmap for that name. For those names, this function returns false.
-// This is on purpose because NODATA denials actually do contain useful information we can reuse later -
-// specifically, the type bitmap for a name that does exist.
-static bool isMinimallyCoveringNSEC3(const DNSName& owner, const std::shared_ptr<NSEC3RecordContent>& nsec)
+static bool commonPrefixIsLong(const string& one, const string& two, size_t bound)
 {
-  std::string ownerHash(owner.getStorage().c_str(), owner.getStorage().size());
-  const std::string& nextHash = nsec->d_nexthash;
-
-  incrementHash(ownerHash);
-  incrementHash(ownerHash);
+  size_t length = 0;
+  const auto minLength = std::min(one.length(), two.length());
+
+  for (size_t i = 0; i < minLength; i++) {
+    const auto byte1 = one.at(i);
+    const auto byte2 = two.at(i);
+    // shortcut
+    if (byte1 == byte2) {
+      length += CHAR_BIT;
+      if (length > bound) {
+        return true;
+      }
+      continue;
+    }
+    // bytes differ, let's look at the bits
+    for (ssize_t j = CHAR_BIT - 1; j >= 0; j--) {
+      const auto bit1 = byte1 & (1 << j);
+      const auto bit2 = byte2 & (1 << j);
+      if (bit1 != bit2) {
+        return length > bound;
+      }
+      length++;
+      if (length > bound) {
+        return true;
+      }
+    }
+  }
+  return length > bound;
+}
 
-  return ownerHash == nextHash;
+// If the NSEC3 hashes have a long common prefix, they deny only a small subset of all possible hashes
+// So don't take the trouble to store those.
+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);
 }
 
-void AggressiveNSECCache::insertNSEC(const DNSName& zone, const DNSName& owner, const DNSRecord& record, const std::vector<std::shared_ptr<RRSIGRecordContent>>& signatures, bool nsec3)
+void AggressiveNSECCache::insertNSEC(const DNSName& zone, const DNSName& owner, const DNSRecord& record, const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures, bool nsec3)
 {
+  if (nsec3 && nsec3Disabled()) {
+    return;
+  }
   if (signatures.empty()) {
     return;
   }
@@ -293,8 +322,8 @@ void AggressiveNSECCache::insertNSEC(const DNSName& zone, const DNSName& owner,
         return;
       }
 
-      if (isMinimallyCoveringNSEC3(owner, content)) {
-        /* not accepting minimally covering answers since they only deny one name */
+      if (isSmallCoveringNSEC3(owner, content->d_nexthash)) {
+        /* not accepting small covering answers since they only deny a small subset */
         return;
       }
 
@@ -314,18 +343,24 @@ 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))) {
+    if (!nsec3 && isWildcardExpanded(owner.countLabels(), *signatures.at(0))) {
       DNSName realOwner = getNSECOwnerName(owner, signatures);
-      auto pair = zoneEntry->d_entries.insert({record.d_content, 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.d_content, 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});
+      }
     }
   }
 }
@@ -337,13 +372,6 @@ bool AggressiveNSECCache::getNSECBefore(time_t now, std::shared_ptr<LockGuarded<
     return false;
   }
 
-#if 0
-  LOG("We have:"<<endl);
-  for (const auto& ent : zoneEntry->d_entries) {
-    LOG("- "<<ent.d_owner<<" -> "<<ent.d_next<<endl);
-  }
-  LOG("=> end of list, looking for the lower bound to "<<name<<endl);
-#endif
   auto& idx = zoneEntry->d_entries.get<ZoneEntry::OrderedTag>();
   auto it = idx.lower_bound(name);
   bool end = false;
@@ -372,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;
 }
 
@@ -400,7 +429,7 @@ bool AggressiveNSECCache::getNSEC3(time_t now, std::shared_ptr<LockGuarded<Aggre
 
     auto firstIndexIterator = zoneEntry->d_entries.project<ZoneEntry::OrderedTag>(it);
     if (it->d_ttd <= now) {
-      moveCacheItemToBack<ZoneEntry::SequencedTag>(zoneEntry->d_entries, firstIndexIterator);
+      moveCacheItemToFront<ZoneEntry::SequencedTag>(zoneEntry->d_entries, firstIndexIterator);
       return false;
     }
 
@@ -412,7 +441,7 @@ bool AggressiveNSECCache::getNSEC3(time_t now, std::shared_ptr<LockGuarded<Aggre
   return false;
 }
 
-static void addToRRSet(const time_t now, std::vector<DNSRecord>& recordSet, std::vector<std::shared_ptr<RRSIGRecordContent>> signatures, const DNSName& owner, bool doDNSSEC, std::vector<DNSRecord>& ret, DNSResourceRecord::Place place = DNSResourceRecord::AUTHORITY)
+static void addToRRSet(const time_t now, std::vector<DNSRecord>& recordSet, std::vector<std::shared_ptr<const RRSIGRecordContent>> signatures, const DNSName& owner, bool doDNSSEC, std::vector<DNSRecord>& ret, DNSResourceRecord::Place place = DNSResourceRecord::AUTHORITY)
 {
   uint32_t ttl = 0;
 
@@ -434,7 +463,7 @@ static void addToRRSet(const time_t now, std::vector<DNSRecord>& recordSet, std:
       dr.d_type = QType::RRSIG;
       dr.d_name = owner;
       dr.d_ttl = ttl;
-      dr.d_content = std::move(signature);
+      dr.setContent(std::move(signature));
       dr.d_place = place;
       dr.d_class = QClass::IN;
       ret.push_back(std::move(dr));
@@ -442,13 +471,13 @@ static void addToRRSet(const time_t now, std::vector<DNSRecord>& recordSet, std:
   }
 }
 
-static void addRecordToRRSet(time_t now, const DNSName& owner, const QType& type, uint32_t ttl, std::shared_ptr<DNSRecordContent>& content, std::vector<std::shared_ptr<RRSIGRecordContent>> signatures, bool doDNSSEC, std::vector<DNSRecord>& ret)
+static void addRecordToRRSet(const DNSName& owner, const QType& type, uint32_t ttl, std::shared_ptr<const DNSRecordContent>& content, std::vector<std::shared_ptr<const RRSIGRecordContent>> signatures, bool doDNSSEC, std::vector<DNSRecord>& ret)
 {
   DNSRecord nsecRec;
   nsecRec.d_type = type.getCode();
   nsecRec.d_name = owner;
   nsecRec.d_ttl = ttl;
-  nsecRec.d_content = std::move(content);
+  nsecRec.setContent(std::move(content));
   nsecRec.d_place = DNSResourceRecord::AUTHORITY;
   nsecRec.d_class = QClass::IN;
   ret.push_back(std::move(nsecRec));
@@ -459,7 +488,7 @@ static void addRecordToRRSet(time_t now, const DNSName& owner, const QType& type
       dr.d_type = QType::RRSIG;
       dr.d_name = owner;
       dr.d_ttl = ttl;
-      dr.d_content = std::move(signature);
+      dr.setContent(std::move(signature));
       dr.d_place = DNSResourceRecord::AUTHORITY;
       dr.d_class = QClass::IN;
       ret.push_back(std::move(dr));
@@ -467,54 +496,53 @@ static void addRecordToRRSet(time_t now, const DNSName& owner, const QType& type
   }
 }
 
-#define LOG(x)                     \
-  if (g_dnssecLOG) {               \
-    g_log << Logger::Warning << x; \
-  }
-
-bool AggressiveNSECCache::synthesizeFromNSEC3Wildcard(time_t now, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, ZoneEntry::CacheEntry& nextCloser, const DNSName& wildcardName)
+bool AggressiveNSECCache::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& log)
 {
   vState cachedState;
 
   std::vector<DNSRecord> wcSet;
-  std::vector<std::shared_ptr<RRSIGRecordContent>> wcSignatures;
+  std::vector<std::shared_ptr<const RRSIGRecordContent>> wcSignatures;
 
-  if (g_recCache->get(now, wildcardName, type, true, &wcSet, ComboAddress("127.0.0.1"), false, boost::none, doDNSSEC ? &wcSignatures : nullptr, nullptr, nullptr, &cachedState) <= 0 || cachedState != vState::Secure) {
-    LOG("Unfortunately we don't have a valid entry for " << wildcardName << ", so we cannot synthesize from that wildcard" << endl);
+  if (g_recCache->get(now, wildcardName, type, MemRecursorCache::RequireAuth, &wcSet, ComboAddress("127.0.0.1"), boost::none, doDNSSEC ? &wcSignatures : nullptr, nullptr, nullptr, &cachedState) <= 0 || cachedState != vState::Secure) {
+    VLOG(log, name << ": Unfortunately we don't have a valid entry for " << wildcardName << ", so we cannot synthesize from that wildcard" << endl);
     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 */
-  addRecordToRRSet(now, nextCloser.d_owner, QType::NSEC3, nextCloser.d_ttd - now, nextCloser.d_record, nextCloser.d_signatures, doDNSSEC, ret);
+  // 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 */
 
-  LOG("Synthesized valid answer from NSEC3s and wildcard!" << endl);
+  VLOG(log, name << ": Synthesized valid answer from NSEC3s and wildcard!" << endl);
   ++d_nsec3WildcardHits;
+  res = RCode::NoError;
   return true;
 }
 
-bool AggressiveNSECCache::synthesizeFromNSECWildcard(time_t now, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, ZoneEntry::CacheEntry& nsec, const DNSName& wildcardName)
+bool AggressiveNSECCache::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& log)
 {
   vState cachedState;
 
   std::vector<DNSRecord> wcSet;
-  std::vector<std::shared_ptr<RRSIGRecordContent>> wcSignatures;
+  std::vector<std::shared_ptr<const RRSIGRecordContent>> wcSignatures;
 
-  if (g_recCache->get(now, wildcardName, type, true, &wcSet, ComboAddress("127.0.0.1"), false, boost::none, doDNSSEC ? &wcSignatures : nullptr, nullptr, nullptr, &cachedState) <= 0 || cachedState != vState::Secure) {
-    LOG("Unfortunately we don't have a valid entry for " << wildcardName << ", so we cannot synthesize from that wildcard" << endl);
+  if (g_recCache->get(now, wildcardName, type, MemRecursorCache::RequireAuth, &wcSet, ComboAddress("127.0.0.1"), boost::none, doDNSSEC ? &wcSignatures : nullptr, nullptr, nullptr, &cachedState) <= 0 || cachedState != vState::Secure) {
+    VLOG(log, name << ": Unfortunately we don't have a valid entry for " << wildcardName << ", so we cannot synthesize from that wildcard" << endl);
     return false;
   }
 
-  addToRRSet(now, wcSet, wcSignatures, name, doDNSSEC, ret, DNSResourceRecord::ANSWER);
-  addRecordToRRSet(now, nsec.d_owner, QType::NSEC, nsec.d_ttd - now, nsec.d_record, nsec.d_signatures, doDNSSEC, ret);
+  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);
 
-  LOG("Synthesized valid answer from NSECs and wildcard!" << endl);
+  VLOG(log, name << ": Synthesized valid answer from NSECs and wildcard!" << endl);
   ++d_nsecWildcardHits;
+  res = RCode::NoError;
   return true;
 }
 
-bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>>& zoneEntry, std::vector<DNSRecord>& soaSet, std::vector<std::shared_ptr<RRSIGRecordContent>>& soaSignatures, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC)
+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;
@@ -530,81 +558,93 @@ 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)) {
-    LOG("Found a direct NSEC3 match for " << nameHash);
-    auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(exactNSEC3.d_record);
+    VLOG(log, name << ": Found a direct NSEC3 match for " << nameHash);
+    auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(exactNSEC3.d_record);
     if (!nsec3 || nsec3->d_iterations != iterations || nsec3->d_salt != salt) {
-      LOG(" but the content is not valid, or has a different salt or iterations count" << endl);
+      VLOG_NO_PREFIX(log, " but the content is not valid, or has a different salt or iterations count" << endl);
       return false;
     }
 
-    if (!isTypeDenied(nsec3, type)) {
-      LOG(" but the requested type (" << type.toString() << ") does exist" << endl);
+    if (!isTypeDenied(*nsec3, type)) {
+      VLOG_NO_PREFIX(log, " but the requested type (" << type.toString() << ") does exist" << endl);
       return false;
     }
 
     const DNSName signer = getSigner(exactNSEC3.d_signatures);
     /* here we need to allow an ancestor NSEC3 proving that a DS does not exist as it is an
        exact match for the name */
-    if (type != QType::DS && isNSEC3AncestorDelegation(signer, exactNSEC3.d_owner, nsec3)) {
+    if (type != QType::DS && isNSEC3AncestorDelegation(signer, exactNSEC3.d_owner, *nsec3)) {
       /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
          Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
          nonexistence of any RRs below that zone cut, which include all RRs at
          that (original) owner name other than DS RRs, and all RRs below that
          owner name regardless of type.
       */
-      LOG(" but this is an ancestor delegation NSEC3" << endl);
+      VLOG_NO_PREFIX(log, " but this is an ancestor delegation NSEC3" << endl);
       return false;
     }
 
     if (type == QType::DS && !name.isRoot() && signer == name) {
-      LOG(" but this NSEC3 comes from the child zone and cannot be used to deny a DS");
+      VLOG_NO_PREFIX(log, " but this NSEC3 comes from the child zone and cannot be used to deny a DS");
       return false;
     }
 
-    LOG(": done!" << endl);
+    VLOG(log, ": done!" << endl);
     ++d_nsec3Hits;
     res = RCode::NoError;
     addToRRSet(now, soaSet, soaSignatures, zone, doDNSSEC, ret);
-    addRecordToRRSet(now, exactNSEC3.d_owner, QType::NSEC3, exactNSEC3.d_ttd - now, exactNSEC3.d_record, exactNSEC3.d_signatures, doDNSSEC, ret);
+    addRecordToRRSet(exactNSEC3.d_owner, QType::NSEC3, exactNSEC3.d_ttd - now, exactNSEC3.d_record, exactNSEC3.d_signatures, doDNSSEC, ret);
     return true;
   }
 
-  LOG("No direct NSEC3 match found for " << nameHash << ", looking for closest encloser" << endl);
+  VLOG(log, name << ": No direct NSEC3 match found for " << nameHash << ", looking for closest encloser" << endl);
   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)) {
-      LOG("Found closest encloser at " << closestEncloser << " (" << closestHash << ")" << endl);
+      VLOG(log, name << ": Found closest encloser at " << closestEncloser << " (" << closestHash << ")" << endl);
 
-      auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(closestNSEC3.d_record);
+      auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(closestNSEC3.d_record);
       if (!nsec3 || nsec3->d_iterations != iterations || nsec3->d_salt != salt) {
-        LOG(" but the content is not valid, or has a different salt or iterations count" << endl);
+        VLOG_NO_PREFIX(log, " but the content is not valid, or has a different salt or iterations count" << endl);
         break;
       }
 
       const DNSName signer = getSigner(closestNSEC3.d_signatures);
       /* This time we do not allow any ancestor NSEC3, as if the closest encloser is a delegation
          NS we know nothing about the names in the child zone. */
-      if (isNSEC3AncestorDelegation(signer, closestNSEC3.d_owner, nsec3)) {
+      if (isNSEC3AncestorDelegation(signer, closestNSEC3.d_owner, *nsec3)) {
         /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
            Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
            nonexistence of any RRs below that zone cut, which include all RRs at
            that (original) owner name other than DS RRs, and all RRs below that
            owner name regardless of type.
         */
-        LOG(" but this is an ancestor delegation NSEC3" << endl);
+        VLOG_NO_PREFIX(log, " but this is an ancestor delegation NSEC3" << endl);
         break;
       }
 
       if (type == QType::DS && !name.isRoot() && signer == name) {
-        LOG(" but this NSEC3 comes from the child zone and cannot be used to deny a DS");
+        VLOG_NO_PREFIX(log, " but this NSEC3 comes from the child zone and cannot be used to deny a DS");
         return false;
       }
 
@@ -614,7 +654,7 @@ bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded
   }
 
   if (!found) {
-    LOG("Nothing found for the closest encloser in NSEC3 aggressive cache either" << endl);
+    VLOG(log, name << ": Nothing found for the closest encloser in NSEC3 aggressive cache either" << endl);
     return false;
   }
 
@@ -626,95 +666,95 @@ 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));
-  LOG("Looking for a NSEC3 covering the next closer " << nextCloser << " (" << nextCloserHash << ")" << endl);
+  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;
   if (!getNSECBefore(now, zoneEntry, DNSName(nextCloserHash) + zone, nextCloserEntry)) {
-    LOG("Nothing found for the next closer in NSEC3 aggressive cache" << endl);
+    VLOG(log, name << ": Nothing found for the next closer in NSEC3 aggressive cache" << endl);
     return false;
   }
 
   if (!isCoveredByNSEC3Hash(DNSName(nextCloserHash) + zone, nextCloserEntry.d_owner, nextCloserEntry.d_next)) {
-    LOG("No covering record found for the next closer in NSEC3 aggressive cache" << endl);
+    VLOG(log, name << ": No covering record found for the next closer in NSEC3 aggressive cache" << endl);
     return false;
   }
 
-  auto nextCloserNsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(nextCloserEntry.d_record);
+  auto nextCloserNsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(nextCloserEntry.d_record);
   if (!nextCloserNsec3 || nextCloserNsec3->d_iterations != iterations || nextCloserNsec3->d_salt != salt) {
-    LOG("The NSEC3 covering the next closer is not valid, or has a different salt or iterations count, bailing out" << endl);
+    VLOG(log, name << ": The NSEC3 covering the next closer is not valid, or has a different salt or iterations count, bailing out" << endl);
     return false;
   }
 
   const DNSName nextCloserSigner = getSigner(nextCloserEntry.d_signatures);
   if (type == QType::DS && !name.isRoot() && nextCloserSigner == name) {
-    LOG(" but this NSEC3 comes from the child zone and cannot be used to deny a DS");
+    VLOG(log, " but this NSEC3 comes from the child zone and cannot be used to deny a DS");
     return false;
   }
 
   /* 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));
-  LOG("Looking for a NSEC3 covering the wildcard " << wildcard << " (" << wcHash << ")" << endl);
+  auto wcHash = toBase32Hex(getHashFromNSEC3(wildcard, iterations, salt, validationContext));
+  VLOG(log, name << ": Looking for a NSEC3 covering the wildcard " << wildcard << " (" << wcHash << ")" << endl);
 
   ZoneEntry::CacheEntry wcEntry;
   if (!getNSECBefore(now, zoneEntry, DNSName(wcHash) + zone, wcEntry)) {
-    LOG("Nothing found for the wildcard in NSEC3 aggressive cache" << endl);
+    VLOG(log, name << ": Nothing found for the wildcard in NSEC3 aggressive cache" << endl);
     return false;
   }
 
   if ((DNSName(wcHash) + zone) == wcEntry.d_owner) {
-    LOG("Found an exact match for the wildcard");
+    VLOG(log, name << ": Found an exact match for the wildcard");
 
-    auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(wcEntry.d_record);
+    auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(wcEntry.d_record);
     if (!nsec3 || nsec3->d_iterations != iterations || nsec3->d_salt != salt) {
-      LOG(" but the content is not valid, or has a different salt or iterations count" << endl);
+      VLOG_NO_PREFIX(log, " but the content is not valid, or has a different salt or iterations count" << endl);
       return false;
     }
 
     const DNSName wcSigner = getSigner(wcEntry.d_signatures);
     /* It's an exact match for the wildcard, so it does exist. If we are looking for a DS
        an ancestor NSEC3 is fine, otherwise it does not prove anything. */
-    if (type != QType::DS && isNSEC3AncestorDelegation(wcSigner, wcEntry.d_owner, nsec3)) {
+    if (type != QType::DS && isNSEC3AncestorDelegation(wcSigner, wcEntry.d_owner, *nsec3)) {
       /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
          Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
          nonexistence of any RRs below that zone cut, which include all RRs at
          that (original) owner name other than DS RRs, and all RRs below that
          owner name regardless of type.
       */
-      LOG(" but the NSEC3 covering the wildcard is an ancestor delegation NSEC3, bailing out" << endl);
+      VLOG_NO_PREFIX(log, " but the NSEC3 covering the wildcard is an ancestor delegation NSEC3, bailing out" << endl);
       return false;
     }
 
     if (type == QType::DS && !name.isRoot() && wcSigner == name) {
-      LOG(" but this wildcard NSEC3 comes from the child zone and cannot be used to deny a DS");
+      VLOG_NO_PREFIX(log, " but this wildcard NSEC3 comes from the child zone and cannot be used to deny a DS");
       return false;
     }
 
-    if (!isTypeDenied(nsec3, type)) {
-      LOG(" but the requested type (" << type.toString() << ") does exist" << endl);
-      return synthesizeFromNSEC3Wildcard(now, name, type, ret, res, doDNSSEC, nextCloserEntry, wildcard);
+    if (!isTypeDenied(*nsec3, type)) {
+      VLOG_NO_PREFIX(log, " but the requested type (" << type.toString() << ") does exist" << endl);
+      return synthesizeFromNSEC3Wildcard(now, name, type, ret, res, doDNSSEC, nextCloserEntry, wildcard, log);
     }
 
     res = RCode::NoError;
-    LOG(endl);
+    VLOG(log, endl);
   }
   else {
     if (!isCoveredByNSEC3Hash(DNSName(wcHash) + zone, wcEntry.d_owner, wcEntry.d_next)) {
-      LOG("No covering record found for the wildcard in aggressive cache" << endl);
+      VLOG(log, name << ": No covering record found for the wildcard in aggressive cache" << endl);
       return false;
     }
 
-    auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(wcEntry.d_record);
+    auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(wcEntry.d_record);
     if (!nsec3 || nsec3->d_iterations != iterations || nsec3->d_salt != salt) {
-      LOG("The content of the NSEC3 covering the wildcard is not valid, or has a different salt or iterations count" << endl);
+      VLOG(log, name << ": The content of the NSEC3 covering the wildcard is not valid, or has a different salt or iterations count" << endl);
       return false;
     }
 
     const DNSName wcSigner = getSigner(wcEntry.d_signatures);
     if (type == QType::DS && !name.isRoot() && wcSigner == name) {
-      LOG(" but this wildcard NSEC3 comes from the child zone and cannot be used to deny a DS");
+      VLOG_NO_PREFIX(log, " but this wildcard NSEC3 comes from the child zone and cannot be used to deny a DS");
       return false;
     }
 
@@ -724,22 +764,23 @@ bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded
   }
 
   addToRRSet(now, soaSet, soaSignatures, zone, doDNSSEC, ret);
-  addRecordToRRSet(now, closestNSEC3.d_owner, QType::NSEC3, closestNSEC3.d_ttd - now, closestNSEC3.d_record, closestNSEC3.d_signatures, doDNSSEC, ret);
+  addRecordToRRSet(closestNSEC3.d_owner, QType::NSEC3, closestNSEC3.d_ttd - now, closestNSEC3.d_record, closestNSEC3.d_signatures, doDNSSEC, ret);
 
   /* no need to include the same NSEC3 twice */
   if (nextCloserEntry.d_owner != closestNSEC3.d_owner) {
-    addRecordToRRSet(now, nextCloserEntry.d_owner, QType::NSEC3, nextCloserEntry.d_ttd - now, nextCloserEntry.d_record, nextCloserEntry.d_signatures, doDNSSEC, ret);
+    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) {
-    addRecordToRRSet(now, wcEntry.d_owner, QType::NSEC3, wcEntry.d_ttd - now, wcEntry.d_record, wcEntry.d_signatures, doDNSSEC, ret);
+    // coverity[store_truncates_time_t]
+    addRecordToRRSet(wcEntry.d_owner, QType::NSEC3, wcEntry.d_ttd - now, wcEntry.d_record, wcEntry.d_signatures, doDNSSEC, ret);
   }
 
-  LOG("Found valid NSEC3s covering the requested name and type!" << endl);
+  VLOG(log, name << ": Found valid NSEC3s covering the requested name and type!" << endl);
   ++d_nsec3Hits;
   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)
+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) {
@@ -771,15 +812,15 @@ bool AggressiveNSECCache::getDenial(time_t now, const DNSName& name, const QType
 
   vState cachedState;
   std::vector<DNSRecord> soaSet;
-  std::vector<std::shared_ptr<RRSIGRecordContent>> soaSignatures;
+  std::vector<std::shared_ptr<const RRSIGRecordContent>> soaSignatures;
   /* we might not actually need the SOA if we find a matching wildcard, but let's not bother for now */
-  if (g_recCache->get(now, zone, QType::SOA, true, &soaSet, who, false, routingTag, doDNSSEC ? &soaSignatures : nullptr, nullptr, nullptr, &cachedState) <= 0 || cachedState != vState::Secure) {
-    LOG("No valid SOA found for " << zone << ", which is the best match for " << name << endl);
+  if (g_recCache->get(now, zone, QType::SOA, MemRecursorCache::RequireAuth, &soaSet, who, routingTag, doDNSSEC ? &soaSignatures : nullptr, nullptr, nullptr, &cachedState) <= 0 || cachedState != vState::Secure) {
+    VLOG(log, name << ": No valid SOA found for " << zone << ", which is the best match for " << name << endl);
     return false;
   }
 
   if (nsec3) {
-    return getNSEC3Denial(now, zoneEntry, soaSet, soaSignatures, name, type, ret, res, doDNSSEC);
+    return getNSEC3Denial(now, zoneEntry, soaSet, soaSignatures, name, type, ret, res, doDNSSEC, log, validationContext);
   }
 
   ZoneEntry::CacheEntry entry;
@@ -787,63 +828,63 @@ bool AggressiveNSECCache::getDenial(time_t now, const DNSName& name, const QType
   bool covered = false;
   bool needWildcard = false;
 
-  LOG("Looking for a NSEC before " << name);
+  VLOG(log, name << ": Looking for a NSEC before " << name);
   if (!getNSECBefore(now, zoneEntry, name, entry)) {
-    LOG(": nothing found in the aggressive cache" << endl);
+    VLOG_NO_PREFIX(log, ": nothing found in the aggressive cache" << endl);
     return false;
   }
 
-  auto content = std::dynamic_pointer_cast<NSECRecordContent>(entry.d_record);
+  auto content = std::dynamic_pointer_cast<const NSECRecordContent>(entry.d_record);
   if (!content) {
     return false;
   }
 
-  LOG(": found a possible NSEC at " << entry.d_owner << " ");
+  VLOG_NO_PREFIX(log, ": found a possible NSEC at " << entry.d_owner << " ");
   // note that matchesNSEC() takes care of ruling out ancestor NSECs for us
-  auto denial = matchesNSEC(name, type.getCode(), entry.d_owner, content, entry.d_signatures);
+  auto denial = matchesNSEC(name, type.getCode(), entry.d_owner, *content, entry.d_signatures, log);
   if (denial == dState::NODENIAL || denial == dState::INCONCLUSIVE) {
-    LOG(" but it does no cover us" << endl);
+    VLOG_NO_PREFIX(log, " but it does not cover us" << endl);
     return false;
   }
   else if (denial == dState::NXQTYPE) {
     covered = true;
-    LOG(" and it proves that the type does not exist" << endl);
+    VLOG_NO_PREFIX(log, " and it proves that the type does not exist" << endl);
     res = RCode::NoError;
   }
   else if (denial == dState::NXDOMAIN) {
-    LOG(" and it proves that the name does not exist" << endl);
+    VLOG_NO_PREFIX(log, " and it proves that the name does not exist" << endl);
     DNSName closestEncloser = getClosestEncloserFromNSEC(name, entry.d_owner, entry.d_next);
     DNSName wc = g_wildcarddnsname + closestEncloser;
 
-    LOG("Now looking for a NSEC before the wildcard " << wc);
+    VLOG(log, name << ": Now looking for a NSEC before the wildcard " << wc);
     if (!getNSECBefore(now, zoneEntry, wc, wcEntry)) {
-      LOG(": nothing found in the aggressive cache" << endl);
+      VLOG_NO_PREFIX(log, ": nothing found in the aggressive cache" << endl);
       return false;
     }
 
-    LOG(": found a possible NSEC at " << wcEntry.d_owner << " ");
+    VLOG_NO_PREFIX(log, ": found a possible NSEC at " << wcEntry.d_owner << " ");
 
-    auto nsecContent = std::dynamic_pointer_cast<NSECRecordContent>(wcEntry.d_record);
+    auto nsecContent = std::dynamic_pointer_cast<const NSECRecordContent>(wcEntry.d_record);
 
-    denial = matchesNSEC(wc, type.getCode(), wcEntry.d_owner, nsecContent, wcEntry.d_signatures);
+    denial = matchesNSEC(wc, type.getCode(), wcEntry.d_owner, *nsecContent, wcEntry.d_signatures, log);
     if (denial == dState::NODENIAL || denial == dState::INCONCLUSIVE) {
 
       if (wcEntry.d_owner == wc) {
-        LOG(" proving that the wildcard does exist" << endl);
-        return synthesizeFromNSECWildcard(now, name, type, ret, res, doDNSSEC, entry, wc);
+        VLOG_NO_PREFIX(log, " proving that the wildcard does exist" << endl);
+        return synthesizeFromNSECWildcard(now, name, type, ret, res, doDNSSEC, entry, wc, log);
       }
 
-      LOG(" but it does no cover us" << endl);
+      VLOG_NO_PREFIX(log, " but it does no cover us" << endl);
 
       return false;
     }
     else if (denial == dState::NXQTYPE) {
-      LOG(" and it proves that there is a matching wildcard, but the type does not exist" << endl);
+      VLOG_NO_PREFIX(log, " and it proves that there is a matching wildcard, but the type does not exist" << endl);
       covered = true;
       res = RCode::NoError;
     }
     else if (denial == dState::NXDOMAIN) {
-      LOG(" and it proves that there is no matching wildcard" << endl);
+      VLOG_NO_PREFIX(log, " and it proves that there is no matching wildcard" << endl);
       covered = true;
       res = RCode::NXDomain;
     }
@@ -859,14 +900,16 @@ 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);
-  addRecordToRRSet(now, entry.d_owner, QType::NSEC, entry.d_ttd - now, entry.d_record, entry.d_signatures, 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) {
-    addRecordToRRSet(now, wcEntry.d_owner, QType::NSEC, wcEntry.d_ttd - now, wcEntry.d_record, wcEntry.d_signatures, doDNSSEC, ret);
+    // coverity[store_truncates_time_t]
+    addRecordToRRSet(wcEntry.d_owner, QType::NSEC, wcEntry.d_ttd - now, wcEntry.d_record, wcEntry.d_signatures, doDNSSEC, ret);
   }
 
-  LOG("Found valid NSECs covering the requested name and type!" << endl);
+  VLOG(log, name << ": Found valid NSECs covering the requested name and type!" << endl);
   ++d_nsecHits;
   return true;
 }
index eb9d2bfa01e48c4f6ce9f255c5040d85654e0d63..6c7dfbb709023383208f1e092f0115505ff3f99d 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>
@@ -35,17 +36,33 @@ using namespace ::boost::multi_index;
 #include "dnsrecords.hh"
 #include "lock.hh"
 #include "stat_t.hh"
+#include "logger.hh"
+#include "validate.hh"
 
 class AggressiveNSECCache
 {
 public:
+  static constexpr uint8_t s_default_maxNSEC3CommonPrefix = 10;
+  static uint64_t s_nsec3DenialProofMaxCost;
+  static uint8_t s_maxNSEC3CommonPrefix;
+
   AggressiveNSECCache(uint64_t entries) :
     d_maxEntries(entries)
   {
   }
 
-  void insertNSEC(const DNSName& zone, const DNSName& owner, const DNSRecord& record, const std::vector<std::shared_ptr<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);
+  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, pdns::validation::ValidationContext& validationContext, const OptLog& log = std::nullopt);
 
   void removeZoneInfo(const DNSName& zone, bool subzones);
 
@@ -74,6 +91,9 @@ public:
     return d_nsec3WildcardHits;
   }
 
+  // exported for unit test purposes
+  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);
 
@@ -102,8 +122,8 @@ private:
 
     struct CacheEntry
     {
-      std::shared_ptr<DNSRecordContent> d_record;
-      std::vector<std::shared_ptr<RRSIGRecordContent>> d_signatures;
+      std::shared_ptr<const DNSRecordContent> d_record;
+      std::vector<std::shared_ptr<const RRSIGRecordContent>> d_signatures;
 
       DNSName d_owner;
       DNSName d_next;
@@ -132,9 +152,9 @@ 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<RRSIGRecordContent>>& soaSignatures, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC);
-  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);
-  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);
+  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&);
 
   /* slowly updates d_entriesCount */
   void updateEntriesCount(SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& zones);
@@ -145,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/ascii.hh b/pdns/recursordist/ascii.hh
deleted file mode 120000 (symlink)
index 6d5e3eb..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../ascii.hh
\ No newline at end of file
diff --git a/pdns/recursordist/auth-catalogzone.hh b/pdns/recursordist/auth-catalogzone.hh
new file mode 120000 (symlink)
index 0000000..6883b89
--- /dev/null
@@ -0,0 +1 @@
+../auth-catalogzone.hh
\ No newline at end of file
diff --git a/pdns/recursordist/burtle.hh b/pdns/recursordist/burtle.hh
new file mode 120000 (symlink)
index 0000000..0a00339
--- /dev/null
@@ -0,0 +1 @@
+../burtle.hh
\ No newline at end of file
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 c8bf30decd3532b72393aa9d0e98c4bbf9c7baef..241e0386d19da438306d8f386fe043c23bfee72c 100644 (file)
@@ -12,8 +12,8 @@ AC_CONFIG_HEADERS([config.h])
 
 AC_CANONICAL_HOST
 # Add some default CFLAGS and CXXFLAGS, can be appended to using the environment variables
-CFLAGS="-Wall -Wextra -Wshadow -Wno-unused-parameter -Wmissing-declarations -Wredundant-decls -fvisibility=hidden -g -O2 $CFLAGS"
-CXXFLAGS="-Wall -Wextra -Wshadow -Wno-unused-parameter -Wmissing-declarations -Wredundant-decls -fvisibility=hidden -g -O2 $CXXFLAGS"
+CFLAGS="-Wall -Wextra -Wshadow -Wmissing-declarations -Wredundant-decls -fvisibility=hidden -g -O2 $CFLAGS"
+CXXFLAGS="-Wall -Wextra -Wshadow -Wmissing-declarations -Wredundant-decls -fvisibility=hidden -g -O2 $CXXFLAGS"
 
 AC_SUBST([pdns_configure_args],["$ac_configure_args"])
 AC_DEFINE_UNQUOTED([PDNS_CONFIG_ARGS],
@@ -35,11 +35,16 @@ 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
 PTHREAD_SET_NAME
+AC_FUNC_STRERROR_R
 
 PDNS_CHECK_CLOCK_GETTIME
 
@@ -62,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"], [
@@ -98,7 +104,7 @@ PDNS_ENABLE_DNS_OVER_TLS
 AS_IF([test "x$enable_dns_over_tls" != "xno"], [
   PDNS_WITH_LIBSSL
   # not runtime selectable at the moment
-  # PDNS_WITH_GNUTLS 
+  # PDNS_WITH_GNUTLS
 
   AS_IF([test "x$HAVE_LIBSSL" != "x1"], [
     AC_MSG_ERROR([DNS over TLS support requested but no OpenSSL available])
@@ -111,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
 
@@ -149,9 +159,12 @@ AS_IF([test "x$enable_hardening" != "xno"], [
   AC_LD_RELRO
 ])
 
+PDNS_INIT_AUTO_VARS
 PDNS_ENABLE_SANITIZERS
+PDNS_ENABLE_LTO
 PDNS_ENABLE_MALLOC_TRACE
 PDNS_ENABLE_VALGRIND
+
 AX_AVAILABLE_SYSTEMD
 AX_CHECK_SYSTEMD_FEATURES
 AM_CONDITIONAL([HAVE_SYSTEMD], [ test x"$systemd" = "xy" ])
@@ -181,12 +194,18 @@ CXXFLAGS="$SANITIZER_FLAGS $PIE_CFLAGS $CXXFLAGS"
 PROGRAM_LDFLAGS="$PIE_LDFLAGS $PROGRAM_LDFLAGS"
 AC_SUBST([PROGRAM_LDFLAGS])
 
+CCVERSION=`$CC --version | head -1`
+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
 
@@ -200,8 +219,8 @@ AS_IF([test "x$pdns_configure_args" != "x"],
 )
 AC_MSG_NOTICE([PowerDNS Recursor $VERSION configured with: $summary_conf_opts])
 AC_MSG_NOTICE([])
-AC_MSG_NOTICE([CC: $CC])
-AC_MSG_NOTICE([CXX: $CXX])
+AC_MSG_NOTICE([CC: $CC ($CCVERSION)])
+AC_MSG_NOTICE([CXX: $CXX ($CXXVERSION)])
 AC_MSG_NOTICE([LD: $LD])
 AC_MSG_NOTICE([CFLAGS: $CFLAGS])
 AC_MSG_NOTICE([CPPFLAGS: $CPPFLAGS])
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 826f4a426abbe51380e96d39312f1b996fb11b12..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.6.
+The currently supported release train of the PowerDNS Recursor is 5.0.
 
-PowerDNS Recursor 4.5 will only receive critical updates and will be
-end of life after PowerDNS Recursor 4.8 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.4 will only receive critical updates and will be
-end of life after PowerDNS Recursor 4.7 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.3, 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,34 @@ 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 30 2023
+     - June 30 2024
+   * - 4.7
+     - May 30 2022
+     - December 12 2022
+     - EOL January 10 2024
    * - 4.6
      - December 17 2021
-     - ~ May 2022
-     - EOL ~ May 2023
+     - May 30 2022
+     - EOL June 30 2023
    * - 4.5
      - May 11 2021
      - December 17 2021
-     - EOL ~ December 2022
+     - EOL December 12 2022
    * - 4.4
      - October 19 2020
      - May 11 2021
-     - EOL ~ May 2022
+     - EOL May 30 2022
    * - 4.3
      - March 3 2020
      - October 19 2020
index 9b9a725da54bcd7272ad05769114d8d4f4fe45d1..74a2e30535ee413b024ea4ce78c7fc64c446bbb8 100644 (file)
@@ -56,3 +56,33 @@ Or, in a diagram::
                  |
       client bufsize (stub => recursor)
      bufsize reported to client (recursor => stub [always 512])
+
+.. _handling-of-root-hints:
+
+
+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, :program:`Recursor` wil use a compiled-in table as root hints.
+
+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.
+
+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. Newer versions solve this by querying the needed information top-down.
+
index 15ee104aa41bce8712b939194000c3e23f44aef1..3e76072dd64d49cc1a5310d37760f1954b827115 100644 (file)
@@ -1,7 +1,7 @@
-Compiling the PowerDNS Recursor
-===============================
+Compiling :program:`PowerDNS Recursor`
+======================================
 
-As the PowerDNS Recursor is distributed with a configure script, compiling it is a matter of::
+As :program:`PowerDNS Recursor` is distributed with a configure script, compiling it is a matter of::
 
   tar xf pdns-recursor-$VERSION.tar.bz2
   cd pdns-recursor-$VERSION
@@ -12,6 +12,9 @@ As the PowerDNS Recursor is distributed with a configure script, compiling it is
 Getting the sources
 -------------------
 
+.. warning::
+   Do not use the tarballs auto-generated by GitHub from the tags, as these are not proper release tarballs.
+
 There are 3 ways of getting the source.
 
 If you want the bleeding edge, you can clone the `repository at GitHub <https://github.com/PowerDNS/pdns>`__ and run ``autoreconf -vi`` in the ``pdns/recursordist`` directory of the clone.
@@ -26,16 +29,69 @@ These releases are PGP-signed with one of these key-ids:
 Dependencies
 ------------
 
-To build the PowerDNS Recursor, a C++ compiler with support for C++ 2011 is required.
-This means gcc 4.9 and newer and clang 3.5 and newer.
-Furthermore, the Makefiles require GNU make, not BSD make.
+To build :program:`PowerDNS Recursor`, a C++ compiler with support for C++ 2017 is required.
+This means ``gcc 5`` and newer and ``clang 5`` and newer.
+Furthermore, the Makefiles require GNU ``make``, not BSD ``make``.
 
-By default, the PowerDNS recursor requires the following libraries and headers:
+By default, the :program:`Recursor` requires the following libraries and headers:
 
 * `Boost <http://boost.org/>`_ 1.35 or newer
 * `Lua <http://www.lua.org/>`_ 5.1+ or `LuaJit <http://luajit.org/>`_
 * `OpenSSL <https://openssl.org>`_
 
+.. note::
+   On Debian and Ubuntu, the following will get you the dependencies::
+
+      apt-get install libboost-dev libboost-filesystem-dev libboost-serialization-dev \
+         libboost-system-dev libboost-thread-dev libboost-context-dev \
+         libboost-test-dev libssl-dev libboost-test-dev g++ make pkg-config \
+         libluajit-5.1-dev
+
+Compiling from a git checkout
+-----------------------------
+Source code is available on GitHub::
+
+   git clone https://github.com/PowerDNS/pdns.git
+
+This repository contains the sources for the PowerDNS Recursor, the PowerDNS
+Authoritative Server, and dnsdist (a powerful DNS loadbalancer). The sources for
+the recursor are located in the `pdns/recursordist` subdirectory of the repository.
+
+To compile from a git checkout, install the dependencies above plus ragel, automake, autoconf, libtool, virtualenv and curl.
+Then run::
+
+    cd pdns/pdns/recursordist/
+    autoreconf -vi
+    ./configure
+    make
+
+
+macOS Notes
+-----------
+
+If you want to compile yourself, the dependencies can be installed using
+Homebrew. You need to tell configure where to find OpenSSL, too::
+
+    brew install boost lua pkg-config ragel openssl
+    ./configure PKG_CONFIG_PATH=/usr/local/opt/openssl/lib/pkgconfig
+    make -j4
+
+
+Lua scripting
+^^^^^^^^^^^^^
+To benefit from Lua scripting, as described on https://doc.powerdns.com/md/recursor/scripting/
+Install Lua and development headers. PowerDNS supports Lua 5.1, 5.2, 5.3 and LuaJIT.
+On Debian/Ubuntu, install e.g. `liblua5.2-dev` to use Lua 5.2.
+
+The configure script will automatically detect the Lua version. If more than one
+version of Lua is installed, the `--with-lua` configure flag can be set to the
+desired version. e.g.::
+
+    ./configure --with-lua=lua51
+
+(On older versions of Debian/Ubuntu, you'll need to pass `--with-lua=lua5.1` instead.)
+
+
 Optional dependencies
 ---------------------
 
@@ -45,7 +101,7 @@ These will require additional dependencies
 ed25519 support with libsodium
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-The PowerDNS Recursor can link with `libsodium <https://download.libsodium.org/doc/>`_ to support ed25519 (DNSSEC algorithm 15).
+The :program:`Recursor` can link with `libsodium <https://download.libsodium.org/doc/>`_ to support ed25519 (DNSSEC algorithm 15).
 To detect libsodium, use the ``--with-libsodium`` configure option.
 
 .. versionchanged:: 4.2.0
@@ -54,7 +110,7 @@ To detect libsodium, use the ``--with-libsodium`` configure option.
 ed25519 and ed448 support with libdecaf
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-`libdecaf <https://sourceforge.net/projects/ed448goldilocks/>`_ is a library that allows the PowerDNS Recursor to support ed25519 and Ed448 (DNSSEC algorithms 15 and 16).
+`libdecaf <https://sourceforge.net/projects/ed448goldilocks/>`_ is a library that allows :program:`Recursor` to support ed25519 and Ed448 (DNSSEC algorithms 15 and 16).
 To detect libdecaf, use the ``--with-libdecaf`` configure option.
 
 .. versionchanged:: 4.2.0
@@ -63,13 +119,22 @@ To detect libdecaf, use the ``--with-libdecaf`` configure option.
 Protobuf to emit DNS logs
 ^^^^^^^^^^^^^^^^^^^^^^^^^
 
-The PowerDNS Recursor can log DNS query information over :doc:`Protocol Buffers <../lua-config/protobuf>`.
+The :program:`Recursor` can log DNS query information over :doc:`Protocol Buffers <../lua-config/protobuf>`.
 
 This functionality from 4.5.0 and upwards, without needing any external library. Before 4.5.0, installing the  `protobuf <https://developers.google.com/protocol-buffers/>`_ library and compiler is required to enable this functionality. The configure script will automatically detect this and bump the Boost version dependency to 1.42. To disable building this functionality before 4.5.0, use ``--without-protobuf``.
 
-systemd notify support
-^^^^^^^^^^^^^^^^^^^^^^
+``systemd`` notify support
+^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 During configure, ``configure`` will attempt to detect the availability of `systemd or systemd-daemon <https://freedesktop.org/wiki/Software/systemd/>`_ headers.
-To force the use of systemd (and failing configure if the headers do not exist), use ``--enable-systemd``.
+To force the use of ``systemd`` (and failing configure if the headers do not exist), use ``--enable-systemd``.
 To set the directory where the unit files should be installed, use ``--with-systemd=/path/to/unit/dir``.
+
+.. note::
+   If you want systemd support, you will need to install the corresponding development package. On Debian and Ubuntu, this means `apt install libsystemd-dev`.
+
+Documentation
+-------------
+After compiling, run `pdns\_recursor --config` to view the configuration options
+and a short description. The full documentation is online at
+https://doc.powerdns.com/recursor/
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
index 66b75a26c99e8b898b903fc7ca85d031a14dd587..371ac1970fbcdebe54ac2ab4ce700b090ea486bf 100644 (file)
@@ -445,6 +445,39 @@ new data should overwrite old data.
 Note that PowerDNS deviates from RFC 2181 (section 5.4.1) in this
 respect.
 
+Starting with version 4.7.0, there is a mechanism to save the
+parent NS set if it contains *more* names than the child NS set.
+This allows falling back to the saved parent NS set on resolution errors
+using the child specified NS set.
+As experience shows, this configuration error is encountered in the
+wild often enough to warrant this workaround.
+See :ref:`setting-save-parent-ns-set`.
+
+.. _serve-stale:
+
+Serve Stale
+-----------
+
+Starting with version 4.8.0, the Recursor implements ``Serve Stale`` (:rfc:`8767`).
+This is a mechanism that allows records in the record cache that are expired
+but that cannot be refreshed (due to network or authoritative server issues) to be served anyway.
+
+The :ref:`setting-serve-stale-extensions` determines how many times the records lifetime can be extended.
+Each extension of the lifetime of a record lasts 30s.
+A value of 1440 means the maximum extra life time is 30 * 1440 seconds which is 12 hours.
+If the original TTL of a record was less than 30s, the original TTLs will be used as extension period.
+
+On each extension an asynchronous task to resolve the name will be created.
+If that task succeeds, the record will not be served stale anymore, as an up-to-date value is now available.
+
+
+If :ref:`setting-serve-stale-extensions` is not zero expired records will be kept in the record cache until the number of records becomes too large.
+Cache eviction will then be done on a least-recently-used basis.
+
+When dumping the cache using ``rec_control dump-cache`` the ``ss`` value shows the serve stale extension count.
+A value of 0 means the record is not being served stale, while
+a positive value shows the number of times the serve stale period has been extended.
+
  Some small things
 ------------------
 
diff --git a/pdns/recursordist/docs/appendices/structuredlogging.rst b/pdns/recursordist/docs/appendices/structuredlogging.rst
new file mode 100644 (file)
index 0000000..d483663
--- /dev/null
@@ -0,0 +1,102 @@
+Structured Logging Dictionary
+=============================
+
+This page describes the common entries of the Structured Logging component.
+Currently there are two possible values for :ref:`setting-structured-logging-backend`:
+
+- The ``default`` text based backend
+- The ``systemd-journal`` backend
+
+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
+
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 ee66bbc635b7f3aadab95a938d0c1bfa76a79fd1..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,
@@ -832,7 +832,7 @@ See :doc:`EOL Statements <../appendices/EOL>`.
     :tags: Internals, Bug Fixes
     :pullreq: 5917
 
-    Use ``_exit()`` when we really really want to exit, for example
+    Use ``_exit()`` when we *really* want to exit, for example
     after a fatal error. This stops us dying while we die. A call to
     ``exit()`` will trigger destructors, which may paradoxically stop
     the process from exiting, taking down only one thread, but harming
@@ -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 2b729a3e75a46f074fd1e21a86b47ec659ed3b59..8afaee00504b3d9ce586659d78e02d13753eb60c 100644 (file)
@@ -1,6 +1,19 @@
 Changelogs for 4.4.x
 ====================
 
+.. changelog::
+  :version: 4.4.8
+  :released: 25th of March 2022
+
+  This is a security fix release for :doc:`PowerDNS Security Advisory 2022-01 <../security-advisories/powerdns-advisory-2022-01>`.
+  Additionally, because CentOS 8 is End Of Life now, we have switched those builds to Oracle Linux 8. The resulting packages are compatible with RHEL and all derivatives.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11456
+
+    Fix validation of incremental zone transfers (IXFRs). 
+
 .. changelog::
   :version: 4.4.7
   :released: 5th of November 2021
index 371f9c5bdbf5a03d0440e5d69e3eadb70727ff7d..08d3a717a28f30af80541a9dcfc44acf942fc45d 100644 (file)
@@ -1,5 +1,131 @@
 Changelogs for 4.5.X
 ====================
+
+.. changelog::
+  :version: 4.5.12
+  :released: 25th of November 2022
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12228
+    :tickets: 12198
+
+    Correct skip record condition in processRecords.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12225
+    :tickets: 12189, 12199
+
+    Also consider recursive forward in the "forwarded DS should not end up in negCache code."
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12192
+    :tickets: 12125
+
+    Timeout handling for IXFRs as a client.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12169
+    :tickets: 12081
+
+    Log invalid RPZ content when obtained via IXFR.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12166
+    :tickets: 12038
+
+    When an expired NSEC3 entry is seen, move it to the front of the expiry queue.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12165
+    :tickets: 11337, 11338
+
+    QType ADDR is supposed to be used internally only.
+
+.. changelog::
+  :version: 4.5.11
+  :released: 20th of September 2022
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11939
+    :tickets: 11904
+
+    For zones having many NS records, we are not interested in all so take a sample.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11942
+    :tickets: 11890
+
+    Failure to retrieve DNSKEYs of an Insecure zone should not be fatal.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11899
+    :tickets: 11848
+
+    Also check qperq limit if throttling happened, as it increases counters.
+
+.. changelog::
+  :version: 4.5.10
+  :released: 23rd of August 2022
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11875,11874
+
+    PowerDNS Security Advisory 2022-02: incomplete exception handling related to protobuf message generation.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11634,11609
+
+    Fix API issue when asking config values for allow-from or allow-notify-from.
+
+.. changelog::
+  :version: 4.5.9
+  :released: 4th of April 2022
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11419
+    :tickets: 11371
+
+    Be more careful using refresh mode only for the record asked.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11384
+    :tickets: 11300
+
+    Use the Lua context stored in SyncRes when calling hooks.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11024
+    :tickets: 10994, 11010
+
+    Do cache negative results, even when wasVariable() is true.
+
+.. changelog::
+  :version: 4.5.8
+  :released: 25th of March 2022
+
+  This is a security fix release for :doc:`PowerDNS Security Advisory 2022-01 <../security-advisories/powerdns-advisory-2022-01>`.
+  Additionally, because CentOS 8 is End Of Life now, we have switched those builds to Oracle Linux 8. The resulting packages are compatible with RHEL and all derivatives.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11457
+
+    Fix validation of incremental zone transfers (IXFRs).
+
 .. changelog::
   :version: 4.5.7
   :released: 5th of November 2021
index b8caa6c7fa3451e875f6be8742e59482f445373b..30b9bc41dc4f7bf2b5699fa9acae7146d9208f01 100644 (file)
@@ -1,6 +1,210 @@
 Changelogs for 4.6.X
 ====================
 
+.. changelog::
+  :version: 4.6.6
+  :released: 29th of March 2023
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12702
+
+    PowerDNS Security Advisory 2023-02: Deterred spoofing attempts can lead to authoritative servers being marked unavailable.
+
+.. changelog::
+  :version: 4.6.5
+  :released: 25th of November 2022
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12229
+    :tickets: 12198
+
+    Correct skip record condition in processRecords.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12226
+    :tickets: 12189, 12199
+
+    Also consider recursive forward in the "forwarded DS should not end up in negCache code."
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12191
+    :tickets: 12125
+
+    Timeout handling for IXFRs as a client.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12172
+    :tickets: 12066
+
+    Detect invalid bytes in makeBytesFromHex().
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12170
+    :tickets: 12081
+
+    Log invalid RPZ content when obtained via IXFR.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12167
+    :tickets: 12038
+
+    When an expired NSEC3 entry is seen, move it to the front of the expiry queue.
+
+.. changelog::
+  :version: 4.6.4
+  :released: 20th of September 2022
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11937
+    :tickets: 11904
+
+    For zones having many NS records, we are not interested in all so take a sample.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11941
+    :tickets: 11890
+
+    Failure to retrieve DNSKEYs of an Insecure zone should not be fatal.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11898
+    :tickets: 11848
+
+    Also check qperq limit if throttling happened, as it increases counters.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11775
+    :tickets: 11773
+
+    Resize answer length to actual received length in udpQueryResponse.
+
+.. changelog::
+  :version: 4.6.3
+  :released: 23th of August 2022
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11876,11874
+
+    PowerDNS Security Advisory 2022-02: incomplete exception handling related to protobuf message generation.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11633,11609
+
+    Fix API issue when asking config values for allow-from or allow-notify-from.
+
+.. changelog::
+  :version: 4.6.2
+  :released: 4th of April 2022
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11418
+    :tickets: 11371
+
+    Be more careful using refresh mode only for the record asked.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11380
+    :tickets: 11300
+
+    Use the Lua context stored in SyncRes when calling hooks.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11363
+    :tickets: 11338
+
+    QType ADDR is supposed to be used internally only.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11362
+    :tickets: 11327
+
+    If we get NODATA on an AAAA in followCNAMERecords, try native dns64.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11360
+    :tickets: 11283
+
+    Allow disabling of processing the root hints.
+
+  .. change::
+    :tags:  Improvements
+    :pullreq: 11361
+    :tickets: 11288
+
+    Log an error if pdns.DROP is used as rcode in Lua callbacks.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11359
+    :tickets: 11257
+
+    Initialize isNew before calling a exception throwing function.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11358
+    :tickets: 11245
+
+    A CNAME answer on DS query should abort DS retrieval.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11357
+    :tickets: 11225
+
+    Reject non-apex NSEC(3)s that have both the NS and SOA bits set.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11260
+
+    Fix build with OpenSSL 3.0.0.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11170
+    :tickets: 11137
+
+    Shorter thread names.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11169
+    :tickets: 11109
+
+    Two more features to print (DoT and scrypt).
+
+.. changelog::
+  :version: 4.6.1
+  :released: 25th of March 2022
+
+  This is a security fix release for :doc:`PowerDNS Security Advisory 2022-01 <../security-advisories/powerdns-advisory-2022-01>`.
+  Additionally, because CentOS 8 is End Of Life now, we have switched those builds to Oracle Linux 8. The resulting packages are compatible with RHEL and all derivatives.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11458
+
+    Fix validation of incremental zone transfers (IXFRs).
+
 .. changelog::
   :version: 4.6.0
   :released: 17th of December 2021
@@ -264,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 149f8cba6e52b464d685b75e65a89dc68e51da46..bbe7eb45488019240121f1d19384c0c976d1624c 100644 (file)
@@ -1,6 +1,302 @@
 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
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12701
+
+    PowerDNS Security Advisory 2023-02: Deterred spoofing attempts can lead to authoritative servers being marked unavailable.
+
+.. changelog::
+  :version: 4.7.4
+  :released: 25th of November 2022
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12231
+    :tickets: 12046
+
+    Fix compilation of the event ports multiplexer.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12230
+    :tickets: 12198
+
+    Correct skip record condition in processRecords.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12227
+    :tickets: 12189, 12199
+
+    Also consider recursive forward in the "forwarded DS should not end up in negCache code."
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12190
+    :tickets: 12125
+
+    Timeout handling for IXFRs as a client.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12173
+    :tickets: 12066
+
+    Detect invalid bytes in makeBytesFromHex().
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12171
+    :tickets: 12081
+
+    Log invalid RPZ content when obtained via IXFR.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12168
+    :tickets: 12038
+
+    When an expired NSEC3 entry is seen, move it to the front of the expiry queue.
+
+.. changelog::
+  :version: 4.7.3
+  :released: 20th of September 2022
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11936
+    :tickets: 11904
+
+    For zones having many NS records, we are not interested in all so take a sample.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11940
+    :tickets: 11890
+
+    Failure to retrieve DNSKEYs of an Insecure zone should not be fatal.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11897
+    :tickets: 11848
+
+    Also check qperq limit if throttling happened, as it increases counters.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11879
+    :tickets: 11850
+
+    Fix recursor not responsive after Lua config reload.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11847
+    :tickets: 11843
+
+    Clear the caches *after* loading authzones.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11774
+    :tickets: 11773
+
+    Resize answer length to actual received length in udpQueryResponse.
+
+.. changelog::
+  :version: 4.7.2
+  :released: 23th of August 2022
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11877,11874
+
+    PowerDNS Security Advisory 2022-02: incomplete exception handling related to protobuf message generation.
+
+.. changelog::
+  :version: 4.7.1
+  :released: 8th of July 2022
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11750
+    :tickets: 11726, 11724
+
+    Allow generic format while parsing zone files for ZoneToCache.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11748
+    :tickets: 11692
+
+    Run tasks from housekeeping thread in the proper way, causing
+    queued DoT probes to run more promptly. Thanks to Jerry Lundström!
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11740
+    :tickets: 11735
+
+    Force gzip compression for debian packages (Zash).
+
+.. changelog::
+  :version: 4.7.0
+  :released: 30th of May 2022
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11632
+    :tickets: 11609
+
+    Fix API issue when asking config values for allow-from or allow-notify-from.
+
+.. changelog::
+  :version: 4.7.0-rc1
+  :released: 6th of May 2022
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11559
+    :tickets: 11539
+
+    Prometheus #HELP texts: DNSSEC counters track responses sent, not actual validations performed.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11560
+    :tickets: 11541
+
+    Fix DoT port and protocol used for probed authoritative servers.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11538
+    :tickets: 11536
+
+    Fix Coverity 1487923 Out-of-bounds read (wrong use of sizeof).
+
+.. changelog::
+  :version: 4.7.0-beta1
+  :released: 14th of April 2022
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11487
+
+    Probe authoritative servers for DoT support (experimental).
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11524
+
+    Update moment.min.js (path traversal fix; we are unaffected).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11492
+
+    Add deferred mode for retrieving additional records.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11484
+
+    Use boost::mult-index for nsspeed table and make it shared.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11496
+
+    Prevent segfault with empty allow-from-file and allow-from options (Sven Wegener).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11312
+
+    Packet cache improvements: do not fill beyond limit and use strict LRU eviction method.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11444
+
+    Use nice format for timestamp printing.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11471
+
+    In the handler thread, call sd_notify() just before entering the main loop in RecursorThread.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11445
+    :tickets: 11440
+
+    Only log "Unable to send NOD lookup" if log-common-errors is set.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11443
+
+    Remember parent NS set, to be able to fallback to it if needed.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11396, 11507
+
+    Proxy by table: allow a table based mapping of source address.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11405
+
+    Distinguish between unreachable and timeout for throttling.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11397
+
+    Use correct task to clean outgoing TCP.
+
 .. changelog::
   :version: 4.7.0-alpha1
   :released: 28th of February 2022
diff --git a/pdns/recursordist/docs/changelog/4.8.rst b/pdns/recursordist/docs/changelog/4.8.rst
new file mode 100644 (file)
index 0000000..dfc66fd
--- /dev/null
@@ -0,0 +1,578 @@
+Changelogs for 4.8.X
+====================
+
+.. 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
+  :released: 29th of March 2023
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12700
+
+    PowerDNS Security Advisory 2023-02: Deterred spoofing attempts can lead to authoritative servers being marked unavailable.
+
+.. changelog::
+  :version: 4.8.3
+  :released: 7th of March 2023
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12613
+    :tickets: 12595, 12610, 12611
+
+    Fix serve-stale logic to not cause intermittent high CPU load by:
+
+    - correcting the removal of a negative cache entry,
+    - correcting the serve-stale main loop with respect to exception handling and
+    - correctly handle negcache entries with serve-state status.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12609
+    :tickets: 12598
+
+    Update validation state after a missing negative indication.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12608
+    :tickets: 12495
+
+    Change a few logging urgency levels
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12607
+    :tickets: 12347
+
+    Use correct name for isEntryUsable(). Existing code used the right logic but wrong name.
+
+.. changelog::
+  :version: 4.8.2
+  :released: 31th of January 2023
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12475
+    :tickets: 12467
+
+    Do not use "message" as key, it has a special meaning to systemd-journal.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12457
+    :tickets: 12395
+
+    When using serve-stale, wrong data can be returned from negative cache and record cache (zjs604381586).
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12456
+    :tickets: 12368
+
+    Add the 'parse packet from auth' error message to structured logging.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12455
+    :tickets: 12352
+
+    Refresh of negcache stale entry might use wrong qtype (zjs604381586).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12418
+    :tickets: 12374
+
+    Make cache cleaning of record an negative cache more fair when under pressure.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12408
+    :tickets: 12407
+
+    Do not chain ECS enabled queries, it can cause the wrong scope to be used for outgoing queries.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12346
+    :tickets: 12317
+
+    Fix compilation on FreeBSD. Reported by HellSpawn.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12345
+    :tickets: 12333
+
+    Do not report "not decreasing socket buf size" as an error.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12344
+    :tickets: 12260
+
+    Properly encode json string containing binary data.
+
+.. changelog::
+  :version: 4.8.1
+  :released: 20th of January 2023
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12442
+
+    Avoid unbounded recursion when retrieving DS records from some misconfigured domains. CVE-2023-22617.
+
+.. changelog::
+  :version: 4.8.0
+  :released: 12th of December 2022
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12293
+    :tickets: 12289
+
+    Refactor unsupported qtype code and make sure we ServFail on all unsupported qtypes.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12221
+    :tickets: 11776, 11376, 12078, 12219
+
+    Infra queries should not use refresh mode.
+
+.. changelog::
+  :version: 4.8.0-rc1
+  :released: 18th of November 2022
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12201
+    :tickets: 12189, 12199
+
+    Also consider recursive forward in the "forwarded DS should not end up in negCache" code.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12200
+    :tickets: 12198
+
+    Correct skip record condition in processRecords.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12197
+    :tickets: 12175
+
+    Get DS records with QName Minimization switched on.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12196
+    :tickets: 12194
+
+    Fix typo in structured logging key.
+
+.. changelog::
+  :version: 4.8.0-beta2
+  :released: 7th of November 2022
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12163
+    :tickets: 12155
+
+    Fix SNMP OID numbers for rcode stats.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12162
+    :tickets: 12122
+
+    Implement output operator for QTypes, avoids numeric qtypes in trace logs.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12161
+    :tickets: 12125
+
+    Handle IXFR connect and transfer timeouts.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12146
+    :tickets: 12063
+
+    Only replace protobuf logger config objects if the reload changed them.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12150
+    :tickets: 12140
+
+    Be more lenient replacing auth by non-auth records in cache.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12145
+    :tickets: 12081
+
+    Log invalid RPZ content when obtained via IXFR.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12147
+    :tickets: 12066
+
+    Detect invalid bytes in makeBytesFromHex().
+
+.. changelog::
+  :version: 4.8.0-beta1
+  :released: 5th of October 2022
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12047
+
+    Add support for NOD/UDR notifications using dnstap.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12048
+
+    Fix --config (should be equal to --config=default),  followup to #11907.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12046
+    :tickets: 12044
+
+    Fix compilation of the event ports multiplexer.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11903, 12049
+    :tickets: 11841
+
+    Protobuf and dnstap metrics, including rec_control subcommand to show them.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12038
+
+    When an expired NSEC3 entry is seen move it to the front of the expiry queue.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11949
+    :tickets: 7164
+
+    Provide metrics for rcode received from authoritative servers.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12027
+    :tickets: 11958
+
+    If new data is auth and existing data is not, replace even if cache locking is active.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11866
+    :tickets: 11648
+
+    Proxymapping metrics, including rec_control subcommand to show them.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11909
+
+    Add querytime attribute to Lua DNSQuestion object, to see the time a query was received.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11768
+    :tickets: 11766
+
+    Enable include-dir by default in RPM builds, to be in line with DEB builds (Frank Louwers).
+
+  .. change::
+    :tags: Removals
+    :pullreq: 11856
+
+    Remove XPF support.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11989
+
+    Improve error message when invalid values for `local-address` are provided in recursor config file.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12011
+    :tickets: 11999
+
+    Enable SNMP support for debian and ubuntu builds.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12009
+    :tickets: 11998
+
+    Warn if snmp-agent is set but SNMP support is not available.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11959
+
+    A few tweaks to structured logging calls.
+
+.. changelog::
+  :version: 4.8.0-alpha1
+  :released: 23rd of September 2022
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11958
+
+    Lock record cache entries if enabled by :ref:`setting-record-cache-locked-ttl-perc`.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11957
+
+    Use ``nullptr`` in ``getNSEC3PARAM`` + init ``bool`` at call site (Axel Viala).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11953
+    :tickets: 11804
+
+    Axfr-retriever: abort on chunk with TC set.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11955
+
+    Clarify return codes for the Lua hooks in the Recursor (Frank Louwers).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11907
+
+    Recursor: Add ``--config[=check|=diff|=default]``.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11776
+
+    Implement optional Serve stale functionality, enabled by :ref:`setting-serve-stale-extensions`..
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11906
+
+    Implement padding of (DoT) messages to authoritative servers, if set by :ref:`setting-edns-padding-out` (default ``yes``).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11800
+
+    Log socket directory path if there is a problem.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11862
+    :tickets: 11853
+
+    Libssl: Properly load ciphers and digests with OpenSSL 3.0.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11823
+
+    Handle Lua script loading errors.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11813
+    :tickets: 4979
+
+    Stop sending Server: header (Chris Hofstaedtler).
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11867
+    :tickets: 11864
+
+    rec_control: test for ``--version`` before requiring an argument.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11869
+    :tickets: 6981
+
+    Keep time and count metrics when maintenance is called.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11849
+
+    Consider dns64 processing in more cases than ``Rcode == NoError``.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11672
+
+    Make rec zone files with trailing dot (phonedph1).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11857
+    :tickets: 11855
+
+    Set ``rec_control_LDFLAGS``, needed for macOS or any platforms where libcrypto is not in default lib path.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11812
+
+    Replace/remove jQuery (Chris Hofstaedtler)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 11820
+    :tickets: 11818, 10079
+
+    Handle file related errors initially loading Lua script.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11811
+
+    Remove unused ``jsrender.js`` (Chris Hofstaedtler).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11780
+    :tickets: 11736
+
+    Save the last nameserver speed recorded plus output it in ``rec_control dump-nsspeeds``.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11754
+    :tickets: 11734
+
+    Set ``TCP_NODELAY`` on in and outgoing TCP.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11744
+
+    Remove > 5 check on TTL of glue from the cache.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11854,11714,11710,11693,11681,11662,11654,11642,11631
+
+    Structured logging for various subsystems.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11704,11779
+
+    Make edns table a sparse table.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11601
+
+    Shared ednsmap.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11682
+    :tickets: 2248
+
+    Load IPv6 entries from etc-hosts file.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11660,11709
+    :tickets: 11705, 11706
+
+    Use ``systemd-journal`` for structured logging if it is available and set by :ref:`setting-structured-logging-backend`.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11680,11671
+    :tickets: 11671,11654
+
+    Fix typos in stats log messages (Matt Nordhoff).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11598
+
+    Shared throttle map.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11381
+
+    Adaptive root refresh interval, normally at 80% of :ref:`setting-max-cache-ttl`.
+
+
+
diff --git a/pdns/recursordist/docs/changelog/4.9.rst b/pdns/recursordist/docs/changelog/4.9.rst
new file mode 100644 (file)
index 0000000..40d819f
--- /dev/null
@@ -0,0 +1,446 @@
+Changelogs for 4.9.X
+====================
+
+.. 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
+
+    Cleanup rcode enums: base one is 8 bit unsigned, extended one 16 bit unsigned
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12594
+
+    Sharded and shared packet cache.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12709
+
+    More fine-grained capping of packet cache TTL.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12655
+    :tickets: 12486
+
+    Rework root priming code to allow multiple addresses per NS.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 10072,12716
+
+    Update Debian packaging for Recursor, including removal of sysv init script (Chris Hofstaedtler).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12497
+
+    Unify shorthands for seconds in log messages (Josh Soref).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12674
+
+    Validate: Stop passing shared pointers all the way down.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12688
+
+    Re-establish "recursion depth is always increasing" invariant.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12672
+
+    Fix a dnsheader unaligned case.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12550,12540,12524,12516,12515,12513,12502,12501,12462,12412,12401
+
+    OpenSSL 3.0 compatibility.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12554
+
+    Serve-stale-extensions works on 30s so an hour should be 120. (Andreas Jakum)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12539
+
+    Fix doc typo (Matt Nordhoff).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12493
+
+    Only store NSEC3 records in aggressive cache if we expect them to be effective.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11777
+
+    rec_control trace-regex: trace to a file or stdout instead of the general log.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12495
+
+    Logging tweaks (Josh Soref).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12434
+
+    Unify trace logging code in syncres and validator.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12446,12695
+
+    Stack protector for mthread stacks.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12425
+
+    Change the way RD=0 forwarded queries are handled.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12381
+
+    Enable FORTIFY_SOURCE=3 when supported by the compiler.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12419
+    :tickets: 12374
+
+    Negcache dump code: close fd on fdopen fail.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12396
+
+    Introduce a thread-safe version of stringerror().
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12399
+    :tickets: 11138
+
+    Name recursor threads consistently with a "rec/" prefix.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12392
+
+    Be more careful saving errno in makeClientSocket() and closesocket()
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12373
+
+    Rec: Warn on high (90%) mthread stack usage.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12334,12691,12698
+
+    Rec: Generate EDE in more cases, specifically on unreachable auths or synthesized results.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12368
+
+    Add the 'parse packet from auth' error message to structured logging.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12292
+
+    Wrap the CURL raw pointers in smart pointers.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12318
+    :tickets: 12241
+
+    Reorganization: move recursor specific files to recursordist.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12193,12348,12323
+
+    Introducing TCounters.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12120
+    :tickets: 12090
+
+    If we encounter a loop in QM, continue with the next iteration.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12121
+    :tickets: 12080
+
+    More clear trace message for cache-only lookups.
+
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..000d37e
--- /dev/null
@@ -0,0 +1,469 @@
+Changelogs for 5.0.X
+====================
+
+Before upgrading, it is advised to read the :doc:`../upgrade`.
+
+.. 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 52f283db4cb23e55df0cb11ead93e97b016df27d..08ff1324b3034409d414a91f5ca62e44b27ec34e 100644 (file)
@@ -3,9 +3,14 @@ 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
     4.6
     4.5
index 1b73df39ae8bb474e7f46d700932f4955c298d61..096ce635b41fbf8282a8d48c10134bf1f97ab697 100644 (file)
@@ -923,7 +923,7 @@ Improvements
    1677 <http://wiki.powerdns.com/projects/trac/changeset/1677>`__.
 -  On some platforms, it may be better to have PowerDNS itself
    distribute queries over threads (instead of leaving it up to the
-   kernel). This experimental feature can be enabled with the
+   kernel). This is an experimental feature and can be enabled with the
    'pdns-distributes-queries' setting. Code in `commit
    1678 <http://wiki.powerdns.com/projects/trac/changeset/1678>`__ and
    beyond. Speeds up Solaris measurably.
@@ -1018,7 +1018,7 @@ Changes between RC2 and -release
 -  'Make install' when an existing configuration file contained a 'fork'
    statement has been fixed. Spotted by Darren Gamble, code in `commit
    1534 <http://wiki.powerdns.com/projects/trac/changeset/1534>`__.
--  Reloading a non-existent allow-from-file caused the control thread to
+-  Reloading a nonexistent allow-from-file caused the control thread to
    stop working. Spotted by Imre Gergely, code in `commit
    1532 <http://wiki.powerdns.com/projects/trac/changeset/1532>`__.
 -  Parser got confused by reading en empty line in auth-forward-zones.
index 9e3a31cbfaa980df7f01fee86904417c194a8edf..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
@@ -94,7 +94,7 @@ changelog_render_ticket = "https://github.com/PowerDNS/pdns/issues/%s"
 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']
+changelog_sections = ['New Features', 'Improvements', 'Bug Fixes', 'Removals']
 changelog_inner_tag_sort = ['General', 'DNSSEC', 'Protobuf', 'RPZ']
 
 changelog_hide_tags_in_entry = True
index 1f7b9ff105f5190c29d33f51ff24d25bb5b73bfe..e6827b81f871a24cf2bf8a743d4de3ffc47dd8bc 100644 (file)
@@ -10,6 +10,24 @@ We do this by retrieving the A records for ``www.example.com``, and translating
 Elsewhere, a NAT64 device listens on these IPv6 addresses, and extracts the IPv4 address from each packet, and proxies it on.
 
 As of 4.4.0, an efficient implementation is built the recursor and can be enabled via the using the :ref:`dns64-prefix setting <setting-dns64-prefix>`.
+
+Native DNS64 support
+--------------------
+Native DNS64 processing will happen after calling a ``nodata`` or ``nxdomain`` Lua hook (if defined), but before calling a ``postresolve`` or ``postresolve_ffi`` Lua hook (if defined).
+
+To consider native DNS64 processing the following conditions must be met:
+
+- The :ref:`setting-dns64-prefix` is defined.
+- A ``nodata`` or ``nxdomain`` Lua hook did not return ``true``.
+- The original query type was ``AAAA``.
+- The result code of the ``AAAA`` query was not ``NXDomain``.
+- No relevant answer was received: the result code was ``NoError`` with no relevant answer records, or an error unequal to ``NXDomain`` occurred.
+- If DNSSEC processing is requested the validation result was not ``Bogus``.
+
+Before version 4.8.0, only ``NoError`` results were considers candidates for DNS64 processing.
+
+Scripted DNS64 Support
+----------------------
 On earlier versions or for maximum flexibility, DNS64 support is included in the :doc:`lua-scripting/index`.
 This allows for example to hand out custom IPv6 gateway ranges depending on the location of the requestor, enabling the use of NAT64 services close to the user.
 
index 69253a021ddb3a285c9f723455d6c1acbeaaafc9..7da19e6e6eae3d3233f41f45dc2a362662b32da6 100644 (file)
@@ -36,7 +36,7 @@ Responses to client queries are the same as with `process`_.
 ``validate``
 ^^^^^^^^^^^^
 The highest mode of DNSSEC processing.
-In this mode, all responses will be be validated and and queries will be answered with a SERVFAIL in case of bogus data, even if the client did not request validation by setting the AD or DO bit.
+In this mode, all responses will be be validated and queries will be answered with a SERVFAIL in case of bogus data, even if the client did not request validation by setting the AD or DO bit.
 
 **Note**: the CD-bit is honored for ``process``, ``log-fail`` and
 ``validate``. This mean that even if validation fails, results are
index c61634befc3056ff187bf8d61775784dc4b32baa..2e16bab51f3e245b84ed326310b58cff1acff5ae 100644 (file)
@@ -1,45 +1,58 @@
 Getting Started
 ===============
-The PowerDNS Recursor can be installed on any modern unix-like system and is available in the software repositories for all major Linux distributions and BSDs.
+:program:`PowerDNS Recursor` can be installed on any modern unix-like system and is available in the software repositories for all major Linux distributions and BSDs.
 
 Installation
 ------------
-The Recursor is available for many platforms, instructions are provided here for several platforms.
+:program:`Recursor` is available for many platforms, instructions are provided here for several platforms.
 
-**note**: PowerDNS itself provides repositories for several Recursor versions for different operating systems.
-Checkout `the repositories <https://repo.powerdns.com>`_ for more information.
+.. note::
+  As distribution provided package repositories are not always up-to-date, PowerDNS itself provides repositories for several :program:`Recursor` versions for different operating systems.
+  Checkout `the repositories <https://repo.powerdns.com>`_ for more information.
 
 Debian-based distributions
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
-On Debian, Ubuntu, Linux Mint and related distributions, running ``apt-get install pdns-recursor`` as root will install the Recursor.
+On Debian, Ubuntu, Linux Mint and related distributions, running ``apt-get install pdns-recursor`` as root will install :program:`Recursor`.
 
 Enterprise Linux
 ^^^^^^^^^^^^^^^^
 On Red Hat, CentOS and related distributions, ensure that `EPEL <https://fedoraproject.org/wiki/EPEL>`_ is available.
-To install the PowerDNS Recursor, run ``yum install pdns-recursor`` as root.
+To install :program:`Recursor`, run ``yum install pdns-recursor`` as root.
 
 FreeBSD
 ^^^^^^^
-On FreeBSD the Recursor is available through the `ports system <http://www.freshports.org/dns/powerdns-recursor>`_.
+On FreeBSD :program:`Recursor` is available through the `FreeBSD ports system <https://www.freshports.org/dns/powerdns-recursor>`_.
 Run ``pkg install powerdns-recursor`` as root to install.
 
 To compile yourself from ports, run ``cd /usr/ports/dns/powerdns-recursor/ && make install clean``.
 
-From Source
-^^^^^^^^^^^
-See :doc:`appendices/compiling` for instructions on how to build the PowerDNS Recursor from source.
+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
+^^^^^
+On macOS :program:`Recursor` is available through `brew <https://brew.sh/>`_.
+Run ``brew install pdnsrec`` to install.
+
+Compiling From Source
+^^^^^^^^^^^^^^^^^^^^^
+See :doc:`appendices/compiling` for instructions on how to build :program:`Recursor` from source.
 
-Configuring the Recursor
-------------------------
+Configuring :program:`PowerDNS Recursor`
+----------------------------------------
 The configuration file is called ``recursor.conf`` and is located in the ``SYSCONFDIR`` defined at compile-time.
 This is usually ``/etc/powerdns``, ``/etc/pdns``, ``/etc/pdns-recursor``, ``/usr/local/etc`` or similar.
 
-Run ``pdns_recursor --config=default | grep config-dir`` to find this location on you installation.
+Run ``pdns_recursor --config=default | grep config-dir`` to find this location on your installation.
+Many packages provide a default configuration file that sets :ref:`setting-include-dir`.
+Consider putting local ``.conf`` files into this directory, to make it clear which settings were locally modified.
 
-The PowerDNS Recursor listens on the local loopback interface by default, this can be changed with the :ref:`setting-local-address` setting.
+:program:`Recursor` listens on the local loopback interface by default, this can be changed with the :ref:`setting-local-address` setting.
 
-Now access will need to be granted to the Recursor.
-The :ref:`setting-allow-from` setting lists the subnets that can communicate with the Recursor.
+Now access will need to be granted to the :program:`Recursor`.
+The :ref:`setting-allow-from` setting lists the subnets that can communicate with :program:`Recursor`.
 
 An example configuration is shown below.
 Change this to match the local infrastructure.
@@ -49,15 +62,15 @@ Change this to match the local infrastructure.
     local-address=192.0.2.25, 2001:DB8::1:25
     allow-from=192.0.2.0/24, 2001:DB8::1:/64
 
-After a restart of the Recursor, it will answer queries on 192.0.2.25 and 2001:DB8::1:25, but only for queries with a source address in the 192.0.2.0/24 and 2001:DB8::1:/64 networks.
+After a restart of :program:`Recursor`, it will answer queries on 192.0.2.25 and 2001:DB8::1:25, but only for queries with a source address in the 192.0.2.0/24 and 2001:DB8::1:/64 networks.
 
-The recursor is now ready to be used.
-For more options that can be set in ``recursor.conf`` see the :doc:`list of settings <settings>`.
-Guidance on interaction with the Recursor is documented in :doc:`operating the PowerDNS recursor <running>`.
-If dynamic answer generation is needed or policies need to be applied to queries, the :doc:`scripting manual <lua-scripting/index>` will come in handy.
+:program:`Recursor` is now ready to be used.
+For more options that can be set in ``recursor.conf`` see the :doc:`PowerDNS Recursor Settings<settings>`.
+Guidance on interaction with :program:`Recursor` is documented in :doc:`Operating PowerDNS Recursor<running>`.
+If dynamic answer generation is needed or policies need to be applied to queries, the :doc:`Scripting PowerDNS Recursor <lua-scripting/index>` will come in handy.
 
 Using Ansible
 -------------
-The PowerDNS Recursor can also be installed and configured with `Ansible <https://ansible.com>`_.
+:program:`PowerDNS Recursor` can also be installed and configured with `Ansible <https://ansible.com>`_.
 There is a `role available <https://github.com/PowerDNS/pdns_recursor-ansible/>`_ from the PowerDNS authors.
 
index 713402d57759323b58de2552cfd31a55e50b8335..0d8f24c6b89b1d91c7192bd6179bb109e9c0f126 100644 (file)
@@ -7,6 +7,10 @@
   .. note::
     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 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 561205e7f6ea9c363adf06ff3ff22e697bb55499..cf56e82f98ba22a321db88c4f66bc20e26bc010c 100644 (file)
@@ -10,6 +10,7 @@ PowerDNS Recursor
     running
     dnssec
     settings
+    yamlsettings
     lua-config/index
     lua-scripting/index
     dns64
@@ -21,5 +22,6 @@ PowerDNS Recursor
     security-advisories/index
     upgrade
     changelog/index
+    nod_udr
     appendices/*
     common/license
index af97adf535fb6a1cbbd5431b5100e748c8251ba4..39d046538198f0c3e10600c26f9f620bed8a3c66 100644 (file)
@@ -33,29 +33,50 @@ 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``
-  Do not do any additional processing for this qtype. This is equivalent to not calling :func:`addAllowedAdditionalQType` for the qtype.
+  Do not do any additional processing for this qtype.
+  This is equivalent to not calling :func:`addAllowedAdditionalQType` for the qtype.
 ``pdns.AdditionalMode.CacheOnly``
-  Look in the record cache for available records. Allow non-authoritative (learned from additional sections received from authoritative servers) records to be added.
+  Look in the record cache for available records.
+  Allow non-authoritative (learned from additional sections received from authoritative servers) records to be added.
 ``pdns.AdditionalMode.CacheOnlyRequireAuth``
-  Look in the record cache for available records. Only authoritative records will be added. These are records received from the nameservers for the specific domain.
+  Look in the record cache for available records.
+  Only authoritative records will be added. These are records received from the nameservers for the specific domain.
 ``pdns.AdditionalMode.ResolveImmediately``
-  Add records from the record cache (including DNSSEC records if relevant). If no record is found in the record cache, actively try to resolve the target name/qtype. This will delay the answer to the client.
+  Add records from the record cache (including DNSSEC records if relevant).
+  If no record is found in the record cache, actively try to resolve the target name/qtype.
+  This will delay the answer to the client.
 ``pdns.AdditionalMode.ResolveDeferred``
-  Add records from the record cache (including DNSSEC records if relevant). If no record is found in the record cache and the negative cache also has no entry, schedule a background task to resolve the target name/qtype. The next time the query is processed, the cache might hold the relevant information. This mode is not implemented yet.
+  Add records from the record cache (including DNSSEC records if relevant).
+  If no record is found in the record cache and the negative cache also has no entry, schedule a task to resolve the target name/qtype.
+  The next time the query is processed, the cache might hold the relevant information.
+  If a task is pushed, the answer that triggered it will be marked as variable and consequently not stored into the packet cache.
 
 If an additional record is not available at that time the query is stored into the packet cache the answer packet stored in the packet cache will not contain the additional record.
 Clients repeating the same question will get an answer from the packet cache if the question is still in the packet cache.
 These answers do not have the additional record, even if the record cache has learned it in the meantime .
 Clients will only see the additional record once the packet cache entry expires and the record cache is consulted again.
 The ``pdns.AdditionalMode.ResolveImmediately`` mode will not have this issue, at the cost of delaying the first query to resolve the additional records needed.
+The ``pdns.AdditionalMode.ResolveDeferred`` mode will only store answers in the packet cache if it determines that no deferred tasks are needed, i.e. either a positive or negative answer for potential additional records is available.
+If the additional records for an answer have low TTLs compared to the records in the answer section, tasks will be pushed often.
+Until all tasks for the answer have completed the packet cache will not contain the answer, making the packet cache less effective for this specific answer.
 
 Configuring additional record processing
 ----------------------------------------
index a602f22d67d80c728104fbfb0a6b707965ac79a4..36a3ec7a2ec8b28d722a03f71c64a3c551b80a4a 100644 (file)
@@ -11,5 +11,6 @@ Since version 4.0.0, the PowerDNS Recursor supports additional configuration opt
     sortlist
     ztc
     additionals
+    proxymapping
 
 In addition, :func:`pdnslog` together with ``pdns.loglevels`` is also supported in the Lua configuration file.
index 21a222f89e78e968678c671f1a7514b4379c0322..82804f0ae235425ac8dd4c941e6727c02e9c82e1 100644 (file)
@@ -37,6 +37,10 @@ Protobuf export to a server is enabled using the ``protobufServer()`` directive:
 
   The values in ``exportTypes`` can be numeric as well as strings. Symbolic names from ``pdns`` can be used, e.g.  ``exportTypes = { pdns.A, pdns.AAAA, pdns.CNAME }``
 
+  .. versionadded:: 4.7.0
+
+  * ``logMappedFrom=false``: bool - whether to log the remote address before substitution by :ref:`proxymapping` (the default) or after
+
 .. function:: protobufServer(server [[[[[[[, timeout=2], maxQueuedEntries=100], reconnectWaitTime=1], maskV4=32], maskV6=128], asyncConnect=false], taggedOnly=false])
 
   .. deprecated:: 4.2.0
@@ -123,7 +127,7 @@ The recursor must have been built with configure ``--enable-dnstap`` to make thi
 
   * ``logQueries=true``: bool - log outgoing queries
   * ``logResponses=true``: bool - log incoming responses
+
   The following options apply to the settings of the framestream library. Refer to the documentation of that
   library for the default values, exact description and allowable values for these options.
   For all these options, absence or a zero value has the effect of using the library-provided default value.
@@ -135,3 +139,30 @@ The recursor must have been built with configure ``--enable-dnstap`` to make thi
   * ``queueNotifyThreshold=0``: unsigned
   * ``reopenInterval=0``: unsigned
 
+.. function:: dnstapNODFrameStreamServer(servers [, options])
+
+  .. versionadded:: 4.8.0
+
+  Send dnstap formatted message for :ref:`Newly Observed Domain` and :ref:`Unique Domain Response`.
+  ``Message.type`` will be set to ``CLIENT_QUERY`` for NOD and ``RESOLVER_RESPONSE`` for UDR. The concerned domain name will be attached in the ``Message.query_zone`` field.
+  UDR notifications will get the reply attached to the ``response_message`` field.
+
+  :param servers: Either a pathname of a unix domain socket starting with a slash or the IP:port to connect to, or a list of those. If more than one server is configured, all messages are sent to every server.
+  :type servers: string or list of strings
+  :param table options: A table with ``key=value`` pairs with options.
+
+  Options:
+
+  * ``logNODs=true``: bool - log NODs
+  * ``logUDRs=false``: bool - log UDRs
+
+  The following options apply to the settings of the framestream library. Refer to the documentation of that
+  library for the default values, exact description and allowable values for these options.
+  For all these options, absence or a zero value has the effect of using the library-provided default value.
+
+  * ``bufferHint=0``: unsigned
+  * ``flushTimeout=0``: unsigned
+  * ``inputQueueSize=0``: unsigned
+  * ``outputQueueSize=0``: unsigned
+  * ``queueNotifyThreshold=0``: unsigned
+  * ``reopenInterval=0``: unsigned
diff --git a/pdns/recursordist/docs/lua-config/proxymapping.rst b/pdns/recursordist/docs/lua-config/proxymapping.rst
new file mode 100644 (file)
index 0000000..21e0408
--- /dev/null
@@ -0,0 +1,56 @@
+.. _proxymapping:
+
+Table Based Proxy Mapping
+=========================
+Starting with version 4.7.0, the PowerDNS Recursor has the ability to map source IP addresses to alternative addresses, which is for example useful when some clients reach the recursor via a reverse-proxy.
+The mapped address is used internally for ACL and similar checks.
+If the :ref:`setting-proxy-protocol-from` is also used, the substitution is done on the source address specified in the proxy protocol header.
+
+Depending on context, the incoming address can be
+
+The physical address ``P``
+  the physical address the query is received on.
+The source address ``S``
+  the source address as specified in the Proxy protocol
+The mapped address ``M``
+  the source address mapped by Table Based Proxy Mapping
+
+``S equals P`` if no Proxy Protocol is used.
+
+``M equals S`` if no Table Based Proxy Mapping is used.
+
+``P`` determines if the Proxy Protocol is used (:ref:`setting-proxy-protocol-from`).
+
+``S`` is passed to Lua functions and RPZ processing
+
+``M`` is used for incoming ACL checking (:ref:`setting-allow-from`) and to determine the ECS processing (:ref:`setting-ecs-add-for`).
+
+An example use:
+
+.. code-block:: Lua
+
+  addProxyMapping("127.0.0.0/24", "203.0.113.1")
+  domains = { "example.com", "example.net" }
+  addProxyMapping("10.0.0.0/8", "203.0.113.2", domains)
+
+
+The following function is available to configure table based proxy mapping.
+Reloading the Lua configuration will replace the current configuration with the new one.
+If the subnets specified in multiple :func:`addProxyMapping` calls overlap, the most specific one is used.
+By default, the address *before* mapping ``S`` is used for internal logging and ``Protobuf`` messages.
+See :func:`protobufServer` on how to tune the source address logged in ``Protobuf`` messages.
+
+.. function:: addProxyMapping(subnet, ip [, domains])
+
+  .. versionadded:: 4.7.0
+
+  Specify a table based mapping for a subnet.
+
+  :param string subnet: a subnet to match
+  :param string ip: the IP address or IPaddress port combination to match the subnet to.
+  :param array domains: An array of strings used to fill a :ref:`dns-suffix-match-group`.
+
+If the optional ``domains`` argument is given to this function, only queries for names matching the :ref:`dns-suffix-match-group` will use the value ``M`` to determine the outgoing ECS; other queries will use the value ``S``.
+The ACL check will be done against the mapped address ``M`` for all queries, independent of the name queried.
+If the ``domains`` argument is absent, no extra condition (apart from matching the subnet) applies to determine the outgoing ECS value.
+
index 4efe0d73232f0bffe83c2c8beeedf53c670c5d8a..dfae45125614be3bcae7691deb4af9758dd872d4 100644 (file)
@@ -92,6 +92,7 @@ RPZ Configuration Functions
 .. function:: rpzFile(filename, settings)
 
   Load an RPZ from disk.
+  If multiple files are to be loaded, the zones can be distinguished by setting a ``policyName``, see below.
 
   :param str filename: The filename to load
   :param {} settings: A table to settings, see below
@@ -150,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.
@@ -158,7 +173,7 @@ The maximum TTL value of the synthesized records, overriding a higher value from
 
 policyName
 ^^^^^^^^^^
-The name logged as 'appliedPolicy' in :doc:`protobuf <protobuf>` messages when this policy is applied.
+The name logged as ``appliedPolicy`` in :doc:`protobuf <protobuf>` messages when this policy is applied.
 Defaults to ``rpzFile`` for RPZs loaded by :func:`rpzFile` or the name of the zone for RPZs loaded by :func:`rpzPrimary`.
 
 tags
@@ -199,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
 ^^^^^^^^^^^^^^^^^
@@ -214,10 +230,18 @@ When unset, :ref:`setting-query-local-address` is used.
 axfrTimeout
 ^^^^^^^^^^^
 .. versionadded:: 4.1.2
-  Before 4.1.2, the timeout was fixed on 10 seconds.
+   Before 4.1.2, the timeout was fixed on 10 seconds.
+
+.. versionchanged:: 4.5.12
+   The same timeout applies to followup IXFR transactions.
+
+.. versionchanged:: 4.6.5
+   The same timeout applies to followup IXFR transactions.
+
+.. versionchanged:: 4.7.4
+   The same timeout applies to followup IXFR transactions.
 
-The timeout in seconds of the total initial AXFR transaction.
-20 by default.
+The timeout in seconds of the total initial AXFR transaction. 20 by default.
 
 dumpFile
 ^^^^^^^^
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 909e5e1506d418534f58a9b236f55fdddb631e7a..8b24df7ca8284ef4c37a9ee5b6147a2c747ab2b7 100644 (file)
@@ -105,10 +105,12 @@ Functions and methods of a ``DNSName``
 
     :param string name: The name to compare to
 
-DNS Suffix Match Groups
------------------------
+.. _dns-suffix-match-group:
+   
+DNS Suffix Match Group
+----------------------
 
-The :func:`newDS` function creates a "Suffix Match group" that allows fast checking if a :class:`DNSName` is part of a group.
+The :func:`newDS` function creates a ``DNS Suffix Match Group`` that allows fast checking if a :class:`DNSName` is part of a group.
 This could e.g. be used to answer questions for known malware domains.
 To check e.g. the :attr:`dq.qname` against a list:
 
@@ -120,27 +122,29 @@ To check e.g. the :attr:`dq.qname` against a list:
 
 .. function:: newDS() -> DNSSuffixMatchGroup
 
-  Creates a new DNS Suffix Match Group.
+  Creates a new ``DNS Suffix Match Group``.
 
 .. class:: DNSSuffixMatchGroup
 
   This class represents a group of DNS names that can be used to quickly compare a single :class:`DNSName` against.
 
   .. method:: DNSSuffixMatchGroup:add(domain)
+              DNSSuffixMatchGroup:add(dnsname)
               DNSSuffixMatchGroup:add(domains)
 
-    Add one or more domains to the Suffix Match Group.
+    Add one or more domains to the ``DNS Suffix Match Group``.
 
     :param str domain: A domain name to add
-    :param {str} domain: A list of Domains to add
+    :param DNSName dnsname: A dnsname to add
+    :param {str} domains: A list of domain names to add
 
-  .. method:: DNSSuffixMatchGroup:check(domain) -> bool
+  .. method:: DNSSuffixMatchGroup:check(dnsname) -> bool
 
-    Check ``domain`` against the Suffix Match Group.
-    Returns true if it is matched, false otherwise.
+    Check ``dnsname`` against the ``DNS Suffix Match Group``.
+    Returns ``true`` if it is matched, ``false`` otherwise.
 
-    :param DNSName domain: The domain name to check
+    :param DNSName dnsname: The dnsname to check
 
   .. method:: DNSSuffixMatchGroup:toString() -> str
 
-    Returns a string of the set of suffixes matched by the Suffix Match Group
+    Returns a string of the set of suffixes matched by the ``DNS Suffix Match Group``.
index 4b386b284b920a8ebea90e2e59905b996737f90b..ff1002cf004f791cad3a251bb745884e817adae6 100644 (file)
@@ -1,7 +1,7 @@
 DNS Record
 ==========
 
-DNS record objects are returned by :meth:`DNSQuestion:getRecords`.
+DNS record objects are returned by :meth:`DNSQuestion:getRecords()` and accepted by :meth:`DNSQuestion:addAnswer()`, :meth:`DNSQuestion:addRecord()` and  :meth:`DNSQuestion:setRecords()`.
 
 .. class:: DNSRecord
 
index bc840d8497343330dbd33001db1a3322b42dd5d1..4ce6a49b60e1f83f0c72b4eb29d9c9680ca33921 100644 (file)
@@ -70,6 +70,14 @@ The DNSQuestion object contains at least the following fields:
       - getFakePTRRecords: Get a fake PTR record, see :doc:`DNS64 <../dns64>`
       - udpQueryResponse: Do a UDP query and call a handler, see :ref:`UDP Query Response <udpqueryresponse>`
 
+  .. attribute:: DNSQuestion.followupName
+
+      see :doc:`DNS64 <../dns64>`
+
+  .. attribute:: DNSQuestion.followupPrefix
+
+      see :doc:`DNS64 <../dns64>`
+
   .. attribute:: DNSQuestion.appliedPolicy
 
     The decision that was made by the policy engine, see :ref:`modifyingpolicydecisions`.
@@ -149,7 +157,7 @@ The DNSQuestion object contains at least the following fields:
 
   .. attribute:: DNSQuestion.udpAnswer
 
-      Answer to the :attr:`udpQuery <DNSQuestion.udpQuery>` when when using the ``udpQueryResponse`` :attr:`followupFunction <DNSQuestion.followupFunction>`.
+      Answer to the :attr:`udpQuery <DNSQuestion.udpQuery>` when using the ``udpQueryResponse`` :attr:`followupFunction <DNSQuestion.followupFunction>`.
       Only filled when the call-back function is invoked.
 
   .. attribute:: DNSQuestion.udpQueryDest
@@ -211,6 +219,24 @@ The DNSQuestion object contains at least the following fields:
 
       Whether the response to this query will be exported to a remote protobuf logger, if one has been configured.
 
+  .. attribute:: DNSQuestion.tag
+
+      The packet-cache tag set via :func:`gettag`, or 0 if it has not been set.
+
+  .. attribute:: DNSQuestion.queryTime
+
+     .. versionadded:: 4.8.0
+
+     The time the query was received
+
+     .. attribute:: DNSQuestion.queryTime.tv_sec
+
+        The number of seconds since the Unix epoch.
+
+     .. attribute:: DNSQuestion.queryTime.tv_usec
+
+        The number of microseconds, to be added to the number of seconds in :attr:`DNSQuestion.queryTime.tv_sec` to get a high accuracy timestamp.
+
   It also supports the following methods:
 
   .. method:: DNSQuestion:addAnswer(type, content, [ttl, name])
@@ -338,6 +364,11 @@ The DNS header as returned by :meth:`DNSQuestion:getDH()` represents a header of
 
       The ID of the query
 
+DNSRecord Object
+================
+
+See :doc:`DNSRecord <dnsrecord>`.
+
 The EDNSOptionView Class
 ========================
 
index dd14b6f3609ec602779a339f6208dc0b04d1f8c7..f64eeff30928e894dc85cc21569a1ccbdf76cd91 100644 (file)
@@ -1,10 +1,10 @@
 Lua FFI API
 ===========
 
-We provide a set of functions available through the LUA FFI library that allow you to interact with the the :func:`gettag_ffi` parameter.
+PowerDNS Recursor provides a set of functions available through the LUA FFI library that allow you to interact with handle passed to :func:`gettag_ffi` and :func:`postresolve_ffi`.
 
-Functions
----------
+Functions for :func:`gettag_ffi`
+--------------------------------
 
 .. function:: pdns_ffi_param_get_qname(pdns_ffi_param_t* ref) -> const char*
 
@@ -132,12 +132,74 @@ Functions
 
 .. function:: pdns_ffi_param_add_meta_single_string_kv(pdns_ffi_param_t* ref, const char* key, const char* val) -> void
 
-    .. versionadded:: 4.6.0
+   .. versionadded:: 4.6.0
 
    This function allows you to add an arbitrary string value for a given key in the ``meta`` field of the produced :doc:`protobuf <../lua-config/protobuf>` log message
 
 .. function:: pdns_ffi_param_add_meta_single_int64_kv(pdns_ffi_param_t *ref, const char* key, int64_t val) -> void
 
-    .. versionadded:: 4.6.0
+   .. versionadded:: 4.6.0
 
    This function allows you to add an arbitrary int value for a given key in the ``meta`` field of the produced :doc:`protobuf <../lua-config/protobuf>` log message
+
+Functions for :func:`postresolve_ffi`
+-------------------------------------
+
+.. versionadded:: 4.7.0
+
+All functions below were added in version 4.7.0.
+
+.. function::  pdns_postresolve_ffi_handle_get_qname(pdns_postresolve_ffi_handle_t* ref) -> const char*
+
+    Get the name queried as a string.
+
+.. function::  pdns_postresolve_ffi_handle_get_qname_raw(pdns_postresolve_ffi_handle_t* ref, const char** qname, size_t* qnameSize) -> void
+
+    Get the name queried (and its size) in DNS wire format.
+
+.. function::  pdns_postresolve_ffi_handle_get_qtype(const pdns_postresolve_ffi_handle_t* ref) -> uint16
+
+    Get the qtype of the query.
+
+.. function::  pdns_postresolve_ffi_handle_get_rcode(const pdns_postresolve_ffi_handle_t* ref) -> uint16
+
+    Get the rcode returned by the resolving process.
+
+.. function::  pdns_postresolve_ffi_handle_set_rcode(const pdns_postresolve_ffi_handle_t* ref, uint16_t rcode) -> void
+
+    Set the rcode to be returned.
+
+.. function::  pdns_postresolve_ffi_handle_get_appliedpolicy_kind(const pdns_postresolve_ffi_handle_t* ref) -> pdns_policy_kind_t
+
+    Get the applied policy.
+
+.. function::  pdns_postresolve_ffi_handle_set_appliedpolicy_kind(pdns_postresolve_ffi_handle_t* ref, pdns_policy_kind_t kind) -> void
+
+    Set the applied policy.
+
+.. function::  pdns_postresolve_ffi_handle_get_record(pdns_postresolve_ffi_handle_t* ref, unsigned int i, pdns_ffi_record_t* record, bool raw) -> bool
+
+    Get a record indexed by i.
+    Returns false if no record is available at index i.
+
+.. function::  pdns_postresolve_ffi_handle_set_record(pdns_postresolve_ffi_handle_t* ref, unsigned int i, const char* content, size_t contentLen, bool raw) -> bool
+
+    Set the record at index i.
+
+.. function::  pdns_postresolve_ffi_handle_clear_records(pdns_postresolve_ffi_handle_t* ref) -> void
+
+    Clear all records.
+
+.. function::  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) -> bool
+
+    Add a record to the existing records.
+
+.. function::  pdns_postresolve_ffi_handle_get_authip(pdns_postresolve_ffi_handle_t* ref) -> const char*
+
+    Get a string representation of the IP address of the authoritative server that answered the query.
+    The string might by empty if the address is not available.
+
+.. function::  pdns_postresolve_ffi_handle_get_authip_raw(pdns_postresolve_ffi_handle_t* ref, const void** addr, size_t* addrSize) -> void
+
+    Get the raw IP address (in network byte order) and size of the raw IP address of the authoritative server that answered the query.
+    The string might be empty if the address is not available.
index 3eddb15d6bc0dfd4d45ef7bcecfee9fec7666733..1200019244b39eccf657e5e32aa7d47f1173e0d5 100644 (file)
@@ -10,7 +10,7 @@ Queries can be intercepted in many places:
 -  before any filtering policy have been applied (:func:`prerpz`)
 -  before the resolving logic starts to work (:func:`preresolve`)
 -  after the resolving process failed to find a correct answer for a domain (:func:`nodata`, :func:`nxdomain`)
--  after the whole process is done and an answer is ready for the client (:func:`postresolve`)
+-  after the whole process is done and an answer is ready for the client (:func:`postresolve` and its FFI counterpart, :func:`postresolve_ffi`).
 -  before an outgoing query is made to an authoritative server (:func:`preoutquery`)
 -  after a filtering policy hit has occurred (:func:`policyEventFilter`)
 
@@ -26,9 +26,10 @@ All of these functions are optional.
 If ``ipfilter`` returns ``true``, the query is dropped.
 If ``preresolve`` returns ``true``, it will indicate it handled a query, and the recursor will send the result as constructed in the function to the client.
 If it returns ``false``, the Recursor will continue processing.
-For the other functions, the return value will indicate that an alteration has been made. In that case DNSSEC validation will be automatically disabled since the content might not be genuine anymore.
+For the other functions, the return value will indicate that an alteration to the result has been made.
+In that case the potentially changed rcode, records and policy will be processed and DNSSEC validation will be automatically disabled since the content might not be genuine anymore.
 At specific points the Recursor will check if policy handling should take place.
-These points are immediately after ``preresolve``, after resolving and after ``postresolve``.
+These points are immediately after ``preresolve``, after resolving and after ``nxdomain``, ``nodata`` and ``postresolve``.
 
 Interception Functions
 ----------------------
@@ -36,7 +37,7 @@ Interception Functions
 .. function:: ipfilter(remoteip, localip, dh) -> bool
 
     This hook gets queried immediately after consulting the packet cache, but before parsing the DNS packet.
-    If this hook returns something else than false, the packet is dropped.
+    If this hook returns something else than ``false``, the packet is dropped.
     However, because this check is after the packet cache, the IP address might still receive answers that require no packet parsing.
 
     With this hook, undesired traffic can be dropped rapidly before using precious CPU cycles for parsing.
@@ -90,7 +91,7 @@ Interception Functions
        If a routing tag is set and a record would be stored with an ENDS subnetmask in the record cache, it will be
        stored with the tag instead. New request using the same tag will be served by the record in the records cache,
        avoiding querying authoritative servers.
+
     The tagged packetcache can e.g. be used to answer queries from cache that have e.g. been filtered for certain IPs (this logic should be implemented in :func:`gettag`).
     This ensure that queries are answered quickly compared to setting :attr:`dq.variable <DNSQuestion.variable>` to true.
     In the latter case, repeated queries will pass through the entire Lua script.
@@ -122,6 +123,7 @@ Interception Functions
 
   This hook is called before any filtering policy have been applied,  making it possible to completely disable filtering by setting  :attr:`dq.wantsRPZ <DNSQuestion.wantsRPZ>` to false.
   Using the :meth:`dq:discardPolicy() <DNSQuestion:discardPolicy>` function, it is also possible to selectively disable one or more filtering policy, for example RPZ zones, based on the content of the ``dq`` object.
+  Currently, the return value of this function is ignored.
 
   As an example, to disable the "malware" policy for example.com queries:
 
@@ -151,6 +153,14 @@ Interception Functions
 
   :param DNSQuestion dq: The DNS question to handle
 
+.. function:: postresolve_ffi(handle) -> bool
+
+  .. versionadded:: 4.7.0
+
+  This is the FFI counterpart of :func:`postresolve`.
+  It accepts a single parameter which can be passed to the functions listed in :doc:`ffi`.
+  The accessor functions retrieve and modify various aspects of the answer returned to the client.
+
 .. function:: nxdomain(dq) -> bool
 
   is called after the DNS resolution process has run its course, but ended in an 'NXDOMAIN' situation, indicating that the domain does not exist.
@@ -168,20 +178,33 @@ Interception Functions
 .. function:: preoutquery(dq) -> bool
 
   This hook is not called in response to a client packet, but fires when the Recursor wants to talk to an authoritative server.
-  When this hook sets the special result code -3, the whole DNS client query causing this outquery gets a `ServFail`.
+
+  When this hook sets the special result code ``-3``, the whole DNS client query causing this outgoing query gets a ``ServFail``.
 
   However, this function can also return records like :func:`preresolve`.
 
-  :param DNSQuestion dq: The DNS question to handle
+  :param DNSQuestion dq: The DNS question to handle.
+
+  In the case of :func:`preoutquery`, only a few attributes if the :class:`dq <DNSQuestion>` object are filled in:
+
+  - :attr:`dq.remoteaddr <DNSQuestion.remoteaddr>` containing the target nameserver address
+  - :attr:`dq.localaddr <DNSQuestion.localaddr>`
+  - :attr:`dq.qname <DNSQuestion.qname>`
+  - :attr:`dq.qtype <DNSQuestion.qtype>`
+  - :attr:`dq.isTcp <DNSQuestion.isTcp>`
+
+  Do not rely on other attributes having a value and do not call any method of the :class:`dq <DNSQuestion>` object apart from the record set manipulation methods.
 
 .. function:: policyEventFilter(event) -> bool
 
-    .. versionadded:: 4.4.0
+  .. versionadded:: 4.4.0
 
   This hook is called when a filtering policy has been hit, before the decision has been applied, making it possible to change a policy decision by altering its content or to skip it entirely.
   Using the :meth:`event:discardPolicy() <PolicyEvent:discardPolicy>` function, it is also possible to selectively disable one or more filtering policy, for example RPZ zones.
   The return value indicates whether the policy hit should be completely ignored (true) or applied (false), possibly after editing the action to take in that latter case (see :ref:`modifyingpolicydecisions` below). when true is returned, the resolution process will resume as if the policy hit never took place.
 
+  :param PolicyEvent event: The event to handle
+
   As an example, to ignore the result of a policy hit for the example.com domain:
 
   .. code-block:: Lua
@@ -209,17 +232,17 @@ Interception Functions
         return false
       end
 
-  :param :class:`PolicyEvent` event: The event to handle
-
- .. _hook-semantics:
+.. _hook-semantics:
 
 Callback Semantics
 ^^^^^^^^^^^^^^^^^^
-The :func:`ipfilter` and :func:`preresolve` callbacks must return ``true`` if they have taken over the query and wish that the nameserver should not proceed with processing.
-When a function returns ``false``, the nameserver will process the query normally until a new function is called.
+The functions which modify or influence the query flow should all return ``true`` when they have performed an action which alters the rcode, result or applied policy. When a function returns ``false``, the nameserver will process the query normally until a new function is called.
+
+:func:`ipfilter` and :func:`preresolve` callbacks must return ``true`` if they have taken over the query and wish that the nameserver should not proceed with processing.
 
-If a function has taken over a request, it should set an rcode (usually 0), and specify a table with records to be put in the answer section of a packet.
+If a function has taken over a request, it can set an rcode (usually 0), and specify a table with records to be put in the answer section of a packet.
 An interesting rcode is `NXDOMAIN` (3, or ``pdns.NXDOMAIN``), which specifies the non-existence of a domain.
+Instead of setting an rcode and records, it can also set fields in the applied policy to influence further processing.
 
 The :func:`ipfilter` and :func:`preoutquery` hooks are different, in that :func:`ipfilter` can only return a true of false value, and that :func:`preoutquery` can also set rcode -3 to signify that the whole query should be terminated.
 
@@ -334,7 +357,7 @@ This script requires PowerDNS Recursor 4.x or later.
         if(lethalgroup:match(dq.remoteaddr))
         then
             print("We matched the group "..lethalgroup:tostring().."! killing query dead from requestor "..dq.localaddr:toString())
-            dq.rcode = -3 -- "kill" 
+            dq.rcode = -3 -- "kill"
             return true
         end
         return false
@@ -425,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 e9ee42caff77133344d15349b7364002ee1e0adc..d3665f695bd311641d304b69cab169790c2323fe 100644 (file)
@@ -28,7 +28,7 @@ To stop the recursor by hand, run::
     # rec_control quit
 
 However, the recommended way of starting and stopping the recursor is to use
-the init.d script or :manpage:`systemctl(1)`.
+:manpage:`systemctl(1)` or the init.d script.
 
 Options
 -------
@@ -46,6 +46,11 @@ at `<https://doc.powerdns.com/>`
     chroot the process to *directory*.
 --client-tcp-timeout=<num>
     Timeout in seconds when talking to TCP clients.
+--config
+    Show the current configuration. Since 4.8.0 there are three optional values:
+    ``--config=default`` to show the default configuration.
+    ``--config=diff``    show modified options in the current configuration.
+    ``--config=check``   to check the current configuration for errors.
 --config-dir=<directory>
     Location of configuration directory (recursor.conf), the default
     depends on the SYSCONFDIR option at build-time, which is usually
@@ -119,3 +124,4 @@ See also
 --------
 :manpage:`rec_control(1)`
 :manpage:`systemctl(1)`
+`<https://docs.powerdns.com/recursor>`__
index eda01c07fb87f867beee1dfcce78f2a210c7a7fe..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
@@ -92,6 +92,9 @@ dump-cache *FILENAME*
     also dumped to the same file. The per-thread positive and negative cache
     dumps are separated with an appropriate comment.
 
+dump-dot-probe-map *FILENAME*
+    Dump the contents of the DoT probe map to the *FILENAME* mentioned.
+
 dump-edns *FILENAME*
     Dumps the EDNS status to the filename mentioned. This file should not exist
     already, PowerDNS will refuse to overwrite it. While dumping, the recursor
@@ -119,7 +122,16 @@ dump-rpz *ZONE NAME* *FILE NAME*
     Dumps the content of the RPZ zone named *ZONE NAME* to the *FILENAME*
     mentioned. This file should not exist already, PowerDNS will refuse to
     overwrite it otherwise. While dumping, the recursor will not answer
-    questions.
+    questions. For details on how RPZ are named see
+    `<https://docs.powerdns.com/recursor/lua-config/rpz.html#policyname>`__.
+
+dump-saved-parent-ns-sets *FILE NAME*
+    Dump the entries of the map containing saved parent NS sets
+    that were successfully used in resolving.
+    The total number of entries is also printed in the header.
+    An entry is saved if the recursor sees that the parent set includes
+    names not in the child set. This is an indication of a
+    misconfigured domain.
 
 dump-throttlemap *FILENAME*
     Dump the contents of the throttle map to the *FILENAME* mentioned.
@@ -129,7 +141,7 @@ dump-throttlemap *FILENAME*
 
 get *STATISTIC* [*STATISTIC*]...
     Retrieve a statistic. For items that can be queried, see
-    :doc:`../metrics`
+    `<https://docs.powerdns.com/recursor/metrics.html>`__.
 
 get-all
     Retrieve all known statistics.
@@ -149,9 +161,15 @@ get-tas
 get-parameter *KEY* [*KEY*]...
     Retrieves the specified configuration parameter(s).
 
+get-proxymapping-stats
+    Get the list of proxy-mapped subnets and associated counters.
+
 get-qtypelist
     Retrieves QType statistics. Queries from cache aren't being counted yet.
 
+get-remotelogger-stats
+    Retrieves the remote logger statistics, per type and address.
+
 hash-password [*WORK-FACTOR*]
     Asks for a password then returns the hashed and salted version,
     to use as a webserver password or API key. This command does
@@ -163,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.
 
@@ -186,8 +207,9 @@ reload-lua-config [*FILENAME*]
     (Re)loads Lua configuration *FILENAME*. If *FILENAME* is empty, attempt
     to reload the currently loaded file. Note that *FILENAME* will be fully
     executed, any settings changed at runtime that are not modified in this
-    file, will still be active. Reloading RPZ, especially by AXFR, can take
-    some time; during which the recursor will not answer questions.
+    file, will still be active. The effects of reloading do not always take
+    place immediately, as some subsystems reload and replace configuration
+    in an asynchronous way.
 
 reload-zones
     Reload authoritative and forward zones. Retains current configuration in
@@ -198,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
@@ -219,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
@@ -267,8 +299,12 @@ top-timeouts
     Shows the top-20 most active downstream timeout destinations.
     Statistics are over the last 'stats-ringbuffer-entries' queries.
 
-trace-regex *REGEX*
-    Emit resolution trace for matching queries. Empty regex to disable trace.
+trace-regex *REGEX* *FILE*
+    Emit resolution trace for matching queries. No arguments disables tracing.
+    Before version 4.9.0, there was no *FILE* argument, traces were always
+    written to the log. Starting with version 4.9.0, trace information is
+    written to the file specified, which may be ``-`` for the standard out
+    stream.
 
     Queries matching this regular expression will generate voluminous tracing
     output. Be aware that matches from the packet cache will still not generate
@@ -299,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.
@@ -314,3 +350,4 @@ wipe-cache-typed *qtype* *DOMAIN* [*DOMAIN*] [...]
 See also
 --------
 :manpage:`pdns_recursor(1)`
+`<https://docs.powerdns.com/recursor>`__
index 137e25e7fbbcac57ea6e64414956091b30cbdc81..6c20957165d40a6a2d8583e7b2e16dd2c3435ef1 100644 (file)
@@ -33,7 +33,7 @@ This ratio can be greater than 100% since additional queries could be needed to
 217 outgoing tcp connections were done, there were 0 queries running at the moment and 9155 queries to authoritative servers saw timeouts.
 
 The packets cache had 4536 entries and 82% of queries were served from it.
-The workload of the the worker queries was 175728 and 169484 respectively.
+The workload of the worker queries was 175728 and 169484 respectively.
 Finally, measured in the last half hour, an average of 1 qps was performed.
 
 Multi-threading and metrics
@@ -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
 ^^^^^^^^^^^^
@@ -232,6 +232,13 @@ auth6-answers100-1000
 ^^^^^^^^^^^^^^^^^^^^^
 counts the number of queries answered by auth6s within 1 second (4.0)
 
+auth-xxx-answers
+^^^^^^^^^^^^^^^^
+where ``xxx`` is an rcode name (``noerror``, ``formerr``, ``servfail``, ``nxdomain``, ``notimp``, ``refused``, ``yxdomain``, ``yxrrset``, ``nxrrset``, ``notauth``, ``rcode10``, ``rcode11``, ``rcode2``, ``rcode13``, ``rcode14``, ``rcode15``).
+Counts the rcodes returned by authoritative servers.
+The corresponding Prometheus metrics consist of multiple entries of the form ``pdns_recursor_auth_rcode_answers{rcode="xxx"}``.
+
+
 auth-zone-queries
 ^^^^^^^^^^^^^^^^^
 counts the number of queries to locally hosted authoritative zones (:ref:`setting-auth-zones`) since starting
@@ -330,7 +337,7 @@ number of queries received with the DO bit set
 
 dnssec-result-bogus
 ^^^^^^^^^^^^^^^^^^^
-number of DNSSEC validations that had the   Bogus state. Since 4.4.2 detailed counters are available, see below.
+number of responses sent, packet-cache hits excluded, that were in the DNSSEC Bogus state. Since 4.4.2 detailed counters are available, see below.
 Since 4.5.0, if :ref:`setting-x-dnssec-names` is set, a separate set of ``x-dnssec-result-...`` metrics become available, counting
 the DNSSEC validation results for names suffix-matching a name in ``x-dnssec-names``.
 
@@ -339,91 +346,91 @@ dnssec-result-bogus-no-valid-dnskey
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 .. versionadded:: 4.4.2
 
-number of DNSSEC validations that had the Bogus state because a valid DNSKEY could not be found.
+number of responses sent, packet-cache hits excluded, that were in the Bogus state because a valid DNSKEY could not be found.
 
 dnssec-result-bogus-invalid-denial
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 .. versionadded:: 4.4.2
 
-number of DNSSEC validations that had the Bogus state because a valid denial of existence proof could not be found.
+number of responses sent, packet-cache hits excluded, that were in the Bogus state because a valid denial of existence proof could not be found.
 
 dnssec-result-bogus-unable-to-get-dss
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 .. versionadded:: 4.4.2
 
-number of DNSSEC validations that had the Bogus state because a valid DS could not be retrieved.
+number of responses sent, packet-cache hits excluded, that were in the Bogus state because a valid DS could not be retrieved.
 
 dnssec-result-bogus-unable-to-get-dnskeys
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 .. versionadded:: 4.4.2
 
-number of DNSSEC validations that had the Bogus state because a valid DNSKEY could not be retrieved.
+number of responses sent, packet-cache hits excluded, that were in the Bogus state because a valid DNSKEY could not be retrieved.
 
 dnssec-result-bogus-self-signed-ds
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 .. versionadded:: 4.4.2
 
-number of DNSSEC validations that had the Bogus state because a DS record was signed by itself.
+number of responses sent, packet-cache hits excluded, that were in the Bogus state because a DS record was signed by itself.
 
 dnssec-result-bogus-no-rrsig
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 .. versionadded:: 4.4.2
 
-number of DNSSEC validations that had the Bogus state because required RRSIG records were not present in an answer.
+number of responses sent, packet-cache hits excluded, that were in the Bogus state because required RRSIG records were not present in an answer.
 
 dnssec-result-bogus-no-valid-rrsig
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 .. versionadded:: 4.4.2
 
-number of DNSSEC validations that had the Bogus state because only invalid RRSIG records were present in an answer.
+number of responses sent, packet-cache hits excluded, that were in the Bogus state because only invalid RRSIG records were present in an answer.
 
 dnssec-result-bogus-missing-negative-indication
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 .. versionadded:: 4.4.2
 
-number of DNSSEC validations that had the Bogus state because a NODATA or NXDOMAIN answer lacked the required SOA and/or NSEC(3) records.
+number of responses sent, packet-cache hits excluded, that were in the Bogus state because a NODATA or NXDOMAIN answer lacked the required SOA and/or NSEC(3) records.
 
 dnssec-result-bogus-signature-no-yet-valid
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 .. versionadded:: 4.4.2
 
-number of DNSSEC validations that had the Bogus state because the signature inception time in the RRSIG was not yet valid.
+number of responses sent, packet-cache hits excluded, that were in the Bogus state because the signature inception time in the RRSIG was not yet valid.
 
 dnssec-result-bogus-signature-expired
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 .. versionadded:: 4.4.2
 
-number of DNSSEC validations that had the Bogus state because the signature expired time in the RRSIG was in the past.
+number of responses sent, packet-cache hits excluded, that were in the Bogus state because the signature expired time in the RRSIG was in the past.
 
 dnssec-result-bogus-unsupported-dnskey-algo
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 .. versionadded:: 4.4.2
 
-number of DNSSEC validations that had the Bogus state because a DNSKEY RRset contained only unsupported DNSSEC algorithms.
+number of responses sent, packet-cache hits excluded, that were in the Bogus state because a DNSKEY RRset contained only unsupported DNSSEC algorithms.
 
 dnssec-result-bogus-unsupported-ds-digest-type
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 .. versionadded:: 4.4.2
 
-number of DNSSEC validations that had the Bogus state because a DS RRset contained only unsupported digest types.
+number of responses sent, packet-cache hits excluded, that were in the Bogus state because a DS RRset contained only unsupported digest types.
 
 dnssec-result-bogus-no-zone-key-bit-set
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 .. versionadded:: 4.4.2
 
-number of DNSSEC validations that had the Bogus state because no DNSKEY with the Zone Key bit set was found.
+number of responses sent, packet-cache hits excluded, that were in the Bogus state because no DNSKEY with the Zone Key bit set was found.
 
 dnssec-result-bogus-revoked-dnskey
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 .. versionadded:: 4.4.2
 
-number of DNSSEC validations that had the Bogus state because all DNSKEYs were revoked.
+number of responses sent, packet-cache hits excluded, that were in the Bogus state because all DNSKEYs were revoked.
 
 dnssec-result-bogus-invalid-dnskey-protocol
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 .. versionadded:: 4.4.2
 
-number of DNSSEC validations that had the Bogus state because all DNSKEYs had invalid protocols.
+number of responses sent, packet-cache hits excluded, that were in the Bogus state because all DNSKEYs had invalid protocols.
 
 dnssec-result-indeterminate
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -431,19 +438,19 @@ number of DNSSEC validations that   had the Indeterminate state
 
 dnssec-result-insecure
 ^^^^^^^^^^^^^^^^^^^^^^
-number of DNSSEC validations that had the   Insecure state
+number of responses sent, packet-cache hits excluded, that were in the Insecure state
 
 dnssec-result-nta
 ^^^^^^^^^^^^^^^^^
-number of DNSSEC validations that had the NTA   (negative trust anchor) state
+number of responses sent, packet-cache hits excluded, that were in the NTA (negative trust anchor) state
 
 dnssec-result-secure
 ^^^^^^^^^^^^^^^^^^^^
-number of DNSSEC validations that had the   Secure state
+number of responses sent, packet-cache hits excluded, that were in the Secure state
 
 dnssec-validations
 ^^^^^^^^^^^^^^^^^^
-number of DNSSEC validations performed
+number of responses sent, packet-cache hits excluded, for which a DNSSEC validation was requested by either the client or the configuration
 
 dont-outqueries
 ^^^^^^^^^^^^^^^
@@ -451,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
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -506,11 +513,19 @@ 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
+^^^^^^^^^^^^^^^^
+time spent doing internal maintenance, including Lua maintenance
+
+maintenance-calls
+^^^^^^^^^^^^^^^^^
+number of times internal maintenance has been called, including Lua maintenance
 
 malloc-bytes
 ^^^^^^^^^^^^
@@ -536,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
@@ -733,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 03acb16ea52c318ec6da3efb885183230fd89201..2da15412ad1b6356425e16cef6de417f99f44518 100644 (file)
@@ -1,3 +1,5 @@
+.. _Newly Observed Domain:
+
 Newly Observed Domain Tracking
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -30,12 +32,15 @@ DNS Lookup
 ++++++++++
 
 The setting ``new-domain-lookup=<base domain>`` will cause the recursor to issue a DNS A record lookup to ``<newly observed domain>.<base domain>``. This can be a suitable method to send NOD data to an offsite or remote partner, however care should be taken to ensure that data is not leaked inadvertently.
+To log NOD information to a dnstap stream, refer to :func:`dnstapNODFrameStreamServer`.
 
 Protobuf Logging
 ++++++++++++++++
 
 If both NOD and protobuf logging are enabled, then the ``newlyObservedDomain`` field of the protobuf message emitted by the recursor will be set to true. Additionally newly observed domains will be tagged in the protobuf stream using the tag ``pdns-nod`` by default. The setting ``new-domain-pb-tag=<tag>`` can be used to alter the tag.
 
+.. _Unique Domain Response:
+
 Unique Domain Response
 ~~~~~~~~~~~~~~~~~~~~~~
 
@@ -60,6 +65,7 @@ Logging
 +++++++
 
 The setting ``unique-response-log`` is enabled by default once the NOD feature is enabled, and will log the newly observed domain to the recursor logfile.
+To log UDR information to a dnstap stream, refer to :func:`dnstapNODFrameStreamServer`.
 
 Protobuf Logging
 ++++++++++++++++
index 12f00025aa21a196f8d50f169f53b4c04da62f11..ac4362d1faa9cd209dac510bd30b9d18e3a0c25d 100644 (file)
@@ -15,21 +15,51 @@ See below for more information about the various caches.
 When deploying (large scale) IPv6, please be aware some Linux distributions leave IPv6 routing cache tables at very small default values.
 Please check and if necessary raise ``sysctl net.ipv6.route.max_size``.
 
-Set :ref:`setting-threads` to your number of CPU cores minus the number of distributor threads (but values above 8 rarely improve performance).
+Set :ref:`setting-threads` to your number of CPU cores minus the number of distributor threads.
 
 Threading and distribution of queries
 -------------------------------------
 
-When running with several threads, you can either ask PowerDNS to start one or more special threads to dispatch the incoming queries to the workers by setting :ref:`setting-pdns-distributes-queries` to true, or let the worker threads handle the incoming queries themselves.
+When running with several threads, you can either ask PowerDNS to start one or more special threads to dispatch the incoming queries to the workers by setting :ref:`setting-pdns-distributes-queries` to ``yes``, or let the worker threads handle the incoming queries themselves.
+The latter is the default since version 4.9.0.
 
 The dispatch thread enabled by :ref:`setting-pdns-distributes-queries` tries to send the same queries to the same thread to maximize the cache-hit ratio.
 If the incoming query rate is so high that the dispatch thread becomes a bottleneck, you can increase :ref:`setting-distributor-threads` to use more than one.
 
-If :ref:`setting-pdns-distributes-queries` is set to false and either ``SO_REUSEPORT`` support is not available or the :ref:`setting-reuseport` directive is set to false, all worker threads share the same listening sockets.
+If :ref:`setting-pdns-distributes-queries` is set to ``no`` and either ``SO_REUSEPORT`` support is not available or the :ref:`setting-reuseport` directive is set to ``no``, all worker threads share the same listening sockets.
 
 This prevents a single thread from having to handle every incoming queries, but can lead to thundering herd issues where all threads are awoken at once when a query arrives.
 
-If ``SO_REUSEPORT`` support is available and :ref:`setting-reuseport` is set to true, 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.
+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.
+
+.. _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.
@@ -37,6 +67,28 @@ If ``SO_REUSEPORT`` support is available and :ref:`setting-reuseport` is set to
 .. versionadded:: 4.2.0
    The :ref:`setting-distributor-threads` parameter can be used to run more than one distributor thread.
 
+.. versionchanged:: 4.9.0
+   The :ref:`setting-reuseport` parameter now defaults to ``yes``.
+
+.. versionchanged:: 4.9.0
+   The :ref:`setting-pdns-distributes-queries` parameter now defaults to ``no``.
+
+
+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.
+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
 ----------------
 
@@ -58,52 +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
-    iptables -t raw -I OUTPUT -p udp --dport 53 -j CT --notrack
+    ## 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 --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
-    ip6tables -t raw -I OUTPUT -p udp --dport 53 -j CT --notrack
+    ## 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 --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
-    firewall-cmd --direct --add-rule ipv4 raw OUTPUT 0 -p udp --dport 53 -j CT --notrack
+    ## 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 --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
-    firewall-cmd --direct --add-rule ipv6 raw OUTPUT 0 -p udp --dport 53 -j CT --notrack
+    ## 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 --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.
 
@@ -125,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.
@@ -136,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 04993d22fd1159c97e4dedea1c367c438ee4d822..08f7ee915349059431201d5b5db46eb5ee008ddf 100644 (file)
@@ -6,3 +6,5 @@ guzzle_sphinx_theme
 sphinxcontrib.httpdomain
 sphinxcontrib-fulltoc
 docutils!=0.15,<0.18
+jinja2<3.1.0
+alabaster==0.7.13
index 88b4b26de690768f7d4bfb2a73103d67a7ee0e6c..c979e35efbd1113e360af384299bdbc1b09c660f 100644 (file)
@@ -76,21 +76,39 @@ When debugging resolving issues, it can be advantageous to have a dump of all th
 
   rec_control dump-cache /tmp/cache
 
+.. _tracing:
+
 Tracing Queries
 ---------------
-To investigate failures with resolving certain domain names, the PowerDNS Recursor features a "tracing" infrastructure.
-This infrastructure will log every step the Recursor takes to resolve a name and will log all DNSSEC related information as well.
+To investigate failures with resolving certain domain names, the PowerDNS :program:`Recursor` features a tracing infrastructure.
+This infrastructure will log every step the :program:`Recursor` takes to resolve a name and will log all DNSSEC related information as well.
 
 To enable tracing for all queries, enable the :ref:`setting-trace` setting.
+Trace information will be written to the log.
 
 .. warning::
 
   Enabling tracing for all queries on a system with a high query rate can severely impact performance.
 
-Tracing can also be enabled at runtime, without restarting the Recursor, for specific domains.
+Tracing can also be enabled at runtime, without restarting the :program:`Recursor`, for specific domains.
 These specific domains can be specified as a regular expression.
 This can be done using :doc:`rec_control trace-regex <manpages/rec_control.1>`::
 
-    rec_control trace-regex '.*\.example.com\.$'
+  rec_control trace-regex '.*\.example.com\.$'
 
 Will enable tracing for any query *in* the example.com domain (but not example.com itself).
+
+Since version 4.9.0 ``trace_regex`` takes an extra file argument.
+Trace information will be written to the file and not to the log.
+If the file argument is a hyphen (``-``), trace information will be written to the standard output stream.
+For example::
+
+  rec_control trace-regex 'example\.com\.$' - | grep asking
+
+will show which authoritative servers were consulted.
+
+Do not forget to disable tracing after diagnosis is done::
+
+  rec_control trace-regex
+
+
index 5ab5e4703c5ef87850d7758887208fd82b272bf5..c717d9c69ddc734175ffeb5294ea1ee52ba16fc6 100644 (file)
@@ -15,7 +15,7 @@ PowerDNS Security Advisory 2017-05: Cross-Site Scripting in the web interface
 
 An issue has been found in the web interface of PowerDNS Recursor, where the
 qname of DNS queries was displayed without any escaping, allowing a remote
-attacker to inject HTML and Javascript code into the web interface, altering
+attacker to inject HTML and JavaScript code into the web interface, altering
 the content. This issue has been assigned CVE-2017-15092.
 
 PowerDNS Recursor from 4.0.0 up to and including 4.0.6 are affected.
diff --git a/pdns/recursordist/docs/security-advisories/powerdns-advisory-2022-01.rst b/pdns/recursordist/docs/security-advisories/powerdns-advisory-2022-01.rst
new file mode 100644 (file)
index 0000000..2a3cda2
--- /dev/null
@@ -0,0 +1,22 @@
+PowerDNS Security Advisory 2022-01: incomplete validation of incoming IXFR transfer in Authoritative Server and Recursor
+========================================================================================================================
+
+- CVE: CVE-2022-27227
+- Date: 25th of March 2022.
+- Affects: PowerDNS Authoritative version 4.4.2, 4.5.3, 4.6.0 and PowerDNS Recursor 4.4.7, 4.5.7 and 4.6.0
+- Not affected: PowerDNS Authoritative Server 4.4.3, 4.5.4, 4.6.1 and PowerDNS Recursor 4.4.8, 4.5.8 and 4.6.1
+- Severity: Low
+- Impact: Denial of service
+- Exploit: This problem can be triggered by an attacker controlling the network path for IXFR transfers
+- Risk of system compromise: None
+- Solution: Upgrade to patched version, do not use IXFR in Authoritative Server
+
+- In the Authoritative server this issue only applies to secondary zones for which IXFR transfers have been enabled and the network path to the primary server is not trusted. Note that IXFR transfers are not enabled by default.
+-  In the Recursor it applies to setups retrieving one or more RPZ zones from a remote server if the network path to the server is not trusted.
+
+IXFR usually exchanges only the modifications between two versions of a zone, but sometimes needs to fall back to a full transfer of the current version.
+When IXFR falls back to a full zone transfer, an attacker in position of man-in-the-middle can cause the transfer to be prematurely interrupted. This interrupted transfer is mistakenly interpreted as a complete transfer, causing an incomplete zone to be processed.
+For the Authoritative Server, IXFR transfers are not enabled by default.
+The Recursor only uses IXFR for retrieving RPZ zones. An incomplete RPZ transfer results in missing policy entries, potentially causing some DNS names and IP addresses to not be properly intercepted.
+
+We would like to thank Nicolas Dehaine and Dmitry Shabanov from ThreatSTOP for reporting and initial analysis of this issue.
diff --git a/pdns/recursordist/docs/security-advisories/powerdns-advisory-2022-02.rst b/pdns/recursordist/docs/security-advisories/powerdns-advisory-2022-02.rst
new file mode 100644 (file)
index 0000000..0564d1d
--- /dev/null
@@ -0,0 +1,21 @@
+PowerDNS Security Advisory 2022-02: incomplete exception handling related to protobuf message generation
+========================================================================================================
+
+- CVE: CVE-2022-37428
+- Date: 23th of August 2022.
+- Affects: PowerDNS Recursor up to and including 4.5.9, 4.6.2 and 4.7.1
+- Not affected: PowerDNS Recursor 4.5.10, 4.6.3 and 4.7.2
+- Severity: Medium
+- Impact: Denial of service
+- Exploit: This problem can be triggered by a remote attacker with access to the recursor if protobuf logging is enabled
+- Risk of system compromise: None
+- Solution: Upgrade to patched version, disable protobuf logging of responses
+
+This issue only affects recursors which have protobuf logging enabled using the
+
+- ``protobufServer`` function with ``logResponses=true`` or
+- ``outgoingProtobufServer`` function with ``logResponses=true``
+
+If either of these functions is used without specifying ``logResponses``, its value is ``true``.
+An attacker needs to have access to the recursor, i.e. the remote IP must be in the access control list.
+If an attacker queries a name that leads to an answer with specific properties, a protobuf message might be generated that causes an exception. The code does not handle this exception correctly, causing a denial of service.
diff --git a/pdns/recursordist/docs/security-advisories/powerdns-advisory-2023-01.rst b/pdns/recursordist/docs/security-advisories/powerdns-advisory-2023-01.rst
new file mode 100644 (file)
index 0000000..eba0c31
--- /dev/null
@@ -0,0 +1,29 @@
+PowerDNS Security Advisory 2023-01: unbounded recursion results in program termination
+======================================================================================
+
+- CVE: CVE-2023-22617
+- Date: 20th of January 2023
+- Affects: PowerDNS Recursor 4.8.0
+- Not affected: PowerDNS Recursor < 4.8.0, PowerDNS Recursor 4.8.1
+- Severity: High
+- Impact: Denial of service
+- Exploit: This problem can be triggered by a remote attacker with access to the recursor by querying names from specific mis-configured domains
+- Risk of system compromise: None
+- Solution: Upgrade to patched version
+
+An issue in the processing of queries for misconfigured domains has been found in PowerDNS Recursor
+4.8.0, allowing a remote attacker to crash the recursor by sending a DNS query for one of these
+domains.  The issue happens because the recursor enters a unbounded loop, exceeding its stack
+memory. Because of the specific way in which this issue happens, we do not believe this issue to be
+exploitable for code execution.
+
+PowerDNS Recursor versions before 4.8.0 are not affected.
+
+Note that when the PowerDNS Recursor is run inside a supervisor like supervisord or systemd, a crash
+will lead to an automatic restart, limiting the impact to a somewhat degraded service.
+
+CVSS 3.0 score: 8.2 (High)
+https://www.first.org/cvss/calculator/3.0#CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H/E:H/RL:U/RC:C
+
+Thanks to applied-privacy.net for reporting this issue and their assistance in diagnosing it.
+
diff --git a/pdns/recursordist/docs/security-advisories/powerdns-advisory-2023-02.rst b/pdns/recursordist/docs/security-advisories/powerdns-advisory-2023-02.rst
new file mode 100644 (file)
index 0000000..f2c4dcc
--- /dev/null
@@ -0,0 +1,28 @@
+PowerDNS Security Advisory 2023-02: Deterred spoofing attempts can lead to authoritative servers being marked unavailable
+=========================================================================================================================
+
+- CVE: CVE-2023-26437
+- Date: 29th of March 2023
+- Affects: PowerDNS Recursor up to and including 4.6.5, 4.7.4 and 4.8.3
+- Not affected: PowerDNS Recursor 4.6.6, 4.7.5 and 4.8.4
+- Severity: Low
+- Impact: Denial of service
+- Exploit: Successful spoofing may lead to authoritative servers being marked unavailable
+- Risk of system compromise: None
+- Solution: Upgrade to patched version
+
+When the recursor detects and deters a spoofing attempt or receives certain malformed DNS packets,
+it throttles the server that was the target of the impersonation attempt so that other authoritative
+servers for the same zone will be more likely to be used in the future, in case the attacker
+controls the path to one server only. Unfortunately this mechanism can be used by an attacker with
+the ability to send queries to the recursor, guess the correct source port of the corresponding
+outgoing query and inject packets with a spoofed IP address to force the recursor to mark specific
+authoritative servers as not available, leading a denial of service for the zones served by those
+servers.
+
+CVSS 3.0 score: 3.7 (Low)
+https://www.first.org/cvss/calculator/3.0#CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:C/C:N/I:N/A:L
+
+Thanks to Xiang Li from Network and Information Security Laboratory, Tsinghua University for reporting this issue.
+
+
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..07a53e2
--- /dev/null
@@ -0,0 +1,33 @@
+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.
index 17667f8ff20eca65618362efc13afce1cc852e4e..0a75f8bd11f673d78e02f8b5a7a03feaf7d93324 100644 (file)
@@ -38,6 +38,3 @@ There are three levels of throttling.
 
 .. include:: common/secpoll.rst
 
-.. _nod_udr:
-
-.. include:: nod_udr.rst
diff --git a/pdns/recursordist/docs/settings.rst b/pdns/recursordist/docs/settings.rst
deleted file mode 100644 (file)
index 1ba03aa..0000000
+++ /dev/null
@@ -1,2397 +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.
-
-As an 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;
-
-
-.. _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-allow-from:
-
-``allow-from``
---------------
--  IP addresses or netmasks, separated by commas
--  Default: 127.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fc00::/7, fe80::/10
-
-Netmasks (both IPv4 and IPv6) that are allowed to use the server.
-The default allows access only from :rfc:`1918` private IP addresses.
-Due to the aggressive nature of the internet these days, it is highly recommended to not open up the recursor for the entire internet.
-Questions from IP addresses not listed here are ignored and do not get an answer.
-
-When the Proxy Protocol is enabled (see `proxy-protocol-from`_), the recursor will check the address of the client IP advertised in the Proxy Protocol header instead of the one of the proxy.
-
-Note that specifying an IP address without a netmask uses an implicit netmask of /32 or /128.
-
-.. _setting-allow-from-file:
-
-``allow-from-file``
--------------------
--  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
--  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.
-
-.. _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
--  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, to maximize the cache hit
-ratio.
-
-.. _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
--  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
--  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
--  Default: (none)
-
-List of netmasks (proxy IP in case of XPF or 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-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
--  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 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.7.0
-
-  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``.
-
-.. _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.
-Higher is more, more logging may destroy performance.
-It is recommended not 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-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.
-
-.. 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-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: 3600
-
-Maximum number of seconds to cache an item in the packet cache, no matter what the original TTL specified.
-
-.. _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.
-
-.. 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-pdns-distributes-queries:
-
-``pdns-distributes-queries``
-----------------------------
--  Boolean
--  Default: yes
-
-If set, PowerDNS will use distinct threads to listen to client sockets and distribute that work to worker-threads using a hash of the query.
-This feature should maximize the cache hit ratio.
-To use more than one thread set `distributor-threads` in version 4.2.0 or newer.
-Enabling should improve performance for medium sized resolvers.
-
-.. _setting-protobuf-use-kernel-timestamp:
-
-``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
--  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-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: no
-
-If ``SO_REUSEPORT`` support is available, allows multiple threads and processes to open listening sockets for the same port.
-
-Since 4.1.0, when ``pdns-distributes-queries`` is set to false and ``reuseport`` is enabled, every worker-thread will open a separate listening socket to let the kernel distribute the incoming queries instead of running a distributor thread (which could otherwise be a bottleneck) and avoiding thundering herd issues, thus leading to much higher performance on multi-core boxes.
-
-.. _setting-rng:
-
-``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-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-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-size:
-
-``stack-size``
---------------
--  Integer
--  Default: 200000
-
-Size 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-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
--  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
-
--  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 is a deprecated feature that will be removed in the near future.
-
-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
-
--  Integer
--  Default: 0
-
-.. note::
-  This is an experimental implementation of `draft-bellis-dnsop-xpf <https://datatracker.ietf.org/doc/draft-bellis-dnsop-xpf/>`_.
-  This is a deprecated feature that will be removed in the near future.
-
-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 3eb750d138670969349f63ed04ad5170a9f69096..da559a483fe9bdd365e1b2d28b97722d09152f96 100644 (file)
@@ -4,7 +4,148 @@ 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.6.x to master
+5.0.1 to 5.0.2 and master, 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.
+This allows for solving a long standing issue that some statistics were not updated on packet cache hits.
+This is now resolved, but has the consequence that some metrics (in particular response related ones) changed behaviour as they now also reflect packet cache hits, while they did not before.
+This affects the results shown by ``rec_control get-qtypelist`` and the ``response-by-qtype``, ``response-sizes`` and ``response-by-rcode`` items returned by the ``/api/v1/servers/localhost/statistics`` API endpoint.
+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 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`
+^^^^^^^^^^^^^^^^^^^^^^
+The ``trace_regex`` subcommand has been changed to take a file argument.
+Refer to :doc:`rec_control trace-regex <manpages/rec_control.1>` and :ref:`tracing` for details and example use.
+
+4.8.1 to 4.8.2
+--------------
+
+Cache eviction policy
+^^^^^^^^^^^^^^^^^^^^^
+The cache eviction policy for the record and the negative caches has been improved to reduce imbalance between shards.
+The maximum size of the negative cache is now 1/8th of the size of the record cache and its number of shards is 1/8th of the :ref:`setting-record-cache-shards` setting.
+Previously the size was 1/10th of the record cache size and the number of shards was equal to the
+number of shards of the record cache.
+The ``rec_control dump-cache`` command now prints more information about shards.
+
+
+4.7.0 to 4.8.0
+--------------
+
+Structured logging
+^^^^^^^^^^^^^^^^^^
+All logging (except query tracing) has been converted to structured logging.
+Switch to old style logging by setting the :ref:`setting-structured-logging` setting to ``no``.
+When using ``systemd``, structured logging information will be sent to ``journald`` using formatted text strings that list the key-value pairs and are human readable.
+Switch to native key-value pair logging (more suitable for automated log processing) by setting :ref:`setting-structured-logging-backend` on the command line to ``systemd-journal``.
+
+New settings
+^^^^^^^^^^^^
+- The :ref:`setting-max-ns-per-resolve` setting to limit the number of NS records processed to resolve a name has been introduced.
+- The :ref:`setting-serve-stale-extensions` setting to control the new ``Serve Stale`` feature has been introduced.
+- The :ref:`setting-record-cache-locked-ttl-perc` setting to control locking of record sets in the record cache has been introduced.
+- The :ref:`setting-edns-padding-out` setting to control EDNS padding for outgoing DoT has been introduced.
+- The :ref:`setting-structured-logging-backend` setting to control the type of structured logging to ``journald`` has been introduced.
+
+:program:`pdns_recursor` changes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+THe ``--config`` command line option now implements the ``check``, ``default`` and ``diff`` keywords.
+
+:program:`rec_control` changes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+The ``dump-throttle`` and ``dump-edns`` subcommands no longer produces a table per thread, as the corresponding tables are now shared by all threads.
+Additionally, the ``dump-edns`` command  now only lists IPs that have a not OK status.
+The ``dump-nsspeeds`` command has changed format to make it more readable and lists the last round trip time recorded for each address.
+The ``get-proxymapping-stats`` and ``get-remotelogger-stats`` subcommands have been added.
+
+4.7.2 to 4.7.3
+--------------
+
+New settings
+^^^^^^^^^^^^
+- The :ref:`setting-max-ns-per-resolve` setting to limit the number of NS records processed to resolve a name has been introduced.
+
+4.6.2 to 4.7.0
 ---------------
 
 Zone to Cache Changes
@@ -14,15 +155,45 @@ be rejected by default, while previously the ``ZONEMD`` records would be ignored
 
 Asynchronous retrieval of ``AAAA`` records for nameservers
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-If IPv6 is enabled for outgoing queries using :ref:`setting-query-local-address`, the Recursor will schedule an asynchronous task to resolve IPv6 addresses of nameservers it did not otherwise learn.
-These addresses will then be used for future queries to authoritative nameservers.
-This has the consequence that authoritative nameservers will be contacted over IPv6 in more case than before.
+If ``IPv6`` is enabled for outgoing queries using :ref:`setting-query-local-address`, the :program:`Recursor` will schedule an asynchronous task to resolve ``IPv6`` addresses of nameservers it did not otherwise learn.
+These addresses will then be used (in addition to ``IPv4`` addresses) for future queries to authoritative nameservers.
+This has the consequence that authoritative nameservers will be contacted over ``IPv6`` in more case than before.
+
+New Lua Configuration Functions
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+- The :func:`addAllowedAdditionalQType` ``Lua`` configuration function was added to make the :program:`Recursor` add additional records to answers for specific query types.
+- The :func:`addProxyMapping` ``Lua`` configuration function was added to map source addresses to alternative addresses.
+
+Post Resolve FFI Function
+^^^^^^^^^^^^^^^^^^^^^^^^^
+A new :func:`postresolve_ffi` Lua callback function has been introduced.
+
+New settings
+^^^^^^^^^^^^
+- The :ref:`setting-save-parent-ns-set` setting has been introduced, enabling fallback cases if the parent ``NS`` set contains names not in the child ``NS`` set.
+- The :ref:`setting-max-busy-dot-probes` settings has been introduced, enabling the :program:`Recursor` probe for ``DoT`` support of authoritative servers.
+  This is an experimental function, use with care.
+
+:program:`rec_control` changes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+The ``dump-nsspeeds``, ``dump-failedservers`` and ``dump-non-resolving`` subcommands no longer produce a table per thread, as the corresponding tables are now shared by all threads.
+They also use a better readable and sortable timestamp format.
+
+4.6.3 to 4.6.4
+--------------
+
+New settings
+^^^^^^^^^^^^
+- The :ref:`setting-max-ns-per-resolve` setting to limit the number of NS records processed to resolve a name has been introduced.
+
+4.6.1 to 4.6.2
+--------------
 
 Deprecated and changed settings
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
--  The :ref:`setting-hint-file` gained a special value ``no`` to indicate that no hint file should not processed. The hint processing code is also made less verbose.
+-  The :ref:`setting-hint-file` gained a special value ``no`` to indicate that no hint file should be processed. The hint processing code is also made less verbose.
 
-4.5.x to 4.6.0
+4.5.x to 4.6.1
 --------------
 
 Offensive language
@@ -55,6 +226,13 @@ Privileged port binding in Docker
 In our Docker image, our binaries are no longer granted the ``net_bind_service`` capability, as this is unnecessary in many deployments.
 For more information, see the section `"Privileged ports" in Docker-README <https://github.com/PowerDNS/pdns/blob/master/Docker-README.md#privileged-ports>`__.
 
+4.5.10 to 4.5.11
+----------------
+
+New settings
+^^^^^^^^^^^^
+- The :ref:`setting-max-ns-per-resolve` setting to limit the number of NS records processed to resolve a name has been introduced.
+
 4.5.1 to 4.5.2
 --------------
 
@@ -95,7 +273,6 @@ That means that they will be answered with ``127.0.0.1``, ``::1`` or a negative
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 For the commands that write to a file, the file to be dumped to is now opened by the :program:`rec_control` command itself using the credentials and the current working directory of the user running :program:`rec_control`.
 A single minus *-* can be used as a filename to write the data to the standard output stream.
-Additionally, a single minus *-* can be used as a filename to write the data to the standard output stream.
 Previously the file was opened by the recursor, possibly in its chroot environment.
 
 New settings
@@ -120,7 +297,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
 --------------
@@ -146,7 +323,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
 ^^^^^^^^^^^^
@@ -198,8 +375,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 bbb1224f9b7b3737a93234dab6c59ccd1dc6991d..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 pdns_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(pdns_string_view(option), eee);
-}
-
-bool getEDNSExtendedErrorOptFromString(const char* option, unsigned int len, EDNSExtendedError& eee)
-{
-  return getEDNSExtendedErrorOptFromStringView(pdns_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 600aa14534429b41a7ef1280a7ce9f795f53daf1..0000000000000000000000000000000000000000
+++ /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 "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
-  };
-  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
deleted file mode 120000 (symlink)
index eabae782391cb2744ae8909676e031b431ffb6b2..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../filterpo.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..f73b95d54d3437e756d4df439531b99b92bfcb48
--- /dev/null
@@ -0,0 +1,823 @@
+/*
+ * 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 <iostream>
+#include <boost/format.hpp>
+
+#include "filterpo.hh"
+#include "namespaces.hh"
+#include "dnsrecords.hh"
+
+// Names below are RPZ Actions and end with a dot (except "Local Data")
+static const std::string rpzDropName("rpz-drop."),
+  rpzTruncateName("rpz-tcp-only."),
+  rpzNoActionName("rpz-passthru."),
+  rpzCustomName("Local Data");
+
+// Names below are (part) of RPZ Trigger names and do NOT end with a dot
+static const std::string rpzClientIPName("rpz-client-ip"),
+  rpzIPName("rpz-ip"),
+  rpzNSDnameName("rpz-nsdname"),
+  rpzNSIPName("rpz-nsip");
+
+DNSFilterEngine::DNSFilterEngine() = default;
+
+bool DNSFilterEngine::Zone::findExactQNamePolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const
+{
+  return findExactNamedPolicy(d_qpolName, qname, pol);
+}
+
+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);
+    return true;
+  }
+  return false;
+}
+
+bool DNSFilterEngine::Zone::findNSIPPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const
+{
+  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();
+    return true;
+  }
+  return false;
+}
+
+bool DNSFilterEngine::Zone::findResponsePolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const
+{
+  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();
+    return true;
+  }
+  return false;
+}
+
+bool DNSFilterEngine::Zone::findClientPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const
+{
+  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();
+    return true;
+  }
+  return false;
+}
+
+bool DNSFilterEngine::Zone::findNamedPolicy(const std::unordered_map<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol)
+{
+  if (polmap.empty()) {
+    return false;
+  }
+
+  /* for www.powerdns.com, we need to check:
+     www.powerdns.com.
+       *.powerdns.com.
+                *.com.
+                    *.
+   */
+
+  std::unordered_map<DNSName, DNSFilterEngine::Policy>::const_iterator iter;
+  iter = polmap.find(qname);
+
+  if (iter != polmap.end()) {
+    pol = iter->second;
+    return true;
+  }
+
+  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();
+      return true;
+    }
+  }
+  return false;
+}
+
+bool DNSFilterEngine::Zone::findExactNamedPolicy(const std::unordered_map<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol)
+{
+  if (polmap.empty()) {
+    return false;
+  }
+
+  const auto iter = polmap.find(qname);
+  if (iter != polmap.end()) {
+    pol = iter->second;
+    pol.d_trigger = qname;
+    pol.d_hit = qname.toStringNoDot();
+    return true;
+  }
+
+  return false;
+}
+
+bool DNSFilterEngine::getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& pol) const
+{
+  // cout<<"Got question for nameserver name "<<qname<<endl;
+  std::vector<bool> zoneEnabled(d_zones.size());
+  size_t count = 0;
+  bool allEmpty = true;
+  for (const auto& zone : d_zones) {
+    bool enabled = true;
+    const auto& zoneName = zone->getName();
+    if (zone->getPriority() >= pol.getPriority() || discardedPolicies.find(zoneName) != discardedPolicies.end()) {
+      enabled = false;
+    }
+    else {
+      if (zone->hasNSPolicies()) {
+        allEmpty = false;
+      }
+      else {
+        enabled = false;
+      }
+    }
+
+    zoneEnabled[count] = enabled;
+    ++count;
+  }
+
+  if (allEmpty) {
+    return false;
+  }
+
+  /* prepare the wildcard-based names */
+  std::vector<DNSName> wcNames;
+  wcNames.reserve(qname.countLabels());
+  DNSName sub(qname);
+  while (sub.chopOff()) {
+    wcNames.emplace_back(g_wildcarddnsname + sub);
+  }
+
+  count = 0;
+  for (const auto& zone : d_zones) {
+    if (!zoneEnabled[count]) {
+      ++count;
+      continue;
+    }
+    if (zone->findExactNSPolicy(qname, pol)) {
+      // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
+      return true;
+    }
+
+    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();
+        return true;
+      }
+    }
+    ++count;
+  }
+
+  return false;
+}
+
+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& zone : d_zones) {
+    if (zone->getPriority() >= pol.getPriority()) {
+      break;
+    }
+    const auto& zoneName = zone->getName();
+    if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
+      continue;
+    }
+
+    if (zone->findNSIPPolicy(address, pol)) {
+      //      cerr<<"Had a hit on the nameserver ("<<address.toString()<<") used to process the query"<<endl;
+      return true;
+    }
+  }
+  return false;
+}
+
+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& zone : d_zones) {
+    if (zone->getPriority() >= pol.getPriority()) {
+      break;
+    }
+    const auto& zoneName = zone->getName();
+    if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
+      continue;
+    }
+
+    if (zone->findClientPolicy(address, pol)) {
+      // cerr<<"Had a hit on the IP address ("<<ca.toString()<<") of the client"<<endl;
+      return true;
+    }
+  }
+  return false;
+}
+
+bool DNSFilterEngine::getQueryPolicy(const DNSName& qname, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& pol) const
+{
+  // cerr<<"Got question for "<<qname<<' '<< pol.getPriority()<< endl;
+  std::vector<bool> zoneEnabled(d_zones.size());
+  size_t count = 0;
+  bool allEmpty = true;
+  for (const auto& zone : d_zones) {
+    bool enabled = true;
+    if (zone->getPriority() >= pol.getPriority()) {
+      enabled = false;
+    }
+    else {
+      const auto& zoneName = zone->getName();
+      if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
+        enabled = false;
+      }
+      else {
+        if (zone->hasQNamePolicies()) {
+          allEmpty = false;
+        }
+        else {
+          enabled = false;
+        }
+      }
+    }
+
+    zoneEnabled[count] = enabled;
+    ++count;
+  }
+
+  if (allEmpty) {
+    return false;
+  }
+
+  /* prepare the wildcard-based names */
+  std::vector<DNSName> wcNames;
+  wcNames.reserve(qname.countLabels());
+  DNSName sub(qname);
+  while (sub.chopOff()) {
+    wcNames.emplace_back(g_wildcarddnsname + sub);
+  }
+
+  count = 0;
+  for (const auto& zone : d_zones) {
+    if (!zoneEnabled[count]) {
+      ++count;
+      continue;
+    }
+
+    if (zone->findExactQNamePolicy(qname, pol)) {
+      // cerr<<"Had a hit on the name of the query"<<endl;
+      return true;
+    }
+
+    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();
+        return true;
+      }
+    }
+
+    ++count;
+  }
+
+  return false;
+}
+
+bool DNSFilterEngine::getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& pol) const
+{
+  for (const auto& record : records) {
+    if (getPostPolicy(record, discardedPolicies, pol)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool DNSFilterEngine::getPostPolicy(const DNSRecord& record, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& pol) const
+{
+  ComboAddress address;
+  if (record.d_place != DNSResourceRecord::ANSWER) {
+    return false;
+  }
+
+  if (record.d_type == QType::A) {
+    if (auto rec = getRR<ARecordContent>(record)) {
+      address = rec->getCA();
+    }
+  }
+  else if (record.d_type == QType::AAAA) {
+    if (auto rec = getRR<AAAARecordContent>(record)) {
+      address = rec->getCA();
+    }
+  }
+  else {
+    return false;
+  }
+
+  for (const auto& zone : d_zones) {
+    if (zone->getPriority() >= pol.getPriority()) {
+      break;
+    }
+    const auto& zoneName = zone->getName();
+    if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
+      return false;
+    }
+
+    if (zone->findResponsePolicy(address, pol)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void DNSFilterEngine::assureZones(size_t zone)
+{
+  if (d_zones.size() <= zone) {
+    d_zones.resize(zone + 1);
+  }
+}
+
+void DNSFilterEngine::Zone::addNameTrigger(std::unordered_map<DNSName, Policy>& map, const DNSName& n, Policy&& pol, bool ignoreDuplicate, PolicyType ptype)
+{
+  auto iter = map.find(n);
+
+  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) {
+      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));
+  }
+  else {
+    auto& qpol = map.insert({n, std::move(pol)}).first->second;
+    qpol.d_zoneData = d_zoneData;
+    qpol.d_type = ptype;
+  }
+}
+
+void DNSFilterEngine::Zone::addNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& netmask, Policy&& pol, bool ignoreDuplicate, PolicyType ptype)
+{
+  bool exists = nmt.has_key(netmask);
+
+  if (exists) {
+    auto& existingPol = nmt.lookup(netmask)->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 netmask: " + netmask.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));
+  }
+  else {
+    pol.d_zoneData = d_zoneData;
+    pol.d_type = ptype;
+    nmt.insert(netmask).second = std::move(pol);
+  }
+}
+
+bool DNSFilterEngine::Zone::rmNameTrigger(std::unordered_map<DNSName, Policy>& map, const DNSName& name, const Policy& pol)
+{
+  auto found = map.find(name);
+  if (found == map.end()) {
+    return false;
+  }
+
+  auto& existing = found->second;
+  if (existing.d_kind != DNSFilterEngine::PolicyKind::Custom) {
+    map.erase(found);
+    return true;
+  }
+
+  /* for custom types, we might have more than one type,
+     and then we need to remove only the right ones. */
+  bool result = false;
+  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.empty()) {
+    map.erase(found);
+    return true;
+  }
+
+  return result;
+}
+
+bool DNSFilterEngine::Zone::rmNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& netmask, const Policy& pol)
+{
+  bool found = nmt.has_key(netmask);
+  if (!found) {
+    return false;
+  }
+
+  auto& existing = nmt.lookup(netmask)->second;
+  if (existing.d_kind != DNSFilterEngine::PolicyKind::Custom) {
+    nmt.erase(netmask);
+    return true;
+  }
+
+  /* for custom types, we might have more than one type,
+     and then we need to remove only the right ones. */
+
+  bool result = false;
+  for (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.empty()) {
+    nmt.erase(netmask);
+    return true;
+  }
+
+  return result;
+}
+
+void DNSFilterEngine::Zone::addClientTrigger(const Netmask& netmask, Policy&& pol, bool ignoreDuplicate)
+{
+  addNetmaskTrigger(d_qpolAddr, netmask, std::move(pol), ignoreDuplicate, PolicyType::ClientIP);
+}
+
+void DNSFilterEngine::Zone::addResponseTrigger(const Netmask& netmask, Policy&& pol, bool ignoreDuplicate)
+{
+  addNetmaskTrigger(d_postpolAddr, netmask, std::move(pol), ignoreDuplicate, PolicyType::ResponseIP);
+}
+
+void DNSFilterEngine::Zone::addQNameTrigger(const DNSName& dnsname, Policy&& pol, bool ignoreDuplicate)
+{
+  addNameTrigger(d_qpolName, dnsname, std::move(pol), ignoreDuplicate, PolicyType::QName);
+}
+
+void DNSFilterEngine::Zone::addNSTrigger(const DNSName& dnsname, Policy&& pol, bool ignoreDuplicate)
+{
+  addNameTrigger(d_propolName, dnsname, std::move(pol), ignoreDuplicate, PolicyType::NSDName);
+}
+
+void DNSFilterEngine::Zone::addNSIPTrigger(const Netmask& netmask, Policy&& pol, bool ignoreDuplicate)
+{
+  addNetmaskTrigger(d_propolNSAddr, netmask, std::move(pol), ignoreDuplicate, PolicyType::NSIP);
+}
+
+bool DNSFilterEngine::Zone::rmClientTrigger(const Netmask& netmask, const Policy& pol)
+{
+  return rmNetmaskTrigger(d_qpolAddr, netmask, pol);
+}
+
+bool DNSFilterEngine::Zone::rmResponseTrigger(const Netmask& netmask, const Policy& pol)
+{
+  return rmNetmaskTrigger(d_postpolAddr, netmask, pol);
+}
+
+bool DNSFilterEngine::Zone::rmQNameTrigger(const DNSName& dnsname, const Policy& pol)
+{
+  return rmNameTrigger(d_qpolName, dnsname, pol);
+}
+
+bool DNSFilterEngine::Zone::rmNSTrigger(const DNSName& dnsname, const Policy& pol)
+{
+  return rmNameTrigger(d_propolName, dnsname, pol);
+}
+
+bool DNSFilterEngine::Zone::rmNSIPTrigger(const Netmask& netmask, const Policy& 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);
+}
+
+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)),
+            "kind", Logging::Loggable(getKindToString(d_kind)));
+}
+
+DNSRecord DNSFilterEngine::Policy::getRecordFromCustom(const DNSName& qname, const std::shared_ptr<const DNSRecordContent>& custom) const
+{
+  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 (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();
+        dnsrecord.setContent(std::make_shared<CNAMERecordContent>(qname + target));
+      }
+    }
+  }
+
+  return dnsrecord;
+}
+
+std::vector<DNSRecord> DNSFilterEngine::Policy::getCustomRecords(const DNSName& qname, uint16_t qtype) const
+{
+  if (d_kind != PolicyKind::Custom) {
+    throw std::runtime_error("Asking for a custom record from a filtering policy of a non-custom type");
+  }
+
+  std::vector<DNSRecord> result;
+
+  for (const auto& custom : d_custom) {
+    if (qtype != QType::ANY && qtype != custom->getType() && custom->getType() != QType::CNAME) {
+      continue;
+    }
+
+    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 (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();
+          dnsrecord.setContent(std::make_shared<CNAMERecordContent>(qname + target));
+        }
+      }
+    }
+
+    result.emplace_back(getRecordFromCustom(qname, custom));
+  }
+
+  return result;
+}
+
+std::string DNSFilterEngine::getKindToString(DNSFilterEngine::PolicyKind kind)
+{
+  // static const std::string rpzPrefix("rpz-");
+
+  switch (kind) {
+  case DNSFilterEngine::PolicyKind::NoAction:
+    return rpzNoActionName;
+  case DNSFilterEngine::PolicyKind::Drop:
+    return rpzDropName;
+  case DNSFilterEngine::PolicyKind::NXDOMAIN:
+    return g_rootdnsname.toString();
+  case PolicyKind::NODATA:
+    return g_wildcarddnsname.toString();
+  case DNSFilterEngine::PolicyKind::Truncate:
+    return rpzTruncateName;
+  case DNSFilterEngine::PolicyKind::Custom:
+    return rpzCustomName;
+  default:
+    throw std::runtime_error("Unexpected DNSFilterEngine::Policy kind");
+  }
+}
+
+std::string DNSFilterEngine::getTypeToString(DNSFilterEngine::PolicyType type)
+{
+  switch (type) {
+  case DNSFilterEngine::PolicyType::None:
+    return "none";
+  case DNSFilterEngine::PolicyType::QName:
+    return "QName";
+  case DNSFilterEngine::PolicyType::ClientIP:
+    return "Client IP";
+  case DNSFilterEngine::PolicyType::ResponseIP:
+    return "Response IP";
+  case DNSFilterEngine::PolicyType::NSDName:
+    return "Name Server Name";
+  case DNSFilterEngine::PolicyType::NSIP:
+    return "Name Server IP";
+  default:
+    throw std::runtime_error("Unexpected DNSFilterEngine::Policy type");
+  }
+}
+
+std::vector<DNSRecord> DNSFilterEngine::Policy::getRecords(const DNSName& qname) const
+{
+  std::vector<DNSRecord> result;
+
+  if (d_kind == PolicyKind::Custom) {
+    result = getCustomRecords(qname, QType::ANY);
+  }
+  else {
+    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* filePtr, const DNSName& name, const Policy& pol)
+{
+  auto records = pol.getRecords(name);
+  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& netmask)
+{
+  int bits = netmask.getBits();
+  DNSName res(std::to_string(bits));
+  const auto& addr = netmask.getNetwork();
+
+  if (addr.isIPv4()) {
+    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);
+    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:
+    //
+    //    If there exists more than one sequence of zero-valued fields of
+    //    identical length, then only the last such sequence is compressed.
+    //    Note that [RFC5952] specifies compressing the first such sequence,
+    //    but our notation here reverses the order of fields, and so must also
+    //    reverse the selection of which zero sequence to compress.
+    //
+    // 'cur.len > best.len' from the original code is replaced by 'cur.len >= best.len', so the last-longest wins.
+
+    struct
+    {
+      int base, len;
+    } best = {-1, 0}, cur = {-1, 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};
+        }
+        else {
+          cur.len++; // continuation of a run of zeroes
+        }
+      }
+      else { // not a zero
+        if (cur.base != -1) { // end of a run of zeroes
+          if (best.base == -1 || cur.len >= best.len) { // first run of zeroes, or a better one than we found before
+            best = cur;
+          }
+          cur.base = -1;
+        }
+      }
+    }
+
+    if (cur.base != -1) { // address ended with a zero
+      if (best.base == -1 || cur.len >= best.len) { // first run of zeroes, or a better one than we found before
+        best = cur;
+      }
+    }
+
+    if (best.base != -1 && best.len < 2) { // if our best run is only one zero long, we do not replace it
+      best.base = -1;
+    }
+    for (int i = 0; i < (int)elems.size(); i++) {
+      if (i == best.base) {
+        temp = DNSName("zz") + temp;
+        i = i + best.len - 1;
+      }
+      else {
+        temp = DNSName((boost::format("%x") % elems.at(i)).str()) + temp;
+      }
+    }
+    res += temp;
+  }
+
+  return res;
+}
+
+void DNSFilterEngine::Zone::dumpAddrPolicy(FILE* filePtr, const Netmask& netmask, const DNSName& name, const Policy& pol)
+{
+  DNSName full = maskToRPZ(netmask);
+  full += name;
+
+  auto records = pol.getRecords(full);
+  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* filePtr) const
+{
+  /* fake the SOA record */
+  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(filePtr, pair.first + d_domain, pair.second);
+  }
+
+  for (const auto& pair : d_propolName) {
+    dumpNamedPolicy(filePtr, pair.first + DNSName(rpzNSDnameName) + d_domain, pair.second);
+  }
+
+  for (const auto& pair : d_qpolAddr) {
+    dumpAddrPolicy(filePtr, pair.first, DNSName(rpzClientIPName) + d_domain, pair.second);
+  }
+
+  for (const auto& pair : d_propolNSAddr) {
+    dumpAddrPolicy(filePtr, pair.first, DNSName(rpzNSIPName) + d_domain, pair.second);
+  }
+
+  for (const auto& pair : d_postpolAddr) {
+    dumpAddrPolicy(filePtr, pair.first, DNSName(rpzIPName) + d_domain, pair.second);
+  }
+}
+
+void mergePolicyTags(std::unordered_set<std::string>& tags, const std::unordered_set<std::string>& newTags)
+{
+  for (const auto& tag : newTags) {
+    tags.insert(tag);
+  }
+}
deleted file mode 120000 (symlink)
index 3ada59aaeeac31120948f4a5ee9b48443065c732..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../filterpo.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..7d20b7a387127d80a9895f96b5f44a4c6df51e0d
--- /dev/null
@@ -0,0 +1,495 @@
+/*
+ * 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"
+#include "dns.hh"
+#include "dnsname.hh"
+#include "dnsparser.hh"
+#include "logging.hh"
+#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.
+
+
+   We know the following actions:
+
+   No action - just pass it on
+   Drop - drop a query, no response
+   NXDOMAIN - fake up an NXDOMAIN for the query
+   NODATA - just return no data for this qtype
+   Truncate - set TC bit
+   Modified - "we fake an answer for you"
+
+   These actions can be caused by the following triggers:
+
+   qname - the query name
+   client-ip - the IP address of the requestor
+   response-ip - an IP address in the response
+   ns-name - the name of a server used in the delegation
+   ns-ip - the IP address of a server used in the delegation
+
+   This means we get several hook points:
+   1) when the query comes in: qname & client-ip
+   2) during processing: ns-name & ns-ip
+   3) after processing: response-ip
+
+   Triggers meanwhile can apply to:
+   Verbatim domain names
+   Wildcard versions (*.domain.com does NOT match domain.com)
+   Netmasks (IPv4 and IPv6)
+   Finally, triggers are grouped in different zones. The "first" zone that has a match
+   is consulted. Then within that zone, rules again have precedences.
+*/
+
+class DNSFilterEngine
+{
+public:
+  enum class PolicyKind : uint8_t
+  {
+    NoAction,
+    Drop,
+    NXDOMAIN,
+    NODATA,
+    Truncate,
+    Custom
+  };
+  enum class PolicyType : uint8_t
+  {
+    None,
+    QName,
+    ClientIP,
+    ResponseIP,
+    NSDName,
+    NSIP
+  };
+  using Priority = uint16_t;
+  static const Priority maximumPriority = std::numeric_limits<Priority>::max();
+
+  static std::string getKindToString(PolicyKind kind);
+  static std::string getTypeToString(PolicyType type);
+
+  struct PolicyZoneData
+  {
+    /* shared by all the policies from a single zone */
+    std::unordered_set<std::string> d_tags;
+    std::string d_name;
+    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
+  {
+    Policy() :
+      d_ttl(0), d_kind(PolicyKind::NoAction), d_type(PolicyType::None)
+    {
+    }
+
+    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(std::move(data)), d_ttl(ttl), d_kind(kind), d_type(type)
+    {
+    }
+
+    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;
+    }
+
+    [[nodiscard]] const std::string& getName() const
+    {
+      static const std::string notSet;
+      if (d_zoneData) {
+        return d_zoneData->d_name;
+      }
+      return notSet;
+    }
+
+    void setName(const std::string& name)
+    {
+      /* until now the PolicyZoneData was shared,
+         we now need to copy it, then write to it */
+      std::shared_ptr<PolicyZoneData> newZoneData;
+      if (d_zoneData) {
+        newZoneData = std::make_shared<PolicyZoneData>(*d_zoneData);
+      }
+      else {
+        newZoneData = std::make_shared<PolicyZoneData>();
+      }
+      newZoneData->d_name = name;
+      d_zoneData = std::move(newZoneData);
+    }
+
+    [[nodiscard]] const std::unordered_set<std::string>& getTags() const
+    {
+      static const std::unordered_set<std::string> notSet;
+      if (d_zoneData) {
+        return d_zoneData->d_tags;
+      }
+      return notSet;
+    }
+
+    [[nodiscard]] Priority getPriority() const
+    {
+      static Priority notSet = maximumPriority;
+      if (d_zoneData) {
+        return d_zoneData->d_priority;
+      }
+      return notSet;
+    }
+
+    [[nodiscard]] bool policyOverridesGettag() const
+    {
+      if (d_zoneData) {
+        return d_zoneData->d_policyOverridesGettag;
+      }
+      return true;
+    }
+
+    [[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);
+    }
+
+    [[nodiscard]] std::string getLogString() const;
+    void info(Logr::Priority prio, const std::shared_ptr<Logr::Logger>& log) 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;
+    /* 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);
+      }
+    }
+
+  private:
+    [[nodiscard]] DNSRecord getRecordFromCustom(const DNSName& qname, const std::shared_ptr<const DNSRecordContent>& custom) const;
+  };
+
+  class Zone
+  {
+  public:
+    Zone() :
+      d_zoneData(std::make_shared<PolicyZoneData>())
+    {
+    }
+
+    void clear()
+    {
+      d_qpolAddr.clear();
+      d_postpolAddr.clear();
+      d_propolName.clear();
+      d_propolNSAddr.clear();
+      d_qpolName.clear();
+    }
+    void reserve(size_t entriesCount)
+    {
+      d_qpolName.reserve(entriesCount);
+    }
+    void setName(const std::string& name)
+    {
+      d_zoneData->d_name = name;
+    }
+    void setDomain(const DNSName& domain)
+    {
+      d_domain = domain;
+    }
+    void setSerial(uint32_t serial)
+    {
+      d_serial = serial;
+    }
+    void setRefresh(uint32_t refresh)
+    {
+      d_refresh = refresh;
+    }
+    void setTags(std::unordered_set<std::string>&& tags)
+    {
+      d_zoneData->d_tags = std::move(tags);
+    }
+    void setPolicyOverridesGettag(bool flag)
+    {
+      d_zoneData->d_policyOverridesGettag = flag;
+    }
+    void setExtendedErrorCode(uint16_t code)
+    {
+      d_zoneData->d_extendedErrorCode = code;
+    }
+    void setExtendedErrorExtra(const std::string& extra)
+    {
+      d_zoneData->d_extendedErrorExtra = extra;
+    }
+    void setSOA(DNSRecord soa)
+    {
+      d_zoneData->d_soa = std::move(soa);
+    }
+    [[nodiscard]] const std::string& getName() const
+    {
+      return d_zoneData->d_name;
+    }
+
+    [[nodiscard]] DNSName getDomain() const
+    {
+      return d_domain;
+    }
+
+    [[nodiscard]] uint32_t getRefresh() const
+    {
+      return d_refresh;
+    }
+
+    [[nodiscard]] uint32_t getSerial() const
+    {
+      return d_serial;
+    }
+
+    [[nodiscard]] size_t size() const
+    {
+      return d_qpolAddr.size() + d_postpolAddr.size() + d_propolName.size() + d_propolNSAddr.size() + d_qpolName.size();
+    }
+
+    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& 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& 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;
+    bool findNSIPPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const;
+    bool findResponsePolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const;
+    bool findClientPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const;
+
+    [[nodiscard]] bool hasClientPolicies() const
+    {
+      return !d_qpolAddr.empty();
+    }
+    [[nodiscard]] bool hasQNamePolicies() const
+    {
+      return !d_qpolName.empty();
+    }
+    [[nodiscard]] bool hasNSPolicies() const
+    {
+      return !d_propolName.empty();
+    }
+    [[nodiscard]] bool hasNSIPPolicies() const
+    {
+      return !d_propolNSAddr.empty();
+    }
+    [[nodiscard]] bool hasResponsePolicies() const
+    {
+      return !d_postpolAddr.empty();
+    }
+    [[nodiscard]] Priority getPriority() const
+    {
+      return d_zoneData->d_priority;
+    }
+    void setPriority(Priority priority)
+    {
+      d_zoneData->d_priority = priority;
+    }
+
+    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& 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);
+
+    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* 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
+    std::unordered_map<DNSName, Policy> d_propolName; // NSDNAME (RPZ)
+    NetmaskTree<Policy> d_propolNSAddr; // NSIP (RPZ)
+    NetmaskTree<Policy> d_postpolAddr; // IP trigger (RPZ)
+    DNSName d_domain;
+    std::shared_ptr<PolicyZoneData> d_zoneData{nullptr};
+    uint32_t d_serial{0};
+    uint32_t d_refresh{0};
+  };
+
+  DNSFilterEngine();
+  void clear()
+  {
+    for (auto& zone : d_zones) {
+      zone->clear();
+    }
+  }
+  void clearZones()
+  {
+    d_zones.clear();
+  }
+  [[nodiscard]] std::shared_ptr<Zone> getZone(size_t zoneIdx) const
+  {
+    std::shared_ptr<Zone> result{nullptr};
+    if (zoneIdx < d_zones.size()) {
+      result = d_zones[zoneIdx];
+    }
+    return result;
+  }
+  [[nodiscard]] std::shared_ptr<Zone> getZone(const std::string& name) const
+  {
+    for (const auto& zone : d_zones) {
+      const auto& zName = zone->getName();
+      if (zName == name) {
+        return zone;
+      }
+    }
+    return nullptr;
+  }
+  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, const std::shared_ptr<Zone>& newZone)
+  {
+    if (newZone) {
+      assureZones(zoneIdx);
+      newZone->setPriority(zoneIdx);
+      d_zones[zoneIdx] = newZone;
+    }
+  }
+
+  bool getQueryPolicy(const DNSName& qname, 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
+  [[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 = priority;
+    getQueryPolicy(qname, discardedPolicies, policy);
+    return policy;
+  }
+
+  [[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 = priority;
+    getClientPolicy(address, discardedPolicies, policy);
+    return policy;
+  }
+
+  [[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 = priority;
+    getProcessingPolicy(qname, discardedPolicies, policy);
+    return policy;
+  }
+
+  [[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 = priority;
+    getProcessingPolicy(address, discardedPolicies, policy);
+    return policy;
+  }
+
+  [[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 = priority;
+    getPostPolicy(records, discardedPolicies, policy);
+    return policy;
+  }
+
+  [[nodiscard]] size_t size() const
+  {
+    return d_zones.size();
+  }
+
+private:
+  void assureZones(size_t zone);
+  vector<std::shared_ptr<Zone>> d_zones;
+};
+
+void mergePolicyTags(std::unordered_set<std::string>& tags, const std::unordered_set<std::string>& newTags);
diff --git a/pdns/recursordist/gss_context.cc b/pdns/recursordist/gss_context.cc
new file mode 120000 (symlink)
index 0000000..3ed3e71
--- /dev/null
@@ -0,0 +1 @@
+../gss_context.cc
\ No newline at end of file
diff --git a/pdns/recursordist/gss_context.hh b/pdns/recursordist/gss_context.hh
new file mode 120000 (symlink)
index 0000000..050b795
--- /dev/null
@@ -0,0 +1 @@
+../gss_context.hh
\ No newline at end of file
index 2624b90fa5fb77b59e8a9d76ca0a8b520f0da983..18cbf298bb374127b663f2e286d21832b93ff944 100644 (file)
@@ -1,20 +1,18 @@
 <!DOCTYPE html>
 <html>
 <head>
-    <meta charset="utf-8"/>
-    <script src="js/jquery-1.8.3.min.js"></script>
-    <script src="js/d3.v3.js"></script>
-    <script src="js/rickshaw.min.js"></script>
-    <script src="js/handlebars-v4.0.11.js"></script>
-    <script src="js/moment.min.js"></script>
+    <meta charset="utf-8">
+    <script src="js/d3.v3-min.js" defer></script>
+    <script src="js/rickshaw.min.js" defer></script>
+    <script src="js/handlebars-v4.0.11-min.js" defer></script>
+    <script src="js/moment.min.js" defer></script>
     <link type="text/css" rel="stylesheet" href="graph.css">
     <link type="text/css" rel="stylesheet" href="detail.css">
     <link type="text/css" rel="stylesheet" href="legend.css">
     <link type="text/css" rel="stylesheet" href="lines.css">
     <link type="text/css" rel="stylesheet" href="styling.css">
 
-    <script src="local.js"></script>
-
+    <script src="local-2022.js" defer></script>
 </head>
 
 <body>
diff --git a/pdns/recursordist/html/js/handlebars-v4.0.11-min.js b/pdns/recursordist/html/js/handlebars-v4.0.11-min.js
new file mode 100644 (file)
index 0000000..f4825ff
--- /dev/null
@@ -0,0 +1,284 @@
+
+(function webpackUniversalModuleDefinition(root,factory){if(typeof exports==='object'&&typeof module==='object')
+module.exports=factory();else if(typeof define==='function'&&define.amd)
+define([],factory);else if(typeof exports==='object')
+exports["Handlebars"]=factory();else
+root["Handlebars"]=factory();})(this,function(){return(function(modules){var installedModules={};function __webpack_require__(moduleId){if(installedModules[moduleId])
+return installedModules[moduleId].exports;var module=installedModules[moduleId]={exports:{},id:moduleId,loaded:false};modules[moduleId].call(module.exports,module,module.exports,__webpack_require__);module.loaded=true;return module.exports;}
+__webpack_require__.m=modules;__webpack_require__.c=installedModules;__webpack_require__.p="";return __webpack_require__(0);})
+([(function(module,exports,__webpack_require__){'use strict';var _interopRequireDefault=__webpack_require__(1)['default'];exports.__esModule=true;var _handlebarsRuntime=__webpack_require__(2);var _handlebarsRuntime2=_interopRequireDefault(_handlebarsRuntime);var _handlebarsCompilerAst=__webpack_require__(35);var _handlebarsCompilerAst2=_interopRequireDefault(_handlebarsCompilerAst);var _handlebarsCompilerBase=__webpack_require__(36);var _handlebarsCompilerCompiler=__webpack_require__(41);var _handlebarsCompilerJavascriptCompiler=__webpack_require__(42);var _handlebarsCompilerJavascriptCompiler2=_interopRequireDefault(_handlebarsCompilerJavascriptCompiler);var _handlebarsCompilerVisitor=__webpack_require__(39);var _handlebarsCompilerVisitor2=_interopRequireDefault(_handlebarsCompilerVisitor);var _handlebarsNoConflict=__webpack_require__(34);var _handlebarsNoConflict2=_interopRequireDefault(_handlebarsNoConflict);var _create=_handlebarsRuntime2['default'].create;function create(){var hb=_create();hb.compile=function(input,options){return _handlebarsCompilerCompiler.compile(input,options,hb);};hb.precompile=function(input,options){return _handlebarsCompilerCompiler.precompile(input,options,hb);};hb.AST=_handlebarsCompilerAst2['default'];hb.Compiler=_handlebarsCompilerCompiler.Compiler;hb.JavaScriptCompiler=_handlebarsCompilerJavascriptCompiler2['default'];hb.Parser=_handlebarsCompilerBase.parser;hb.parse=_handlebarsCompilerBase.parse;return hb;}
+var inst=create();inst.create=create;_handlebarsNoConflict2['default'](inst);inst.Visitor=_handlebarsCompilerVisitor2['default'];inst['default']=inst;exports['default']=inst;module.exports=exports['default'];}),(function(module,exports){"use strict";exports["default"]=function(obj){return obj&&obj.__esModule?obj:{"default":obj};};exports.__esModule=true;}),(function(module,exports,__webpack_require__){'use strict';var _interopRequireWildcard=__webpack_require__(3)['default'];var _interopRequireDefault=__webpack_require__(1)['default'];exports.__esModule=true;var _handlebarsBase=__webpack_require__(4);var base=_interopRequireWildcard(_handlebarsBase);var _handlebarsSafeString=__webpack_require__(21);var _handlebarsSafeString2=_interopRequireDefault(_handlebarsSafeString);var _handlebarsException=__webpack_require__(6);var _handlebarsException2=_interopRequireDefault(_handlebarsException);var _handlebarsUtils=__webpack_require__(5);var Utils=_interopRequireWildcard(_handlebarsUtils);var _handlebarsRuntime=__webpack_require__(22);var runtime=_interopRequireWildcard(_handlebarsRuntime);var _handlebarsNoConflict=__webpack_require__(34);var _handlebarsNoConflict2=_interopRequireDefault(_handlebarsNoConflict);function create(){var hb=new base.HandlebarsEnvironment();Utils.extend(hb,base);hb.SafeString=_handlebarsSafeString2['default'];hb.Exception=_handlebarsException2['default'];hb.Utils=Utils;hb.escapeExpression=Utils.escapeExpression;hb.VM=runtime;hb.template=function(spec){return runtime.template(spec,hb);};return hb;}
+var inst=create();inst.create=create;_handlebarsNoConflict2['default'](inst);inst['default']=inst;exports['default']=inst;module.exports=exports['default'];}),(function(module,exports){"use strict";exports["default"]=function(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}
+newObj["default"]=obj;return newObj;}};exports.__esModule=true;}),(function(module,exports,__webpack_require__){'use strict';var _interopRequireDefault=__webpack_require__(1)['default'];exports.__esModule=true;exports.HandlebarsEnvironment=HandlebarsEnvironment;var _utils=__webpack_require__(5);var _exception=__webpack_require__(6);var _exception2=_interopRequireDefault(_exception);var _helpers=__webpack_require__(10);var _decorators=__webpack_require__(18);var _logger=__webpack_require__(20);var _logger2=_interopRequireDefault(_logger);var VERSION='4.0.11';exports.VERSION=VERSION;var COMPILER_REVISION=7;exports.COMPILER_REVISION=COMPILER_REVISION;var REVISION_CHANGES={1:'<= 1.0.rc.2',2:'== 1.0.0-rc.3',3:'== 1.0.0-rc.4',4:'== 1.x.x',5:'== 2.0.0-alpha.x',6:'>= 2.0.0-beta.1',7:'>= 4.0.0'};exports.REVISION_CHANGES=REVISION_CHANGES;var objectType='[object Object]';function HandlebarsEnvironment(helpers,partials,decorators){this.helpers=helpers||{};this.partials=partials||{};this.decorators=decorators||{};_helpers.registerDefaultHelpers(this);_decorators.registerDefaultDecorators(this);}
+HandlebarsEnvironment.prototype={constructor:HandlebarsEnvironment,logger:_logger2['default'],log:_logger2['default'].log,registerHelper:function registerHelper(name,fn){if(_utils.toString.call(name)===objectType){if(fn){throw new _exception2['default']('Arg not supported with multiple helpers');}
+_utils.extend(this.helpers,name);}else{this.helpers[name]=fn;}},unregisterHelper:function unregisterHelper(name){delete this.helpers[name];},registerPartial:function registerPartial(name,partial){if(_utils.toString.call(name)===objectType){_utils.extend(this.partials,name);}else{if(typeof partial==='undefined'){throw new _exception2['default']('Attempting to register a partial called "'+name+'" as undefined');}
+this.partials[name]=partial;}},unregisterPartial:function unregisterPartial(name){delete this.partials[name];},registerDecorator:function registerDecorator(name,fn){if(_utils.toString.call(name)===objectType){if(fn){throw new _exception2['default']('Arg not supported with multiple decorators');}
+_utils.extend(this.decorators,name);}else{this.decorators[name]=fn;}},unregisterDecorator:function unregisterDecorator(name){delete this.decorators[name];}};var log=_logger2['default'].log;exports.log=log;exports.createFrame=_utils.createFrame;exports.logger=_logger2['default'];}),(function(module,exports){'use strict';exports.__esModule=true;exports.extend=extend;exports.indexOf=indexOf;exports.escapeExpression=escapeExpression;exports.isEmpty=isEmpty;exports.createFrame=createFrame;exports.blockParams=blockParams;exports.appendContextPath=appendContextPath;var escape={'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#x27;','`':'&#x60;','=':'&#x3D;'};var badChars=/[&<>"'`=]/g,possible=/[&<>"'`=]/;function escapeChar(chr){return escape[chr];}
+function extend(obj){for(var i=1;i<arguments.length;i++){for(var key in arguments[i]){if(Object.prototype.hasOwnProperty.call(arguments[i],key)){obj[key]=arguments[i][key];}}}
+return obj;}
+var toString=Object.prototype.toString;exports.toString=toString;var isFunction=function isFunction(value){return typeof value==='function';};if(isFunction(/x/)){exports.isFunction=isFunction=function(value){return typeof value==='function'&&toString.call(value)==='[object Function]';};}
+exports.isFunction=isFunction;var isArray=Array.isArray||function(value){return value&&typeof value==='object'?toString.call(value)==='[object Array]':false;};exports.isArray=isArray;function indexOf(array,value){for(var i=0,len=array.length;i<len;i++){if(array[i]===value){return i;}}
+return-1;}
+function escapeExpression(string){if(typeof string!=='string'){if(string&&string.toHTML){return string.toHTML();}else if(string==null){return'';}else if(!string){return string+'';}
+string=''+string;}
+if(!possible.test(string)){return string;}
+return string.replace(badChars,escapeChar);}
+function isEmpty(value){if(!value&&value!==0){return true;}else if(isArray(value)&&value.length===0){return true;}else{return false;}}
+function createFrame(object){var frame=extend({},object);frame._parent=object;return frame;}
+function blockParams(params,ids){params.path=ids;return params;}
+function appendContextPath(contextPath,id){return(contextPath?contextPath+'.':'')+id;}}),(function(module,exports,__webpack_require__){'use strict';var _Object$defineProperty=__webpack_require__(7)['default'];exports.__esModule=true;var errorProps=['description','fileName','lineNumber','message','name','number','stack'];function Exception(message,node){var loc=node&&node.loc,line=undefined,column=undefined;if(loc){line=loc.start.line;column=loc.start.column;message+=' - '+line+':'+column;}
+var tmp=Error.prototype.constructor.call(this,message);for(var idx=0;idx<errorProps.length;idx++){this[errorProps[idx]]=tmp[errorProps[idx]];}
+if(Error.captureStackTrace){Error.captureStackTrace(this,Exception);}
+try{if(loc){this.lineNumber=line;if(_Object$defineProperty){Object.defineProperty(this,'column',{value:column,enumerable:true});}else{this.column=column;}}}catch(nop){}}
+Exception.prototype=new Error();exports['default']=Exception;module.exports=exports['default'];}),(function(module,exports,__webpack_require__){module.exports={"default":__webpack_require__(8),__esModule:true};}),(function(module,exports,__webpack_require__){var $=__webpack_require__(9);module.exports=function defineProperty(it,key,desc){return $.setDesc(it,key,desc);};}),(function(module,exports){var $Object=Object;module.exports={create:$Object.create,getProto:$Object.getPrototypeOf,isEnum:{}.propertyIsEnumerable,getDesc:$Object.getOwnPropertyDescriptor,setDesc:$Object.defineProperty,setDescs:$Object.defineProperties,getKeys:$Object.keys,getNames:$Object.getOwnPropertyNames,getSymbols:$Object.getOwnPropertySymbols,each:[].forEach};}),(function(module,exports,__webpack_require__){'use strict';var _interopRequireDefault=__webpack_require__(1)['default'];exports.__esModule=true;exports.registerDefaultHelpers=registerDefaultHelpers;var _helpersBlockHelperMissing=__webpack_require__(11);var _helpersBlockHelperMissing2=_interopRequireDefault(_helpersBlockHelperMissing);var _helpersEach=__webpack_require__(12);var _helpersEach2=_interopRequireDefault(_helpersEach);var _helpersHelperMissing=__webpack_require__(13);var _helpersHelperMissing2=_interopRequireDefault(_helpersHelperMissing);var _helpersIf=__webpack_require__(14);var _helpersIf2=_interopRequireDefault(_helpersIf);var _helpersLog=__webpack_require__(15);var _helpersLog2=_interopRequireDefault(_helpersLog);var _helpersLookup=__webpack_require__(16);var _helpersLookup2=_interopRequireDefault(_helpersLookup);var _helpersWith=__webpack_require__(17);var _helpersWith2=_interopRequireDefault(_helpersWith);function registerDefaultHelpers(instance){_helpersBlockHelperMissing2['default'](instance);_helpersEach2['default'](instance);_helpersHelperMissing2['default'](instance);_helpersIf2['default'](instance);_helpersLog2['default'](instance);_helpersLookup2['default'](instance);_helpersWith2['default'](instance);}}),(function(module,exports,__webpack_require__){'use strict';exports.__esModule=true;var _utils=__webpack_require__(5);exports['default']=function(instance){instance.registerHelper('blockHelperMissing',function(context,options){var inverse=options.inverse,fn=options.fn;if(context===true){return fn(this);}else if(context===false||context==null){return inverse(this);}else if(_utils.isArray(context)){if(context.length>0){if(options.ids){options.ids=[options.name];}
+return instance.helpers.each(context,options);}else{return inverse(this);}}else{if(options.data&&options.ids){var data=_utils.createFrame(options.data);data.contextPath=_utils.appendContextPath(options.data.contextPath,options.name);options={data:data};}
+return fn(context,options);}});};module.exports=exports['default'];}),(function(module,exports,__webpack_require__){'use strict';var _interopRequireDefault=__webpack_require__(1)['default'];exports.__esModule=true;var _utils=__webpack_require__(5);var _exception=__webpack_require__(6);var _exception2=_interopRequireDefault(_exception);exports['default']=function(instance){instance.registerHelper('each',function(context,options){if(!options){throw new _exception2['default']('Must pass iterator to #each');}
+var fn=options.fn,inverse=options.inverse,i=0,ret='',data=undefined,contextPath=undefined;if(options.data&&options.ids){contextPath=_utils.appendContextPath(options.data.contextPath,options.ids[0])+'.';}
+if(_utils.isFunction(context)){context=context.call(this);}
+if(options.data){data=_utils.createFrame(options.data);}
+function execIteration(field,index,last){if(data){data.key=field;data.index=index;data.first=index===0;data.last=!!last;if(contextPath){data.contextPath=contextPath+field;}}
+ret=ret+fn(context[field],{data:data,blockParams:_utils.blockParams([context[field],field],[contextPath+field,null])});}
+if(context&&typeof context==='object'){if(_utils.isArray(context)){for(var j=context.length;i<j;i++){if(i in context){execIteration(i,i,i===context.length-1);}}}else{var priorKey=undefined;for(var key in context){if(context.hasOwnProperty(key)){if(priorKey!==undefined){execIteration(priorKey,i-1);}
+priorKey=key;i++;}}
+if(priorKey!==undefined){execIteration(priorKey,i-1,true);}}}
+if(i===0){ret=inverse(this);}
+return ret;});};module.exports=exports['default'];}),(function(module,exports,__webpack_require__){'use strict';var _interopRequireDefault=__webpack_require__(1)['default'];exports.__esModule=true;var _exception=__webpack_require__(6);var _exception2=_interopRequireDefault(_exception);exports['default']=function(instance){instance.registerHelper('helperMissing',function(){if(arguments.length===1){return undefined;}else{throw new _exception2['default']('Missing helper: "'+arguments[arguments.length-1].name+'"');}});};module.exports=exports['default'];}),(function(module,exports,__webpack_require__){'use strict';exports.__esModule=true;var _utils=__webpack_require__(5);exports['default']=function(instance){instance.registerHelper('if',function(conditional,options){if(_utils.isFunction(conditional)){conditional=conditional.call(this);}
+if(!options.hash.includeZero&&!conditional||_utils.isEmpty(conditional)){return options.inverse(this);}else{return options.fn(this);}});instance.registerHelper('unless',function(conditional,options){return instance.helpers['if'].call(this,conditional,{fn:options.inverse,inverse:options.fn,hash:options.hash});});};module.exports=exports['default'];}),(function(module,exports){'use strict';exports.__esModule=true;exports['default']=function(instance){instance.registerHelper('log',function(){var args=[undefined],options=arguments[arguments.length-1];for(var i=0;i<arguments.length-1;i++){args.push(arguments[i]);}
+var level=1;if(options.hash.level!=null){level=options.hash.level;}else if(options.data&&options.data.level!=null){level=options.data.level;}
+args[0]=level;instance.log.apply(instance,args);});};module.exports=exports['default'];}),(function(module,exports){'use strict';exports.__esModule=true;exports['default']=function(instance){instance.registerHelper('lookup',function(obj,field){return obj&&obj[field];});};module.exports=exports['default'];}),(function(module,exports,__webpack_require__){'use strict';exports.__esModule=true;var _utils=__webpack_require__(5);exports['default']=function(instance){instance.registerHelper('with',function(context,options){if(_utils.isFunction(context)){context=context.call(this);}
+var fn=options.fn;if(!_utils.isEmpty(context)){var data=options.data;if(options.data&&options.ids){data=_utils.createFrame(options.data);data.contextPath=_utils.appendContextPath(options.data.contextPath,options.ids[0]);}
+return fn(context,{data:data,blockParams:_utils.blockParams([context],[data&&data.contextPath])});}else{return options.inverse(this);}});};module.exports=exports['default'];}),(function(module,exports,__webpack_require__){'use strict';var _interopRequireDefault=__webpack_require__(1)['default'];exports.__esModule=true;exports.registerDefaultDecorators=registerDefaultDecorators;var _decoratorsInline=__webpack_require__(19);var _decoratorsInline2=_interopRequireDefault(_decoratorsInline);function registerDefaultDecorators(instance){_decoratorsInline2['default'](instance);}}),(function(module,exports,__webpack_require__){'use strict';exports.__esModule=true;var _utils=__webpack_require__(5);exports['default']=function(instance){instance.registerDecorator('inline',function(fn,props,container,options){var ret=fn;if(!props.partials){props.partials={};ret=function(context,options){var original=container.partials;container.partials=_utils.extend({},original,props.partials);var ret=fn(context,options);container.partials=original;return ret;};}
+props.partials[options.args[0]]=options.fn;return ret;});};module.exports=exports['default'];}),(function(module,exports,__webpack_require__){'use strict';exports.__esModule=true;var _utils=__webpack_require__(5);var logger={methodMap:['debug','info','warn','error'],level:'info',lookupLevel:function lookupLevel(level){if(typeof level==='string'){var levelMap=_utils.indexOf(logger.methodMap,level.toLowerCase());if(levelMap>=0){level=levelMap;}else{level=parseInt(level,10);}}
+return level;},log:function log(level){level=logger.lookupLevel(level);if(typeof console!=='undefined'&&logger.lookupLevel(logger.level)<=level){var method=logger.methodMap[level];if(!console[method]){method='log';}
+for(var _len=arguments.length,message=Array(_len>1?_len-1:0),_key=1;_key<_len;_key++){message[_key-1]=arguments[_key];}
+console[method].apply(console,message);}}};exports['default']=logger;module.exports=exports['default'];}),(function(module,exports){'use strict';exports.__esModule=true;function SafeString(string){this.string=string;}
+SafeString.prototype.toString=SafeString.prototype.toHTML=function(){return''+this.string;};exports['default']=SafeString;module.exports=exports['default'];}),(function(module,exports,__webpack_require__){'use strict';var _Object$seal=__webpack_require__(23)['default'];var _interopRequireWildcard=__webpack_require__(3)['default'];var _interopRequireDefault=__webpack_require__(1)['default'];exports.__esModule=true;exports.checkRevision=checkRevision;exports.template=template;exports.wrapProgram=wrapProgram;exports.resolvePartial=resolvePartial;exports.invokePartial=invokePartial;exports.noop=noop;var _utils=__webpack_require__(5);var Utils=_interopRequireWildcard(_utils);var _exception=__webpack_require__(6);var _exception2=_interopRequireDefault(_exception);var _base=__webpack_require__(4);function checkRevision(compilerInfo){var compilerRevision=compilerInfo&&compilerInfo[0]||1,currentRevision=_base.COMPILER_REVISION;if(compilerRevision!==currentRevision){if(compilerRevision<currentRevision){var runtimeVersions=_base.REVISION_CHANGES[currentRevision],compilerVersions=_base.REVISION_CHANGES[compilerRevision];throw new _exception2['default']('Template was precompiled with an older version of Handlebars than the current runtime. '+'Please update your precompiler to a newer version ('+runtimeVersions+') or downgrade your runtime to an older version ('+compilerVersions+').');}else{throw new _exception2['default']('Template was precompiled with a newer version of Handlebars than the current runtime. '+'Please update your runtime to a newer version ('+compilerInfo[1]+').');}}}
+function template(templateSpec,env){if(!env){throw new _exception2['default']('No environment passed to template');}
+if(!templateSpec||!templateSpec.main){throw new _exception2['default']('Unknown template object: '+typeof templateSpec);}
+templateSpec.main.decorator=templateSpec.main_d;env.VM.checkRevision(templateSpec.compiler);function invokePartialWrapper(partial,context,options){if(options.hash){context=Utils.extend({},context,options.hash);if(options.ids){options.ids[0]=true;}}
+partial=env.VM.resolvePartial.call(this,partial,context,options);var result=env.VM.invokePartial.call(this,partial,context,options);if(result==null&&env.compile){options.partials[options.name]=env.compile(partial,templateSpec.compilerOptions,env);result=options.partials[options.name](context,options);}
+if(result!=null){if(options.indent){var lines=result.split('\n');for(var i=0,l=lines.length;i<l;i++){if(!lines[i]&&i+1===l){break;}
+lines[i]=options.indent+lines[i];}
+result=lines.join('\n');}
+return result;}else{throw new _exception2['default']('The partial '+options.name+' could not be compiled when running in runtime-only mode');}}
+var container={strict:function strict(obj,name){if(!(name in obj)){throw new _exception2['default']('"'+name+'" not defined in '+obj);}
+return obj[name];},lookup:function lookup(depths,name){var len=depths.length;for(var i=0;i<len;i++){if(depths[i]&&depths[i][name]!=null){return depths[i][name];}}},lambda:function lambda(current,context){return typeof current==='function'?current.call(context):current;},escapeExpression:Utils.escapeExpression,invokePartial:invokePartialWrapper,fn:function fn(i){var ret=templateSpec[i];ret.decorator=templateSpec[i+'_d'];return ret;},programs:[],program:function program(i,data,declaredBlockParams,blockParams,depths){var programWrapper=this.programs[i],fn=this.fn(i);if(data||depths||blockParams||declaredBlockParams){programWrapper=wrapProgram(this,i,fn,data,declaredBlockParams,blockParams,depths);}else if(!programWrapper){programWrapper=this.programs[i]=wrapProgram(this,i,fn);}
+return programWrapper;},data:function data(value,depth){while(value&&depth--){value=value._parent;}
+return value;},merge:function merge(param,common){var obj=param||common;if(param&&common&&param!==common){obj=Utils.extend({},common,param);}
+return obj;},nullContext:_Object$seal({}),noop:env.VM.noop,compilerInfo:templateSpec.compiler};function ret(context){var options=arguments.length<=1||arguments[1]===undefined?{}:arguments[1];var data=options.data;ret._setup(options);if(!options.partial&&templateSpec.useData){data=initData(context,data);}
+var depths=undefined,blockParams=templateSpec.useBlockParams?[]:undefined;if(templateSpec.useDepths){if(options.depths){depths=context!=options.depths[0]?[context].concat(options.depths):options.depths;}else{depths=[context];}}
+function main(context){return''+templateSpec.main(container,context,container.helpers,container.partials,data,blockParams,depths);}
+main=executeDecorators(templateSpec.main,main,container,options.depths||[],data,blockParams);return main(context,options);}
+ret.isTop=true;ret._setup=function(options){if(!options.partial){container.helpers=container.merge(options.helpers,env.helpers);if(templateSpec.usePartial){container.partials=container.merge(options.partials,env.partials);}
+if(templateSpec.usePartial||templateSpec.useDecorators){container.decorators=container.merge(options.decorators,env.decorators);}}else{container.helpers=options.helpers;container.partials=options.partials;container.decorators=options.decorators;}};ret._child=function(i,data,blockParams,depths){if(templateSpec.useBlockParams&&!blockParams){throw new _exception2['default']('must pass block params');}
+if(templateSpec.useDepths&&!depths){throw new _exception2['default']('must pass parent depths');}
+return wrapProgram(container,i,templateSpec[i],data,0,blockParams,depths);};return ret;}
+function wrapProgram(container,i,fn,data,declaredBlockParams,blockParams,depths){function prog(context){var options=arguments.length<=1||arguments[1]===undefined?{}:arguments[1];var currentDepths=depths;if(depths&&context!=depths[0]&&!(context===container.nullContext&&depths[0]===null)){currentDepths=[context].concat(depths);}
+return fn(container,context,container.helpers,container.partials,options.data||data,blockParams&&[options.blockParams].concat(blockParams),currentDepths);}
+prog=executeDecorators(fn,prog,container,depths,data,blockParams);prog.program=i;prog.depth=depths?depths.length:0;prog.blockParams=declaredBlockParams||0;return prog;}
+function resolvePartial(partial,context,options){if(!partial){if(options.name==='@partial-block'){partial=options.data['partial-block'];}else{partial=options.partials[options.name];}}else if(!partial.call&&!options.name){options.name=partial;partial=options.partials[partial];}
+return partial;}
+function invokePartial(partial,context,options){var currentPartialBlock=options.data&&options.data['partial-block'];options.partial=true;if(options.ids){options.data.contextPath=options.ids[0]||options.data.contextPath;}
+var partialBlock=undefined;if(options.fn&&options.fn!==noop){(function(){options.data=_base.createFrame(options.data);var fn=options.fn;partialBlock=options.data['partial-block']=function partialBlockWrapper(context){var options=arguments.length<=1||arguments[1]===undefined?{}:arguments[1];options.data=_base.createFrame(options.data);options.data['partial-block']=currentPartialBlock;return fn(context,options);};if(fn.partials){options.partials=Utils.extend({},options.partials,fn.partials);}})();}
+if(partial===undefined&&partialBlock){partial=partialBlock;}
+if(partial===undefined){throw new _exception2['default']('The partial '+options.name+' could not be found');}else if(partial instanceof Function){return partial(context,options);}}
+function noop(){return'';}
+function initData(context,data){if(!data||!('root'in data)){data=data?_base.createFrame(data):{};data.root=context;}
+return data;}
+function executeDecorators(fn,prog,container,depths,data,blockParams){if(fn.decorator){var props={};prog=fn.decorator(prog,props,container,depths&&depths[0],data,blockParams,depths);Utils.extend(prog,props);}
+return prog;}}),(function(module,exports,__webpack_require__){module.exports={"default":__webpack_require__(24),__esModule:true};}),(function(module,exports,__webpack_require__){__webpack_require__(25);module.exports=__webpack_require__(30).Object.seal;}),(function(module,exports,__webpack_require__){var isObject=__webpack_require__(26);__webpack_require__(27)('seal',function($seal){return function seal(it){return $seal&&isObject(it)?$seal(it):it;};});}),(function(module,exports){module.exports=function(it){return typeof it==='object'?it!==null:typeof it==='function';};}),(function(module,exports,__webpack_require__){var $export=__webpack_require__(28),core=__webpack_require__(30),fails=__webpack_require__(33);module.exports=function(KEY,exec){var fn=(core.Object||{})[KEY]||Object[KEY],exp={};exp[KEY]=exec(fn);$export($export.S+$export.F*fails(function(){fn(1);}),'Object',exp);};}),(function(module,exports,__webpack_require__){var global=__webpack_require__(29),core=__webpack_require__(30),ctx=__webpack_require__(31),PROTOTYPE='prototype';var $export=function(type,name,source){var IS_FORCED=type&$export.F,IS_GLOBAL=type&$export.G,IS_STATIC=type&$export.S,IS_PROTO=type&$export.P,IS_BIND=type&$export.B,IS_WRAP=type&$export.W,exports=IS_GLOBAL?core:core[name]||(core[name]={}),target=IS_GLOBAL?global:IS_STATIC?global[name]:(global[name]||{})[PROTOTYPE],key,own,out;if(IS_GLOBAL)source=name;for(key in source){own=!IS_FORCED&&target&&key in target;if(own&&key in exports)continue;out=own?target[key]:source[key];exports[key]=IS_GLOBAL&&typeof target[key]!='function'?source[key]:IS_BIND&&own?ctx(out,global):IS_WRAP&&target[key]==out?(function(C){var F=function(param){return this instanceof C?new C(param):C(param);};F[PROTOTYPE]=C[PROTOTYPE];return F;})(out):IS_PROTO&&typeof out=='function'?ctx(Function.call,out):out;if(IS_PROTO)(exports[PROTOTYPE]||(exports[PROTOTYPE]={}))[key]=out;}};$export.F=1;$export.G=2;$export.S=4;$export.P=8;$export.B=16;$export.W=32;module.exports=$export;}),(function(module,exports){var global=module.exports=typeof window!='undefined'&&window.Math==Math?window:typeof self!='undefined'&&self.Math==Math?self:Function('return this')();if(typeof __g=='number')__g=global;}),(function(module,exports){var core=module.exports={version:'1.2.6'};if(typeof __e=='number')__e=core;}),(function(module,exports,__webpack_require__){var aFunction=__webpack_require__(32);module.exports=function(fn,that,length){aFunction(fn);if(that===undefined)return fn;switch(length){case 1:return function(a){return fn.call(that,a);};case 2:return function(a,b){return fn.call(that,a,b);};case 3:return function(a,b,c){return fn.call(that,a,b,c);};}
+return function(){return fn.apply(that,arguments);};};}),(function(module,exports){module.exports=function(it){if(typeof it!='function')throw TypeError(it+' is not a function!');return it;};}),(function(module,exports){module.exports=function(exec){try{return!!exec();}catch(e){return true;}};}),(function(module,exports){(function(global){'use strict';exports.__esModule=true;exports['default']=function(Handlebars){var root=typeof global!=='undefined'?global:window,$Handlebars=root.Handlebars;Handlebars.noConflict=function(){if(root.Handlebars===Handlebars){root.Handlebars=$Handlebars;}
+return Handlebars;};};module.exports=exports['default'];}.call(exports,(function(){return this;}())))}),(function(module,exports){'use strict';exports.__esModule=true;var AST={helpers:{helperExpression:function helperExpression(node){return node.type==='SubExpression'||(node.type==='MustacheStatement'||node.type==='BlockStatement')&&!!(node.params&&node.params.length||node.hash);},scopedId:function scopedId(path){return(/^\.|this\b/.test(path.original));},simpleId:function simpleId(path){return path.parts.length===1&&!AST.helpers.scopedId(path)&&!path.depth;}}};exports['default']=AST;module.exports=exports['default'];}),(function(module,exports,__webpack_require__){'use strict';var _interopRequireDefault=__webpack_require__(1)['default'];var _interopRequireWildcard=__webpack_require__(3)['default'];exports.__esModule=true;exports.parse=parse;var _parser=__webpack_require__(37);var _parser2=_interopRequireDefault(_parser);var _whitespaceControl=__webpack_require__(38);var _whitespaceControl2=_interopRequireDefault(_whitespaceControl);var _helpers=__webpack_require__(40);var Helpers=_interopRequireWildcard(_helpers);var _utils=__webpack_require__(5);exports.parser=_parser2['default'];var yy={};_utils.extend(yy,Helpers);function parse(input,options){if(input.type==='Program'){return input;}
+_parser2['default'].yy=yy;yy.locInfo=function(locInfo){return new yy.SourceLocation(options&&options.srcName,locInfo);};var strip=new _whitespaceControl2['default'](options);return strip.accept(_parser2['default'].parse(input));}}),(function(module,exports){"use strict";exports.__esModule=true;var handlebars=(function(){var parser={trace:function trace(){},yy:{},symbols_:{"error":2,"root":3,"program":4,"EOF":5,"program_repetition0":6,"statement":7,"mustache":8,"block":9,"rawBlock":10,"partial":11,"partialBlock":12,"content":13,"COMMENT":14,"CONTENT":15,"openRawBlock":16,"rawBlock_repetition_plus0":17,"END_RAW_BLOCK":18,"OPEN_RAW_BLOCK":19,"helperName":20,"openRawBlock_repetition0":21,"openRawBlock_option0":22,"CLOSE_RAW_BLOCK":23,"openBlock":24,"block_option0":25,"closeBlock":26,"openInverse":27,"block_option1":28,"OPEN_BLOCK":29,"openBlock_repetition0":30,"openBlock_option0":31,"openBlock_option1":32,"CLOSE":33,"OPEN_INVERSE":34,"openInverse_repetition0":35,"openInverse_option0":36,"openInverse_option1":37,"openInverseChain":38,"OPEN_INVERSE_CHAIN":39,"openInverseChain_repetition0":40,"openInverseChain_option0":41,"openInverseChain_option1":42,"inverseAndProgram":43,"INVERSE":44,"inverseChain":45,"inverseChain_option0":46,"OPEN_ENDBLOCK":47,"OPEN":48,"mustache_repetition0":49,"mustache_option0":50,"OPEN_UNESCAPED":51,"mustache_repetition1":52,"mustache_option1":53,"CLOSE_UNESCAPED":54,"OPEN_PARTIAL":55,"partialName":56,"partial_repetition0":57,"partial_option0":58,"openPartialBlock":59,"OPEN_PARTIAL_BLOCK":60,"openPartialBlock_repetition0":61,"openPartialBlock_option0":62,"param":63,"sexpr":64,"OPEN_SEXPR":65,"sexpr_repetition0":66,"sexpr_option0":67,"CLOSE_SEXPR":68,"hash":69,"hash_repetition_plus0":70,"hashSegment":71,"ID":72,"EQUALS":73,"blockParams":74,"OPEN_BLOCK_PARAMS":75,"blockParams_repetition_plus0":76,"CLOSE_BLOCK_PARAMS":77,"path":78,"dataName":79,"STRING":80,"NUMBER":81,"BOOLEAN":82,"UNDEFINED":83,"NULL":84,"DATA":85,"pathSegments":86,"SEP":87,"$accept":0,"$end":1},terminals_:{2:"error",5:"EOF",14:"COMMENT",15:"CONTENT",18:"END_RAW_BLOCK",19:"OPEN_RAW_BLOCK",23:"CLOSE_RAW_BLOCK",29:"OPEN_BLOCK",33:"CLOSE",34:"OPEN_INVERSE",39:"OPEN_INVERSE_CHAIN",44:"INVERSE",47:"OPEN_ENDBLOCK",48:"OPEN",51:"OPEN_UNESCAPED",54:"CLOSE_UNESCAPED",55:"OPEN_PARTIAL",60:"OPEN_PARTIAL_BLOCK",65:"OPEN_SEXPR",68:"CLOSE_SEXPR",72:"ID",73:"EQUALS",75:"OPEN_BLOCK_PARAMS",77:"CLOSE_BLOCK_PARAMS",80:"STRING",81:"NUMBER",82:"BOOLEAN",83:"UNDEFINED",84:"NULL",85:"DATA",87:"SEP"},productions_:[0,[3,2],[4,1],[7,1],[7,1],[7,1],[7,1],[7,1],[7,1],[7,1],[13,1],[10,3],[16,5],[9,4],[9,4],[24,6],[27,6],[38,6],[43,2],[45,3],[45,1],[26,3],[8,5],[8,5],[11,5],[12,3],[59,5],[63,1],[63,1],[64,5],[69,1],[71,3],[74,3],[20,1],[20,1],[20,1],[20,1],[20,1],[20,1],[20,1],[56,1],[56,1],[79,2],[78,1],[86,3],[86,1],[6,0],[6,2],[17,1],[17,2],[21,0],[21,2],[22,0],[22,1],[25,0],[25,1],[28,0],[28,1],[30,0],[30,2],[31,0],[31,1],[32,0],[32,1],[35,0],[35,2],[36,0],[36,1],[37,0],[37,1],[40,0],[40,2],[41,0],[41,1],[42,0],[42,1],[46,0],[46,1],[49,0],[49,2],[50,0],[50,1],[52,0],[52,2],[53,0],[53,1],[57,0],[57,2],[58,0],[58,1],[61,0],[61,2],[62,0],[62,1],[66,0],[66,2],[67,0],[67,1],[70,1],[70,2],[76,1],[76,2]],performAction:function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$){var $0=$$.length-1;switch(yystate){case 1:return $$[$0-1];break;case 2:this.$=yy.prepareProgram($$[$0]);break;case 3:this.$=$$[$0];break;case 4:this.$=$$[$0];break;case 5:this.$=$$[$0];break;case 6:this.$=$$[$0];break;case 7:this.$=$$[$0];break;case 8:this.$=$$[$0];break;case 9:this.$={type:'CommentStatement',value:yy.stripComment($$[$0]),strip:yy.stripFlags($$[$0],$$[$0]),loc:yy.locInfo(this._$)};break;case 10:this.$={type:'ContentStatement',original:$$[$0],value:$$[$0],loc:yy.locInfo(this._$)};break;case 11:this.$=yy.prepareRawBlock($$[$0-2],$$[$0-1],$$[$0],this._$);break;case 12:this.$={path:$$[$0-3],params:$$[$0-2],hash:$$[$0-1]};break;case 13:this.$=yy.prepareBlock($$[$0-3],$$[$0-2],$$[$0-1],$$[$0],false,this._$);break;case 14:this.$=yy.prepareBlock($$[$0-3],$$[$0-2],$$[$0-1],$$[$0],true,this._$);break;case 15:this.$={open:$$[$0-5],path:$$[$0-4],params:$$[$0-3],hash:$$[$0-2],blockParams:$$[$0-1],strip:yy.stripFlags($$[$0-5],$$[$0])};break;case 16:this.$={path:$$[$0-4],params:$$[$0-3],hash:$$[$0-2],blockParams:$$[$0-1],strip:yy.stripFlags($$[$0-5],$$[$0])};break;case 17:this.$={path:$$[$0-4],params:$$[$0-3],hash:$$[$0-2],blockParams:$$[$0-1],strip:yy.stripFlags($$[$0-5],$$[$0])};break;case 18:this.$={strip:yy.stripFlags($$[$0-1],$$[$0-1]),program:$$[$0]};break;case 19:var inverse=yy.prepareBlock($$[$0-2],$$[$0-1],$$[$0],$$[$0],false,this._$),program=yy.prepareProgram([inverse],$$[$0-1].loc);program.chained=true;this.$={strip:$$[$0-2].strip,program:program,chain:true};break;case 20:this.$=$$[$0];break;case 21:this.$={path:$$[$0-1],strip:yy.stripFlags($$[$0-2],$$[$0])};break;case 22:this.$=yy.prepareMustache($$[$0-3],$$[$0-2],$$[$0-1],$$[$0-4],yy.stripFlags($$[$0-4],$$[$0]),this._$);break;case 23:this.$=yy.prepareMustache($$[$0-3],$$[$0-2],$$[$0-1],$$[$0-4],yy.stripFlags($$[$0-4],$$[$0]),this._$);break;case 24:this.$={type:'PartialStatement',name:$$[$0-3],params:$$[$0-2],hash:$$[$0-1],indent:'',strip:yy.stripFlags($$[$0-4],$$[$0]),loc:yy.locInfo(this._$)};break;case 25:this.$=yy.preparePartialBlock($$[$0-2],$$[$0-1],$$[$0],this._$);break;case 26:this.$={path:$$[$0-3],params:$$[$0-2],hash:$$[$0-1],strip:yy.stripFlags($$[$0-4],$$[$0])};break;case 27:this.$=$$[$0];break;case 28:this.$=$$[$0];break;case 29:this.$={type:'SubExpression',path:$$[$0-3],params:$$[$0-2],hash:$$[$0-1],loc:yy.locInfo(this._$)};break;case 30:this.$={type:'Hash',pairs:$$[$0],loc:yy.locInfo(this._$)};break;case 31:this.$={type:'HashPair',key:yy.id($$[$0-2]),value:$$[$0],loc:yy.locInfo(this._$)};break;case 32:this.$=yy.id($$[$0-1]);break;case 33:this.$=$$[$0];break;case 34:this.$=$$[$0];break;case 35:this.$={type:'StringLiteral',value:$$[$0],original:$$[$0],loc:yy.locInfo(this._$)};break;case 36:this.$={type:'NumberLiteral',value:Number($$[$0]),original:Number($$[$0]),loc:yy.locInfo(this._$)};break;case 37:this.$={type:'BooleanLiteral',value:$$[$0]==='true',original:$$[$0]==='true',loc:yy.locInfo(this._$)};break;case 38:this.$={type:'UndefinedLiteral',original:undefined,value:undefined,loc:yy.locInfo(this._$)};break;case 39:this.$={type:'NullLiteral',original:null,value:null,loc:yy.locInfo(this._$)};break;case 40:this.$=$$[$0];break;case 41:this.$=$$[$0];break;case 42:this.$=yy.preparePath(true,$$[$0],this._$);break;case 43:this.$=yy.preparePath(false,$$[$0],this._$);break;case 44:$$[$0-2].push({part:yy.id($$[$0]),original:$$[$0],separator:$$[$0-1]});this.$=$$[$0-2];break;case 45:this.$=[{part:yy.id($$[$0]),original:$$[$0]}];break;case 46:this.$=[];break;case 47:$$[$0-1].push($$[$0]);break;case 48:this.$=[$$[$0]];break;case 49:$$[$0-1].push($$[$0]);break;case 50:this.$=[];break;case 51:$$[$0-1].push($$[$0]);break;case 58:this.$=[];break;case 59:$$[$0-1].push($$[$0]);break;case 64:this.$=[];break;case 65:$$[$0-1].push($$[$0]);break;case 70:this.$=[];break;case 71:$$[$0-1].push($$[$0]);break;case 78:this.$=[];break;case 79:$$[$0-1].push($$[$0]);break;case 82:this.$=[];break;case 83:$$[$0-1].push($$[$0]);break;case 86:this.$=[];break;case 87:$$[$0-1].push($$[$0]);break;case 90:this.$=[];break;case 91:$$[$0-1].push($$[$0]);break;case 94:this.$=[];break;case 95:$$[$0-1].push($$[$0]);break;case 98:this.$=[$$[$0]];break;case 99:$$[$0-1].push($$[$0]);break;case 100:this.$=[$$[$0]];break;case 101:$$[$0-1].push($$[$0]);break;}},table:[{3:1,4:2,5:[2,46],6:3,14:[2,46],15:[2,46],19:[2,46],29:[2,46],34:[2,46],48:[2,46],51:[2,46],55:[2,46],60:[2,46]},{1:[3]},{5:[1,4]},{5:[2,2],7:5,8:6,9:7,10:8,11:9,12:10,13:11,14:[1,12],15:[1,20],16:17,19:[1,23],24:15,27:16,29:[1,21],34:[1,22],39:[2,2],44:[2,2],47:[2,2],48:[1,13],51:[1,14],55:[1,18],59:19,60:[1,24]},{1:[2,1]},{5:[2,47],14:[2,47],15:[2,47],19:[2,47],29:[2,47],34:[2,47],39:[2,47],44:[2,47],47:[2,47],48:[2,47],51:[2,47],55:[2,47],60:[2,47]},{5:[2,3],14:[2,3],15:[2,3],19:[2,3],29:[2,3],34:[2,3],39:[2,3],44:[2,3],47:[2,3],48:[2,3],51:[2,3],55:[2,3],60:[2,3]},{5:[2,4],14:[2,4],15:[2,4],19:[2,4],29:[2,4],34:[2,4],39:[2,4],44:[2,4],47:[2,4],48:[2,4],51:[2,4],55:[2,4],60:[2,4]},{5:[2,5],14:[2,5],15:[2,5],19:[2,5],29:[2,5],34:[2,5],39:[2,5],44:[2,5],47:[2,5],48:[2,5],51:[2,5],55:[2,5],60:[2,5]},{5:[2,6],14:[2,6],15:[2,6],19:[2,6],29:[2,6],34:[2,6],39:[2,6],44:[2,6],47:[2,6],48:[2,6],51:[2,6],55:[2,6],60:[2,6]},{5:[2,7],14:[2,7],15:[2,7],19:[2,7],29:[2,7],34:[2,7],39:[2,7],44:[2,7],47:[2,7],48:[2,7],51:[2,7],55:[2,7],60:[2,7]},{5:[2,8],14:[2,8],15:[2,8],19:[2,8],29:[2,8],34:[2,8],39:[2,8],44:[2,8],47:[2,8],48:[2,8],51:[2,8],55:[2,8],60:[2,8]},{5:[2,9],14:[2,9],15:[2,9],19:[2,9],29:[2,9],34:[2,9],39:[2,9],44:[2,9],47:[2,9],48:[2,9],51:[2,9],55:[2,9],60:[2,9]},{20:25,72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{20:36,72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{4:37,6:3,14:[2,46],15:[2,46],19:[2,46],29:[2,46],34:[2,46],39:[2,46],44:[2,46],47:[2,46],48:[2,46],51:[2,46],55:[2,46],60:[2,46]},{4:38,6:3,14:[2,46],15:[2,46],19:[2,46],29:[2,46],34:[2,46],44:[2,46],47:[2,46],48:[2,46],51:[2,46],55:[2,46],60:[2,46]},{13:40,15:[1,20],17:39},{20:42,56:41,64:43,65:[1,44],72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{4:45,6:3,14:[2,46],15:[2,46],19:[2,46],29:[2,46],34:[2,46],47:[2,46],48:[2,46],51:[2,46],55:[2,46],60:[2,46]},{5:[2,10],14:[2,10],15:[2,10],18:[2,10],19:[2,10],29:[2,10],34:[2,10],39:[2,10],44:[2,10],47:[2,10],48:[2,10],51:[2,10],55:[2,10],60:[2,10]},{20:46,72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{20:47,72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{20:48,72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{20:42,56:49,64:43,65:[1,44],72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{33:[2,78],49:50,65:[2,78],72:[2,78],80:[2,78],81:[2,78],82:[2,78],83:[2,78],84:[2,78],85:[2,78]},{23:[2,33],33:[2,33],54:[2,33],65:[2,33],68:[2,33],72:[2,33],75:[2,33],80:[2,33],81:[2,33],82:[2,33],83:[2,33],84:[2,33],85:[2,33]},{23:[2,34],33:[2,34],54:[2,34],65:[2,34],68:[2,34],72:[2,34],75:[2,34],80:[2,34],81:[2,34],82:[2,34],83:[2,34],84:[2,34],85:[2,34]},{23:[2,35],33:[2,35],54:[2,35],65:[2,35],68:[2,35],72:[2,35],75:[2,35],80:[2,35],81:[2,35],82:[2,35],83:[2,35],84:[2,35],85:[2,35]},{23:[2,36],33:[2,36],54:[2,36],65:[2,36],68:[2,36],72:[2,36],75:[2,36],80:[2,36],81:[2,36],82:[2,36],83:[2,36],84:[2,36],85:[2,36]},{23:[2,37],33:[2,37],54:[2,37],65:[2,37],68:[2,37],72:[2,37],75:[2,37],80:[2,37],81:[2,37],82:[2,37],83:[2,37],84:[2,37],85:[2,37]},{23:[2,38],33:[2,38],54:[2,38],65:[2,38],68:[2,38],72:[2,38],75:[2,38],80:[2,38],81:[2,38],82:[2,38],83:[2,38],84:[2,38],85:[2,38]},{23:[2,39],33:[2,39],54:[2,39],65:[2,39],68:[2,39],72:[2,39],75:[2,39],80:[2,39],81:[2,39],82:[2,39],83:[2,39],84:[2,39],85:[2,39]},{23:[2,43],33:[2,43],54:[2,43],65:[2,43],68:[2,43],72:[2,43],75:[2,43],80:[2,43],81:[2,43],82:[2,43],83:[2,43],84:[2,43],85:[2,43],87:[1,51]},{72:[1,35],86:52},{23:[2,45],33:[2,45],54:[2,45],65:[2,45],68:[2,45],72:[2,45],75:[2,45],80:[2,45],81:[2,45],82:[2,45],83:[2,45],84:[2,45],85:[2,45],87:[2,45]},{52:53,54:[2,82],65:[2,82],72:[2,82],80:[2,82],81:[2,82],82:[2,82],83:[2,82],84:[2,82],85:[2,82]},{25:54,38:56,39:[1,58],43:57,44:[1,59],45:55,47:[2,54]},{28:60,43:61,44:[1,59],47:[2,56]},{13:63,15:[1,20],18:[1,62]},{15:[2,48],18:[2,48]},{33:[2,86],57:64,65:[2,86],72:[2,86],80:[2,86],81:[2,86],82:[2,86],83:[2,86],84:[2,86],85:[2,86]},{33:[2,40],65:[2,40],72:[2,40],80:[2,40],81:[2,40],82:[2,40],83:[2,40],84:[2,40],85:[2,40]},{33:[2,41],65:[2,41],72:[2,41],80:[2,41],81:[2,41],82:[2,41],83:[2,41],84:[2,41],85:[2,41]},{20:65,72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{26:66,47:[1,67]},{30:68,33:[2,58],65:[2,58],72:[2,58],75:[2,58],80:[2,58],81:[2,58],82:[2,58],83:[2,58],84:[2,58],85:[2,58]},{33:[2,64],35:69,65:[2,64],72:[2,64],75:[2,64],80:[2,64],81:[2,64],82:[2,64],83:[2,64],84:[2,64],85:[2,64]},{21:70,23:[2,50],65:[2,50],72:[2,50],80:[2,50],81:[2,50],82:[2,50],83:[2,50],84:[2,50],85:[2,50]},{33:[2,90],61:71,65:[2,90],72:[2,90],80:[2,90],81:[2,90],82:[2,90],83:[2,90],84:[2,90],85:[2,90]},{20:75,33:[2,80],50:72,63:73,64:76,65:[1,44],69:74,70:77,71:78,72:[1,79],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{72:[1,80]},{23:[2,42],33:[2,42],54:[2,42],65:[2,42],68:[2,42],72:[2,42],75:[2,42],80:[2,42],81:[2,42],82:[2,42],83:[2,42],84:[2,42],85:[2,42],87:[1,51]},{20:75,53:81,54:[2,84],63:82,64:76,65:[1,44],69:83,70:77,71:78,72:[1,79],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{26:84,47:[1,67]},{47:[2,55]},{4:85,6:3,14:[2,46],15:[2,46],19:[2,46],29:[2,46],34:[2,46],39:[2,46],44:[2,46],47:[2,46],48:[2,46],51:[2,46],55:[2,46],60:[2,46]},{47:[2,20]},{20:86,72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{4:87,6:3,14:[2,46],15:[2,46],19:[2,46],29:[2,46],34:[2,46],47:[2,46],48:[2,46],51:[2,46],55:[2,46],60:[2,46]},{26:88,47:[1,67]},{47:[2,57]},{5:[2,11],14:[2,11],15:[2,11],19:[2,11],29:[2,11],34:[2,11],39:[2,11],44:[2,11],47:[2,11],48:[2,11],51:[2,11],55:[2,11],60:[2,11]},{15:[2,49],18:[2,49]},{20:75,33:[2,88],58:89,63:90,64:76,65:[1,44],69:91,70:77,71:78,72:[1,79],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{65:[2,94],66:92,68:[2,94],72:[2,94],80:[2,94],81:[2,94],82:[2,94],83:[2,94],84:[2,94],85:[2,94]},{5:[2,25],14:[2,25],15:[2,25],19:[2,25],29:[2,25],34:[2,25],39:[2,25],44:[2,25],47:[2,25],48:[2,25],51:[2,25],55:[2,25],60:[2,25]},{20:93,72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{20:75,31:94,33:[2,60],63:95,64:76,65:[1,44],69:96,70:77,71:78,72:[1,79],75:[2,60],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{20:75,33:[2,66],36:97,63:98,64:76,65:[1,44],69:99,70:77,71:78,72:[1,79],75:[2,66],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{20:75,22:100,23:[2,52],63:101,64:76,65:[1,44],69:102,70:77,71:78,72:[1,79],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{20:75,33:[2,92],62:103,63:104,64:76,65:[1,44],69:105,70:77,71:78,72:[1,79],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{33:[1,106]},{33:[2,79],65:[2,79],72:[2,79],80:[2,79],81:[2,79],82:[2,79],83:[2,79],84:[2,79],85:[2,79]},{33:[2,81]},{23:[2,27],33:[2,27],54:[2,27],65:[2,27],68:[2,27],72:[2,27],75:[2,27],80:[2,27],81:[2,27],82:[2,27],83:[2,27],84:[2,27],85:[2,27]},{23:[2,28],33:[2,28],54:[2,28],65:[2,28],68:[2,28],72:[2,28],75:[2,28],80:[2,28],81:[2,28],82:[2,28],83:[2,28],84:[2,28],85:[2,28]},{23:[2,30],33:[2,30],54:[2,30],68:[2,30],71:107,72:[1,108],75:[2,30]},{23:[2,98],33:[2,98],54:[2,98],68:[2,98],72:[2,98],75:[2,98]},{23:[2,45],33:[2,45],54:[2,45],65:[2,45],68:[2,45],72:[2,45],73:[1,109],75:[2,45],80:[2,45],81:[2,45],82:[2,45],83:[2,45],84:[2,45],85:[2,45],87:[2,45]},{23:[2,44],33:[2,44],54:[2,44],65:[2,44],68:[2,44],72:[2,44],75:[2,44],80:[2,44],81:[2,44],82:[2,44],83:[2,44],84:[2,44],85:[2,44],87:[2,44]},{54:[1,110]},{54:[2,83],65:[2,83],72:[2,83],80:[2,83],81:[2,83],82:[2,83],83:[2,83],84:[2,83],85:[2,83]},{54:[2,85]},{5:[2,13],14:[2,13],15:[2,13],19:[2,13],29:[2,13],34:[2,13],39:[2,13],44:[2,13],47:[2,13],48:[2,13],51:[2,13],55:[2,13],60:[2,13]},{38:56,39:[1,58],43:57,44:[1,59],45:112,46:111,47:[2,76]},{33:[2,70],40:113,65:[2,70],72:[2,70],75:[2,70],80:[2,70],81:[2,70],82:[2,70],83:[2,70],84:[2,70],85:[2,70]},{47:[2,18]},{5:[2,14],14:[2,14],15:[2,14],19:[2,14],29:[2,14],34:[2,14],39:[2,14],44:[2,14],47:[2,14],48:[2,14],51:[2,14],55:[2,14],60:[2,14]},{33:[1,114]},{33:[2,87],65:[2,87],72:[2,87],80:[2,87],81:[2,87],82:[2,87],83:[2,87],84:[2,87],85:[2,87]},{33:[2,89]},{20:75,63:116,64:76,65:[1,44],67:115,68:[2,96],69:117,70:77,71:78,72:[1,79],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{33:[1,118]},{32:119,33:[2,62],74:120,75:[1,121]},{33:[2,59],65:[2,59],72:[2,59],75:[2,59],80:[2,59],81:[2,59],82:[2,59],83:[2,59],84:[2,59],85:[2,59]},{33:[2,61],75:[2,61]},{33:[2,68],37:122,74:123,75:[1,121]},{33:[2,65],65:[2,65],72:[2,65],75:[2,65],80:[2,65],81:[2,65],82:[2,65],83:[2,65],84:[2,65],85:[2,65]},{33:[2,67],75:[2,67]},{23:[1,124]},{23:[2,51],65:[2,51],72:[2,51],80:[2,51],81:[2,51],82:[2,51],83:[2,51],84:[2,51],85:[2,51]},{23:[2,53]},{33:[1,125]},{33:[2,91],65:[2,91],72:[2,91],80:[2,91],81:[2,91],82:[2,91],83:[2,91],84:[2,91],85:[2,91]},{33:[2,93]},{5:[2,22],14:[2,22],15:[2,22],19:[2,22],29:[2,22],34:[2,22],39:[2,22],44:[2,22],47:[2,22],48:[2,22],51:[2,22],55:[2,22],60:[2,22]},{23:[2,99],33:[2,99],54:[2,99],68:[2,99],72:[2,99],75:[2,99]},{73:[1,109]},{20:75,63:126,64:76,65:[1,44],72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{5:[2,23],14:[2,23],15:[2,23],19:[2,23],29:[2,23],34:[2,23],39:[2,23],44:[2,23],47:[2,23],48:[2,23],51:[2,23],55:[2,23],60:[2,23]},{47:[2,19]},{47:[2,77]},{20:75,33:[2,72],41:127,63:128,64:76,65:[1,44],69:129,70:77,71:78,72:[1,79],75:[2,72],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{5:[2,24],14:[2,24],15:[2,24],19:[2,24],29:[2,24],34:[2,24],39:[2,24],44:[2,24],47:[2,24],48:[2,24],51:[2,24],55:[2,24],60:[2,24]},{68:[1,130]},{65:[2,95],68:[2,95],72:[2,95],80:[2,95],81:[2,95],82:[2,95],83:[2,95],84:[2,95],85:[2,95]},{68:[2,97]},{5:[2,21],14:[2,21],15:[2,21],19:[2,21],29:[2,21],34:[2,21],39:[2,21],44:[2,21],47:[2,21],48:[2,21],51:[2,21],55:[2,21],60:[2,21]},{33:[1,131]},{33:[2,63]},{72:[1,133],76:132},{33:[1,134]},{33:[2,69]},{15:[2,12]},{14:[2,26],15:[2,26],19:[2,26],29:[2,26],34:[2,26],47:[2,26],48:[2,26],51:[2,26],55:[2,26],60:[2,26]},{23:[2,31],33:[2,31],54:[2,31],68:[2,31],72:[2,31],75:[2,31]},{33:[2,74],42:135,74:136,75:[1,121]},{33:[2,71],65:[2,71],72:[2,71],75:[2,71],80:[2,71],81:[2,71],82:[2,71],83:[2,71],84:[2,71],85:[2,71]},{33:[2,73],75:[2,73]},{23:[2,29],33:[2,29],54:[2,29],65:[2,29],68:[2,29],72:[2,29],75:[2,29],80:[2,29],81:[2,29],82:[2,29],83:[2,29],84:[2,29],85:[2,29]},{14:[2,15],15:[2,15],19:[2,15],29:[2,15],34:[2,15],39:[2,15],44:[2,15],47:[2,15],48:[2,15],51:[2,15],55:[2,15],60:[2,15]},{72:[1,138],77:[1,137]},{72:[2,100],77:[2,100]},{14:[2,16],15:[2,16],19:[2,16],29:[2,16],34:[2,16],44:[2,16],47:[2,16],48:[2,16],51:[2,16],55:[2,16],60:[2,16]},{33:[1,139]},{33:[2,75]},{33:[2,32]},{72:[2,101],77:[2,101]},{14:[2,17],15:[2,17],19:[2,17],29:[2,17],34:[2,17],39:[2,17],44:[2,17],47:[2,17],48:[2,17],51:[2,17],55:[2,17],60:[2,17]}],defaultActions:{4:[2,1],55:[2,55],57:[2,20],61:[2,57],74:[2,81],83:[2,85],87:[2,18],91:[2,89],102:[2,53],105:[2,93],111:[2,19],112:[2,77],117:[2,97],120:[2,63],123:[2,69],124:[2,12],136:[2,75],137:[2,32]},parseError:function parseError(str,hash){throw new Error(str);},parse:function parse(input){var self=this,stack=[0],vstack=[null],lstack=[],table=this.table,yytext="",yylineno=0,yyleng=0,recovering=0,TERROR=2,EOF=1;this.lexer.setInput(input);this.lexer.yy=this.yy;this.yy.lexer=this.lexer;this.yy.parser=this;if(typeof this.lexer.yylloc=="undefined")this.lexer.yylloc={};var yyloc=this.lexer.yylloc;lstack.push(yyloc);var ranges=this.lexer.options&&this.lexer.options.ranges;if(typeof this.yy.parseError==="function")this.parseError=this.yy.parseError;function popStack(n){stack.length=stack.length-2*n;vstack.length=vstack.length-n;lstack.length=lstack.length-n;}
+function lex(){var token;token=self.lexer.lex()||1;if(typeof token!=="number"){token=self.symbols_[token]||token;}
+return token;}
+var symbol,preErrorSymbol,state,action,a,r,yyval={},p,len,newState,expected;while(true){state=stack[stack.length-1];if(this.defaultActions[state]){action=this.defaultActions[state];}else{if(symbol===null||typeof symbol=="undefined"){symbol=lex();}
+action=table[state]&&table[state][symbol];}
+if(typeof action==="undefined"||!action.length||!action[0]){var errStr="";if(!recovering){expected=[];for(p in table[state])if(this.terminals_[p]&&p>2){expected.push("'"+this.terminals_[p]+"'");}
+if(this.lexer.showPosition){errStr="Parse error on line "+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(", ")+", got '"+(this.terminals_[symbol]||symbol)+"'";}else{errStr="Parse error on line "+(yylineno+1)+": Unexpected "+(symbol==1?"end of input":"'"+(this.terminals_[symbol]||symbol)+"'");}
+this.parseError(errStr,{text:this.lexer.match,token:this.terminals_[symbol]||symbol,line:this.lexer.yylineno,loc:yyloc,expected:expected});}}
+if(action[0]instanceof Array&&action.length>1){throw new Error("Parse Error: multiple actions possible at state: "+state+", token: "+symbol);}
+switch(action[0]){case 1:stack.push(symbol);vstack.push(this.lexer.yytext);lstack.push(this.lexer.yylloc);stack.push(action[1]);symbol=null;if(!preErrorSymbol){yyleng=this.lexer.yyleng;yytext=this.lexer.yytext;yylineno=this.lexer.yylineno;yyloc=this.lexer.yylloc;if(recovering>0)recovering--;}else{symbol=preErrorSymbol;preErrorSymbol=null;}
+break;case 2:len=this.productions_[action[1]][1];yyval.$=vstack[vstack.length-len];yyval._$={first_line:lstack[lstack.length-(len||1)].first_line,last_line:lstack[lstack.length-1].last_line,first_column:lstack[lstack.length-(len||1)].first_column,last_column:lstack[lstack.length-1].last_column};if(ranges){yyval._$.range=[lstack[lstack.length-(len||1)].range[0],lstack[lstack.length-1].range[1]];}
+r=this.performAction.call(yyval,yytext,yyleng,yylineno,this.yy,action[1],vstack,lstack);if(typeof r!=="undefined"){return r;}
+if(len){stack=stack.slice(0,-1*len*2);vstack=vstack.slice(0,-1*len);lstack=lstack.slice(0,-1*len);}
+stack.push(this.productions_[action[1]][0]);vstack.push(yyval.$);lstack.push(yyval._$);newState=table[stack[stack.length-2]][stack[stack.length-1]];stack.push(newState);break;case 3:return true;}}
+return true;}};var lexer=(function(){var lexer={EOF:1,parseError:function parseError(str,hash){if(this.yy.parser){this.yy.parser.parseError(str,hash);}else{throw new Error(str);}},setInput:function setInput(input){this._input=input;this._more=this._less=this.done=false;this.yylineno=this.yyleng=0;this.yytext=this.matched=this.match='';this.conditionStack=['INITIAL'];this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0};if(this.options.ranges)this.yylloc.range=[0,0];this.offset=0;return this;},input:function input(){var ch=this._input[0];this.yytext+=ch;this.yyleng++;this.offset++;this.match+=ch;this.matched+=ch;var lines=ch.match(/(?:\r\n?|\n).*/g);if(lines){this.yylineno++;this.yylloc.last_line++;}else{this.yylloc.last_column++;}
+if(this.options.ranges)this.yylloc.range[1]++;this._input=this._input.slice(1);return ch;},unput:function unput(ch){var len=ch.length;var lines=ch.split(/(?:\r\n?|\n)/g);this._input=ch+this._input;this.yytext=this.yytext.substr(0,this.yytext.length-len-1);this.offset-=len;var oldLines=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1);this.matched=this.matched.substr(0,this.matched.length-1);if(lines.length-1)this.yylineno-=lines.length-1;var r=this.yylloc.range;this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:lines?(lines.length===oldLines.length?this.yylloc.first_column:0)+oldLines[oldLines.length-lines.length].length-lines[0].length:this.yylloc.first_column-len};if(this.options.ranges){this.yylloc.range=[r[0],r[0]+this.yyleng-len];}
+return this;},more:function more(){this._more=true;return this;},less:function less(n){this.unput(this.match.slice(n));},pastInput:function pastInput(){var past=this.matched.substr(0,this.matched.length-this.match.length);return(past.length>20?'...':'')+past.substr(-20).replace(/\n/g,"");},upcomingInput:function upcomingInput(){var next=this.match;if(next.length<20){next+=this._input.substr(0,20-next.length);}
+return(next.substr(0,20)+(next.length>20?'...':'')).replace(/\n/g,"");},showPosition:function showPosition(){var pre=this.pastInput();var c=new Array(pre.length+1).join("-");return pre+this.upcomingInput()+"\n"+c+"^";},next:function next(){if(this.done){return this.EOF;}
+if(!this._input)this.done=true;var token,match,tempMatch,index,col,lines;if(!this._more){this.yytext='';this.match='';}
+var rules=this._currentRules();for(var i=0;i<rules.length;i++){tempMatch=this._input.match(this.rules[rules[i]]);if(tempMatch&&(!match||tempMatch[0].length>match[0].length)){match=tempMatch;index=i;if(!this.options.flex)break;}}
+if(match){lines=match[0].match(/(?:\r\n?|\n).*/g);if(lines)this.yylineno+=lines.length;this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:lines?lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+match[0].length};this.yytext+=match[0];this.match+=match[0];this.matches=match;this.yyleng=this.yytext.length;if(this.options.ranges){this.yylloc.range=[this.offset,this.offset+=this.yyleng];}
+this._more=false;this._input=this._input.slice(match[0].length);this.matched+=match[0];token=this.performAction.call(this,this.yy,this,rules[index],this.conditionStack[this.conditionStack.length-1]);if(this.done&&this._input)this.done=false;if(token)return token;else return;}
+if(this._input===""){return this.EOF;}else{return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),{text:"",token:null,line:this.yylineno});}},lex:function lex(){var r=this.next();if(typeof r!=='undefined'){return r;}else{return this.lex();}},begin:function begin(condition){this.conditionStack.push(condition);},popState:function popState(){return this.conditionStack.pop();},_currentRules:function _currentRules(){return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;},topState:function topState(){return this.conditionStack[this.conditionStack.length-2];},pushState:function begin(condition){this.begin(condition);}};lexer.options={};lexer.performAction=function anonymous(yy,yy_,$avoiding_name_collisions,YY_START){function strip(start,end){return yy_.yytext=yy_.yytext.substr(start,yy_.yyleng-end);}
+var YYSTATE=YY_START;switch($avoiding_name_collisions){case 0:if(yy_.yytext.slice(-2)==="\\\\"){strip(0,1);this.begin("mu");}else if(yy_.yytext.slice(-1)==="\\"){strip(0,1);this.begin("emu");}else{this.begin("mu");}
+if(yy_.yytext)return 15;break;case 1:return 15;break;case 2:this.popState();return 15;break;case 3:this.begin('raw');return 15;break;case 4:this.popState();if(this.conditionStack[this.conditionStack.length-1]==='raw'){return 15;}else{yy_.yytext=yy_.yytext.substr(5,yy_.yyleng-9);return'END_RAW_BLOCK';}
+break;case 5:return 15;break;case 6:this.popState();return 14;break;case 7:return 65;break;case 8:return 68;break;case 9:return 19;break;case 10:this.popState();this.begin('raw');return 23;break;case 11:return 55;break;case 12:return 60;break;case 13:return 29;break;case 14:return 47;break;case 15:this.popState();return 44;break;case 16:this.popState();return 44;break;case 17:return 34;break;case 18:return 39;break;case 19:return 51;break;case 20:return 48;break;case 21:this.unput(yy_.yytext);this.popState();this.begin('com');break;case 22:this.popState();return 14;break;case 23:return 48;break;case 24:return 73;break;case 25:return 72;break;case 26:return 72;break;case 27:return 87;break;case 28:break;case 29:this.popState();return 54;break;case 30:this.popState();return 33;break;case 31:yy_.yytext=strip(1,2).replace(/\\"/g,'"');return 80;break;case 32:yy_.yytext=strip(1,2).replace(/\\'/g,"'");return 80;break;case 33:return 85;break;case 34:return 82;break;case 35:return 82;break;case 36:return 83;break;case 37:return 84;break;case 38:return 81;break;case 39:return 75;break;case 40:return 77;break;case 41:return 72;break;case 42:yy_.yytext=yy_.yytext.replace(/\\([\\\]])/g,'$1');return 72;break;case 43:return'INVALID';break;case 44:return 5;break;}};lexer.rules=[/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/,/^(?:\{\{\{\{(?=[^\/]))/,/^(?:\{\{\{\{\/[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.])\}\}\}\})/,/^(?:[^\x00]*?(?=(\{\{\{\{)))/,/^(?:[\s\S]*?--(~)?\}\})/,/^(?:\()/,/^(?:\))/,/^(?:\{\{\{\{)/,/^(?:\}\}\}\})/,/^(?:\{\{(~)?>)/,/^(?:\{\{(~)?#>)/,/^(?:\{\{(~)?#\*?)/,/^(?:\{\{(~)?\/)/,/^(?:\{\{(~)?\^\s*(~)?\}\})/,/^(?:\{\{(~)?\s*else\s*(~)?\}\})/,/^(?:\{\{(~)?\^)/,/^(?:\{\{(~)?\s*else\b)/,/^(?:\{\{(~)?\{)/,/^(?:\{\{(~)?&)/,/^(?:\{\{(~)?!--)/,/^(?:\{\{(~)?![\s\S]*?\}\})/,/^(?:\{\{(~)?\*?)/,/^(?:=)/,/^(?:\.\.)/,/^(?:\.(?=([=~}\s\/.)|])))/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}(~)?\}\})/,/^(?:(~)?\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=([~}\s)])))/,/^(?:false(?=([~}\s)])))/,/^(?:undefined(?=([~}\s)])))/,/^(?:null(?=([~}\s)])))/,/^(?:-?[0-9]+(?:\.[0-9]+)?(?=([~}\s)])))/,/^(?:as\s+\|)/,/^(?:\|)/,/^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.)|]))))/,/^(?:\[(\\\]|[^\]])*\])/,/^(?:.)/,/^(?:$)/];lexer.conditions={"mu":{"rules":[7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[6],"inclusive":false},"raw":{"rules":[3,4,5],"inclusive":false},"INITIAL":{"rules":[0,1,44],"inclusive":true}};return lexer;})();parser.lexer=lexer;function Parser(){this.yy={};}Parser.prototype=parser;parser.Parser=Parser;return new Parser();})();exports["default"]=handlebars;module.exports=exports["default"];}),(function(module,exports,__webpack_require__){'use strict';var _interopRequireDefault=__webpack_require__(1)['default'];exports.__esModule=true;var _visitor=__webpack_require__(39);var _visitor2=_interopRequireDefault(_visitor);function WhitespaceControl(){var options=arguments.length<=0||arguments[0]===undefined?{}:arguments[0];this.options=options;}
+WhitespaceControl.prototype=new _visitor2['default']();WhitespaceControl.prototype.Program=function(program){var doStandalone=!this.options.ignoreStandalone;var isRoot=!this.isRootSeen;this.isRootSeen=true;var body=program.body;for(var i=0,l=body.length;i<l;i++){var current=body[i],strip=this.accept(current);if(!strip){continue;}
+var _isPrevWhitespace=isPrevWhitespace(body,i,isRoot),_isNextWhitespace=isNextWhitespace(body,i,isRoot),openStandalone=strip.openStandalone&&_isPrevWhitespace,closeStandalone=strip.closeStandalone&&_isNextWhitespace,inlineStandalone=strip.inlineStandalone&&_isPrevWhitespace&&_isNextWhitespace;if(strip.close){omitRight(body,i,true);}
+if(strip.open){omitLeft(body,i,true);}
+if(doStandalone&&inlineStandalone){omitRight(body,i);if(omitLeft(body,i)){if(current.type==='PartialStatement'){current.indent=/([ \t]+$)/.exec(body[i-1].original)[1];}}}
+if(doStandalone&&openStandalone){omitRight((current.program||current.inverse).body);omitLeft(body,i);}
+if(doStandalone&&closeStandalone){omitRight(body,i);omitLeft((current.inverse||current.program).body);}}
+return program;};WhitespaceControl.prototype.BlockStatement=WhitespaceControl.prototype.DecoratorBlock=WhitespaceControl.prototype.PartialBlockStatement=function(block){this.accept(block.program);this.accept(block.inverse);var program=block.program||block.inverse,inverse=block.program&&block.inverse,firstInverse=inverse,lastInverse=inverse;if(inverse&&inverse.chained){firstInverse=inverse.body[0].program;while(lastInverse.chained){lastInverse=lastInverse.body[lastInverse.body.length-1].program;}}
+var strip={open:block.openStrip.open,close:block.closeStrip.close,openStandalone:isNextWhitespace(program.body),closeStandalone:isPrevWhitespace((firstInverse||program).body)};if(block.openStrip.close){omitRight(program.body,null,true);}
+if(inverse){var inverseStrip=block.inverseStrip;if(inverseStrip.open){omitLeft(program.body,null,true);}
+if(inverseStrip.close){omitRight(firstInverse.body,null,true);}
+if(block.closeStrip.open){omitLeft(lastInverse.body,null,true);}
+if(!this.options.ignoreStandalone&&isPrevWhitespace(program.body)&&isNextWhitespace(firstInverse.body)){omitLeft(program.body);omitRight(firstInverse.body);}}else if(block.closeStrip.open){omitLeft(program.body,null,true);}
+return strip;};WhitespaceControl.prototype.Decorator=WhitespaceControl.prototype.MustacheStatement=function(mustache){return mustache.strip;};WhitespaceControl.prototype.PartialStatement=WhitespaceControl.prototype.CommentStatement=function(node){var strip=node.strip||{};return{inlineStandalone:true,open:strip.open,close:strip.close};};function isPrevWhitespace(body,i,isRoot){if(i===undefined){i=body.length;}
+var prev=body[i-1],sibling=body[i-2];if(!prev){return isRoot;}
+if(prev.type==='ContentStatement'){return(sibling||!isRoot?/\r?\n\s*?$/:/(^|\r?\n)\s*?$/).test(prev.original);}}
+function isNextWhitespace(body,i,isRoot){if(i===undefined){i=-1;}
+var next=body[i+1],sibling=body[i+2];if(!next){return isRoot;}
+if(next.type==='ContentStatement'){return(sibling||!isRoot?/^\s*?\r?\n/:/^\s*?(\r?\n|$)/).test(next.original);}}
+function omitRight(body,i,multiple){var current=body[i==null?0:i+1];if(!current||current.type!=='ContentStatement'||!multiple&&current.rightStripped){return;}
+var original=current.value;current.value=current.value.replace(multiple?/^\s+/:/^[ \t]*\r?\n?/,'');current.rightStripped=current.value!==original;}
+function omitLeft(body,i,multiple){var current=body[i==null?body.length-1:i-1];if(!current||current.type!=='ContentStatement'||!multiple&&current.leftStripped){return;}
+var original=current.value;current.value=current.value.replace(multiple?/\s+$/:/[ \t]+$/,'');current.leftStripped=current.value!==original;return current.leftStripped;}
+exports['default']=WhitespaceControl;module.exports=exports['default'];}),(function(module,exports,__webpack_require__){'use strict';var _interopRequireDefault=__webpack_require__(1)['default'];exports.__esModule=true;var _exception=__webpack_require__(6);var _exception2=_interopRequireDefault(_exception);function Visitor(){this.parents=[];}
+Visitor.prototype={constructor:Visitor,mutating:false,acceptKey:function acceptKey(node,name){var value=this.accept(node[name]);if(this.mutating){if(value&&!Visitor.prototype[value.type]){throw new _exception2['default']('Unexpected node type "'+value.type+'" found when accepting '+name+' on '+node.type);}
+node[name]=value;}},acceptRequired:function acceptRequired(node,name){this.acceptKey(node,name);if(!node[name]){throw new _exception2['default'](node.type+' requires '+name);}},acceptArray:function acceptArray(array){for(var i=0,l=array.length;i<l;i++){this.acceptKey(array,i);if(!array[i]){array.splice(i,1);i--;l--;}}},accept:function accept(object){if(!object){return;}
+if(!this[object.type]){throw new _exception2['default']('Unknown type: '+object.type,object);}
+if(this.current){this.parents.unshift(this.current);}
+this.current=object;var ret=this[object.type](object);this.current=this.parents.shift();if(!this.mutating||ret){return ret;}else if(ret!==false){return object;}},Program:function Program(program){this.acceptArray(program.body);},MustacheStatement:visitSubExpression,Decorator:visitSubExpression,BlockStatement:visitBlock,DecoratorBlock:visitBlock,PartialStatement:visitPartial,PartialBlockStatement:function PartialBlockStatement(partial){visitPartial.call(this,partial);this.acceptKey(partial,'program');},ContentStatement:function ContentStatement(){},CommentStatement:function CommentStatement(){},SubExpression:visitSubExpression,PathExpression:function PathExpression(){},StringLiteral:function StringLiteral(){},NumberLiteral:function NumberLiteral(){},BooleanLiteral:function BooleanLiteral(){},UndefinedLiteral:function UndefinedLiteral(){},NullLiteral:function NullLiteral(){},Hash:function Hash(hash){this.acceptArray(hash.pairs);},HashPair:function HashPair(pair){this.acceptRequired(pair,'value');}};function visitSubExpression(mustache){this.acceptRequired(mustache,'path');this.acceptArray(mustache.params);this.acceptKey(mustache,'hash');}
+function visitBlock(block){visitSubExpression.call(this,block);this.acceptKey(block,'program');this.acceptKey(block,'inverse');}
+function visitPartial(partial){this.acceptRequired(partial,'name');this.acceptArray(partial.params);this.acceptKey(partial,'hash');}
+exports['default']=Visitor;module.exports=exports['default'];}),(function(module,exports,__webpack_require__){'use strict';var _interopRequireDefault=__webpack_require__(1)['default'];exports.__esModule=true;exports.SourceLocation=SourceLocation;exports.id=id;exports.stripFlags=stripFlags;exports.stripComment=stripComment;exports.preparePath=preparePath;exports.prepareMustache=prepareMustache;exports.prepareRawBlock=prepareRawBlock;exports.prepareBlock=prepareBlock;exports.prepareProgram=prepareProgram;exports.preparePartialBlock=preparePartialBlock;var _exception=__webpack_require__(6);var _exception2=_interopRequireDefault(_exception);function validateClose(open,close){close=close.path?close.path.original:close;if(open.path.original!==close){var errorNode={loc:open.path.loc};throw new _exception2['default'](open.path.original+" doesn't match "+close,errorNode);}}
+function SourceLocation(source,locInfo){this.source=source;this.start={line:locInfo.first_line,column:locInfo.first_column};this.end={line:locInfo.last_line,column:locInfo.last_column};}
+function id(token){if(/^\[.*\]$/.test(token)){return token.substr(1,token.length-2);}else{return token;}}
+function stripFlags(open,close){return{open:open.charAt(2)==='~',close:close.charAt(close.length-3)==='~'};}
+function stripComment(comment){return comment.replace(/^\{\{~?\!-?-?/,'').replace(/-?-?~?\}\}$/,'');}
+function preparePath(data,parts,loc){loc=this.locInfo(loc);var original=data?'@':'',dig=[],depth=0,depthString='';for(var i=0,l=parts.length;i<l;i++){var part=parts[i].part,isLiteral=parts[i].original!==part;original+=(parts[i].separator||'')+part;if(!isLiteral&&(part==='..'||part==='.'||part==='this')){if(dig.length>0){throw new _exception2['default']('Invalid path: '+original,{loc:loc});}else if(part==='..'){depth++;depthString+='../';}}else{dig.push(part);}}
+return{type:'PathExpression',data:data,depth:depth,parts:dig,original:original,loc:loc};}
+function prepareMustache(path,params,hash,open,strip,locInfo){var escapeFlag=open.charAt(3)||open.charAt(2),escaped=escapeFlag!=='{'&&escapeFlag!=='&';var decorator=/\*/.test(open);return{type:decorator?'Decorator':'MustacheStatement',path:path,params:params,hash:hash,escaped:escaped,strip:strip,loc:this.locInfo(locInfo)};}
+function prepareRawBlock(openRawBlock,contents,close,locInfo){validateClose(openRawBlock,close);locInfo=this.locInfo(locInfo);var program={type:'Program',body:contents,strip:{},loc:locInfo};return{type:'BlockStatement',path:openRawBlock.path,params:openRawBlock.params,hash:openRawBlock.hash,program:program,openStrip:{},inverseStrip:{},closeStrip:{},loc:locInfo};}
+function prepareBlock(openBlock,program,inverseAndProgram,close,inverted,locInfo){if(close&&close.path){validateClose(openBlock,close);}
+var decorator=/\*/.test(openBlock.open);program.blockParams=openBlock.blockParams;var inverse=undefined,inverseStrip=undefined;if(inverseAndProgram){if(decorator){throw new _exception2['default']('Unexpected inverse block on decorator',inverseAndProgram);}
+if(inverseAndProgram.chain){inverseAndProgram.program.body[0].closeStrip=close.strip;}
+inverseStrip=inverseAndProgram.strip;inverse=inverseAndProgram.program;}
+if(inverted){inverted=inverse;inverse=program;program=inverted;}
+return{type:decorator?'DecoratorBlock':'BlockStatement',path:openBlock.path,params:openBlock.params,hash:openBlock.hash,program:program,inverse:inverse,openStrip:openBlock.strip,inverseStrip:inverseStrip,closeStrip:close&&close.strip,loc:this.locInfo(locInfo)};}
+function prepareProgram(statements,loc){if(!loc&&statements.length){var firstLoc=statements[0].loc,lastLoc=statements[statements.length-1].loc;if(firstLoc&&lastLoc){loc={source:firstLoc.source,start:{line:firstLoc.start.line,column:firstLoc.start.column},end:{line:lastLoc.end.line,column:lastLoc.end.column}};}}
+return{type:'Program',body:statements,strip:{},loc:loc};}
+function preparePartialBlock(open,program,close,locInfo){validateClose(open,close);return{type:'PartialBlockStatement',name:open.path,params:open.params,hash:open.hash,program:program,openStrip:open.strip,closeStrip:close&&close.strip,loc:this.locInfo(locInfo)};}}),(function(module,exports,__webpack_require__){'use strict';var _interopRequireDefault=__webpack_require__(1)['default'];exports.__esModule=true;exports.Compiler=Compiler;exports.precompile=precompile;exports.compile=compile;var _exception=__webpack_require__(6);var _exception2=_interopRequireDefault(_exception);var _utils=__webpack_require__(5);var _ast=__webpack_require__(35);var _ast2=_interopRequireDefault(_ast);var slice=[].slice;function Compiler(){}
+Compiler.prototype={compiler:Compiler,equals:function equals(other){var len=this.opcodes.length;if(other.opcodes.length!==len){return false;}
+for(var i=0;i<len;i++){var opcode=this.opcodes[i],otherOpcode=other.opcodes[i];if(opcode.opcode!==otherOpcode.opcode||!argEquals(opcode.args,otherOpcode.args)){return false;}}
+len=this.children.length;for(var i=0;i<len;i++){if(!this.children[i].equals(other.children[i])){return false;}}
+return true;},guid:0,compile:function compile(program,options){this.sourceNode=[];this.opcodes=[];this.children=[];this.options=options;this.stringParams=options.stringParams;this.trackIds=options.trackIds;options.blockParams=options.blockParams||[];var knownHelpers=options.knownHelpers;options.knownHelpers={'helperMissing':true,'blockHelperMissing':true,'each':true,'if':true,'unless':true,'with':true,'log':true,'lookup':true};if(knownHelpers){for(var _name in knownHelpers){if(_name in knownHelpers){this.options.knownHelpers[_name]=knownHelpers[_name];}}}
+return this.accept(program);},compileProgram:function compileProgram(program){var childCompiler=new this.compiler(),result=childCompiler.compile(program,this.options),guid=this.guid++;this.usePartial=this.usePartial||result.usePartial;this.children[guid]=result;this.useDepths=this.useDepths||result.useDepths;return guid;},accept:function accept(node){if(!this[node.type]){throw new _exception2['default']('Unknown type: '+node.type,node);}
+this.sourceNode.unshift(node);var ret=this[node.type](node);this.sourceNode.shift();return ret;},Program:function Program(program){this.options.blockParams.unshift(program.blockParams);var body=program.body,bodyLength=body.length;for(var i=0;i<bodyLength;i++){this.accept(body[i]);}
+this.options.blockParams.shift();this.isSimple=bodyLength===1;this.blockParams=program.blockParams?program.blockParams.length:0;return this;},BlockStatement:function BlockStatement(block){transformLiteralToPath(block);var program=block.program,inverse=block.inverse;program=program&&this.compileProgram(program);inverse=inverse&&this.compileProgram(inverse);var type=this.classifySexpr(block);if(type==='helper'){this.helperSexpr(block,program,inverse);}else if(type==='simple'){this.simpleSexpr(block);this.opcode('pushProgram',program);this.opcode('pushProgram',inverse);this.opcode('emptyHash');this.opcode('blockValue',block.path.original);}else{this.ambiguousSexpr(block,program,inverse);this.opcode('pushProgram',program);this.opcode('pushProgram',inverse);this.opcode('emptyHash');this.opcode('ambiguousBlockValue');}
+this.opcode('append');},DecoratorBlock:function DecoratorBlock(decorator){var program=decorator.program&&this.compileProgram(decorator.program);var params=this.setupFullMustacheParams(decorator,program,undefined),path=decorator.path;this.useDecorators=true;this.opcode('registerDecorator',params.length,path.original);},PartialStatement:function PartialStatement(partial){this.usePartial=true;var program=partial.program;if(program){program=this.compileProgram(partial.program);}
+var params=partial.params;if(params.length>1){throw new _exception2['default']('Unsupported number of partial arguments: '+params.length,partial);}else if(!params.length){if(this.options.explicitPartialContext){this.opcode('pushLiteral','undefined');}else{params.push({type:'PathExpression',parts:[],depth:0});}}
+var partialName=partial.name.original,isDynamic=partial.name.type==='SubExpression';if(isDynamic){this.accept(partial.name);}
+this.setupFullMustacheParams(partial,program,undefined,true);var indent=partial.indent||'';if(this.options.preventIndent&&indent){this.opcode('appendContent',indent);indent='';}
+this.opcode('invokePartial',isDynamic,partialName,indent);this.opcode('append');},PartialBlockStatement:function PartialBlockStatement(partialBlock){this.PartialStatement(partialBlock);},MustacheStatement:function MustacheStatement(mustache){this.SubExpression(mustache);if(mustache.escaped&&!this.options.noEscape){this.opcode('appendEscaped');}else{this.opcode('append');}},Decorator:function Decorator(decorator){this.DecoratorBlock(decorator);},ContentStatement:function ContentStatement(content){if(content.value){this.opcode('appendContent',content.value);}},CommentStatement:function CommentStatement(){},SubExpression:function SubExpression(sexpr){transformLiteralToPath(sexpr);var type=this.classifySexpr(sexpr);if(type==='simple'){this.simpleSexpr(sexpr);}else if(type==='helper'){this.helperSexpr(sexpr);}else{this.ambiguousSexpr(sexpr);}},ambiguousSexpr:function ambiguousSexpr(sexpr,program,inverse){var path=sexpr.path,name=path.parts[0],isBlock=program!=null||inverse!=null;this.opcode('getContext',path.depth);this.opcode('pushProgram',program);this.opcode('pushProgram',inverse);path.strict=true;this.accept(path);this.opcode('invokeAmbiguous',name,isBlock);},simpleSexpr:function simpleSexpr(sexpr){var path=sexpr.path;path.strict=true;this.accept(path);this.opcode('resolvePossibleLambda');},helperSexpr:function helperSexpr(sexpr,program,inverse){var params=this.setupFullMustacheParams(sexpr,program,inverse),path=sexpr.path,name=path.parts[0];if(this.options.knownHelpers[name]){this.opcode('invokeKnownHelper',params.length,name);}else if(this.options.knownHelpersOnly){throw new _exception2['default']('You specified knownHelpersOnly, but used the unknown helper '+name,sexpr);}else{path.strict=true;path.falsy=true;this.accept(path);this.opcode('invokeHelper',params.length,path.original,_ast2['default'].helpers.simpleId(path));}},PathExpression:function PathExpression(path){this.addDepth(path.depth);this.opcode('getContext',path.depth);var name=path.parts[0],scoped=_ast2['default'].helpers.scopedId(path),blockParamId=!path.depth&&!scoped&&this.blockParamIndex(name);if(blockParamId){this.opcode('lookupBlockParam',blockParamId,path.parts);}else if(!name){this.opcode('pushContext');}else if(path.data){this.options.data=true;this.opcode('lookupData',path.depth,path.parts,path.strict);}else{this.opcode('lookupOnContext',path.parts,path.falsy,path.strict,scoped);}},StringLiteral:function StringLiteral(string){this.opcode('pushString',string.value);},NumberLiteral:function NumberLiteral(number){this.opcode('pushLiteral',number.value);},BooleanLiteral:function BooleanLiteral(bool){this.opcode('pushLiteral',bool.value);},UndefinedLiteral:function UndefinedLiteral(){this.opcode('pushLiteral','undefined');},NullLiteral:function NullLiteral(){this.opcode('pushLiteral','null');},Hash:function Hash(hash){var pairs=hash.pairs,i=0,l=pairs.length;this.opcode('pushHash');for(;i<l;i++){this.pushParam(pairs[i].value);}
+while(i--){this.opcode('assignToHash',pairs[i].key);}
+this.opcode('popHash');},opcode:function opcode(name){this.opcodes.push({opcode:name,args:slice.call(arguments,1),loc:this.sourceNode[0].loc});},addDepth:function addDepth(depth){if(!depth){return;}
+this.useDepths=true;},classifySexpr:function classifySexpr(sexpr){var isSimple=_ast2['default'].helpers.simpleId(sexpr.path);var isBlockParam=isSimple&&!!this.blockParamIndex(sexpr.path.parts[0]);var isHelper=!isBlockParam&&_ast2['default'].helpers.helperExpression(sexpr);var isEligible=!isBlockParam&&(isHelper||isSimple);if(isEligible&&!isHelper){var _name2=sexpr.path.parts[0],options=this.options;if(options.knownHelpers[_name2]){isHelper=true;}else if(options.knownHelpersOnly){isEligible=false;}}
+if(isHelper){return'helper';}else if(isEligible){return'ambiguous';}else{return'simple';}},pushParams:function pushParams(params){for(var i=0,l=params.length;i<l;i++){this.pushParam(params[i]);}},pushParam:function pushParam(val){var value=val.value!=null?val.value:val.original||'';if(this.stringParams){if(value.replace){value=value.replace(/^(\.?\.\/)*/g,'').replace(/\//g,'.');}
+if(val.depth){this.addDepth(val.depth);}
+this.opcode('getContext',val.depth||0);this.opcode('pushStringParam',value,val.type);if(val.type==='SubExpression'){this.accept(val);}}else{if(this.trackIds){var blockParamIndex=undefined;if(val.parts&&!_ast2['default'].helpers.scopedId(val)&&!val.depth){blockParamIndex=this.blockParamIndex(val.parts[0]);}
+if(blockParamIndex){var blockParamChild=val.parts.slice(1).join('.');this.opcode('pushId','BlockParam',blockParamIndex,blockParamChild);}else{value=val.original||value;if(value.replace){value=value.replace(/^this(?:\.|$)/,'').replace(/^\.\//,'').replace(/^\.$/,'');}
+this.opcode('pushId',val.type,value);}}
+this.accept(val);}},setupFullMustacheParams:function setupFullMustacheParams(sexpr,program,inverse,omitEmpty){var params=sexpr.params;this.pushParams(params);this.opcode('pushProgram',program);this.opcode('pushProgram',inverse);if(sexpr.hash){this.accept(sexpr.hash);}else{this.opcode('emptyHash',omitEmpty);}
+return params;},blockParamIndex:function blockParamIndex(name){for(var depth=0,len=this.options.blockParams.length;depth<len;depth++){var blockParams=this.options.blockParams[depth],param=blockParams&&_utils.indexOf(blockParams,name);if(blockParams&&param>=0){return[depth,param];}}}};function precompile(input,options,env){if(input==null||typeof input!=='string'&&input.type!=='Program'){throw new _exception2['default']('You must pass a string or Handlebars AST to Handlebars.precompile. You passed '+input);}
+options=options||{};if(!('data'in options)){options.data=true;}
+if(options.compat){options.useDepths=true;}
+var ast=env.parse(input,options),environment=new env.Compiler().compile(ast,options);return new env.JavaScriptCompiler().compile(environment,options);}
+function compile(input,options,env){if(options===undefined)options={};if(input==null||typeof input!=='string'&&input.type!=='Program'){throw new _exception2['default']('You must pass a string or Handlebars AST to Handlebars.compile. You passed '+input);}
+options=_utils.extend({},options);if(!('data'in options)){options.data=true;}
+if(options.compat){options.useDepths=true;}
+var compiled=undefined;function compileInput(){var ast=env.parse(input,options),environment=new env.Compiler().compile(ast,options),templateSpec=new env.JavaScriptCompiler().compile(environment,options,undefined,true);return env.template(templateSpec);}
+function ret(context,execOptions){if(!compiled){compiled=compileInput();}
+return compiled.call(this,context,execOptions);}
+ret._setup=function(setupOptions){if(!compiled){compiled=compileInput();}
+return compiled._setup(setupOptions);};ret._child=function(i,data,blockParams,depths){if(!compiled){compiled=compileInput();}
+return compiled._child(i,data,blockParams,depths);};return ret;}
+function argEquals(a,b){if(a===b){return true;}
+if(_utils.isArray(a)&&_utils.isArray(b)&&a.length===b.length){for(var i=0;i<a.length;i++){if(!argEquals(a[i],b[i])){return false;}}
+return true;}}
+function transformLiteralToPath(sexpr){if(!sexpr.path.parts){var literal=sexpr.path;sexpr.path={type:'PathExpression',data:false,depth:0,parts:[literal.original+''],original:literal.original+'',loc:literal.loc};}}}),(function(module,exports,__webpack_require__){'use strict';var _interopRequireDefault=__webpack_require__(1)['default'];exports.__esModule=true;var _base=__webpack_require__(4);var _exception=__webpack_require__(6);var _exception2=_interopRequireDefault(_exception);var _utils=__webpack_require__(5);var _codeGen=__webpack_require__(43);var _codeGen2=_interopRequireDefault(_codeGen);function Literal(value){this.value=value;}
+function JavaScriptCompiler(){}
+JavaScriptCompiler.prototype={nameLookup:function nameLookup(parent,name){if(JavaScriptCompiler.isValidJavaScriptVariableName(name)){return[parent,'.',name];}else{return[parent,'[',JSON.stringify(name),']'];}},depthedLookup:function depthedLookup(name){return[this.aliasable('container.lookup'),'(depths, "',name,'")'];},compilerInfo:function compilerInfo(){var revision=_base.COMPILER_REVISION,versions=_base.REVISION_CHANGES[revision];return[revision,versions];},appendToBuffer:function appendToBuffer(source,location,explicit){if(!_utils.isArray(source)){source=[source];}
+source=this.source.wrap(source,location);if(this.environment.isSimple){return['return ',source,';'];}else if(explicit){return['buffer += ',source,';'];}else{source.appendToBuffer=true;return source;}},initializeBuffer:function initializeBuffer(){return this.quotedString('');},compile:function compile(environment,options,context,asObject){this.environment=environment;this.options=options;this.stringParams=this.options.stringParams;this.trackIds=this.options.trackIds;this.precompile=!asObject;this.name=this.environment.name;this.isChild=!!context;this.context=context||{decorators:[],programs:[],environments:[]};this.preamble();this.stackSlot=0;this.stackVars=[];this.aliases={};this.registers={list:[]};this.hashes=[];this.compileStack=[];this.inlineStack=[];this.blockParams=[];this.compileChildren(environment,options);this.useDepths=this.useDepths||environment.useDepths||environment.useDecorators||this.options.compat;this.useBlockParams=this.useBlockParams||environment.useBlockParams;var opcodes=environment.opcodes,opcode=undefined,firstLoc=undefined,i=undefined,l=undefined;for(i=0,l=opcodes.length;i<l;i++){opcode=opcodes[i];this.source.currentLocation=opcode.loc;firstLoc=firstLoc||opcode.loc;this[opcode.opcode].apply(this,opcode.args);}
+this.source.currentLocation=firstLoc;this.pushSource('');if(this.stackSlot||this.inlineStack.length||this.compileStack.length){throw new _exception2['default']('Compile completed with content left on stack');}
+if(!this.decorators.isEmpty()){this.useDecorators=true;this.decorators.prepend('var decorators = container.decorators;\n');this.decorators.push('return fn;');if(asObject){this.decorators=Function.apply(this,['fn','props','container','depth0','data','blockParams','depths',this.decorators.merge()]);}else{this.decorators.prepend('function(fn, props, container, depth0, data, blockParams, depths) {\n');this.decorators.push('}\n');this.decorators=this.decorators.merge();}}else{this.decorators=undefined;}
+var fn=this.createFunctionContext(asObject);if(!this.isChild){var ret={compiler:this.compilerInfo(),main:fn};if(this.decorators){ret.main_d=this.decorators;ret.useDecorators=true;}
+var _context=this.context;var programs=_context.programs;var decorators=_context.decorators;for(i=0,l=programs.length;i<l;i++){if(programs[i]){ret[i]=programs[i];if(decorators[i]){ret[i+'_d']=decorators[i];ret.useDecorators=true;}}}
+if(this.environment.usePartial){ret.usePartial=true;}
+if(this.options.data){ret.useData=true;}
+if(this.useDepths){ret.useDepths=true;}
+if(this.useBlockParams){ret.useBlockParams=true;}
+if(this.options.compat){ret.compat=true;}
+if(!asObject){ret.compiler=JSON.stringify(ret.compiler);this.source.currentLocation={start:{line:1,column:0}};ret=this.objectLiteral(ret);if(options.srcName){ret=ret.toStringWithSourceMap({file:options.destName});ret.map=ret.map&&ret.map.toString();}else{ret=ret.toString();}}else{ret.compilerOptions=this.options;}
+return ret;}else{return fn;}},preamble:function preamble(){this.lastContext=0;this.source=new _codeGen2['default'](this.options.srcName);this.decorators=new _codeGen2['default'](this.options.srcName);},createFunctionContext:function createFunctionContext(asObject){var varDeclarations='';var locals=this.stackVars.concat(this.registers.list);if(locals.length>0){varDeclarations+=', '+locals.join(', ');}
+var aliasCount=0;for(var alias in this.aliases){var node=this.aliases[alias];if(this.aliases.hasOwnProperty(alias)&&node.children&&node.referenceCount>1){varDeclarations+=', alias'+ ++aliasCount+'='+alias;node.children[0]='alias'+aliasCount;}}
+var params=['container','depth0','helpers','partials','data'];if(this.useBlockParams||this.useDepths){params.push('blockParams');}
+if(this.useDepths){params.push('depths');}
+var source=this.mergeSource(varDeclarations);if(asObject){params.push(source);return Function.apply(this,params);}else{return this.source.wrap(['function(',params.join(','),') {\n  ',source,'}']);}},mergeSource:function mergeSource(varDeclarations){var isSimple=this.environment.isSimple,appendOnly=!this.forceBuffer,appendFirst=undefined,sourceSeen=undefined,bufferStart=undefined,bufferEnd=undefined;this.source.each(function(line){if(line.appendToBuffer){if(bufferStart){line.prepend('  + ');}else{bufferStart=line;}
+bufferEnd=line;}else{if(bufferStart){if(!sourceSeen){appendFirst=true;}else{bufferStart.prepend('buffer += ');}
+bufferEnd.add(';');bufferStart=bufferEnd=undefined;}
+sourceSeen=true;if(!isSimple){appendOnly=false;}}});if(appendOnly){if(bufferStart){bufferStart.prepend('return ');bufferEnd.add(';');}else if(!sourceSeen){this.source.push('return "";');}}else{varDeclarations+=', buffer = '+(appendFirst?'':this.initializeBuffer());if(bufferStart){bufferStart.prepend('return buffer + ');bufferEnd.add(';');}else{this.source.push('return buffer;');}}
+if(varDeclarations){this.source.prepend('var '+varDeclarations.substring(2)+(appendFirst?'':';\n'));}
+return this.source.merge();},blockValue:function blockValue(name){var blockHelperMissing=this.aliasable('helpers.blockHelperMissing'),params=[this.contextName(0)];this.setupHelperArgs(name,0,params);var blockName=this.popStack();params.splice(1,0,blockName);this.push(this.source.functionCall(blockHelperMissing,'call',params));},ambiguousBlockValue:function ambiguousBlockValue(){var blockHelperMissing=this.aliasable('helpers.blockHelperMissing'),params=[this.contextName(0)];this.setupHelperArgs('',0,params,true);this.flushInline();var current=this.topStack();params.splice(1,0,current);this.pushSource(['if (!',this.lastHelper,') { ',current,' = ',this.source.functionCall(blockHelperMissing,'call',params),'}']);},appendContent:function appendContent(content){if(this.pendingContent){content=this.pendingContent+content;}else{this.pendingLocation=this.source.currentLocation;}
+this.pendingContent=content;},append:function append(){if(this.isInline()){this.replaceStack(function(current){return[' != null ? ',current,' : ""'];});this.pushSource(this.appendToBuffer(this.popStack()));}else{var local=this.popStack();this.pushSource(['if (',local,' != null) { ',this.appendToBuffer(local,undefined,true),' }']);if(this.environment.isSimple){this.pushSource(['else { ',this.appendToBuffer("''",undefined,true),' }']);}}},appendEscaped:function appendEscaped(){this.pushSource(this.appendToBuffer([this.aliasable('container.escapeExpression'),'(',this.popStack(),')']));},getContext:function getContext(depth){this.lastContext=depth;},pushContext:function pushContext(){this.pushStackLiteral(this.contextName(this.lastContext));},lookupOnContext:function lookupOnContext(parts,falsy,strict,scoped){var i=0;if(!scoped&&this.options.compat&&!this.lastContext){this.push(this.depthedLookup(parts[i++]));}else{this.pushContext();}
+this.resolvePath('context',parts,i,falsy,strict);},lookupBlockParam:function lookupBlockParam(blockParamId,parts){this.useBlockParams=true;this.push(['blockParams[',blockParamId[0],'][',blockParamId[1],']']);this.resolvePath('context',parts,1);},lookupData:function lookupData(depth,parts,strict){if(!depth){this.pushStackLiteral('data');}else{this.pushStackLiteral('container.data(data, '+depth+')');}
+this.resolvePath('data',parts,0,true,strict);},resolvePath:function resolvePath(type,parts,i,falsy,strict){var _this=this;if(this.options.strict||this.options.assumeObjects){this.push(strictLookup(this.options.strict&&strict,this,parts,type));return;}
+var len=parts.length;for(;i<len;i++){this.replaceStack(function(current){var lookup=_this.nameLookup(current,parts[i],type);if(!falsy){return[' != null ? ',lookup,' : ',current];}else{return[' && ',lookup];}});}},resolvePossibleLambda:function resolvePossibleLambda(){this.push([this.aliasable('container.lambda'),'(',this.popStack(),', ',this.contextName(0),')']);},pushStringParam:function pushStringParam(string,type){this.pushContext();this.pushString(type);if(type!=='SubExpression'){if(typeof string==='string'){this.pushString(string);}else{this.pushStackLiteral(string);}}},emptyHash:function emptyHash(omitEmpty){if(this.trackIds){this.push('{}');}
+if(this.stringParams){this.push('{}');this.push('{}');}
+this.pushStackLiteral(omitEmpty?'undefined':'{}');},pushHash:function pushHash(){if(this.hash){this.hashes.push(this.hash);}
+this.hash={values:[],types:[],contexts:[],ids:[]};},popHash:function popHash(){var hash=this.hash;this.hash=this.hashes.pop();if(this.trackIds){this.push(this.objectLiteral(hash.ids));}
+if(this.stringParams){this.push(this.objectLiteral(hash.contexts));this.push(this.objectLiteral(hash.types));}
+this.push(this.objectLiteral(hash.values));},pushString:function pushString(string){this.pushStackLiteral(this.quotedString(string));},pushLiteral:function pushLiteral(value){this.pushStackLiteral(value);},pushProgram:function pushProgram(guid){if(guid!=null){this.pushStackLiteral(this.programExpression(guid));}else{this.pushStackLiteral(null);}},registerDecorator:function registerDecorator(paramSize,name){var foundDecorator=this.nameLookup('decorators',name,'decorator'),options=this.setupHelperArgs(name,paramSize);this.decorators.push(['fn = ',this.decorators.functionCall(foundDecorator,'',['fn','props','container',options]),' || fn;']);},invokeHelper:function invokeHelper(paramSize,name,isSimple){var nonHelper=this.popStack(),helper=this.setupHelper(paramSize,name),simple=isSimple?[helper.name,' || ']:'';var lookup=['('].concat(simple,nonHelper);if(!this.options.strict){lookup.push(' || ',this.aliasable('helpers.helperMissing'));}
+lookup.push(')');this.push(this.source.functionCall(lookup,'call',helper.callParams));},invokeKnownHelper:function invokeKnownHelper(paramSize,name){var helper=this.setupHelper(paramSize,name);this.push(this.source.functionCall(helper.name,'call',helper.callParams));},invokeAmbiguous:function invokeAmbiguous(name,helperCall){this.useRegister('helper');var nonHelper=this.popStack();this.emptyHash();var helper=this.setupHelper(0,name,helperCall);var helperName=this.lastHelper=this.nameLookup('helpers',name,'helper');var lookup=['(','(helper = ',helperName,' || ',nonHelper,')'];if(!this.options.strict){lookup[0]='(helper = ';lookup.push(' != null ? helper : ',this.aliasable('helpers.helperMissing'));}
+this.push(['(',lookup,helper.paramsInit?['),(',helper.paramsInit]:[],'),','(typeof helper === ',this.aliasable('"function"'),' ? ',this.source.functionCall('helper','call',helper.callParams),' : helper))']);},invokePartial:function invokePartial(isDynamic,name,indent){var params=[],options=this.setupParams(name,1,params);if(isDynamic){name=this.popStack();delete options.name;}
+if(indent){options.indent=JSON.stringify(indent);}
+options.helpers='helpers';options.partials='partials';options.decorators='container.decorators';if(!isDynamic){params.unshift(this.nameLookup('partials',name,'partial'));}else{params.unshift(name);}
+if(this.options.compat){options.depths='depths';}
+options=this.objectLiteral(options);params.push(options);this.push(this.source.functionCall('container.invokePartial','',params));},assignToHash:function assignToHash(key){var value=this.popStack(),context=undefined,type=undefined,id=undefined;if(this.trackIds){id=this.popStack();}
+if(this.stringParams){type=this.popStack();context=this.popStack();}
+var hash=this.hash;if(context){hash.contexts[key]=context;}
+if(type){hash.types[key]=type;}
+if(id){hash.ids[key]=id;}
+hash.values[key]=value;},pushId:function pushId(type,name,child){if(type==='BlockParam'){this.pushStackLiteral('blockParams['+name[0]+'].path['+name[1]+']'+(child?' + '+JSON.stringify('.'+child):''));}else if(type==='PathExpression'){this.pushString(name);}else if(type==='SubExpression'){this.pushStackLiteral('true');}else{this.pushStackLiteral('null');}},compiler:JavaScriptCompiler,compileChildren:function compileChildren(environment,options){var children=environment.children,child=undefined,compiler=undefined;for(var i=0,l=children.length;i<l;i++){child=children[i];compiler=new this.compiler();var existing=this.matchExistingProgram(child);if(existing==null){this.context.programs.push('');var index=this.context.programs.length;child.index=index;child.name='program'+index;this.context.programs[index]=compiler.compile(child,options,this.context,!this.precompile);this.context.decorators[index]=compiler.decorators;this.context.environments[index]=child;this.useDepths=this.useDepths||compiler.useDepths;this.useBlockParams=this.useBlockParams||compiler.useBlockParams;child.useDepths=this.useDepths;child.useBlockParams=this.useBlockParams;}else{child.index=existing.index;child.name='program'+existing.index;this.useDepths=this.useDepths||existing.useDepths;this.useBlockParams=this.useBlockParams||existing.useBlockParams;}}},matchExistingProgram:function matchExistingProgram(child){for(var i=0,len=this.context.environments.length;i<len;i++){var environment=this.context.environments[i];if(environment&&environment.equals(child)){return environment;}}},programExpression:function programExpression(guid){var child=this.environment.children[guid],programParams=[child.index,'data',child.blockParams];if(this.useBlockParams||this.useDepths){programParams.push('blockParams');}
+if(this.useDepths){programParams.push('depths');}
+return'container.program('+programParams.join(', ')+')';},useRegister:function useRegister(name){if(!this.registers[name]){this.registers[name]=true;this.registers.list.push(name);}},push:function push(expr){if(!(expr instanceof Literal)){expr=this.source.wrap(expr);}
+this.inlineStack.push(expr);return expr;},pushStackLiteral:function pushStackLiteral(item){this.push(new Literal(item));},pushSource:function pushSource(source){if(this.pendingContent){this.source.push(this.appendToBuffer(this.source.quotedString(this.pendingContent),this.pendingLocation));this.pendingContent=undefined;}
+if(source){this.source.push(source);}},replaceStack:function replaceStack(callback){var prefix=['('],stack=undefined,createdStack=undefined,usedLiteral=undefined;if(!this.isInline()){throw new _exception2['default']('replaceStack on non-inline');}
+var top=this.popStack(true);if(top instanceof Literal){stack=[top.value];prefix=['(',stack];usedLiteral=true;}else{createdStack=true;var _name=this.incrStack();prefix=['((',this.push(_name),' = ',top,')'];stack=this.topStack();}
+var item=callback.call(this,stack);if(!usedLiteral){this.popStack();}
+if(createdStack){this.stackSlot--;}
+this.push(prefix.concat(item,')'));},incrStack:function incrStack(){this.stackSlot++;if(this.stackSlot>this.stackVars.length){this.stackVars.push('stack'+this.stackSlot);}
+return this.topStackName();},topStackName:function topStackName(){return'stack'+this.stackSlot;},flushInline:function flushInline(){var inlineStack=this.inlineStack;this.inlineStack=[];for(var i=0,len=inlineStack.length;i<len;i++){var entry=inlineStack[i];if(entry instanceof Literal){this.compileStack.push(entry);}else{var stack=this.incrStack();this.pushSource([stack,' = ',entry,';']);this.compileStack.push(stack);}}},isInline:function isInline(){return this.inlineStack.length;},popStack:function popStack(wrapped){var inline=this.isInline(),item=(inline?this.inlineStack:this.compileStack).pop();if(!wrapped&&item instanceof Literal){return item.value;}else{if(!inline){if(!this.stackSlot){throw new _exception2['default']('Invalid stack pop');}
+this.stackSlot--;}
+return item;}},topStack:function topStack(){var stack=this.isInline()?this.inlineStack:this.compileStack,item=stack[stack.length-1];if(item instanceof Literal){return item.value;}else{return item;}},contextName:function contextName(context){if(this.useDepths&&context){return'depths['+context+']';}else{return'depth'+context;}},quotedString:function quotedString(str){return this.source.quotedString(str);},objectLiteral:function objectLiteral(obj){return this.source.objectLiteral(obj);},aliasable:function aliasable(name){var ret=this.aliases[name];if(ret){ret.referenceCount++;return ret;}
+ret=this.aliases[name]=this.source.wrap(name);ret.aliasable=true;ret.referenceCount=1;return ret;},setupHelper:function setupHelper(paramSize,name,blockHelper){var params=[],paramsInit=this.setupHelperArgs(name,paramSize,params,blockHelper);var foundHelper=this.nameLookup('helpers',name,'helper'),callContext=this.aliasable(this.contextName(0)+' != null ? '+this.contextName(0)+' : (container.nullContext || {})');return{params:params,paramsInit:paramsInit,name:foundHelper,callParams:[callContext].concat(params)};},setupParams:function setupParams(helper,paramSize,params){var options={},contexts=[],types=[],ids=[],objectArgs=!params,param=undefined;if(objectArgs){params=[];}
+options.name=this.quotedString(helper);options.hash=this.popStack();if(this.trackIds){options.hashIds=this.popStack();}
+if(this.stringParams){options.hashTypes=this.popStack();options.hashContexts=this.popStack();}
+var inverse=this.popStack(),program=this.popStack();if(program||inverse){options.fn=program||'container.noop';options.inverse=inverse||'container.noop';}
+var i=paramSize;while(i--){param=this.popStack();params[i]=param;if(this.trackIds){ids[i]=this.popStack();}
+if(this.stringParams){types[i]=this.popStack();contexts[i]=this.popStack();}}
+if(objectArgs){options.args=this.source.generateArray(params);}
+if(this.trackIds){options.ids=this.source.generateArray(ids);}
+if(this.stringParams){options.types=this.source.generateArray(types);options.contexts=this.source.generateArray(contexts);}
+if(this.options.data){options.data='data';}
+if(this.useBlockParams){options.blockParams='blockParams';}
+return options;},setupHelperArgs:function setupHelperArgs(helper,paramSize,params,useRegister){var options=this.setupParams(helper,paramSize,params);options=this.objectLiteral(options);if(useRegister){this.useRegister('options');params.push('options');return['options=',options];}else if(params){params.push(options);return'';}else{return options;}}};(function(){var reservedWords=('break else new var'+' case finally return void'+' catch for switch while'+' continue function this with'+' default if throw'+' delete in try'+' do instanceof typeof'+' abstract enum int short'+' boolean export interface static'+' byte extends long super'+' char final native synchronized'+' class float package throws'+' const goto private transient'+' debugger implements protected volatile'+' double import public let yield await'+' null true false').split(' ');var compilerWords=JavaScriptCompiler.RESERVED_WORDS={};for(var i=0,l=reservedWords.length;i<l;i++){compilerWords[reservedWords[i]]=true;}})();JavaScriptCompiler.isValidJavaScriptVariableName=function(name){return!JavaScriptCompiler.RESERVED_WORDS[name]&&/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name);};function strictLookup(requireTerminal,compiler,parts,type){var stack=compiler.popStack(),i=0,len=parts.length;if(requireTerminal){len--;}
+for(;i<len;i++){stack=compiler.nameLookup(stack,parts[i],type);}
+if(requireTerminal){return[compiler.aliasable('container.strict'),'(',stack,', ',compiler.quotedString(parts[i]),')'];}else{return stack;}}
+exports['default']=JavaScriptCompiler;module.exports=exports['default'];}),(function(module,exports,__webpack_require__){'use strict';exports.__esModule=true;var _utils=__webpack_require__(5);var SourceNode=undefined;try{if(false){var SourceMap=require('source-map');SourceNode=SourceMap.SourceNode;}}catch(err){}
+if(!SourceNode){SourceNode=function(line,column,srcFile,chunks){this.src='';if(chunks){this.add(chunks);}};SourceNode.prototype={add:function add(chunks){if(_utils.isArray(chunks)){chunks=chunks.join('');}
+this.src+=chunks;},prepend:function prepend(chunks){if(_utils.isArray(chunks)){chunks=chunks.join('');}
+this.src=chunks+this.src;},toStringWithSourceMap:function toStringWithSourceMap(){return{code:this.toString()};},toString:function toString(){return this.src;}};}
+function castChunk(chunk,codeGen,loc){if(_utils.isArray(chunk)){var ret=[];for(var i=0,len=chunk.length;i<len;i++){ret.push(codeGen.wrap(chunk[i],loc));}
+return ret;}else if(typeof chunk==='boolean'||typeof chunk==='number'){return chunk+'';}
+return chunk;}
+function CodeGen(srcFile){this.srcFile=srcFile;this.source=[];}
+CodeGen.prototype={isEmpty:function isEmpty(){return!this.source.length;},prepend:function prepend(source,loc){this.source.unshift(this.wrap(source,loc));},push:function push(source,loc){this.source.push(this.wrap(source,loc));},merge:function merge(){var source=this.empty();this.each(function(line){source.add(['  ',line,'\n']);});return source;},each:function each(iter){for(var i=0,len=this.source.length;i<len;i++){iter(this.source[i]);}},empty:function empty(){var loc=this.currentLocation||{start:{}};return new SourceNode(loc.start.line,loc.start.column,this.srcFile);},wrap:function wrap(chunk){var loc=arguments.length<=1||arguments[1]===undefined?this.currentLocation||{start:{}}:arguments[1];if(chunk instanceof SourceNode){return chunk;}
+chunk=castChunk(chunk,this,loc);return new SourceNode(loc.start.line,loc.start.column,this.srcFile,chunk);},functionCall:function functionCall(fn,type,params){params=this.generateList(params);return this.wrap([fn,type?'.'+type+'(':'(',params,')']);},quotedString:function quotedString(str){return'"'+(str+'').replace(/\\/g,'\\\\').replace(/"/g,'\\"').replace(/\n/g,'\\n').replace(/\r/g,'\\r').replace(/\u2028/g,'\\u2028').replace(/\u2029/g,'\\u2029')+'"';},objectLiteral:function objectLiteral(obj){var pairs=[];for(var key in obj){if(obj.hasOwnProperty(key)){var value=castChunk(obj[key],this);if(value!=='undefined'){pairs.push([this.quotedString(key),':',value]);}}}
+var ret=this.generateList(pairs);ret.prepend('{');ret.add('}');return ret;},generateList:function generateList(entries){var ret=this.empty();for(var i=0,len=entries.length;i<len;i++){if(i){ret.add(',');}
+ret.add(castChunk(entries[i],this));}
+return ret;},generateArray:function generateArray(entries){var ret=this.generateList(entries);ret.prepend('[');ret.add(']');return ret;}};exports['default']=CodeGen;module.exports=exports['default'];})])});;
\ No newline at end of file
diff --git a/pdns/recursordist/html/js/jquery-1.8.3.js b/pdns/recursordist/html/js/jquery-1.8.3.js
deleted file mode 100644 (file)
index 8c24ffc..0000000
+++ /dev/null
@@ -1,9472 +0,0 @@
-/*!
- * jQuery JavaScript Library v1.8.3
- * http://jquery.com/
- *
- * Includes Sizzle.js
- * http://sizzlejs.com/
- *
- * Copyright 2012 jQuery Foundation and other contributors
- * Released under the MIT license
- * http://jquery.org/license
- *
- * Date: Tue Nov 13 2012 08:20:33 GMT-0500 (Eastern Standard Time)
- */
-(function( window, undefined ) {
-var
-       // A central reference to the root jQuery(document)
-       rootjQuery,
-
-       // The deferred used on DOM ready
-       readyList,
-
-       // Use the correct document accordingly with window argument (sandbox)
-       document = window.document,
-       location = window.location,
-       navigator = window.navigator,
-
-       // Map over jQuery in case of overwrite
-       _jQuery = window.jQuery,
-
-       // Map over the $ in case of overwrite
-       _$ = window.$,
-
-       // Save a reference to some core methods
-       core_push = Array.prototype.push,
-       core_slice = Array.prototype.slice,
-       core_indexOf = Array.prototype.indexOf,
-       core_toString = Object.prototype.toString,
-       core_hasOwn = Object.prototype.hasOwnProperty,
-       core_trim = String.prototype.trim,
-
-       // Define a local copy of jQuery
-       jQuery = function( selector, context ) {
-               // The jQuery object is actually just the init constructor 'enhanced'
-               return new jQuery.fn.init( selector, context, rootjQuery );
-       },
-
-       // Used for matching numbers
-       core_pnum = /[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,
-
-       // Used for detecting and trimming whitespace
-       core_rnotwhite = /\S/,
-       core_rspace = /\s+/,
-
-       // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE)
-       rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
-
-       // A simple way to check for HTML strings
-       // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
-       rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
-
-       // Match a standalone tag
-       rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
-
-       // JSON RegExp
-       rvalidchars = /^[\],:{}\s]*$/,
-       rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
-       rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
-       rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,
-
-       // Matches dashed string for camelizing
-       rmsPrefix = /^-ms-/,
-       rdashAlpha = /-([\da-z])/gi,
-
-       // Used by jQuery.camelCase as callback to replace()
-       fcamelCase = function( all, letter ) {
-               return ( letter + "" ).toUpperCase();
-       },
-
-       // The ready event handler and self cleanup method
-       DOMContentLoaded = function() {
-               if ( document.addEventListener ) {
-                       document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
-                       jQuery.ready();
-               } else if ( document.readyState === "complete" ) {
-                       // we're here because readyState === "complete" in oldIE
-                       // which is good enough for us to call the dom ready!
-                       document.detachEvent( "onreadystatechange", DOMContentLoaded );
-                       jQuery.ready();
-               }
-       },
-
-       // [[Class]] -> type pairs
-       class2type = {};
-
-jQuery.fn = jQuery.prototype = {
-       constructor: jQuery,
-       init: function( selector, context, rootjQuery ) {
-               var match, elem, ret, doc;
-
-               // Handle $(""), $(null), $(undefined), $(false)
-               if ( !selector ) {
-                       return this;
-               }
-
-               // Handle $(DOMElement)
-               if ( selector.nodeType ) {
-                       this.context = this[0] = selector;
-                       this.length = 1;
-                       return this;
-               }
-
-               // Handle HTML strings
-               if ( typeof selector === "string" ) {
-                       if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
-                               // Assume that strings that start and end with <> are HTML and skip the regex check
-                               match = [ null, selector, null ];
-
-                       } else {
-                               match = rquickExpr.exec( selector );
-                       }
-
-                       // Match html or make sure no context is specified for #id
-                       if ( match && (match[1] || !context) ) {
-
-                               // HANDLE: $(html) -> $(array)
-                               if ( match[1] ) {
-                                       context = context instanceof jQuery ? context[0] : context;
-                                       doc = ( context && context.nodeType ? context.ownerDocument || context : document );
-
-                                       // scripts is true for back-compat
-                                       selector = jQuery.parseHTML( match[1], doc, true );
-                                       if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
-                                               this.attr.call( selector, context, true );
-                                       }
-
-                                       return jQuery.merge( this, selector );
-
-                               // HANDLE: $(#id)
-                               } else {
-                                       elem = document.getElementById( match[2] );
-
-                                       // Check parentNode to catch when Blackberry 4.6 returns
-                                       // nodes that are no longer in the document #6963
-                                       if ( elem && elem.parentNode ) {
-                                               // Handle the case where IE and Opera return items
-                                               // by name instead of ID
-                                               if ( elem.id !== match[2] ) {
-                                                       return rootjQuery.find( selector );
-                                               }
-
-                                               // Otherwise, we inject the element directly into the jQuery object
-                                               this.length = 1;
-                                               this[0] = elem;
-                                       }
-
-                                       this.context = document;
-                                       this.selector = selector;
-                                       return this;
-                               }
-
-                       // HANDLE: $(expr, $(...))
-                       } else if ( !context || context.jquery ) {
-                               return ( context || rootjQuery ).find( selector );
-
-                       // HANDLE: $(expr, context)
-                       // (which is just equivalent to: $(context).find(expr)
-                       } else {
-                               return this.constructor( context ).find( selector );
-                       }
-
-               // HANDLE: $(function)
-               // Shortcut for document ready
-               } else if ( jQuery.isFunction( selector ) ) {
-                       return rootjQuery.ready( selector );
-               }
-
-               if ( selector.selector !== undefined ) {
-                       this.selector = selector.selector;
-                       this.context = selector.context;
-               }
-
-               return jQuery.makeArray( selector, this );
-       },
-
-       // Start with an empty selector
-       selector: "",
-
-       // The current version of jQuery being used
-       jquery: "1.8.3",
-
-       // The default length of a jQuery object is 0
-       length: 0,
-
-       // The number of elements contained in the matched element set
-       size: function() {
-               return this.length;
-       },
-
-       toArray: function() {
-               return core_slice.call( this );
-       },
-
-       // Get the Nth element in the matched element set OR
-       // Get the whole matched element set as a clean array
-       get: function( num ) {
-               return num == null ?
-
-                       // Return a 'clean' array
-                       this.toArray() :
-
-                       // Return just the object
-                       ( num < 0 ? this[ this.length + num ] : this[ num ] );
-       },
-
-       // Take an array of elements and push it onto the stack
-       // (returning the new matched element set)
-       pushStack: function( elems, name, selector ) {
-
-               // Build a new jQuery matched element set
-               var ret = jQuery.merge( this.constructor(), elems );
-
-               // Add the old object onto the stack (as a reference)
-               ret.prevObject = this;
-
-               ret.context = this.context;
-
-               if ( name === "find" ) {
-                       ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
-               } else if ( name ) {
-                       ret.selector = this.selector + "." + name + "(" + selector + ")";
-               }
-
-               // Return the newly-formed element set
-               return ret;
-       },
-
-       // Execute a callback for every element in the matched set.
-       // (You can seed the arguments with an array of args, but this is
-       // only used internally.)
-       each: function( callback, args ) {
-               return jQuery.each( this, callback, args );
-       },
-
-       ready: function( fn ) {
-               // Add the callback
-               jQuery.ready.promise().done( fn );
-
-               return this;
-       },
-
-       eq: function( i ) {
-               i = +i;
-               return i === -1 ?
-                       this.slice( i ) :
-                       this.slice( i, i + 1 );
-       },
-
-       first: function() {
-               return this.eq( 0 );
-       },
-
-       last: function() {
-               return this.eq( -1 );
-       },
-
-       slice: function() {
-               return this.pushStack( core_slice.apply( this, arguments ),
-                       "slice", core_slice.call(arguments).join(",") );
-       },
-
-       map: function( callback ) {
-               return this.pushStack( jQuery.map(this, function( elem, i ) {
-                       return callback.call( elem, i, elem );
-               }));
-       },
-
-       end: function() {
-               return this.prevObject || this.constructor(null);
-       },
-
-       // For internal use only.
-       // Behaves like an Array's method, not like a jQuery method.
-       push: core_push,
-       sort: [].sort,
-       splice: [].splice
-};
-
-// Give the init function the jQuery prototype for later instantiation
-jQuery.fn.init.prototype = jQuery.fn;
-
-jQuery.extend = jQuery.fn.extend = function() {
-       var options, name, src, copy, copyIsArray, clone,
-               target = arguments[0] || {},
-               i = 1,
-               length = arguments.length,
-               deep = false;
-
-       // Handle a deep copy situation
-       if ( typeof target === "boolean" ) {
-               deep = target;
-               target = arguments[1] || {};
-               // skip the boolean and the target
-               i = 2;
-       }
-
-       // Handle case when target is a string or something (possible in deep copy)
-       if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
-               target = {};
-       }
-
-       // extend jQuery itself if only one argument is passed
-       if ( length === i ) {
-               target = this;
-               --i;
-       }
-
-       for ( ; i < length; i++ ) {
-               // Only deal with non-null/undefined values
-               if ( (options = arguments[ i ]) != null ) {
-                       // Extend the base object
-                       for ( name in options ) {
-                               src = target[ name ];
-                               copy = options[ name ];
-
-                               // Prevent never-ending loop
-                               if ( target === copy ) {
-                                       continue;
-                               }
-
-                               // Recurse if we're merging plain objects or arrays
-                               if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
-                                       if ( copyIsArray ) {
-                                               copyIsArray = false;
-                                               clone = src && jQuery.isArray(src) ? src : [];
-
-                                       } else {
-                                               clone = src && jQuery.isPlainObject(src) ? src : {};
-                                       }
-
-                                       // Never move original objects, clone them
-                                       target[ name ] = jQuery.extend( deep, clone, copy );
-
-                               // Don't bring in undefined values
-                               } else if ( copy !== undefined ) {
-                                       target[ name ] = copy;
-                               }
-                       }
-               }
-       }
-
-       // Return the modified object
-       return target;
-};
-
-jQuery.extend({
-       noConflict: function( deep ) {
-               if ( window.$ === jQuery ) {
-                       window.$ = _$;
-               }
-
-               if ( deep && window.jQuery === jQuery ) {
-                       window.jQuery = _jQuery;
-               }
-
-               return jQuery;
-       },
-
-       // Is the DOM ready to be used? Set to true once it occurs.
-       isReady: false,
-
-       // A counter to track how many items to wait for before
-       // the ready event fires. See #6781
-       readyWait: 1,
-
-       // Hold (or release) the ready event
-       holdReady: function( hold ) {
-               if ( hold ) {
-                       jQuery.readyWait++;
-               } else {
-                       jQuery.ready( true );
-               }
-       },
-
-       // Handle when the DOM is ready
-       ready: function( wait ) {
-
-               // Abort if there are pending holds or we're already ready
-               if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
-                       return;
-               }
-
-               // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
-               if ( !document.body ) {
-                       return setTimeout( jQuery.ready, 1 );
-               }
-
-               // Remember that the DOM is ready
-               jQuery.isReady = true;
-
-               // If a normal DOM Ready event fired, decrement, and wait if need be
-               if ( wait !== true && --jQuery.readyWait > 0 ) {
-                       return;
-               }
-
-               // If there are functions bound, to execute
-               readyList.resolveWith( document, [ jQuery ] );
-
-               // Trigger any bound ready events
-               if ( jQuery.fn.trigger ) {
-                       jQuery( document ).trigger("ready").off("ready");
-               }
-       },
-
-       // See test/unit/core.js for details concerning isFunction.
-       // Since version 1.3, DOM methods and functions like alert
-       // aren't supported. They return false on IE (#2968).
-       isFunction: function( obj ) {
-               return jQuery.type(obj) === "function";
-       },
-
-       isArray: Array.isArray || function( obj ) {
-               return jQuery.type(obj) === "array";
-       },
-
-       isWindow: function( obj ) {
-               return obj != null && obj == obj.window;
-       },
-
-       isNumeric: function( obj ) {
-               return !isNaN( parseFloat(obj) ) && isFinite( obj );
-       },
-
-       type: function( obj ) {
-               return obj == null ?
-                       String( obj ) :
-                       class2type[ core_toString.call(obj) ] || "object";
-       },
-
-       isPlainObject: function( obj ) {
-               // Must be an Object.
-               // Because of IE, we also have to check the presence of the constructor property.
-               // Make sure that DOM nodes and window objects don't pass through, as well
-               if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
-                       return false;
-               }
-
-               try {
-                       // Not own constructor property must be Object
-                       if ( obj.constructor &&
-                               !core_hasOwn.call(obj, "constructor") &&
-                               !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
-                               return false;
-                       }
-               } catch ( e ) {
-                       // IE8,9 Will throw exceptions on certain host objects #9897
-                       return false;
-               }
-
-               // Own properties are enumerated firstly, so to speed up,
-               // if last one is own, then all properties are own.
-
-               var key;
-               for ( key in obj ) {}
-
-               return key === undefined || core_hasOwn.call( obj, key );
-       },
-
-       isEmptyObject: function( obj ) {
-               var name;
-               for ( name in obj ) {
-                       return false;
-               }
-               return true;
-       },
-
-       error: function( msg ) {
-               throw new Error( msg );
-       },
-
-       // data: string of html
-       // context (optional): If specified, the fragment will be created in this context, defaults to document
-       // scripts (optional): If true, will include scripts passed in the html string
-       parseHTML: function( data, context, scripts ) {
-               var parsed;
-               if ( !data || typeof data !== "string" ) {
-                       return null;
-               }
-               if ( typeof context === "boolean" ) {
-                       scripts = context;
-                       context = 0;
-               }
-               context = context || document;
-
-               // Single tag
-               if ( (parsed = rsingleTag.exec( data )) ) {
-                       return [ context.createElement( parsed[1] ) ];
-               }
-
-               parsed = jQuery.buildFragment( [ data ], context, scripts ? null : [] );
-               return jQuery.merge( [],
-                       (parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment).childNodes );
-       },
-
-       parseJSON: function( data ) {
-               if ( !data || typeof data !== "string") {
-                       return null;
-               }
-
-               // Make sure leading/trailing whitespace is removed (IE can't handle it)
-               data = jQuery.trim( data );
-
-               // Attempt to parse using the native JSON parser first
-               if ( window.JSON && window.JSON.parse ) {
-                       return window.JSON.parse( data );
-               }
-
-               // Make sure the incoming data is actual JSON
-               // Logic borrowed from http://json.org/json2.js
-               if ( rvalidchars.test( data.replace( rvalidescape, "@" )
-                       .replace( rvalidtokens, "]" )
-                       .replace( rvalidbraces, "")) ) {
-
-                       return ( new Function( "return " + data ) )();
-
-               }
-               jQuery.error( "Invalid JSON: " + data );
-       },
-
-       // Cross-browser xml parsing
-       parseXML: function( data ) {
-               var xml, tmp;
-               if ( !data || typeof data !== "string" ) {
-                       return null;
-               }
-               try {
-                       if ( window.DOMParser ) { // Standard
-                               tmp = new DOMParser();
-                               xml = tmp.parseFromString( data , "text/xml" );
-                       } else { // IE
-                               xml = new ActiveXObject( "Microsoft.XMLDOM" );
-                               xml.async = "false";
-                               xml.loadXML( data );
-                       }
-               } catch( e ) {
-                       xml = undefined;
-               }
-               if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
-                       jQuery.error( "Invalid XML: " + data );
-               }
-               return xml;
-       },
-
-       noop: function() {},
-
-       // Evaluates a script in a global context
-       // Workarounds based on findings by Jim Driscoll
-       // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
-       globalEval: function( data ) {
-               if ( data && core_rnotwhite.test( data ) ) {
-                       // We use execScript on Internet Explorer
-                       // We use an anonymous function so that context is window
-                       // rather than jQuery in Firefox
-                       ( window.execScript || function( data ) {
-                               window[ "eval" ].call( window, data );
-                       } )( data );
-               }
-       },
-
-       // Convert dashed to camelCase; used by the css and data modules
-       // Microsoft forgot to hump their vendor prefix (#9572)
-       camelCase: function( string ) {
-               return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
-       },
-
-       nodeName: function( elem, name ) {
-               return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
-       },
-
-       // args is for internal usage only
-       each: function( obj, callback, args ) {
-               var name,
-                       i = 0,
-                       length = obj.length,
-                       isObj = length === undefined || jQuery.isFunction( obj );
-
-               if ( args ) {
-                       if ( isObj ) {
-                               for ( name in obj ) {
-                                       if ( callback.apply( obj[ name ], args ) === false ) {
-                                               break;
-                                       }
-                               }
-                       } else {
-                               for ( ; i < length; ) {
-                                       if ( callback.apply( obj[ i++ ], args ) === false ) {
-                                               break;
-                                       }
-                               }
-                       }
-
-               // A special, fast, case for the most common use of each
-               } else {
-                       if ( isObj ) {
-                               for ( name in obj ) {
-                                       if ( callback.call( obj[ name ], name, obj[ name ] ) === false ) {
-                                               break;
-                                       }
-                               }
-                       } else {
-                               for ( ; i < length; ) {
-                                       if ( callback.call( obj[ i ], i, obj[ i++ ] ) === false ) {
-                                               break;
-                                       }
-                               }
-                       }
-               }
-
-               return obj;
-       },
-
-       // Use native String.trim function wherever possible
-       trim: core_trim && !core_trim.call("\uFEFF\xA0") ?
-               function( text ) {
-                       return text == null ?
-                               "" :
-                               core_trim.call( text );
-               } :
-
-               // Otherwise use our own trimming functionality
-               function( text ) {
-                       return text == null ?
-                               "" :
-                               ( text + "" ).replace( rtrim, "" );
-               },
-
-       // results is for internal usage only
-       makeArray: function( arr, results ) {
-               var type,
-                       ret = results || [];
-
-               if ( arr != null ) {
-                       // The window, strings (and functions) also have 'length'
-                       // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
-                       type = jQuery.type( arr );
-
-                       if ( arr.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( arr ) ) {
-                               core_push.call( ret, arr );
-                       } else {
-                               jQuery.merge( ret, arr );
-                       }
-               }
-
-               return ret;
-       },
-
-       inArray: function( elem, arr, i ) {
-               var len;
-
-               if ( arr ) {
-                       if ( core_indexOf ) {
-                               return core_indexOf.call( arr, elem, i );
-                       }
-
-                       len = arr.length;
-                       i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
-
-                       for ( ; i < len; i++ ) {
-                               // Skip accessing in sparse arrays
-                               if ( i in arr && arr[ i ] === elem ) {
-                                       return i;
-                               }
-                       }
-               }
-
-               return -1;
-       },
-
-       merge: function( first, second ) {
-               var l = second.length,
-                       i = first.length,
-                       j = 0;
-
-               if ( typeof l === "number" ) {
-                       for ( ; j < l; j++ ) {
-                               first[ i++ ] = second[ j ];
-                       }
-
-               } else {
-                       while ( second[j] !== undefined ) {
-                               first[ i++ ] = second[ j++ ];
-                       }
-               }
-
-               first.length = i;
-
-               return first;
-       },
-
-       grep: function( elems, callback, inv ) {
-               var retVal,
-                       ret = [],
-                       i = 0,
-                       length = elems.length;
-               inv = !!inv;
-
-               // Go through the array, only saving the items
-               // that pass the validator function
-               for ( ; i < length; i++ ) {
-                       retVal = !!callback( elems[ i ], i );
-                       if ( inv !== retVal ) {
-                               ret.push( elems[ i ] );
-                       }
-               }
-
-               return ret;
-       },
-
-       // arg is for internal usage only
-       map: function( elems, callback, arg ) {
-               var value, key,
-                       ret = [],
-                       i = 0,
-                       length = elems.length,
-                       // jquery objects are treated as arrays
-                       isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
-
-               // Go through the array, translating each of the items to their
-               if ( isArray ) {
-                       for ( ; i < length; i++ ) {
-                               value = callback( elems[ i ], i, arg );
-
-                               if ( value != null ) {
-                                       ret[ ret.length ] = value;
-                               }
-                       }
-
-               // Go through every key on the object,
-               } else {
-                       for ( key in elems ) {
-                               value = callback( elems[ key ], key, arg );
-
-                               if ( value != null ) {
-                                       ret[ ret.length ] = value;
-                               }
-                       }
-               }
-
-               // Flatten any nested arrays
-               return ret.concat.apply( [], ret );
-       },
-
-       // A global GUID counter for objects
-       guid: 1,
-
-       // Bind a function to a context, optionally partially applying any
-       // arguments.
-       proxy: function( fn, context ) {
-               var tmp, args, proxy;
-
-               if ( typeof context === "string" ) {
-                       tmp = fn[ context ];
-                       context = fn;
-                       fn = tmp;
-               }
-
-               // Quick check to determine if target is callable, in the spec
-               // this throws a TypeError, but we will just return undefined.
-               if ( !jQuery.isFunction( fn ) ) {
-                       return undefined;
-               }
-
-               // Simulated bind
-               args = core_slice.call( arguments, 2 );
-               proxy = function() {
-                       return fn.apply( context, args.concat( core_slice.call( arguments ) ) );
-               };
-
-               // Set the guid of unique handler to the same of original handler, so it can be removed
-               proxy.guid = fn.guid = fn.guid || jQuery.guid++;
-
-               return proxy;
-       },
-
-       // Multifunctional method to get and set values of a collection
-       // The value/s can optionally be executed if it's a function
-       access: function( elems, fn, key, value, chainable, emptyGet, pass ) {
-               var exec,
-                       bulk = key == null,
-                       i = 0,
-                       length = elems.length;
-
-               // Sets many values
-               if ( key && typeof key === "object" ) {
-                       for ( i in key ) {
-                               jQuery.access( elems, fn, i, key[i], 1, emptyGet, value );
-                       }
-                       chainable = 1;
-
-               // Sets one value
-               } else if ( value !== undefined ) {
-                       // Optionally, function values get executed if exec is true
-                       exec = pass === undefined && jQuery.isFunction( value );
-
-                       if ( bulk ) {
-                               // Bulk operations only iterate when executing function values
-                               if ( exec ) {
-                                       exec = fn;
-                                       fn = function( elem, key, value ) {
-                                               return exec.call( jQuery( elem ), value );
-                                       };
-
-                               // Otherwise they run against the entire set
-                               } else {
-                                       fn.call( elems, value );
-                                       fn = null;
-                               }
-                       }
-
-                       if ( fn ) {
-                               for (; i < length; i++ ) {
-                                       fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
-                               }
-                       }
-
-                       chainable = 1;
-               }
-
-               return chainable ?
-                       elems :
-
-                       // Gets
-                       bulk ?
-                               fn.call( elems ) :
-                               length ? fn( elems[0], key ) : emptyGet;
-       },
-
-       now: function() {
-               return ( new Date() ).getTime();
-       }
-});
-
-jQuery.ready.promise = function( obj ) {
-       if ( !readyList ) {
-
-               readyList = jQuery.Deferred();
-
-               // Catch cases where $(document).ready() is called after the browser event has already occurred.
-               // we once tried to use readyState "interactive" here, but it caused issues like the one
-               // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
-               if ( document.readyState === "complete" ) {
-                       // Handle it asynchronously to allow scripts the opportunity to delay ready
-                       setTimeout( jQuery.ready, 1 );
-
-               // Standards-based browsers support DOMContentLoaded
-               } else if ( document.addEventListener ) {
-                       // Use the handy event callback
-                       document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
-
-                       // A fallback to window.onload, that will always work
-                       window.addEventListener( "load", jQuery.ready, false );
-
-               // If IE event model is used
-               } else {
-                       // Ensure firing before onload, maybe late but safe also for iframes
-                       document.attachEvent( "onreadystatechange", DOMContentLoaded );
-
-                       // A fallback to window.onload, that will always work
-                       window.attachEvent( "onload", jQuery.ready );
-
-                       // If IE and not a frame
-                       // continually check to see if the document is ready
-                       var top = false;
-
-                       try {
-                               top = window.frameElement == null && document.documentElement;
-                       } catch(e) {}
-
-                       if ( top && top.doScroll ) {
-                               (function doScrollCheck() {
-                                       if ( !jQuery.isReady ) {
-
-                                               try {
-                                                       // Use the trick by Diego Perini
-                                                       // http://javascript.nwbox.com/IEContentLoaded/
-                                                       top.doScroll("left");
-                                               } catch(e) {
-                                                       return setTimeout( doScrollCheck, 50 );
-                                               }
-
-                                               // and execute any waiting functions
-                                               jQuery.ready();
-                                       }
-                               })();
-                       }
-               }
-       }
-       return readyList.promise( obj );
-};
-
-// Populate the class2type map
-jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
-       class2type[ "[object " + name + "]" ] = name.toLowerCase();
-});
-
-// All jQuery objects should point back to these
-rootjQuery = jQuery(document);
-// String to Object options format cache
-var optionsCache = {};
-
-// Convert String-formatted options into Object-formatted ones and store in cache
-function createOptions( options ) {
-       var object = optionsCache[ options ] = {};
-       jQuery.each( options.split( core_rspace ), function( _, flag ) {
-               object[ flag ] = true;
-       });
-       return object;
-}
-
-/*
- * Create a callback list using the following parameters:
- *
- *     options: an optional list of space-separated options that will change how
- *                     the callback list behaves or a more traditional option object
- *
- * By default a callback list will act like an event callback list and can be
- * "fired" multiple times.
- *
- * Possible options:
- *
- *     once:                   will ensure the callback list can only be fired once (like a Deferred)
- *
- *     memory:                 will keep track of previous values and will call any callback added
- *                                     after the list has been fired right away with the latest "memorized"
- *                                     values (like a Deferred)
- *
- *     unique:                 will ensure a callback can only be added once (no duplicate in the list)
- *
- *     stopOnFalse:    interrupt callings when a callback returns false
- *
- */
-jQuery.Callbacks = function( options ) {
-
-       // Convert options from String-formatted to Object-formatted if needed
-       // (we check in cache first)
-       options = typeof options === "string" ?
-               ( optionsCache[ options ] || createOptions( options ) ) :
-               jQuery.extend( {}, options );
-
-       var // Last fire value (for non-forgettable lists)
-               memory,
-               // Flag to know if list was already fired
-               fired,
-               // Flag to know if list is currently firing
-               firing,
-               // First callback to fire (used internally by add and fireWith)
-               firingStart,
-               // End of the loop when firing
-               firingLength,
-               // Index of currently firing callback (modified by remove if needed)
-               firingIndex,
-               // Actual callback list
-               list = [],
-               // Stack of fire calls for repeatable lists
-               stack = !options.once && [],
-               // Fire callbacks
-               fire = function( data ) {
-                       memory = options.memory && data;
-                       fired = true;
-                       firingIndex = firingStart || 0;
-                       firingStart = 0;
-                       firingLength = list.length;
-                       firing = true;
-                       for ( ; list && firingIndex < firingLength; firingIndex++ ) {
-                               if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
-                                       memory = false; // To prevent further calls using add
-                                       break;
-                               }
-                       }
-                       firing = false;
-                       if ( list ) {
-                               if ( stack ) {
-                                       if ( stack.length ) {
-                                               fire( stack.shift() );
-                                       }
-                               } else if ( memory ) {
-                                       list = [];
-                               } else {
-                                       self.disable();
-                               }
-                       }
-               },
-               // Actual Callbacks object
-               self = {
-                       // Add a callback or a collection of callbacks to the list
-                       add: function() {
-                               if ( list ) {
-                                       // First, we save the current length
-                                       var start = list.length;
-                                       (function add( args ) {
-                                               jQuery.each( args, function( _, arg ) {
-                                                       var type = jQuery.type( arg );
-                                                       if ( type === "function" ) {
-                                                               if ( !options.unique || !self.has( arg ) ) {
-                                                                       list.push( arg );
-                                                               }
-                                                       } else if ( arg && arg.length && type !== "string" ) {
-                                                               // Inspect recursively
-                                                               add( arg );
-                                                       }
-                                               });
-                                       })( arguments );
-                                       // Do we need to add the callbacks to the
-                                       // current firing batch?
-                                       if ( firing ) {
-                                               firingLength = list.length;
-                                       // With memory, if we're not firing then
-                                       // we should call right away
-                                       } else if ( memory ) {
-                                               firingStart = start;
-                                               fire( memory );
-                                       }
-                               }
-                               return this;
-                       },
-                       // Remove a callback from the list
-                       remove: function() {
-                               if ( list ) {
-                                       jQuery.each( arguments, function( _, arg ) {
-                                               var index;
-                                               while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
-                                                       list.splice( index, 1 );
-                                                       // Handle firing indexes
-                                                       if ( firing ) {
-                                                               if ( index <= firingLength ) {
-                                                                       firingLength--;
-                                                               }
-                                                               if ( index <= firingIndex ) {
-                                                                       firingIndex--;
-                                                               }
-                                                       }
-                                               }
-                                       });
-                               }
-                               return this;
-                       },
-                       // Control if a given callback is in the list
-                       has: function( fn ) {
-                               return jQuery.inArray( fn, list ) > -1;
-                       },
-                       // Remove all callbacks from the list
-                       empty: function() {
-                               list = [];
-                               return this;
-                       },
-                       // Have the list do nothing anymore
-                       disable: function() {
-                               list = stack = memory = undefined;
-                               return this;
-                       },
-                       // Is it disabled?
-                       disabled: function() {
-                               return !list;
-                       },
-                       // Lock the list in its current state
-                       lock: function() {
-                               stack = undefined;
-                               if ( !memory ) {
-                                       self.disable();
-                               }
-                               return this;
-                       },
-                       // Is it locked?
-                       locked: function() {
-                               return !stack;
-                       },
-                       // Call all callbacks with the given context and arguments
-                       fireWith: function( context, args ) {
-                               args = args || [];
-                               args = [ context, args.slice ? args.slice() : args ];
-                               if ( list && ( !fired || stack ) ) {
-                                       if ( firing ) {
-                                               stack.push( args );
-                                       } else {
-                                               fire( args );
-                                       }
-                               }
-                               return this;
-                       },
-                       // Call all the callbacks with the given arguments
-                       fire: function() {
-                               self.fireWith( this, arguments );
-                               return this;
-                       },
-                       // To know if the callbacks have already been called at least once
-                       fired: function() {
-                               return !!fired;
-                       }
-               };
-
-       return self;
-};
-jQuery.extend({
-
-       Deferred: function( func ) {
-               var tuples = [
-                               // action, add listener, listener list, final state
-                               [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
-                               [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
-                               [ "notify", "progress", jQuery.Callbacks("memory") ]
-                       ],
-                       state = "pending",
-                       promise = {
-                               state: function() {
-                                       return state;
-                               },
-                               always: function() {
-                                       deferred.done( arguments ).fail( arguments );
-                                       return this;
-                               },
-                               then: function( /* fnDone, fnFail, fnProgress */ ) {
-                                       var fns = arguments;
-                                       return jQuery.Deferred(function( newDefer ) {
-                                               jQuery.each( tuples, function( i, tuple ) {
-                                                       var action = tuple[ 0 ],
-                                                               fn = fns[ i ];
-                                                       // deferred[ done | fail | progress ] for forwarding actions to newDefer
-                                                       deferred[ tuple[1] ]( jQuery.isFunction( fn ) ?
-                                                               function() {
-                                                                       var returned = fn.apply( this, arguments );
-                                                                       if ( returned && jQuery.isFunction( returned.promise ) ) {
-                                                                               returned.promise()
-                                                                                       .done( newDefer.resolve )
-                                                                                       .fail( newDefer.reject )
-                                                                                       .progress( newDefer.notify );
-                                                                       } else {
-                                                                               newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
-                                                                       }
-                                                               } :
-                                                               newDefer[ action ]
-                                                       );
-                                               });
-                                               fns = null;
-                                       }).promise();
-                               },
-                               // Get a promise for this deferred
-                               // If obj is provided, the promise aspect is added to the object
-                               promise: function( obj ) {
-                                       return obj != null ? jQuery.extend( obj, promise ) : promise;
-                               }
-                       },
-                       deferred = {};
-
-               // Keep pipe for back-compat
-               promise.pipe = promise.then;
-
-               // Add list-specific methods
-               jQuery.each( tuples, function( i, tuple ) {
-                       var list = tuple[ 2 ],
-                               stateString = tuple[ 3 ];
-
-                       // promise[ done | fail | progress ] = list.add
-                       promise[ tuple[1] ] = list.add;
-
-                       // Handle state
-                       if ( stateString ) {
-                               list.add(function() {
-                                       // state = [ resolved | rejected ]
-                                       state = stateString;
-
-                               // [ reject_list | resolve_list ].disable; progress_list.lock
-                               }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
-                       }
-
-                       // deferred[ resolve | reject | notify ] = list.fire
-                       deferred[ tuple[0] ] = list.fire;
-                       deferred[ tuple[0] + "With" ] = list.fireWith;
-               });
-
-               // Make the deferred a promise
-               promise.promise( deferred );
-
-               // Call given func if any
-               if ( func ) {
-                       func.call( deferred, deferred );
-               }
-
-               // All done!
-               return deferred;
-       },
-
-       // Deferred helper
-       when: function( subordinate /* , ..., subordinateN */ ) {
-               var i = 0,
-                       resolveValues = core_slice.call( arguments ),
-                       length = resolveValues.length,
-
-                       // the count of uncompleted subordinates
-                       remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
-
-                       // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
-                       deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
-
-                       // Update function for both resolve and progress values
-                       updateFunc = function( i, contexts, values ) {
-                               return function( value ) {
-                                       contexts[ i ] = this;
-                                       values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
-                                       if( values === progressValues ) {
-                                               deferred.notifyWith( contexts, values );
-                                       } else if ( !( --remaining ) ) {
-                                               deferred.resolveWith( contexts, values );
-                                       }
-                               };
-                       },
-
-                       progressValues, progressContexts, resolveContexts;
-
-               // add listeners to Deferred subordinates; treat others as resolved
-               if ( length > 1 ) {
-                       progressValues = new Array( length );
-                       progressContexts = new Array( length );
-                       resolveContexts = new Array( length );
-                       for ( ; i < length; i++ ) {
-                               if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
-                                       resolveValues[ i ].promise()
-                                               .done( updateFunc( i, resolveContexts, resolveValues ) )
-                                               .fail( deferred.reject )
-                                               .progress( updateFunc( i, progressContexts, progressValues ) );
-                               } else {
-                                       --remaining;
-                               }
-                       }
-               }
-
-               // if we're not waiting on anything, resolve the master
-               if ( !remaining ) {
-                       deferred.resolveWith( resolveContexts, resolveValues );
-               }
-
-               return deferred.promise();
-       }
-});
-jQuery.support = (function() {
-
-       var support,
-               all,
-               a,
-               select,
-               opt,
-               input,
-               fragment,
-               eventName,
-               i,
-               isSupported,
-               clickFn,
-               div = document.createElement("div");
-
-       // Setup
-       div.setAttribute( "className", "t" );
-       div.innerHTML = "  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
-
-       // Support tests won't run in some limited or non-browser environments
-       all = div.getElementsByTagName("*");
-       a = div.getElementsByTagName("a")[ 0 ];
-       if ( !all || !a || !all.length ) {
-               return {};
-       }
-
-       // First batch of tests
-       select = document.createElement("select");
-       opt = select.appendChild( document.createElement("option") );
-       input = div.getElementsByTagName("input")[ 0 ];
-
-       a.style.cssText = "top:1px;float:left;opacity:.5";
-       support = {
-               // IE strips leading whitespace when .innerHTML is used
-               leadingWhitespace: ( div.firstChild.nodeType === 3 ),
-
-               // Make sure that tbody elements aren't automatically inserted
-               // IE will insert them into empty tables
-               tbody: !div.getElementsByTagName("tbody").length,
-
-               // Make sure that link elements get serialized correctly by innerHTML
-               // This requires a wrapper element in IE
-               htmlSerialize: !!div.getElementsByTagName("link").length,
-
-               // Get the style information from getAttribute
-               // (IE uses .cssText instead)
-               style: /top/.test( a.getAttribute("style") ),
-
-               // Make sure that URLs aren't manipulated
-               // (IE normalizes it by default)
-               hrefNormalized: ( a.getAttribute("href") === "/a" ),
-
-               // Make sure that element opacity exists
-               // (IE uses filter instead)
-               // Use a regex to work around a WebKit issue. See #5145
-               opacity: /^0.5/.test( a.style.opacity ),
-
-               // Verify style float existence
-               // (IE uses styleFloat instead of cssFloat)
-               cssFloat: !!a.style.cssFloat,
-
-               // Make sure that if no value is specified for a checkbox
-               // that it defaults to "on".
-               // (WebKit defaults to "" instead)
-               checkOn: ( input.value === "on" ),
-
-               // Make sure that a selected-by-default option has a working selected property.
-               // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
-               optSelected: opt.selected,
-
-               // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
-               getSetAttribute: div.className !== "t",
-
-               // Tests for enctype support on a form (#6743)
-               enctype: !!document.createElement("form").enctype,
-
-               // Makes sure cloning an html5 element does not cause problems
-               // Where outerHTML is undefined, this still works
-               html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",
-
-               // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode
-               boxModel: ( document.compatMode === "CSS1Compat" ),
-
-               // Will be defined later
-               submitBubbles: true,
-               changeBubbles: true,
-               focusinBubbles: false,
-               deleteExpando: true,
-               noCloneEvent: true,
-               inlineBlockNeedsLayout: false,
-               shrinkWrapBlocks: false,
-               reliableMarginRight: true,
-               boxSizingReliable: true,
-               pixelPosition: false
-       };
-
-       // Make sure checked status is properly cloned
-       input.checked = true;
-       support.noCloneChecked = input.cloneNode( true ).checked;
-
-       // Make sure that the options inside disabled selects aren't marked as disabled
-       // (WebKit marks them as disabled)
-       select.disabled = true;
-       support.optDisabled = !opt.disabled;
-
-       // Test to see if it's possible to delete an expando from an element
-       // Fails in Internet Explorer
-       try {
-               delete div.test;
-       } catch( e ) {
-               support.deleteExpando = false;
-       }
-
-       if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
-               div.attachEvent( "onclick", clickFn = function() {
-                       // Cloning a node shouldn't copy over any
-                       // bound event handlers (IE does this)
-                       support.noCloneEvent = false;
-               });
-               div.cloneNode( true ).fireEvent("onclick");
-               div.detachEvent( "onclick", clickFn );
-       }
-
-       // Check if a radio maintains its value
-       // after being appended to the DOM
-       input = document.createElement("input");
-       input.value = "t";
-       input.setAttribute( "type", "radio" );
-       support.radioValue = input.value === "t";
-
-       input.setAttribute( "checked", "checked" );
-
-       // #11217 - WebKit loses check when the name is after the checked attribute
-       input.setAttribute( "name", "t" );
-
-       div.appendChild( input );
-       fragment = document.createDocumentFragment();
-       fragment.appendChild( div.lastChild );
-
-       // WebKit doesn't clone checked state correctly in fragments
-       support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
-
-       // Check if a disconnected checkbox will retain its checked
-       // value of true after appended to the DOM (IE6/7)
-       support.appendChecked = input.checked;
-
-       fragment.removeChild( input );
-       fragment.appendChild( div );
-
-       // Technique from Juriy Zaytsev
-       // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
-       // We only care about the case where non-standard event systems
-       // are used, namely in IE. Short-circuiting here helps us to
-       // avoid an eval call (in setAttribute) which can cause CSP
-       // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
-       if ( div.attachEvent ) {
-               for ( i in {
-                       submit: true,
-                       change: true,
-                       focusin: true
-               }) {
-                       eventName = "on" + i;
-                       isSupported = ( eventName in div );
-                       if ( !isSupported ) {
-                               div.setAttribute( eventName, "return;" );
-                               isSupported = ( typeof div[ eventName ] === "function" );
-                       }
-                       support[ i + "Bubbles" ] = isSupported;
-               }
-       }
-
-       // Run tests that need a body at doc ready
-       jQuery(function() {
-               var container, div, tds, marginDiv,
-                       divReset = "padding:0;margin:0;border:0;display:block;overflow:hidden;",
-                       body = document.getElementsByTagName("body")[0];
-
-               if ( !body ) {
-                       // Return for frameset docs that don't have a body
-                       return;
-               }
-
-               container = document.createElement("div");
-               container.style.cssText = "visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px";
-               body.insertBefore( container, body.firstChild );
-
-               // Construct the test element
-               div = document.createElement("div");
-               container.appendChild( div );
-
-               // Check if table cells still have offsetWidth/Height when they are set
-               // to display:none and there are still other visible table cells in a
-               // table row; if so, offsetWidth/Height are not reliable for use when
-               // determining if an element has been hidden directly using
-               // display:none (it is still safe to use offsets if a parent element is
-               // hidden; don safety goggles and see bug #4512 for more information).
-               // (only IE 8 fails this test)
-               div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>";
-               tds = div.getElementsByTagName("td");
-               tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none";
-               isSupported = ( tds[ 0 ].offsetHeight === 0 );
-
-               tds[ 0 ].style.display = "";
-               tds[ 1 ].style.display = "none";
-
-               // Check if empty table cells still have offsetWidth/Height
-               // (IE <= 8 fail this test)
-               support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
-
-               // Check box-sizing and margin behavior
-               div.innerHTML = "";
-               div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";
-               support.boxSizing = ( div.offsetWidth === 4 );
-               support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 );
-
-               // NOTE: To any future maintainer, we've window.getComputedStyle
-               // because jsdom on node.js will break without it.
-               if ( window.getComputedStyle ) {
-                       support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
-                       support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
-
-                       // Check if div with explicit width and no margin-right incorrectly
-                       // gets computed margin-right based on width of container. For more
-                       // info see bug #3333
-                       // Fails in WebKit before Feb 2011 nightlies
-                       // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
-                       marginDiv = document.createElement("div");
-                       marginDiv.style.cssText = div.style.cssText = divReset;
-                       marginDiv.style.marginRight = marginDiv.style.width = "0";
-                       div.style.width = "1px";
-                       div.appendChild( marginDiv );
-                       support.reliableMarginRight =
-                               !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
-               }
-
-               if ( typeof div.style.zoom !== "undefined" ) {
-                       // Check if natively block-level elements act like inline-block
-                       // elements when setting their display to 'inline' and giving
-                       // them layout
-                       // (IE < 8 does this)
-                       div.innerHTML = "";
-                       div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1";
-                       support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
-
-                       // Check if elements with layout shrink-wrap their children
-                       // (IE 6 does this)
-                       div.style.display = "block";
-                       div.style.overflow = "visible";
-                       div.innerHTML = "<div></div>";
-                       div.firstChild.style.width = "5px";
-                       support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
-
-                       container.style.zoom = 1;
-               }
-
-               // Null elements to avoid leaks in IE
-               body.removeChild( container );
-               container = div = tds = marginDiv = null;
-       });
-
-       // Null elements to avoid leaks in IE
-       fragment.removeChild( div );
-       all = a = select = opt = input = fragment = div = null;
-
-       return support;
-})();
-var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
-       rmultiDash = /([A-Z])/g;
-
-jQuery.extend({
-       cache: {},
-
-       deletedIds: [],
-
-       // Remove at next major release (1.9/2.0)
-       uuid: 0,
-
-       // Unique for each copy of jQuery on the page
-       // Non-digits removed to match rinlinejQuery
-       expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
-
-       // The following elements throw uncatchable exceptions if you
-       // attempt to add expando properties to them.
-       noData: {
-               "embed": true,
-               // Ban all objects except for Flash (which handle expandos)
-               "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
-               "applet": true
-       },
-
-       hasData: function( elem ) {
-               elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
-               return !!elem && !isEmptyDataObject( elem );
-       },
-
-       data: function( elem, name, data, pvt /* Internal Use Only */ ) {
-               if ( !jQuery.acceptData( elem ) ) {
-                       return;
-               }
-
-               var thisCache, ret,
-                       internalKey = jQuery.expando,
-                       getByName = typeof name === "string",
-
-                       // We have to handle DOM nodes and JS objects differently because IE6-7
-                       // can't GC object references properly across the DOM-JS boundary
-                       isNode = elem.nodeType,
-
-                       // Only DOM nodes need the global jQuery cache; JS object data is
-                       // attached directly to the object so GC can occur automatically
-                       cache = isNode ? jQuery.cache : elem,
-
-                       // Only defining an ID for JS objects if its cache already exists allows
-                       // the code to shortcut on the same path as a DOM node with no cache
-                       id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
-
-               // Avoid doing any more work than we need to when trying to get data on an
-               // object that has no data at all
-               if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
-                       return;
-               }
-
-               if ( !id ) {
-                       // Only DOM nodes need a new unique ID for each element since their data
-                       // ends up in the global cache
-                       if ( isNode ) {
-                               elem[ internalKey ] = id = jQuery.deletedIds.pop() || jQuery.guid++;
-                       } else {
-                               id = internalKey;
-                       }
-               }
-
-               if ( !cache[ id ] ) {
-                       cache[ id ] = {};
-
-                       // Avoids exposing jQuery metadata on plain JS objects when the object
-                       // is serialized using JSON.stringify
-                       if ( !isNode ) {
-                               cache[ id ].toJSON = jQuery.noop;
-                       }
-               }
-
-               // An object can be passed to jQuery.data instead of a key/value pair; this gets
-               // shallow copied over onto the existing cache
-               if ( typeof name === "object" || typeof name === "function" ) {
-                       if ( pvt ) {
-                               cache[ id ] = jQuery.extend( cache[ id ], name );
-                       } else {
-                               cache[ id ].data = jQuery.extend( cache[ id ].data, name );
-                       }
-               }
-
-               thisCache = cache[ id ];
-
-               // jQuery data() is stored in a separate object inside the object's internal data
-               // cache in order to avoid key collisions between internal data and user-defined
-               // data.
-               if ( !pvt ) {
-                       if ( !thisCache.data ) {
-                               thisCache.data = {};
-                       }
-
-                       thisCache = thisCache.data;
-               }
-
-               if ( data !== undefined ) {
-                       thisCache[ jQuery.camelCase( name ) ] = data;
-               }
-
-               // Check for both converted-to-camel and non-converted data property names
-               // If a data property was specified
-               if ( getByName ) {
-
-                       // First Try to find as-is property data
-                       ret = thisCache[ name ];
-
-                       // Test for null|undefined property data
-                       if ( ret == null ) {
-
-                               // Try to find the camelCased property
-                               ret = thisCache[ jQuery.camelCase( name ) ];
-                       }
-               } else {
-                       ret = thisCache;
-               }
-
-               return ret;
-       },
-
-       removeData: function( elem, name, pvt /* Internal Use Only */ ) {
-               if ( !jQuery.acceptData( elem ) ) {
-                       return;
-               }
-
-               var thisCache, i, l,
-
-                       isNode = elem.nodeType,
-
-                       // See jQuery.data for more information
-                       cache = isNode ? jQuery.cache : elem,
-                       id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
-
-               // If there is already no cache entry for this object, there is no
-               // purpose in continuing
-               if ( !cache[ id ] ) {
-                       return;
-               }
-
-               if ( name ) {
-
-                       thisCache = pvt ? cache[ id ] : cache[ id ].data;
-
-                       if ( thisCache ) {
-
-                               // Support array or space separated string names for data keys
-                               if ( !jQuery.isArray( name ) ) {
-
-                                       // try the string as a key before any manipulation
-                                       if ( name in thisCache ) {
-                                               name = [ name ];
-                                       } else {
-
-                                               // split the camel cased version by spaces unless a key with the spaces exists
-                                               name = jQuery.camelCase( name );
-                                               if ( name in thisCache ) {
-                                                       name = [ name ];
-                                               } else {
-                                                       name = name.split(" ");
-                                               }
-                                       }
-                               }
-
-                               for ( i = 0, l = name.length; i < l; i++ ) {
-                                       delete thisCache[ name[i] ];
-                               }
-
-                               // If there is no data left in the cache, we want to continue
-                               // and let the cache object itself get destroyed
-                               if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
-                                       return;
-                               }
-                       }
-               }
-
-               // See jQuery.data for more information
-               if ( !pvt ) {
-                       delete cache[ id ].data;
-
-                       // Don't destroy the parent cache unless the internal data object
-                       // had been the only thing left in it
-                       if ( !isEmptyDataObject( cache[ id ] ) ) {
-                               return;
-                       }
-               }
-
-               // Destroy the cache
-               if ( isNode ) {
-                       jQuery.cleanData( [ elem ], true );
-
-               // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
-               } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
-                       delete cache[ id ];
-
-               // When all else fails, null
-               } else {
-                       cache[ id ] = null;
-               }
-       },
-
-       // For internal use only.
-       _data: function( elem, name, data ) {
-               return jQuery.data( elem, name, data, true );
-       },
-
-       // A method for determining if a DOM node can handle the data expando
-       acceptData: function( elem ) {
-               var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
-
-               // nodes accept data unless otherwise specified; rejection can be conditional
-               return !noData || noData !== true && elem.getAttribute("classid") === noData;
-       }
-});
-
-jQuery.fn.extend({
-       data: function( key, value ) {
-               var parts, part, attr, name, l,
-                       elem = this[0],
-                       i = 0,
-                       data = null;
-
-               // Gets all values
-               if ( key === undefined ) {
-                       if ( this.length ) {
-                               data = jQuery.data( elem );
-
-                               if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
-                                       attr = elem.attributes;
-                                       for ( l = attr.length; i < l; i++ ) {
-                                               name = attr[i].name;
-
-                                               if ( !name.indexOf( "data-" ) ) {
-                                                       name = jQuery.camelCase( name.substring(5) );
-
-                                                       dataAttr( elem, name, data[ name ] );
-                                               }
-                                       }
-                                       jQuery._data( elem, "parsedAttrs", true );
-                               }
-                       }
-
-                       return data;
-               }
-
-               // Sets multiple values
-               if ( typeof key === "object" ) {
-                       return this.each(function() {
-                               jQuery.data( this, key );
-                       });
-               }
-
-               parts = key.split( ".", 2 );
-               parts[1] = parts[1] ? "." + parts[1] : "";
-               part = parts[1] + "!";
-
-               return jQuery.access( this, function( value ) {
-
-                       if ( value === undefined ) {
-                               data = this.triggerHandler( "getData" + part, [ parts[0] ] );
-
-                               // Try to fetch any internally stored data first
-                               if ( data === undefined && elem ) {
-                                       data = jQuery.data( elem, key );
-                                       data = dataAttr( elem, key, data );
-                               }
-
-                               return data === undefined && parts[1] ?
-                                       this.data( parts[0] ) :
-                                       data;
-                       }
-
-                       parts[1] = value;
-                       this.each(function() {
-                               var self = jQuery( this );
-
-                               self.triggerHandler( "setData" + part, parts );
-                               jQuery.data( this, key, value );
-                               self.triggerHandler( "changeData" + part, parts );
-                       });
-               }, null, value, arguments.length > 1, null, false );
-       },
-
-       removeData: function( key ) {
-               return this.each(function() {
-                       jQuery.removeData( this, key );
-               });
-       }
-});
-
-function dataAttr( elem, key, data ) {
-       // If nothing was found internally, try to fetch any
-       // data from the HTML5 data-* attribute
-       if ( data === undefined && elem.nodeType === 1 ) {
-
-               var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
-
-               data = elem.getAttribute( name );
-
-               if ( typeof data === "string" ) {
-                       try {
-                               data = data === "true" ? true :
-                               data === "false" ? false :
-                               data === "null" ? null :
-                               // Only convert to a number if it doesn't change the string
-                               +data + "" === data ? +data :
-                               rbrace.test( data ) ? jQuery.parseJSON( data ) :
-                                       data;
-                       } catch( e ) {}
-
-                       // Make sure we set the data so it isn't changed later
-                       jQuery.data( elem, key, data );
-
-               } else {
-                       data = undefined;
-               }
-       }
-
-       return data;
-}
-
-// checks a cache object for emptiness
-function isEmptyDataObject( obj ) {
-       var name;
-       for ( name in obj ) {
-
-               // if the public data object is empty, the private is still empty
-               if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
-                       continue;
-               }
-               if ( name !== "toJSON" ) {
-                       return false;
-               }
-       }
-
-       return true;
-}
-jQuery.extend({
-       queue: function( elem, type, data ) {
-               var queue;
-
-               if ( elem ) {
-                       type = ( type || "fx" ) + "queue";
-                       queue = jQuery._data( elem, type );
-
-                       // Speed up dequeue by getting out quickly if this is just a lookup
-                       if ( data ) {
-                               if ( !queue || jQuery.isArray(data) ) {
-                                       queue = jQuery._data( elem, type, jQuery.makeArray(data) );
-                               } else {
-                                       queue.push( data );
-                               }
-                       }
-                       return queue || [];
-               }
-       },
-
-       dequeue: function( elem, type ) {
-               type = type || "fx";
-
-               var queue = jQuery.queue( elem, type ),
-                       startLength = queue.length,
-                       fn = queue.shift(),
-                       hooks = jQuery._queueHooks( elem, type ),
-                       next = function() {
-                               jQuery.dequeue( elem, type );
-                       };
-
-               // If the fx queue is dequeued, always remove the progress sentinel
-               if ( fn === "inprogress" ) {
-                       fn = queue.shift();
-                       startLength--;
-               }
-
-               if ( fn ) {
-
-                       // Add a progress sentinel to prevent the fx queue from being
-                       // automatically dequeued
-                       if ( type === "fx" ) {
-                               queue.unshift( "inprogress" );
-                       }
-
-                       // clear up the last queue stop function
-                       delete hooks.stop;
-                       fn.call( elem, next, hooks );
-               }
-
-               if ( !startLength && hooks ) {
-                       hooks.empty.fire();
-               }
-       },
-
-       // not intended for public consumption - generates a queueHooks object, or returns the current one
-       _queueHooks: function( elem, type ) {
-               var key = type + "queueHooks";
-               return jQuery._data( elem, key ) || jQuery._data( elem, key, {
-                       empty: jQuery.Callbacks("once memory").add(function() {
-                               jQuery.removeData( elem, type + "queue", true );
-                               jQuery.removeData( elem, key, true );
-                       })
-               });
-       }
-});
-
-jQuery.fn.extend({
-       queue: function( type, data ) {
-               var setter = 2;
-
-               if ( typeof type !== "string" ) {
-                       data = type;
-                       type = "fx";
-                       setter--;
-               }
-
-               if ( arguments.length < setter ) {
-                       return jQuery.queue( this[0], type );
-               }
-
-               return data === undefined ?
-                       this :
-                       this.each(function() {
-                               var queue = jQuery.queue( this, type, data );
-
-                               // ensure a hooks for this queue
-                               jQuery._queueHooks( this, type );
-
-                               if ( type === "fx" && queue[0] !== "inprogress" ) {
-                                       jQuery.dequeue( this, type );
-                               }
-                       });
-       },
-       dequeue: function( type ) {
-               return this.each(function() {
-                       jQuery.dequeue( this, type );
-               });
-       },
-       // Based off of the plugin by Clint Helfers, with permission.
-       // http://blindsignals.com/index.php/2009/07/jquery-delay/
-       delay: function( time, type ) {
-               time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
-               type = type || "fx";
-
-               return this.queue( type, function( next, hooks ) {
-                       var timeout = setTimeout( next, time );
-                       hooks.stop = function() {
-                               clearTimeout( timeout );
-                       };
-               });
-       },
-       clearQueue: function( type ) {
-               return this.queue( type || "fx", [] );
-       },
-       // Get a promise resolved when queues of a certain type
-       // are emptied (fx is the type by default)
-       promise: function( type, obj ) {
-               var tmp,
-                       count = 1,
-                       defer = jQuery.Deferred(),
-                       elements = this,
-                       i = this.length,
-                       resolve = function() {
-                               if ( !( --count ) ) {
-                                       defer.resolveWith( elements, [ elements ] );
-                               }
-                       };
-
-               if ( typeof type !== "string" ) {
-                       obj = type;
-                       type = undefined;
-               }
-               type = type || "fx";
-
-               while( i-- ) {
-                       tmp = jQuery._data( elements[ i ], type + "queueHooks" );
-                       if ( tmp && tmp.empty ) {
-                               count++;
-                               tmp.empty.add( resolve );
-                       }
-               }
-               resolve();
-               return defer.promise( obj );
-       }
-});
-var nodeHook, boolHook, fixSpecified,
-       rclass = /[\t\r\n]/g,
-       rreturn = /\r/g,
-       rtype = /^(?:button|input)$/i,
-       rfocusable = /^(?:button|input|object|select|textarea)$/i,
-       rclickable = /^a(?:rea|)$/i,
-       rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
-       getSetAttribute = jQuery.support.getSetAttribute;
-
-jQuery.fn.extend({
-       attr: function( name, value ) {
-               return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
-       },
-
-       removeAttr: function( name ) {
-               return this.each(function() {
-                       jQuery.removeAttr( this, name );
-               });
-       },
-
-       prop: function( name, value ) {
-               return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
-       },
-
-       removeProp: function( name ) {
-               name = jQuery.propFix[ name ] || name;
-               return this.each(function() {
-                       // try/catch handles cases where IE balks (such as removing a property on window)
-                       try {
-                               this[ name ] = undefined;
-                               delete this[ name ];
-                       } catch( e ) {}
-               });
-       },
-
-       addClass: function( value ) {
-               var classNames, i, l, elem,
-                       setClass, c, cl;
-
-               if ( jQuery.isFunction( value ) ) {
-                       return this.each(function( j ) {
-                               jQuery( this ).addClass( value.call(this, j, this.className) );
-                       });
-               }
-
-               if ( value && typeof value === "string" ) {
-                       classNames = value.split( core_rspace );
-
-                       for ( i = 0, l = this.length; i < l; i++ ) {
-                               elem = this[ i ];
-
-                               if ( elem.nodeType === 1 ) {
-                                       if ( !elem.className && classNames.length === 1 ) {
-                                               elem.className = value;
-
-                                       } else {
-                                               setClass = " " + elem.className + " ";
-
-                                               for ( c = 0, cl = classNames.length; c < cl; c++ ) {
-                                                       if ( setClass.indexOf( " " + classNames[ c ] + " " ) < 0 ) {
-                                                               setClass += classNames[ c ] + " ";
-                                                       }
-                                               }
-                                               elem.className = jQuery.trim( setClass );
-                                       }
-                               }
-                       }
-               }
-
-               return this;
-       },
-
-       removeClass: function( value ) {
-               var removes, className, elem, c, cl, i, l;
-
-               if ( jQuery.isFunction( value ) ) {
-                       return this.each(function( j ) {
-                               jQuery( this ).removeClass( value.call(this, j, this.className) );
-                       });
-               }
-               if ( (value && typeof value === "string") || value === undefined ) {
-                       removes = ( value || "" ).split( core_rspace );
-
-                       for ( i = 0, l = this.length; i < l; i++ ) {
-                               elem = this[ i ];
-                               if ( elem.nodeType === 1 && elem.className ) {
-
-                                       className = (" " + elem.className + " ").replace( rclass, " " );
-
-                                       // loop over each item in the removal list
-                                       for ( c = 0, cl = removes.length; c < cl; c++ ) {
-                                               // Remove until there is nothing to remove,
-                                               while ( className.indexOf(" " + removes[ c ] + " ") >= 0 ) {
-                                                       className = className.replace( " " + removes[ c ] + " " , " " );
-                                               }
-                                       }
-                                       elem.className = value ? jQuery.trim( className ) : "";
-                               }
-                       }
-               }
-
-               return this;
-       },
-
-       toggleClass: function( value, stateVal ) {
-               var type = typeof value,
-                       isBool = typeof stateVal === "boolean";
-
-               if ( jQuery.isFunction( value ) ) {
-                       return this.each(function( i ) {
-                               jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
-                       });
-               }
-
-               return this.each(function() {
-                       if ( type === "string" ) {
-                               // toggle individual class names
-                               var className,
-                                       i = 0,
-                                       self = jQuery( this ),
-                                       state = stateVal,
-                                       classNames = value.split( core_rspace );
-
-                               while ( (className = classNames[ i++ ]) ) {
-                                       // check each className given, space separated list
-                                       state = isBool ? state : !self.hasClass( className );
-                                       self[ state ? "addClass" : "removeClass" ]( className );
-                               }
-
-                       } else if ( type === "undefined" || type === "boolean" ) {
-                               if ( this.className ) {
-                                       // store className if set
-                                       jQuery._data( this, "__className__", this.className );
-                               }
-
-                               // toggle whole className
-                               this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
-                       }
-               });
-       },
-
-       hasClass: function( selector ) {
-               var className = " " + selector + " ",
-                       i = 0,
-                       l = this.length;
-               for ( ; i < l; i++ ) {
-                       if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
-                               return true;
-                       }
-               }
-
-               return false;
-       },
-
-       val: function( value ) {
-               var hooks, ret, isFunction,
-                       elem = this[0];
-
-               if ( !arguments.length ) {
-                       if ( elem ) {
-                               hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
-
-                               if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
-                                       return ret;
-                               }
-
-                               ret = elem.value;
-
-                               return typeof ret === "string" ?
-                                       // handle most common string cases
-                                       ret.replace(rreturn, "") :
-                                       // handle cases where value is null/undef or number
-                                       ret == null ? "" : ret;
-                       }
-
-                       return;
-               }
-
-               isFunction = jQuery.isFunction( value );
-
-               return this.each(function( i ) {
-                       var val,
-                               self = jQuery(this);
-
-                       if ( this.nodeType !== 1 ) {
-                               return;
-                       }
-
-                       if ( isFunction ) {
-                               val = value.call( this, i, self.val() );
-                       } else {
-                               val = value;
-                       }
-
-                       // Treat null/undefined as ""; convert numbers to string
-                       if ( val == null ) {
-                               val = "";
-                       } else if ( typeof val === "number" ) {
-                               val += "";
-                       } else if ( jQuery.isArray( val ) ) {
-                               val = jQuery.map(val, function ( value ) {
-                                       return value == null ? "" : value + "";
-                               });
-                       }
-
-                       hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
-
-                       // If set returns undefined, fall back to normal setting
-                       if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
-                               this.value = val;
-                       }
-               });
-       }
-});
-
-jQuery.extend({
-       valHooks: {
-               option: {
-                       get: function( elem ) {
-                               // attributes.value is undefined in Blackberry 4.7 but
-                               // uses .value. See #6932
-                               var val = elem.attributes.value;
-                               return !val || val.specified ? elem.value : elem.text;
-                       }
-               },
-               select: {
-                       get: function( elem ) {
-                               var value, option,
-                                       options = elem.options,
-                                       index = elem.selectedIndex,
-                                       one = elem.type === "select-one" || index < 0,
-                                       values = one ? null : [],
-                                       max = one ? index + 1 : options.length,
-                                       i = index < 0 ?
-                                               max :
-                                               one ? index : 0;
-
-                               // Loop through all the selected options
-                               for ( ; i < max; i++ ) {
-                                       option = options[ i ];
-
-                                       // oldIE doesn't update selected after form reset (#2551)
-                                       if ( ( option.selected || i === index ) &&
-                                                       // Don't return options that are disabled or in a disabled optgroup
-                                                       ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
-                                                       ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
-
-                                               // Get the specific value for the option
-                                               value = jQuery( option ).val();
-
-                                               // We don't need an array for one selects
-                                               if ( one ) {
-                                                       return value;
-                                               }
-
-                                               // Multi-Selects return an array
-                                               values.push( value );
-                                       }
-                               }
-
-                               return values;
-                       },
-
-                       set: function( elem, value ) {
-                               var values = jQuery.makeArray( value );
-
-                               jQuery(elem).find("option").each(function() {
-                                       this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
-                               });
-
-                               if ( !values.length ) {
-                                       elem.selectedIndex = -1;
-                               }
-                               return values;
-                       }
-               }
-       },
-
-       // Unused in 1.8, left in so attrFn-stabbers won't die; remove in 1.9
-       attrFn: {},
-
-       attr: function( elem, name, value, pass ) {
-               var ret, hooks, notxml,
-                       nType = elem.nodeType;
-
-               // don't get/set attributes on text, comment and attribute nodes
-               if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
-                       return;
-               }
-
-               if ( pass && jQuery.isFunction( jQuery.fn[ name ] ) ) {
-                       return jQuery( elem )[ name ]( value );
-               }
-
-               // Fallback to prop when attributes are not supported
-               if ( typeof elem.getAttribute === "undefined" ) {
-                       return jQuery.prop( elem, name, value );
-               }
-
-               notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
-
-               // All attributes are lowercase
-               // Grab necessary hook if one is defined
-               if ( notxml ) {
-                       name = name.toLowerCase();
-                       hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
-               }
-
-               if ( value !== undefined ) {
-
-                       if ( value === null ) {
-                               jQuery.removeAttr( elem, name );
-                               return;
-
-                       } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
-                               return ret;
-
-                       } else {
-                               elem.setAttribute( name, value + "" );
-                               return value;
-                       }
-
-               } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
-                       return ret;
-
-               } else {
-
-                       ret = elem.getAttribute( name );
-
-                       // Non-existent attributes return null, we normalize to undefined
-                       return ret === null ?
-                               undefined :
-                               ret;
-               }
-       },
-
-       removeAttr: function( elem, value ) {
-               var propName, attrNames, name, isBool,
-                       i = 0;
-
-               if ( value && elem.nodeType === 1 ) {
-
-                       attrNames = value.split( core_rspace );
-
-                       for ( ; i < attrNames.length; i++ ) {
-                               name = attrNames[ i ];
-
-                               if ( name ) {
-                                       propName = jQuery.propFix[ name ] || name;
-                                       isBool = rboolean.test( name );
-
-                                       // See #9699 for explanation of this approach (setting first, then removal)
-                                       // Do not do this for boolean attributes (see #10870)
-                                       if ( !isBool ) {
-                                               jQuery.attr( elem, name, "" );
-                                       }
-                                       elem.removeAttribute( getSetAttribute ? name : propName );
-
-                                       // Set corresponding property to false for boolean attributes
-                                       if ( isBool && propName in elem ) {
-                                               elem[ propName ] = false;
-                                       }
-                               }
-                       }
-               }
-       },
-
-       attrHooks: {
-               type: {
-                       set: function( elem, value ) {
-                               // We can't allow the type property to be changed (since it causes problems in IE)
-                               if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
-                                       jQuery.error( "type property can't be changed" );
-                               } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
-                                       // Setting the type on a radio button after the value resets the value in IE6-9
-                                       // Reset value to it's default in case type is set after value
-                                       // This is for element creation
-                                       var val = elem.value;
-                                       elem.setAttribute( "type", value );
-                                       if ( val ) {
-                                               elem.value = val;
-                                       }
-                                       return value;
-                               }
-                       }
-               },
-               // Use the value property for back compat
-               // Use the nodeHook for button elements in IE6/7 (#1954)
-               value: {
-                       get: function( elem, name ) {
-                               if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
-                                       return nodeHook.get( elem, name );
-                               }
-                               return name in elem ?
-                                       elem.value :
-                                       null;
-                       },
-                       set: function( elem, value, name ) {
-                               if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
-                                       return nodeHook.set( elem, value, name );
-                               }
-                               // Does not return so that setAttribute is also used
-                               elem.value = value;
-                       }
-               }
-       },
-
-       propFix: {
-               tabindex: "tabIndex",
-               readonly: "readOnly",
-               "for": "htmlFor",
-               "class": "className",
-               maxlength: "maxLength",
-               cellspacing: "cellSpacing",
-               cellpadding: "cellPadding",
-               rowspan: "rowSpan",
-               colspan: "colSpan",
-               usemap: "useMap",
-               frameborder: "frameBorder",
-               contenteditable: "contentEditable"
-       },
-
-       prop: function( elem, name, value ) {
-               var ret, hooks, notxml,
-                       nType = elem.nodeType;
-
-               // don't get/set properties on text, comment and attribute nodes
-               if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
-                       return;
-               }
-
-               notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
-
-               if ( notxml ) {
-                       // Fix name and attach hooks
-                       name = jQuery.propFix[ name ] || name;
-                       hooks = jQuery.propHooks[ name ];
-               }
-
-               if ( value !== undefined ) {
-                       if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
-                               return ret;
-
-                       } else {
-                               return ( elem[ name ] = value );
-                       }
-
-               } else {
-                       if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
-                               return ret;
-
-                       } else {
-                               return elem[ name ];
-                       }
-               }
-       },
-
-       propHooks: {
-               tabIndex: {
-                       get: function( elem ) {
-                               // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
-                               // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
-                               var attributeNode = elem.getAttributeNode("tabindex");
-
-                               return attributeNode && attributeNode.specified ?
-                                       parseInt( attributeNode.value, 10 ) :
-                                       rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
-                                               0 :
-                                               undefined;
-                       }
-               }
-       }
-});
-
-// Hook for boolean attributes
-boolHook = {
-       get: function( elem, name ) {
-               // Align boolean attributes with corresponding properties
-               // Fall back to attribute presence where some booleans are not supported
-               var attrNode,
-                       property = jQuery.prop( elem, name );
-               return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
-                       name.toLowerCase() :
-                       undefined;
-       },
-       set: function( elem, value, name ) {
-               var propName;
-               if ( value === false ) {
-                       // Remove boolean attributes when set to false
-                       jQuery.removeAttr( elem, name );
-               } else {
-                       // value is true since we know at this point it's type boolean and not false
-                       // Set boolean attributes to the same name and set the DOM property
-                       propName = jQuery.propFix[ name ] || name;
-                       if ( propName in elem ) {
-                               // Only set the IDL specifically if it already exists on the element
-                               elem[ propName ] = true;
-                       }
-
-                       elem.setAttribute( name, name.toLowerCase() );
-               }
-               return name;
-       }
-};
-
-// IE6/7 do not support getting/setting some attributes with get/setAttribute
-if ( !getSetAttribute ) {
-
-       fixSpecified = {
-               name: true,
-               id: true,
-               coords: true
-       };
-
-       // Use this for any attribute in IE6/7
-       // This fixes almost every IE6/7 issue
-       nodeHook = jQuery.valHooks.button = {
-               get: function( elem, name ) {
-                       var ret;
-                       ret = elem.getAttributeNode( name );
-                       return ret && ( fixSpecified[ name ] ? ret.value !== "" : ret.specified ) ?
-                               ret.value :
-                               undefined;
-               },
-               set: function( elem, value, name ) {
-                       // Set the existing or create a new attribute node
-                       var ret = elem.getAttributeNode( name );
-                       if ( !ret ) {
-                               ret = document.createAttribute( name );
-                               elem.setAttributeNode( ret );
-                       }
-                       return ( ret.value = value + "" );
-               }
-       };
-
-       // Set width and height to auto instead of 0 on empty string( Bug #8150 )
-       // This is for removals
-       jQuery.each([ "width", "height" ], function( i, name ) {
-               jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
-                       set: function( elem, value ) {
-                               if ( value === "" ) {
-                                       elem.setAttribute( name, "auto" );
-                                       return value;
-                               }
-                       }
-               });
-       });
-
-       // Set contenteditable to false on removals(#10429)
-       // Setting to empty string throws an error as an invalid value
-       jQuery.attrHooks.contenteditable = {
-               get: nodeHook.get,
-               set: function( elem, value, name ) {
-                       if ( value === "" ) {
-                               value = "false";
-                       }
-                       nodeHook.set( elem, value, name );
-               }
-       };
-}
-
-
-// Some attributes require a special call on IE
-if ( !jQuery.support.hrefNormalized ) {
-       jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
-               jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
-                       get: function( elem ) {
-                               var ret = elem.getAttribute( name, 2 );
-                               return ret === null ? undefined : ret;
-                       }
-               });
-       });
-}
-
-if ( !jQuery.support.style ) {
-       jQuery.attrHooks.style = {
-               get: function( elem ) {
-                       // Return undefined in the case of empty string
-                       // Normalize to lowercase since IE uppercases css property names
-                       return elem.style.cssText.toLowerCase() || undefined;
-               },
-               set: function( elem, value ) {
-                       return ( elem.style.cssText = value + "" );
-               }
-       };
-}
-
-// Safari mis-reports the default selected property of an option
-// Accessing the parent's selectedIndex property fixes it
-if ( !jQuery.support.optSelected ) {
-       jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
-               get: function( elem ) {
-                       var parent = elem.parentNode;
-
-                       if ( parent ) {
-                               parent.selectedIndex;
-
-                               // Make sure that it also works with optgroups, see #5701
-                               if ( parent.parentNode ) {
-                                       parent.parentNode.selectedIndex;
-                               }
-                       }
-                       return null;
-               }
-       });
-}
-
-// IE6/7 call enctype encoding
-if ( !jQuery.support.enctype ) {
-       jQuery.propFix.enctype = "encoding";
-}
-
-// Radios and checkboxes getter/setter
-if ( !jQuery.support.checkOn ) {
-       jQuery.each([ "radio", "checkbox" ], function() {
-               jQuery.valHooks[ this ] = {
-                       get: function( elem ) {
-                               // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
-                               return elem.getAttribute("value") === null ? "on" : elem.value;
-                       }
-               };
-       });
-}
-jQuery.each([ "radio", "checkbox" ], function() {
-       jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
-               set: function( elem, value ) {
-                       if ( jQuery.isArray( value ) ) {
-                               return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
-                       }
-               }
-       });
-});
-var rformElems = /^(?:textarea|input|select)$/i,
-       rtypenamespace = /^([^\.]*|)(?:\.(.+)|)$/,
-       rhoverHack = /(?:^|\s)hover(\.\S+|)\b/,
-       rkeyEvent = /^key/,
-       rmouseEvent = /^(?:mouse|contextmenu)|click/,
-       rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
-       hoverHack = function( events ) {
-               return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
-       };
-
-/*
- * Helper functions for managing events -- not part of the public interface.
- * Props to Dean Edwards' addEvent library for many of the ideas.
- */
-jQuery.event = {
-
-       add: function( elem, types, handler, data, selector ) {
-
-               var elemData, eventHandle, events,
-                       t, tns, type, namespaces, handleObj,
-                       handleObjIn, handlers, special;
-
-               // Don't attach events to noData or text/comment nodes (allow plain objects tho)
-               if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
-                       return;
-               }
-
-               // Caller can pass in an object of custom data in lieu of the handler
-               if ( handler.handler ) {
-                       handleObjIn = handler;
-                       handler = handleObjIn.handler;
-                       selector = handleObjIn.selector;
-               }
-
-               // Make sure that the handler has a unique ID, used to find/remove it later
-               if ( !handler.guid ) {
-                       handler.guid = jQuery.guid++;
-               }
-
-               // Init the element's event structure and main handler, if this is the first
-               events = elemData.events;
-               if ( !events ) {
-                       elemData.events = events = {};
-               }
-               eventHandle = elemData.handle;
-               if ( !eventHandle ) {
-                       elemData.handle = eventHandle = function( e ) {
-                               // Discard the second event of a jQuery.event.trigger() and
-                               // when an event is called after a page has unloaded
-                               return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
-                                       jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
-                                       undefined;
-                       };
-                       // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
-                       eventHandle.elem = elem;
-               }
-
-               // Handle multiple events separated by a space
-               // jQuery(...).bind("mouseover mouseout", fn);
-               types = jQuery.trim( hoverHack(types) ).split( " " );
-               for ( t = 0; t < types.length; t++ ) {
-
-                       tns = rtypenamespace.exec( types[t] ) || [];
-                       type = tns[1];
-                       namespaces = ( tns[2] || "" ).split( "." ).sort();
-
-                       // If event changes its type, use the special event handlers for the changed type
-                       special = jQuery.event.special[ type ] || {};
-
-                       // If selector defined, determine special event api type, otherwise given type
-                       type = ( selector ? special.delegateType : special.bindType ) || type;
-
-                       // Update special based on newly reset type
-                       special = jQuery.event.special[ type ] || {};
-
-                       // handleObj is passed to all event handlers
-                       handleObj = jQuery.extend({
-                               type: type,
-                               origType: tns[1],
-                               data: data,
-                               handler: handler,
-                               guid: handler.guid,
-                               selector: selector,
-                               needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
-                               namespace: namespaces.join(".")
-                       }, handleObjIn );
-
-                       // Init the event handler queue if we're the first
-                       handlers = events[ type ];
-                       if ( !handlers ) {
-                               handlers = events[ type ] = [];
-                               handlers.delegateCount = 0;
-
-                               // Only use addEventListener/attachEvent if the special events handler returns false
-                               if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
-                                       // Bind the global event handler to the element
-                                       if ( elem.addEventListener ) {
-                                               elem.addEventListener( type, eventHandle, false );
-
-                                       } else if ( elem.attachEvent ) {
-                                               elem.attachEvent( "on" + type, eventHandle );
-                                       }
-                               }
-                       }
-
-                       if ( special.add ) {
-                               special.add.call( elem, handleObj );
-
-                               if ( !handleObj.handler.guid ) {
-                                       handleObj.handler.guid = handler.guid;
-                               }
-                       }
-
-                       // Add to the element's handler list, delegates in front
-                       if ( selector ) {
-                               handlers.splice( handlers.delegateCount++, 0, handleObj );
-                       } else {
-                               handlers.push( handleObj );
-                       }
-
-                       // Keep track of which events have ever been used, for event optimization
-                       jQuery.event.global[ type ] = true;
-               }
-
-               // Nullify elem to prevent memory leaks in IE
-               elem = null;
-       },
-
-       global: {},
-
-       // Detach an event or set of events from an element
-       remove: function( elem, types, handler, selector, mappedTypes ) {
-
-               var t, tns, type, origType, namespaces, origCount,
-                       j, events, special, eventType, handleObj,
-                       elemData = jQuery.hasData( elem ) && jQuery._data( elem );
-
-               if ( !elemData || !(events = elemData.events) ) {
-                       return;
-               }
-
-               // Once for each type.namespace in types; type may be omitted
-               types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
-               for ( t = 0; t < types.length; t++ ) {
-                       tns = rtypenamespace.exec( types[t] ) || [];
-                       type = origType = tns[1];
-                       namespaces = tns[2];
-
-                       // Unbind all events (on this namespace, if provided) for the element
-                       if ( !type ) {
-                               for ( type in events ) {
-                                       jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
-                               }
-                               continue;
-                       }
-
-                       special = jQuery.event.special[ type ] || {};
-                       type = ( selector? special.delegateType : special.bindType ) || type;
-                       eventType = events[ type ] || [];
-                       origCount = eventType.length;
-                       namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
-
-                       // Remove matching events
-                       for ( j = 0; j < eventType.length; j++ ) {
-                               handleObj = eventType[ j ];
-
-                               if ( ( mappedTypes || origType === handleObj.origType ) &&
-                                        ( !handler || handler.guid === handleObj.guid ) &&
-                                        ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
-                                        ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
-                                       eventType.splice( j--, 1 );
-
-                                       if ( handleObj.selector ) {
-                                               eventType.delegateCount--;
-                                       }
-                                       if ( special.remove ) {
-                                               special.remove.call( elem, handleObj );
-                                       }
-                               }
-                       }
-
-                       // Remove generic event handler if we removed something and no more handlers exist
-                       // (avoids potential for endless recursion during removal of special event handlers)
-                       if ( eventType.length === 0 && origCount !== eventType.length ) {
-                               if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
-                                       jQuery.removeEvent( elem, type, elemData.handle );
-                               }
-
-                               delete events[ type ];
-                       }
-               }
-
-               // Remove the expando if it's no longer used
-               if ( jQuery.isEmptyObject( events ) ) {
-                       delete elemData.handle;
-
-                       // removeData also checks for emptiness and clears the expando if empty
-                       // so use it instead of delete
-                       jQuery.removeData( elem, "events", true );
-               }
-       },
-
-       // Events that are safe to short-circuit if no handlers are attached.
-       // Native DOM events should not be added, they may have inline handlers.
-       customEvent: {
-               "getData": true,
-               "setData": true,
-               "changeData": true
-       },
-
-       trigger: function( event, data, elem, onlyHandlers ) {
-               // Don't do events on text and comment nodes
-               if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
-                       return;
-               }
-
-               // Event object or event type
-               var cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType,
-                       type = event.type || event,
-                       namespaces = [];
-
-               // focus/blur morphs to focusin/out; ensure we're not firing them right now
-               if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
-                       return;
-               }
-
-               if ( type.indexOf( "!" ) >= 0 ) {
-                       // Exclusive events trigger only for the exact event (no namespaces)
-                       type = type.slice(0, -1);
-                       exclusive = true;
-               }
-
-               if ( type.indexOf( "." ) >= 0 ) {
-                       // Namespaced trigger; create a regexp to match event type in handle()
-                       namespaces = type.split(".");
-                       type = namespaces.shift();
-                       namespaces.sort();
-               }
-
-               if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
-                       // No jQuery handlers for this event type, and it can't have inline handlers
-                       return;
-               }
-
-               // Caller can pass in an Event, Object, or just an event type string
-               event = typeof event === "object" ?
-                       // jQuery.Event object
-                       event[ jQuery.expando ] ? event :
-                       // Object literal
-                       new jQuery.Event( type, event ) :
-                       // Just the event type (string)
-                       new jQuery.Event( type );
-
-               event.type = type;
-               event.isTrigger = true;
-               event.exclusive = exclusive;
-               event.namespace = namespaces.join( "." );
-               event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
-               ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
-
-               // Handle a global trigger
-               if ( !elem ) {
-
-                       // TODO: Stop taunting the data cache; remove global events and always attach to document
-                       cache = jQuery.cache;
-                       for ( i in cache ) {
-                               if ( cache[ i ].events && cache[ i ].events[ type ] ) {
-                                       jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
-                               }
-                       }
-                       return;
-               }
-
-               // Clean up the event in case it is being reused
-               event.result = undefined;
-               if ( !event.target ) {
-                       event.target = elem;
-               }
-
-               // Clone any incoming data and prepend the event, creating the handler arg list
-               data = data != null ? jQuery.makeArray( data ) : [];
-               data.unshift( event );
-
-               // Allow special events to draw outside the lines
-               special = jQuery.event.special[ type ] || {};
-               if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
-                       return;
-               }
-
-               // Determine event propagation path in advance, per W3C events spec (#9951)
-               // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
-               eventPath = [[ elem, special.bindType || type ]];
-               if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
-
-                       bubbleType = special.delegateType || type;
-                       cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
-                       for ( old = elem; cur; cur = cur.parentNode ) {
-                               eventPath.push([ cur, bubbleType ]);
-                               old = cur;
-                       }
-
-                       // Only add window if we got to document (e.g., not plain obj or detached DOM)
-                       if ( old === (elem.ownerDocument || document) ) {
-                               eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
-                       }
-               }
-
-               // Fire handlers on the event path
-               for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
-
-                       cur = eventPath[i][0];
-                       event.type = eventPath[i][1];
-
-                       handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
-                       if ( handle ) {
-                               handle.apply( cur, data );
-                       }
-                       // Note that this is a bare JS function and not a jQuery handler
-                       handle = ontype && cur[ ontype ];
-                       if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
-                               event.preventDefault();
-                       }
-               }
-               event.type = type;
-
-               // If nobody prevented the default action, do it now
-               if ( !onlyHandlers && !event.isDefaultPrevented() ) {
-
-                       if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
-                               !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
-
-                               // Call a native DOM method on the target with the same name name as the event.
-                               // Can't use an .isFunction() check here because IE6/7 fails that test.
-                               // Don't do default actions on window, that's where global variables be (#6170)
-                               // IE<9 dies on focus/blur to hidden element (#1486)
-                               if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {
-
-                                       // Don't re-trigger an onFOO event when we call its FOO() method
-                                       old = elem[ ontype ];
-
-                                       if ( old ) {
-                                               elem[ ontype ] = null;
-                                       }
-
-                                       // Prevent re-triggering of the same event, since we already bubbled it above
-                                       jQuery.event.triggered = type;
-                                       elem[ type ]();
-                                       jQuery.event.triggered = undefined;
-
-                                       if ( old ) {
-                                               elem[ ontype ] = old;
-                                       }
-                               }
-                       }
-               }
-
-               return event.result;
-       },
-
-       dispatch: function( event ) {
-
-               // Make a writable jQuery.Event from the native event object
-               event = jQuery.event.fix( event || window.event );
-
-               var i, j, cur, ret, selMatch, matched, matches, handleObj, sel, related,
-                       handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
-                       delegateCount = handlers.delegateCount,
-                       args = core_slice.call( arguments ),
-                       run_all = !event.exclusive && !event.namespace,
-                       special = jQuery.event.special[ event.type ] || {},
-                       handlerQueue = [];
-
-               // Use the fix-ed jQuery.Event rather than the (read-only) native event
-               args[0] = event;
-               event.delegateTarget = this;
-
-               // Call the preDispatch hook for the mapped type, and let it bail if desired
-               if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
-                       return;
-               }
-
-               // Determine handlers that should run if there are delegated events
-               // Avoid non-left-click bubbling in Firefox (#3861)
-               if ( delegateCount && !(event.button && event.type === "click") ) {
-
-                       for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
-
-                               // Don't process clicks (ONLY) on disabled elements (#6911, #8165, #11382, #11764)
-                               if ( cur.disabled !== true || event.type !== "click" ) {
-                                       selMatch = {};
-                                       matches = [];
-                                       for ( i = 0; i < delegateCount; i++ ) {
-                                               handleObj = handlers[ i ];
-                                               sel = handleObj.selector;
-
-                                               if ( selMatch[ sel ] === undefined ) {
-                                                       selMatch[ sel ] = handleObj.needsContext ?
-                                                               jQuery( sel, this ).index( cur ) >= 0 :
-                                                               jQuery.find( sel, this, null, [ cur ] ).length;
-                                               }
-                                               if ( selMatch[ sel ] ) {
-                                                       matches.push( handleObj );
-                                               }
-                                       }
-                                       if ( matches.length ) {
-                                               handlerQueue.push({ elem: cur, matches: matches });
-                                       }
-                               }
-                       }
-               }
-
-               // Add the remaining (directly-bound) handlers
-               if ( handlers.length > delegateCount ) {
-                       handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
-               }
-
-               // Run delegates first; they may want to stop propagation beneath us
-               for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
-                       matched = handlerQueue[ i ];
-                       event.currentTarget = matched.elem;
-
-                       for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
-                               handleObj = matched.matches[ j ];
-
-                               // Triggered event must either 1) be non-exclusive and have no namespace, or
-                               // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
-                               if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
-
-                                       event.data = handleObj.data;
-                                       event.handleObj = handleObj;
-
-                                       ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
-                                                       .apply( matched.elem, args );
-
-                                       if ( ret !== undefined ) {
-                                               event.result = ret;
-                                               if ( ret === false ) {
-                                                       event.preventDefault();
-                                                       event.stopPropagation();
-                                               }
-                                       }
-                               }
-                       }
-               }
-
-               // Call the postDispatch hook for the mapped type
-               if ( special.postDispatch ) {
-                       special.postDispatch.call( this, event );
-               }
-
-               return event.result;
-       },
-
-       // Includes some event props shared by KeyEvent and MouseEvent
-       // *** attrChange attrName relatedNode srcElement  are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
-       props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
-
-       fixHooks: {},
-
-       keyHooks: {
-               props: "char charCode key keyCode".split(" "),
-               filter: function( event, original ) {
-
-                       // Add which for key events
-                       if ( event.which == null ) {
-                               event.which = original.charCode != null ? original.charCode : original.keyCode;
-                       }
-
-                       return event;
-               }
-       },
-
-       mouseHooks: {
-               props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
-               filter: function( event, original ) {
-                       var eventDoc, doc, body,
-                               button = original.button,
-                               fromElement = original.fromElement;
-
-                       // Calculate pageX/Y if missing and clientX/Y available
-                       if ( event.pageX == null && original.clientX != null ) {
-                               eventDoc = event.target.ownerDocument || document;
-                               doc = eventDoc.documentElement;
-                               body = eventDoc.body;
-
-                               event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
-                               event.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );
-                       }
-
-                       // Add relatedTarget, if necessary
-                       if ( !event.relatedTarget && fromElement ) {
-                               event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
-                       }
-
-                       // Add which for click: 1 === left; 2 === middle; 3 === right
-                       // Note: button is not normalized, so don't use it
-                       if ( !event.which && button !== undefined ) {
-                               event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
-                       }
-
-                       return event;
-               }
-       },
-
-       fix: function( event ) {
-               if ( event[ jQuery.expando ] ) {
-                       return event;
-               }
-
-               // Create a writable copy of the event object and normalize some properties
-               var i, prop,
-                       originalEvent = event,
-                       fixHook = jQuery.event.fixHooks[ event.type ] || {},
-                       copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
-
-               event = jQuery.Event( originalEvent );
-
-               for ( i = copy.length; i; ) {
-                       prop = copy[ --i ];
-                       event[ prop ] = originalEvent[ prop ];
-               }
-
-               // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
-               if ( !event.target ) {
-                       event.target = originalEvent.srcElement || document;
-               }
-
-               // Target should not be a text node (#504, Safari)
-               if ( event.target.nodeType === 3 ) {
-                       event.target = event.target.parentNode;
-               }
-
-               // For mouse/key events, metaKey==false if it's undefined (#3368, #11328; IE6/7/8)
-               event.metaKey = !!event.metaKey;
-
-               return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
-       },
-
-       special: {
-               load: {
-                       // Prevent triggered image.load events from bubbling to window.load
-                       noBubble: true
-               },
-
-               focus: {
-                       delegateType: "focusin"
-               },
-               blur: {
-                       delegateType: "focusout"
-               },
-
-               beforeunload: {
-                       setup: function( data, namespaces, eventHandle ) {
-                               // We only want to do this special case on windows
-                               if ( jQuery.isWindow( this ) ) {
-                                       this.onbeforeunload = eventHandle;
-                               }
-                       },
-
-                       teardown: function( namespaces, eventHandle ) {
-                               if ( this.onbeforeunload === eventHandle ) {
-                                       this.onbeforeunload = null;
-                               }
-                       }
-               }
-       },
-
-       simulate: function( type, elem, event, bubble ) {
-               // Piggyback on a donor event to simulate a different one.
-               // Fake originalEvent to avoid donor's stopPropagation, but if the
-               // simulated event prevents default then we do the same on the donor.
-               var e = jQuery.extend(
-                       new jQuery.Event(),
-                       event,
-                       { type: type,
-                               isSimulated: true,
-                               originalEvent: {}
-                       }
-               );
-               if ( bubble ) {
-                       jQuery.event.trigger( e, null, elem );
-               } else {
-                       jQuery.event.dispatch.call( elem, e );
-               }
-               if ( e.isDefaultPrevented() ) {
-                       event.preventDefault();
-               }
-       }
-};
-
-// Some plugins are using, but it's undocumented/deprecated and will be removed.
-// The 1.7 special event interface should provide all the hooks needed now.
-jQuery.event.handle = jQuery.event.dispatch;
-
-jQuery.removeEvent = document.removeEventListener ?
-       function( elem, type, handle ) {
-               if ( elem.removeEventListener ) {
-                       elem.removeEventListener( type, handle, false );
-               }
-       } :
-       function( elem, type, handle ) {
-               var name = "on" + type;
-
-               if ( elem.detachEvent ) {
-
-                       // #8545, #7054, preventing memory leaks for custom events in IE6-8
-                       // detachEvent needed property on element, by name of that event, to properly expose it to GC
-                       if ( typeof elem[ name ] === "undefined" ) {
-                               elem[ name ] = null;
-                       }
-
-                       elem.detachEvent( name, handle );
-               }
-       };
-
-jQuery.Event = function( src, props ) {
-       // Allow instantiation without the 'new' keyword
-       if ( !(this instanceof jQuery.Event) ) {
-               return new jQuery.Event( src, props );
-       }
-
-       // Event object
-       if ( src && src.type ) {
-               this.originalEvent = src;
-               this.type = src.type;
-
-               // Events bubbling up the document may have been marked as prevented
-               // by a handler lower down the tree; reflect the correct value.
-               this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
-                       src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
-
-       // Event type
-       } else {
-               this.type = src;
-       }
-
-       // Put explicitly provided properties onto the event object
-       if ( props ) {
-               jQuery.extend( this, props );
-       }
-
-       // Create a timestamp if incoming event doesn't have one
-       this.timeStamp = src && src.timeStamp || jQuery.now();
-
-       // Mark it as fixed
-       this[ jQuery.expando ] = true;
-};
-
-function returnFalse() {
-       return false;
-}
-function returnTrue() {
-       return true;
-}
-
-// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
-// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
-jQuery.Event.prototype = {
-       preventDefault: function() {
-               this.isDefaultPrevented = returnTrue;
-
-               var e = this.originalEvent;
-               if ( !e ) {
-                       return;
-               }
-
-               // if preventDefault exists run it on the original event
-               if ( e.preventDefault ) {
-                       e.preventDefault();
-
-               // otherwise set the returnValue property of the original event to false (IE)
-               } else {
-                       e.returnValue = false;
-               }
-       },
-       stopPropagation: function() {
-               this.isPropagationStopped = returnTrue;
-
-               var e = this.originalEvent;
-               if ( !e ) {
-                       return;
-               }
-               // if stopPropagation exists run it on the original event
-               if ( e.stopPropagation ) {
-                       e.stopPropagation();
-               }
-               // otherwise set the cancelBubble property of the original event to true (IE)
-               e.cancelBubble = true;
-       },
-       stopImmediatePropagation: function() {
-               this.isImmediatePropagationStopped = returnTrue;
-               this.stopPropagation();
-       },
-       isDefaultPrevented: returnFalse,
-       isPropagationStopped: returnFalse,
-       isImmediatePropagationStopped: returnFalse
-};
-
-// Create mouseenter/leave events using mouseover/out and event-time checks
-jQuery.each({
-       mouseenter: "mouseover",
-       mouseleave: "mouseout"
-}, function( orig, fix ) {
-       jQuery.event.special[ orig ] = {
-               delegateType: fix,
-               bindType: fix,
-
-               handle: function( event ) {
-                       var ret,
-                               target = this,
-                               related = event.relatedTarget,
-                               handleObj = event.handleObj,
-                               selector = handleObj.selector;
-
-                       // For mousenter/leave call the handler if related is outside the target.
-                       // NB: No relatedTarget if the mouse left/entered the browser window
-                       if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
-                               event.type = handleObj.origType;
-                               ret = handleObj.handler.apply( this, arguments );
-                               event.type = fix;
-                       }
-                       return ret;
-               }
-       };
-});
-
-// IE submit delegation
-if ( !jQuery.support.submitBubbles ) {
-
-       jQuery.event.special.submit = {
-               setup: function() {
-                       // Only need this for delegated form submit events
-                       if ( jQuery.nodeName( this, "form" ) ) {
-                               return false;
-                       }
-
-                       // Lazy-add a submit handler when a descendant form may potentially be submitted
-                       jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
-                               // Node name check avoids a VML-related crash in IE (#9807)
-                               var elem = e.target,
-                                       form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
-                               if ( form && !jQuery._data( form, "_submit_attached" ) ) {
-                                       jQuery.event.add( form, "submit._submit", function( event ) {
-                                               event._submit_bubble = true;
-                                       });
-                                       jQuery._data( form, "_submit_attached", true );
-                               }
-                       });
-                       // return undefined since we don't need an event listener
-               },
-
-               postDispatch: function( event ) {
-                       // If form was submitted by the user, bubble the event up the tree
-                       if ( event._submit_bubble ) {
-                               delete event._submit_bubble;
-                               if ( this.parentNode && !event.isTrigger ) {
-                                       jQuery.event.simulate( "submit", this.parentNode, event, true );
-                               }
-                       }
-               },
-
-               teardown: function() {
-                       // Only need this for delegated form submit events
-                       if ( jQuery.nodeName( this, "form" ) ) {
-                               return false;
-                       }
-
-                       // Remove delegated handlers; cleanData eventually reaps submit handlers attached above
-                       jQuery.event.remove( this, "._submit" );
-               }
-       };
-}
-
-// IE change delegation and checkbox/radio fix
-if ( !jQuery.support.changeBubbles ) {
-
-       jQuery.event.special.change = {
-
-               setup: function() {
-
-                       if ( rformElems.test( this.nodeName ) ) {
-                               // IE doesn't fire change on a check/radio until blur; trigger it on click
-                               // after a propertychange. Eat the blur-change in special.change.handle.
-                               // This still fires onchange a second time for check/radio after blur.
-                               if ( this.type === "checkbox" || this.type === "radio" ) {
-                                       jQuery.event.add( this, "propertychange._change", function( event ) {
-                                               if ( event.originalEvent.propertyName === "checked" ) {
-                                                       this._just_changed = true;
-                                               }
-                                       });
-                                       jQuery.event.add( this, "click._change", function( event ) {
-                                               if ( this._just_changed && !event.isTrigger ) {
-                                                       this._just_changed = false;
-                                               }
-                                               // Allow triggered, simulated change events (#11500)
-                                               jQuery.event.simulate( "change", this, event, true );
-                                       });
-                               }
-                               return false;
-                       }
-                       // Delegated event; lazy-add a change handler on descendant inputs
-                       jQuery.event.add( this, "beforeactivate._change", function( e ) {
-                               var elem = e.target;
-
-                               if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "_change_attached" ) ) {
-                                       jQuery.event.add( elem, "change._change", function( event ) {
-                                               if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
-                                                       jQuery.event.simulate( "change", this.parentNode, event, true );
-                                               }
-                                       });
-                                       jQuery._data( elem, "_change_attached", true );
-                               }
-                       });
-               },
-
-               handle: function( event ) {
-                       var elem = event.target;
-
-                       // Swallow native change events from checkbox/radio, we already triggered them above
-                       if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
-                               return event.handleObj.handler.apply( this, arguments );
-                       }
-               },
-
-               teardown: function() {
-                       jQuery.event.remove( this, "._change" );
-
-                       return !rformElems.test( this.nodeName );
-               }
-       };
-}
-
-// Create "bubbling" focus and blur events
-if ( !jQuery.support.focusinBubbles ) {
-       jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
-
-               // Attach a single capturing handler while someone wants focusin/focusout
-               var attaches = 0,
-                       handler = function( event ) {
-                               jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
-                       };
-
-               jQuery.event.special[ fix ] = {
-                       setup: function() {
-                               if ( attaches++ === 0 ) {
-                                       document.addEventListener( orig, handler, true );
-                               }
-                       },
-                       teardown: function() {
-                               if ( --attaches === 0 ) {
-                                       document.removeEventListener( orig, handler, true );
-                               }
-                       }
-               };
-       });
-}
-
-jQuery.fn.extend({
-
-       on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
-               var origFn, type;
-
-               // Types can be a map of types/handlers
-               if ( typeof types === "object" ) {
-                       // ( types-Object, selector, data )
-                       if ( typeof selector !== "string" ) { // && selector != null
-                               // ( types-Object, data )
-                               data = data || selector;
-                               selector = undefined;
-                       }
-                       for ( type in types ) {
-                               this.on( type, selector, data, types[ type ], one );
-                       }
-                       return this;
-               }
-
-               if ( data == null && fn == null ) {
-                       // ( types, fn )
-                       fn = selector;
-                       data = selector = undefined;
-               } else if ( fn == null ) {
-                       if ( typeof selector === "string" ) {
-                               // ( types, selector, fn )
-                               fn = data;
-                               data = undefined;
-                       } else {
-                               // ( types, data, fn )
-                               fn = data;
-                               data = selector;
-                               selector = undefined;
-                       }
-               }
-               if ( fn === false ) {
-                       fn = returnFalse;
-               } else if ( !fn ) {
-                       return this;
-               }
-
-               if ( one === 1 ) {
-                       origFn = fn;
-                       fn = function( event ) {
-                               // Can use an empty set, since event contains the info
-                               jQuery().off( event );
-                               return origFn.apply( this, arguments );
-                       };
-                       // Use same guid so caller can remove using origFn
-                       fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
-               }
-               return this.each( function() {
-                       jQuery.event.add( this, types, fn, data, selector );
-               });
-       },
-       one: function( types, selector, data, fn ) {
-               return this.on( types, selector, data, fn, 1 );
-       },
-       off: function( types, selector, fn ) {
-               var handleObj, type;
-               if ( types && types.preventDefault && types.handleObj ) {
-                       // ( event )  dispatched jQuery.Event
-                       handleObj = types.handleObj;
-                       jQuery( types.delegateTarget ).off(
-                               handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
-                               handleObj.selector,
-                               handleObj.handler
-                       );
-                       return this;
-               }
-               if ( typeof types === "object" ) {
-                       // ( types-object [, selector] )
-                       for ( type in types ) {
-                               this.off( type, selector, types[ type ] );
-                       }
-                       return this;
-               }
-               if ( selector === false || typeof selector === "function" ) {
-                       // ( types [, fn] )
-                       fn = selector;
-                       selector = undefined;
-               }
-               if ( fn === false ) {
-                       fn = returnFalse;
-               }
-               return this.each(function() {
-                       jQuery.event.remove( this, types, fn, selector );
-               });
-       },
-
-       bind: function( types, data, fn ) {
-               return this.on( types, null, data, fn );
-       },
-       unbind: function( types, fn ) {
-               return this.off( types, null, fn );
-       },
-
-       live: function( types, data, fn ) {
-               jQuery( this.context ).on( types, this.selector, data, fn );
-               return this;
-       },
-       die: function( types, fn ) {
-               jQuery( this.context ).off( types, this.selector || "**", fn );
-               return this;
-       },
-
-       delegate: function( selector, types, data, fn ) {
-               return this.on( types, selector, data, fn );
-       },
-       undelegate: function( selector, types, fn ) {
-               // ( namespace ) or ( selector, types [, fn] )
-               return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
-       },
-
-       trigger: function( type, data ) {
-               return this.each(function() {
-                       jQuery.event.trigger( type, data, this );
-               });
-       },
-       triggerHandler: function( type, data ) {
-               if ( this[0] ) {
-                       return jQuery.event.trigger( type, data, this[0], true );
-               }
-       },
-
-       toggle: function( fn ) {
-               // Save reference to arguments for access in closure
-               var args = arguments,
-                       guid = fn.guid || jQuery.guid++,
-                       i = 0,
-                       toggler = function( event ) {
-                               // Figure out which function to execute
-                               var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
-                               jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
-
-                               // Make sure that clicks stop
-                               event.preventDefault();
-
-                               // and execute the function
-                               return args[ lastToggle ].apply( this, arguments ) || false;
-                       };
-
-               // link all the functions, so any of them can unbind this click handler
-               toggler.guid = guid;
-               while ( i < args.length ) {
-                       args[ i++ ].guid = guid;
-               }
-
-               return this.click( toggler );
-       },
-
-       hover: function( fnOver, fnOut ) {
-               return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
-       }
-});
-
-jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
-       "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
-       "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
-
-       // Handle event binding
-       jQuery.fn[ name ] = function( data, fn ) {
-               if ( fn == null ) {
-                       fn = data;
-                       data = null;
-               }
-
-               return arguments.length > 0 ?
-                       this.on( name, null, data, fn ) :
-                       this.trigger( name );
-       };
-
-       if ( rkeyEvent.test( name ) ) {
-               jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
-       }
-
-       if ( rmouseEvent.test( name ) ) {
-               jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
-       }
-});
-/*!
- * Sizzle CSS Selector Engine
- * Copyright 2012 jQuery Foundation and other contributors
- * Released under the MIT license
- * http://sizzlejs.com/
- */
-(function( window, undefined ) {
-
-var cachedruns,
-       assertGetIdNotName,
-       Expr,
-       getText,
-       isXML,
-       contains,
-       compile,
-       sortOrder,
-       hasDuplicate,
-       outermostContext,
-
-       baseHasDuplicate = true,
-       strundefined = "undefined",
-
-       expando = ( "sizcache" + Math.random() ).replace( ".", "" ),
-
-       Token = String,
-       document = window.document,
-       docElem = document.documentElement,
-       dirruns = 0,
-       done = 0,
-       pop = [].pop,
-       push = [].push,
-       slice = [].slice,
-       // Use a stripped-down indexOf if a native one is unavailable
-       indexOf = [].indexOf || function( elem ) {
-               var i = 0,
-                       len = this.length;
-               for ( ; i < len; i++ ) {
-                       if ( this[i] === elem ) {
-                               return i;
-                       }
-               }
-               return -1;
-       },
-
-       // Augment a function for special use by Sizzle
-       markFunction = function( fn, value ) {
-               fn[ expando ] = value == null || value;
-               return fn;
-       },
-
-       createCache = function() {
-               var cache = {},
-                       keys = [];
-
-               return markFunction(function( key, value ) {
-                       // Only keep the most recent entries
-                       if ( keys.push( key ) > Expr.cacheLength ) {
-                               delete cache[ keys.shift() ];
-                       }
-
-                       // Retrieve with (key + " ") to avoid collision with native Object.prototype properties (see Issue #157)
-                       return (cache[ key + " " ] = value);
-               }, cache );
-       },
-
-       classCache = createCache(),
-       tokenCache = createCache(),
-       compilerCache = createCache(),
-
-       // Regex
-
-       // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
-       whitespace = "[\\x20\\t\\r\\n\\f]",
-       // http://www.w3.org/TR/css3-syntax/#characters
-       characterEncoding = "(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",
-
-       // Loosely modeled on CSS identifier characters
-       // An unquoted value should be a CSS identifier (http://www.w3.org/TR/css3-selectors/#attribute-selectors)
-       // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
-       identifier = characterEncoding.replace( "w", "w#" ),
-
-       // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
-       operators = "([*^$|!~]?=)",
-       attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
-               "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
-
-       // Prefer arguments not in parens/brackets,
-       //   then attribute selectors and non-pseudos (denoted by :),
-       //   then anything else
-       // These preferences are here to reduce the number of selectors
-       //   needing tokenize in the PSEUDO preFilter
-       pseudos = ":(" + characterEncoding + ")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:" + attributes + ")|[^:]|\\\\.)*|.*))\\)|)",
-
-       // For matchExpr.POS and matchExpr.needsContext
-       pos = ":(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +
-               "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)",
-
-       // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
-       rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
-
-       rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
-       rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ),
-       rpseudo = new RegExp( pseudos ),
-
-       // Easily-parseable/retrievable ID or TAG or CLASS selectors
-       rquickExpr = /^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,
-
-       rnot = /^:not/,
-       rsibling = /[\x20\t\r\n\f]*[+~]/,
-       rendsWithNot = /:not\($/,
-
-       rheader = /h\d/i,
-       rinputs = /input|select|textarea|button/i,
-
-       rbackslash = /\\(?!\\)/g,
-
-       matchExpr = {
-               "ID": new RegExp( "^#(" + characterEncoding + ")" ),
-               "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
-               "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ),
-               "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
-               "ATTR": new RegExp( "^" + attributes ),
-               "PSEUDO": new RegExp( "^" + pseudos ),
-               "POS": new RegExp( pos, "i" ),
-               "CHILD": new RegExp( "^:(only|nth|first|last)-child(?:\\(" + whitespace +
-                       "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
-                       "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
-               // For use in libraries implementing .is()
-               "needsContext": new RegExp( "^" + whitespace + "*[>+~]|" + pos, "i" )
-       },
-
-       // Support
-
-       // Used for testing something on an element
-       assert = function( fn ) {
-               var div = document.createElement("div");
-
-               try {
-                       return fn( div );
-               } catch (e) {
-                       return false;
-               } finally {
-                       // release memory in IE
-                       div = null;
-               }
-       },
-
-       // Check if getElementsByTagName("*") returns only elements
-       assertTagNameNoComments = assert(function( div ) {
-               div.appendChild( document.createComment("") );
-               return !div.getElementsByTagName("*").length;
-       }),
-
-       // Check if getAttribute returns normalized href attributes
-       assertHrefNotNormalized = assert(function( div ) {
-               div.innerHTML = "<a href='#'></a>";
-               return div.firstChild && typeof div.firstChild.getAttribute !== strundefined &&
-                       div.firstChild.getAttribute("href") === "#";
-       }),
-
-       // Check if attributes should be retrieved by attribute nodes
-       assertAttributes = assert(function( div ) {
-               div.innerHTML = "<select></select>";
-               var type = typeof div.lastChild.getAttribute("multiple");
-               // IE8 returns a string for some attributes even when not present
-               return type !== "boolean" && type !== "string";
-       }),
-
-       // Check if getElementsByClassName can be trusted
-       assertUsableClassName = assert(function( div ) {
-               // Opera can't find a second classname (in 9.6)
-               div.innerHTML = "<div class='hidden e'></div><div class='hidden'></div>";
-               if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) {
-                       return false;
-               }
-
-               // Safari 3.2 caches class attributes and doesn't catch changes
-               div.lastChild.className = "e";
-               return div.getElementsByClassName("e").length === 2;
-       }),
-
-       // Check if getElementById returns elements by name
-       // Check if getElementsByName privileges form controls or returns elements by ID
-       assertUsableName = assert(function( div ) {
-               // Inject content
-               div.id = expando + 0;
-               div.innerHTML = "<a name='" + expando + "'></a><div name='" + expando + "'></div>";
-               docElem.insertBefore( div, docElem.firstChild );
-
-               // Test
-               var pass = document.getElementsByName &&
-                       // buggy browsers will return fewer than the correct 2
-                       document.getElementsByName( expando ).length === 2 +
-                       // buggy browsers will return more than the correct 0
-                       document.getElementsByName( expando + 0 ).length;
-               assertGetIdNotName = !document.getElementById( expando );
-
-               // Cleanup
-               docElem.removeChild( div );
-
-               return pass;
-       });
-
-// If slice is not available, provide a backup
-try {
-       slice.call( docElem.childNodes, 0 )[0].nodeType;
-} catch ( e ) {
-       slice = function( i ) {
-               var elem,
-                       results = [];
-               for ( ; (elem = this[i]); i++ ) {
-                       results.push( elem );
-               }
-               return results;
-       };
-}
-
-function Sizzle( selector, context, results, seed ) {
-       results = results || [];
-       context = context || document;
-       var match, elem, xml, m,
-               nodeType = context.nodeType;
-
-       if ( !selector || typeof selector !== "string" ) {
-               return results;
-       }
-
-       if ( nodeType !== 1 && nodeType !== 9 ) {
-               return [];
-       }
-
-       xml = isXML( context );
-
-       if ( !xml && !seed ) {
-               if ( (match = rquickExpr.exec( selector )) ) {
-                       // Speed-up: Sizzle("#ID")
-                       if ( (m = match[1]) ) {
-                               if ( nodeType === 9 ) {
-                                       elem = context.getElementById( m );
-                                       // Check parentNode to catch when Blackberry 4.6 returns
-                                       // nodes that are no longer in the document #6963
-                                       if ( elem && elem.parentNode ) {
-                                               // Handle the case where IE, Opera, and Webkit return items
-                                               // by name instead of ID
-                                               if ( elem.id === m ) {
-                                                       results.push( elem );
-                                                       return results;
-                                               }
-                                       } else {
-                                               return results;
-                                       }
-                               } else {
-                                       // Context is not a document
-                                       if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
-                                               contains( context, elem ) && elem.id === m ) {
-                                               results.push( elem );
-                                               return results;
-                                       }
-                               }
-
-                       // Speed-up: Sizzle("TAG")
-                       } else if ( match[2] ) {
-                               push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) );
-                               return results;
-
-                       // Speed-up: Sizzle(".CLASS")
-                       } else if ( (m = match[3]) && assertUsableClassName && context.getElementsByClassName ) {
-                               push.apply( results, slice.call(context.getElementsByClassName( m ), 0) );
-                               return results;
-                       }
-               }
-       }
-
-       // All others
-       return select( selector.replace( rtrim, "$1" ), context, results, seed, xml );
-}
-
-Sizzle.matches = function( expr, elements ) {
-       return Sizzle( expr, null, null, elements );
-};
-
-Sizzle.matchesSelector = function( elem, expr ) {
-       return Sizzle( expr, null, null, [ elem ] ).length > 0;
-};
-
-// Returns a function to use in pseudos for input types
-function createInputPseudo( type ) {
-       return function( elem ) {
-               var name = elem.nodeName.toLowerCase();
-               return name === "input" && elem.type === type;
-       };
-}
-
-// Returns a function to use in pseudos for buttons
-function createButtonPseudo( type ) {
-       return function( elem ) {
-               var name = elem.nodeName.toLowerCase();
-               return (name === "input" || name === "button") && elem.type === type;
-       };
-}
-
-// Returns a function to use in pseudos for positionals
-function createPositionalPseudo( fn ) {
-       return markFunction(function( argument ) {
-               argument = +argument;
-               return markFunction(function( seed, matches ) {
-                       var j,
-                               matchIndexes = fn( [], seed.length, argument ),
-                               i = matchIndexes.length;
-
-                       // Match elements found at the specified indexes
-                       while ( i-- ) {
-                               if ( seed[ (j = matchIndexes[i]) ] ) {
-                                       seed[j] = !(matches[j] = seed[j]);
-                               }
-                       }
-               });
-       });
-}
-
-/**
- * Utility function for retrieving the text value of an array of DOM nodes
- * @param {Array|Element} elem
- */
-getText = Sizzle.getText = function( elem ) {
-       var node,
-               ret = "",
-               i = 0,
-               nodeType = elem.nodeType;
-
-       if ( nodeType ) {
-               if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
-                       // Use textContent for elements
-                       // innerText usage removed for consistency of new lines (see #11153)
-                       if ( typeof elem.textContent === "string" ) {
-                               return elem.textContent;
-                       } else {
-                               // Traverse its children
-                               for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
-                                       ret += getText( elem );
-                               }
-                       }
-               } else if ( nodeType === 3 || nodeType === 4 ) {
-                       return elem.nodeValue;
-               }
-               // Do not include comment or processing instruction nodes
-       } else {
-
-               // If no nodeType, this is expected to be an array
-               for ( ; (node = elem[i]); i++ ) {
-                       // Do not traverse comment nodes
-                       ret += getText( node );
-               }
-       }
-       return ret;
-};
-
-isXML = Sizzle.isXML = function( elem ) {
-       // documentElement is verified for cases where it doesn't yet exist
-       // (such as loading iframes in IE - #4833)
-       var documentElement = elem && (elem.ownerDocument || elem).documentElement;
-       return documentElement ? documentElement.nodeName !== "HTML" : false;
-};
-
-// Element contains another
-contains = Sizzle.contains = docElem.contains ?
-       function( a, b ) {
-               var adown = a.nodeType === 9 ? a.documentElement : a,
-                       bup = b && b.parentNode;
-               return a === bup || !!( bup && bup.nodeType === 1 && adown.contains && adown.contains(bup) );
-       } :
-       docElem.compareDocumentPosition ?
-       function( a, b ) {
-               return b && !!( a.compareDocumentPosition( b ) & 16 );
-       } :
-       function( a, b ) {
-               while ( (b = b.parentNode) ) {
-                       if ( b === a ) {
-                               return true;
-                       }
-               }
-               return false;
-       };
-
-Sizzle.attr = function( elem, name ) {
-       var val,
-               xml = isXML( elem );
-
-       if ( !xml ) {
-               name = name.toLowerCase();
-       }
-       if ( (val = Expr.attrHandle[ name ]) ) {
-               return val( elem );
-       }
-       if ( xml || assertAttributes ) {
-               return elem.getAttribute( name );
-       }
-       val = elem.getAttributeNode( name );
-       return val ?
-               typeof elem[ name ] === "boolean" ?
-                       elem[ name ] ? name : null :
-                       val.specified ? val.value : null :
-               null;
-};
-
-Expr = Sizzle.selectors = {
-
-       // Can be adjusted by the user
-       cacheLength: 50,
-
-       createPseudo: markFunction,
-
-       match: matchExpr,
-
-       // IE6/7 return a modified href
-       attrHandle: assertHrefNotNormalized ?
-               {} :
-               {
-                       "href": function( elem ) {
-                               return elem.getAttribute( "href", 2 );
-                       },
-                       "type": function( elem ) {
-                               return elem.getAttribute("type");
-                       }
-               },
-
-       find: {
-               "ID": assertGetIdNotName ?
-                       function( id, context, xml ) {
-                               if ( typeof context.getElementById !== strundefined && !xml ) {
-                                       var m = context.getElementById( id );
-                                       // Check parentNode to catch when Blackberry 4.6 returns
-                                       // nodes that are no longer in the document #6963
-                                       return m && m.parentNode ? [m] : [];
-                               }
-                       } :
-                       function( id, context, xml ) {
-                               if ( typeof context.getElementById !== strundefined && !xml ) {
-                                       var m = context.getElementById( id );
-
-                                       return m ?
-                                               m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ?
-                                                       [m] :
-                                                       undefined :
-                                               [];
-                               }
-                       },
-
-               "TAG": assertTagNameNoComments ?
-                       function( tag, context ) {
-                               if ( typeof context.getElementsByTagName !== strundefined ) {
-                                       return context.getElementsByTagName( tag );
-                               }
-                       } :
-                       function( tag, context ) {
-                               var results = context.getElementsByTagName( tag );
-
-                               // Filter out possible comments
-                               if ( tag === "*" ) {
-                                       var elem,
-                                               tmp = [],
-                                               i = 0;
-
-                                       for ( ; (elem = results[i]); i++ ) {
-                                               if ( elem.nodeType === 1 ) {
-                                                       tmp.push( elem );
-                                               }
-                                       }
-
-                                       return tmp;
-                               }
-                               return results;
-                       },
-
-               "NAME": assertUsableName && function( tag, context ) {
-                       if ( typeof context.getElementsByName !== strundefined ) {
-                               return context.getElementsByName( name );
-                       }
-               },
-
-               "CLASS": assertUsableClassName && function( className, context, xml ) {
-                       if ( typeof context.getElementsByClassName !== strundefined && !xml ) {
-                               return context.getElementsByClassName( className );
-                       }
-               }
-       },
-
-       relative: {
-               ">": { dir: "parentNode", first: true },
-               " ": { dir: "parentNode" },
-               "+": { dir: "previousSibling", first: true },
-               "~": { dir: "previousSibling" }
-       },
-
-       preFilter: {
-               "ATTR": function( match ) {
-                       match[1] = match[1].replace( rbackslash, "" );
-
-                       // Move the given value to match[3] whether quoted or unquoted
-                       match[3] = ( match[4] || match[5] || "" ).replace( rbackslash, "" );
-
-                       if ( match[2] === "~=" ) {
-                               match[3] = " " + match[3] + " ";
-                       }
-
-                       return match.slice( 0, 4 );
-               },
-
-               "CHILD": function( match ) {
-                       /* matches from matchExpr["CHILD"]
-                               1 type (only|nth|...)
-                               2 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
-                               3 xn-component of xn+y argument ([+-]?\d*n|)
-                               4 sign of xn-component
-                               5 x of xn-component
-                               6 sign of y-component
-                               7 y of y-component
-                       */
-                       match[1] = match[1].toLowerCase();
-
-                       if ( match[1] === "nth" ) {
-                               // nth-child requires argument
-                               if ( !match[2] ) {
-                                       Sizzle.error( match[0] );
-                               }
-
-                               // numeric x and y parameters for Expr.filter.CHILD
-                               // remember that false/true cast respectively to 0/1
-                               match[3] = +( match[3] ? match[4] + (match[5] || 1) : 2 * ( match[2] === "even" || match[2] === "odd" ) );
-                               match[4] = +( ( match[6] + match[7] ) || match[2] === "odd" );
-
-                       // other types prohibit arguments
-                       } else if ( match[2] ) {
-                               Sizzle.error( match[0] );
-                       }
-
-                       return match;
-               },
-
-               "PSEUDO": function( match ) {
-                       var unquoted, excess;
-                       if ( matchExpr["CHILD"].test( match[0] ) ) {
-                               return null;
-                       }
-
-                       if ( match[3] ) {
-                               match[2] = match[3];
-                       } else if ( (unquoted = match[4]) ) {
-                               // Only check arguments that contain a pseudo
-                               if ( rpseudo.test(unquoted) &&
-                                       // Get excess from tokenize (recursively)
-                                       (excess = tokenize( unquoted, true )) &&
-                                       // advance to the next closing parenthesis
-                                       (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
-
-                                       // excess is a negative index
-                                       unquoted = unquoted.slice( 0, excess );
-                                       match[0] = match[0].slice( 0, excess );
-                               }
-                               match[2] = unquoted;
-                       }
-
-                       // Return only captures needed by the pseudo filter method (type and argument)
-                       return match.slice( 0, 3 );
-               }
-       },
-
-       filter: {
-               "ID": assertGetIdNotName ?
-                       function( id ) {
-                               id = id.replace( rbackslash, "" );
-                               return function( elem ) {
-                                       return elem.getAttribute("id") === id;
-                               };
-                       } :
-                       function( id ) {
-                               id = id.replace( rbackslash, "" );
-                               return function( elem ) {
-                                       var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
-                                       return node && node.value === id;
-                               };
-                       },
-
-               "TAG": function( nodeName ) {
-                       if ( nodeName === "*" ) {
-                               return function() { return true; };
-                       }
-                       nodeName = nodeName.replace( rbackslash, "" ).toLowerCase();
-
-                       return function( elem ) {
-                               return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
-                       };
-               },
-
-               "CLASS": function( className ) {
-                       var pattern = classCache[ expando ][ className + " " ];
-
-                       return pattern ||
-                               (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
-                               classCache( className, function( elem ) {
-                                       return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" );
-                               });
-               },
-
-               "ATTR": function( name, operator, check ) {
-                       return function( elem, context ) {
-                               var result = Sizzle.attr( elem, name );
-
-                               if ( result == null ) {
-                                       return operator === "!=";
-                               }
-                               if ( !operator ) {
-                                       return true;
-                               }
-
-                               result += "";
-
-                               return operator === "=" ? result === check :
-                                       operator === "!=" ? result !== check :
-                                       operator === "^=" ? check && result.indexOf( check ) === 0 :
-                                       operator === "*=" ? check && result.indexOf( check ) > -1 :
-                                       operator === "$=" ? check && result.substr( result.length - check.length ) === check :
-                                       operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
-                                       operator === "|=" ? result === check || result.substr( 0, check.length + 1 ) === check + "-" :
-                                       false;
-                       };
-               },
-
-               "CHILD": function( type, argument, first, last ) {
-
-                       if ( type === "nth" ) {
-                               return function( elem ) {
-                                       var node, diff,
-                                               parent = elem.parentNode;
-
-                                       if ( first === 1 && last === 0 ) {
-                                               return true;
-                                       }
-
-                                       if ( parent ) {
-                                               diff = 0;
-                                               for ( node = parent.firstChild; node; node = node.nextSibling ) {
-                                                       if ( node.nodeType === 1 ) {
-                                                               diff++;
-                                                               if ( elem === node ) {
-                                                                       break;
-                                                               }
-                                                       }
-                                               }
-                                       }
-
-                                       // Incorporate the offset (or cast to NaN), then check against cycle size
-                                       diff -= last;
-                                       return diff === first || ( diff % first === 0 && diff / first >= 0 );
-                               };
-                       }
-
-                       return function( elem ) {
-                               var node = elem;
-
-                               switch ( type ) {
-                                       case "only":
-                                       case "first":
-                                               while ( (node = node.previousSibling) ) {
-                                                       if ( node.nodeType === 1 ) {
-                                                               return false;
-                                                       }
-                                               }
-
-                                               if ( type === "first" ) {
-                                                       return true;
-                                               }
-
-                                               node = elem;
-
-                                               /* falls through */
-                                       case "last":
-                                               while ( (node = node.nextSibling) ) {
-                                                       if ( node.nodeType === 1 ) {
-                                                               return false;
-                                                       }
-                                               }
-
-                                               return true;
-                               }
-                       };
-               },
-
-               "PSEUDO": function( pseudo, argument ) {
-                       // pseudo-class names are case-insensitive
-                       // http://www.w3.org/TR/selectors/#pseudo-classes
-                       // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
-                       // Remember that setFilters inherits from pseudos
-                       var args,
-                               fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
-                                       Sizzle.error( "unsupported pseudo: " + pseudo );
-
-                       // The user may use createPseudo to indicate that
-                       // arguments are needed to create the filter function
-                       // just as Sizzle does
-                       if ( fn[ expando ] ) {
-                               return fn( argument );
-                       }
-
-                       // But maintain support for old signatures
-                       if ( fn.length > 1 ) {
-                               args = [ pseudo, pseudo, "", argument ];
-                               return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
-                                       markFunction(function( seed, matches ) {
-                                               var idx,
-                                                       matched = fn( seed, argument ),
-                                                       i = matched.length;
-                                               while ( i-- ) {
-                                                       idx = indexOf.call( seed, matched[i] );
-                                                       seed[ idx ] = !( matches[ idx ] = matched[i] );
-                                               }
-                                       }) :
-                                       function( elem ) {
-                                               return fn( elem, 0, args );
-                                       };
-                       }
-
-                       return fn;
-               }
-       },
-
-       pseudos: {
-               "not": markFunction(function( selector ) {
-                       // Trim the selector passed to compile
-                       // to avoid treating leading and trailing
-                       // spaces as combinators
-                       var input = [],
-                               results = [],
-                               matcher = compile( selector.replace( rtrim, "$1" ) );
-
-                       return matcher[ expando ] ?
-                               markFunction(function( seed, matches, context, xml ) {
-                                       var elem,
-                                               unmatched = matcher( seed, null, xml, [] ),
-                                               i = seed.length;
-
-                                       // Match elements unmatched by `matcher`
-                                       while ( i-- ) {
-                                               if ( (elem = unmatched[i]) ) {
-                                                       seed[i] = !(matches[i] = elem);
-                                               }
-                                       }
-                               }) :
-                               function( elem, context, xml ) {
-                                       input[0] = elem;
-                                       matcher( input, null, xml, results );
-                                       return !results.pop();
-                               };
-               }),
-
-               "has": markFunction(function( selector ) {
-                       return function( elem ) {
-                               return Sizzle( selector, elem ).length > 0;
-                       };
-               }),
-
-               "contains": markFunction(function( text ) {
-                       return function( elem ) {
-                               return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
-                       };
-               }),
-
-               "enabled": function( elem ) {
-                       return elem.disabled === false;
-               },
-
-               "disabled": function( elem ) {
-                       return elem.disabled === true;
-               },
-
-               "checked": function( elem ) {
-                       // In CSS3, :checked should return both checked and selected elements
-                       // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
-                       var nodeName = elem.nodeName.toLowerCase();
-                       return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
-               },
-
-               "selected": function( elem ) {
-                       // Accessing this property makes selected-by-default
-                       // options in Safari work properly
-                       if ( elem.parentNode ) {
-                               elem.parentNode.selectedIndex;
-                       }
-
-                       return elem.selected === true;
-               },
-
-               "parent": function( elem ) {
-                       return !Expr.pseudos["empty"]( elem );
-               },
-
-               "empty": function( elem ) {
-                       // http://www.w3.org/TR/selectors/#empty-pseudo
-                       // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),
-                       //   not comment, processing instructions, or others
-                       // Thanks to Diego Perini for the nodeName shortcut
-                       //   Greater than "@" means alpha characters (specifically not starting with "#" or "?")
-                       var nodeType;
-                       elem = elem.firstChild;
-                       while ( elem ) {
-                               if ( elem.nodeName > "@" || (nodeType = elem.nodeType) === 3 || nodeType === 4 ) {
-                                       return false;
-                               }
-                               elem = elem.nextSibling;
-                       }
-                       return true;
-               },
-
-               "header": function( elem ) {
-                       return rheader.test( elem.nodeName );
-               },
-
-               "text": function( elem ) {
-                       var type, attr;
-                       // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
-                       // use getAttribute instead to test this case
-                       return elem.nodeName.toLowerCase() === "input" &&
-                               (type = elem.type) === "text" &&
-                               ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === type );
-               },
-
-               // Input types
-               "radio": createInputPseudo("radio"),
-               "checkbox": createInputPseudo("checkbox"),
-               "file": createInputPseudo("file"),
-               "password": createInputPseudo("password"),
-               "image": createInputPseudo("image"),
-
-               "submit": createButtonPseudo("submit"),
-               "reset": createButtonPseudo("reset"),
-
-               "button": function( elem ) {
-                       var name = elem.nodeName.toLowerCase();
-                       return name === "input" && elem.type === "button" || name === "button";
-               },
-
-               "input": function( elem ) {
-                       return rinputs.test( elem.nodeName );
-               },
-
-               "focus": function( elem ) {
-                       var doc = elem.ownerDocument;
-                       return elem === doc.activeElement && (!doc.hasFocus || doc.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
-               },
-
-               "active": function( elem ) {
-                       return elem === elem.ownerDocument.activeElement;
-               },
-
-               // Positional types
-               "first": createPositionalPseudo(function() {
-                       return [ 0 ];
-               }),
-
-               "last": createPositionalPseudo(function( matchIndexes, length ) {
-                       return [ length - 1 ];
-               }),
-
-               "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
-                       return [ argument < 0 ? argument + length : argument ];
-               }),
-
-               "even": createPositionalPseudo(function( matchIndexes, length ) {
-                       for ( var i = 0; i < length; i += 2 ) {
-                               matchIndexes.push( i );
-                       }
-                       return matchIndexes;
-               }),
-
-               "odd": createPositionalPseudo(function( matchIndexes, length ) {
-                       for ( var i = 1; i < length; i += 2 ) {
-                               matchIndexes.push( i );
-                       }
-                       return matchIndexes;
-               }),
-
-               "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
-                       for ( var i = argument < 0 ? argument + length : argument; --i >= 0; ) {
-                               matchIndexes.push( i );
-                       }
-                       return matchIndexes;
-               }),
-
-               "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
-                       for ( var i = argument < 0 ? argument + length : argument; ++i < length; ) {
-                               matchIndexes.push( i );
-                       }
-                       return matchIndexes;
-               })
-       }
-};
-
-function siblingCheck( a, b, ret ) {
-       if ( a === b ) {
-               return ret;
-       }
-
-       var cur = a.nextSibling;
-
-       while ( cur ) {
-               if ( cur === b ) {
-                       return -1;
-               }
-
-               cur = cur.nextSibling;
-       }
-
-       return 1;
-}
-
-sortOrder = docElem.compareDocumentPosition ?
-       function( a, b ) {
-               if ( a === b ) {
-                       hasDuplicate = true;
-                       return 0;
-               }
-
-               return ( !a.compareDocumentPosition || !b.compareDocumentPosition ?
-                       a.compareDocumentPosition :
-                       a.compareDocumentPosition(b) & 4
-               ) ? -1 : 1;
-       } :
-       function( a, b ) {
-               // The nodes are identical, we can exit early
-               if ( a === b ) {
-                       hasDuplicate = true;
-                       return 0;
-
-               // Fallback to using sourceIndex (in IE) if it's available on both nodes
-               } else if ( a.sourceIndex && b.sourceIndex ) {
-                       return a.sourceIndex - b.sourceIndex;
-               }
-
-               var al, bl,
-                       ap = [],
-                       bp = [],
-                       aup = a.parentNode,
-                       bup = b.parentNode,
-                       cur = aup;
-
-               // If the nodes are siblings (or identical) we can do a quick check
-               if ( aup === bup ) {
-                       return siblingCheck( a, b );
-
-               // If no parents were found then the nodes are disconnected
-               } else if ( !aup ) {
-                       return -1;
-
-               } else if ( !bup ) {
-                       return 1;
-               }
-
-               // Otherwise they're somewhere else in the tree so we need
-               // to build up a full list of the parentNodes for comparison
-               while ( cur ) {
-                       ap.unshift( cur );
-                       cur = cur.parentNode;
-               }
-
-               cur = bup;
-
-               while ( cur ) {
-                       bp.unshift( cur );
-                       cur = cur.parentNode;
-               }
-
-               al = ap.length;
-               bl = bp.length;
-
-               // Start walking down the tree looking for a discrepancy
-               for ( var i = 0; i < al && i < bl; i++ ) {
-                       if ( ap[i] !== bp[i] ) {
-                               return siblingCheck( ap[i], bp[i] );
-                       }
-               }
-
-               // We ended someplace up the tree so do a sibling check
-               return i === al ?
-                       siblingCheck( a, bp[i], -1 ) :
-                       siblingCheck( ap[i], b, 1 );
-       };
-
-// Always assume the presence of duplicates if sort doesn't
-// pass them to our comparison function (as in Google Chrome).
-[0, 0].sort( sortOrder );
-baseHasDuplicate = !hasDuplicate;
-
-// Document sorting and removing duplicates
-Sizzle.uniqueSort = function( results ) {
-       var elem,
-               duplicates = [],
-               i = 1,
-               j = 0;
-
-       hasDuplicate = baseHasDuplicate;
-       results.sort( sortOrder );
-
-       if ( hasDuplicate ) {
-               for ( ; (elem = results[i]); i++ ) {
-                       if ( elem === results[ i - 1 ] ) {
-                               j = duplicates.push( i );
-                       }
-               }
-               while ( j-- ) {
-                       results.splice( duplicates[ j ], 1 );
-               }
-       }
-
-       return results;
-};
-
-Sizzle.error = function( msg ) {
-       throw new Error( "Syntax error, unrecognized expression: " + msg );
-};
-
-function tokenize( selector, parseOnly ) {
-       var matched, match, tokens, type,
-               soFar, groups, preFilters,
-               cached = tokenCache[ expando ][ selector + " " ];
-
-       if ( cached ) {
-               return parseOnly ? 0 : cached.slice( 0 );
-       }
-
-       soFar = selector;
-       groups = [];
-       preFilters = Expr.preFilter;
-
-       while ( soFar ) {
-
-               // Comma and first run
-               if ( !matched || (match = rcomma.exec( soFar )) ) {
-                       if ( match ) {
-                               // Don't consume trailing commas as valid
-                               soFar = soFar.slice( match[0].length ) || soFar;
-                       }
-                       groups.push( tokens = [] );
-               }
-
-               matched = false;
-
-               // Combinators
-               if ( (match = rcombinators.exec( soFar )) ) {
-                       tokens.push( matched = new Token( match.shift() ) );
-                       soFar = soFar.slice( matched.length );
-
-                       // Cast descendant combinators to space
-                       matched.type = match[0].replace( rtrim, " " );
-               }
-
-               // Filters
-               for ( type in Expr.filter ) {
-                       if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
-                               (match = preFilters[ type ]( match ))) ) {
-
-                               tokens.push( matched = new Token( match.shift() ) );
-                               soFar = soFar.slice( matched.length );
-                               matched.type = type;
-                               matched.matches = match;
-                       }
-               }
-
-               if ( !matched ) {
-                       break;
-               }
-       }
-
-       // Return the length of the invalid excess
-       // if we're just parsing
-       // Otherwise, throw an error or return tokens
-       return parseOnly ?
-               soFar.length :
-               soFar ?
-                       Sizzle.error( selector ) :
-                       // Cache the tokens
-                       tokenCache( selector, groups ).slice( 0 );
-}
-
-function addCombinator( matcher, combinator, base ) {
-       var dir = combinator.dir,
-               checkNonElements = base && combinator.dir === "parentNode",
-               doneName = done++;
-
-       return combinator.first ?
-               // Check against closest ancestor/preceding element
-               function( elem, context, xml ) {
-                       while ( (elem = elem[ dir ]) ) {
-                               if ( checkNonElements || elem.nodeType === 1  ) {
-                                       return matcher( elem, context, xml );
-                               }
-                       }
-               } :
-
-               // Check against all ancestor/preceding elements
-               function( elem, context, xml ) {
-                       // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
-                       if ( !xml ) {
-                               var cache,
-                                       dirkey = dirruns + " " + doneName + " ",
-                                       cachedkey = dirkey + cachedruns;
-                               while ( (elem = elem[ dir ]) ) {
-                                       if ( checkNonElements || elem.nodeType === 1 ) {
-                                               if ( (cache = elem[ expando ]) === cachedkey ) {
-                                                       return elem.sizset;
-                                               } else if ( typeof cache === "string" && cache.indexOf(dirkey) === 0 ) {
-                                                       if ( elem.sizset ) {
-                                                               return elem;
-                                                       }
-                                               } else {
-                                                       elem[ expando ] = cachedkey;
-                                                       if ( matcher( elem, context, xml ) ) {
-                                                               elem.sizset = true;
-                                                               return elem;
-                                                       }
-                                                       elem.sizset = false;
-                                               }
-                                       }
-                               }
-                       } else {
-                               while ( (elem = elem[ dir ]) ) {
-                                       if ( checkNonElements || elem.nodeType === 1 ) {
-                                               if ( matcher( elem, context, xml ) ) {
-                                                       return elem;
-                                               }
-                                       }
-                               }
-                       }
-               };
-}
-
-function elementMatcher( matchers ) {
-       return matchers.length > 1 ?
-               function( elem, context, xml ) {
-                       var i = matchers.length;
-                       while ( i-- ) {
-                               if ( !matchers[i]( elem, context, xml ) ) {
-                                       return false;
-                               }
-                       }
-                       return true;
-               } :
-               matchers[0];
-}
-
-function condense( unmatched, map, filter, context, xml ) {
-       var elem,
-               newUnmatched = [],
-               i = 0,
-               len = unmatched.length,
-               mapped = map != null;
-
-       for ( ; i < len; i++ ) {
-               if ( (elem = unmatched[i]) ) {
-                       if ( !filter || filter( elem, context, xml ) ) {
-                               newUnmatched.push( elem );
-                               if ( mapped ) {
-                                       map.push( i );
-                               }
-                       }
-               }
-       }
-
-       return newUnmatched;
-}
-
-function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
-       if ( postFilter && !postFilter[ expando ] ) {
-               postFilter = setMatcher( postFilter );
-       }
-       if ( postFinder && !postFinder[ expando ] ) {
-               postFinder = setMatcher( postFinder, postSelector );
-       }
-       return markFunction(function( seed, results, context, xml ) {
-               var temp, i, elem,
-                       preMap = [],
-                       postMap = [],
-                       preexisting = results.length,
-
-                       // Get initial elements from seed or context
-                       elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
-
-                       // Prefilter to get matcher input, preserving a map for seed-results synchronization
-                       matcherIn = preFilter && ( seed || !selector ) ?
-                               condense( elems, preMap, preFilter, context, xml ) :
-                               elems,
-
-                       matcherOut = matcher ?
-                               // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
-                               postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
-
-                                       // ...intermediate processing is necessary
-                                       [] :
-
-                                       // ...otherwise use results directly
-                                       results :
-                               matcherIn;
-
-               // Find primary matches
-               if ( matcher ) {
-                       matcher( matcherIn, matcherOut, context, xml );
-               }
-
-               // Apply postFilter
-               if ( postFilter ) {
-                       temp = condense( matcherOut, postMap );
-                       postFilter( temp, [], context, xml );
-
-                       // Un-match failing elements by moving them back to matcherIn
-                       i = temp.length;
-                       while ( i-- ) {
-                               if ( (elem = temp[i]) ) {
-                                       matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
-                               }
-                       }
-               }
-
-               if ( seed ) {
-                       if ( postFinder || preFilter ) {
-                               if ( postFinder ) {
-                                       // Get the final matcherOut by condensing this intermediate into postFinder contexts
-                                       temp = [];
-                                       i = matcherOut.length;
-                                       while ( i-- ) {
-                                               if ( (elem = matcherOut[i]) ) {
-                                                       // Restore matcherIn since elem is not yet a final match
-                                                       temp.push( (matcherIn[i] = elem) );
-                                               }
-                                       }
-                                       postFinder( null, (matcherOut = []), temp, xml );
-                               }
-
-                               // Move matched elements from seed to results to keep them synchronized
-                               i = matcherOut.length;
-                               while ( i-- ) {
-                                       if ( (elem = matcherOut[i]) &&
-                                               (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {
-
-                                               seed[temp] = !(results[temp] = elem);
-                                       }
-                               }
-                       }
-
-               // Add elements to results, through postFinder if defined
-               } else {
-                       matcherOut = condense(
-                               matcherOut === results ?
-                                       matcherOut.splice( preexisting, matcherOut.length ) :
-                                       matcherOut
-                       );
-                       if ( postFinder ) {
-                               postFinder( null, results, matcherOut, xml );
-                       } else {
-                               push.apply( results, matcherOut );
-                       }
-               }
-       });
-}
-
-function matcherFromTokens( tokens ) {
-       var checkContext, matcher, j,
-               len = tokens.length,
-               leadingRelative = Expr.relative[ tokens[0].type ],
-               implicitRelative = leadingRelative || Expr.relative[" "],
-               i = leadingRelative ? 1 : 0,
-
-               // The foundational matcher ensures that elements are reachable from top-level context(s)
-               matchContext = addCombinator( function( elem ) {
-                       return elem === checkContext;
-               }, implicitRelative, true ),
-               matchAnyContext = addCombinator( function( elem ) {
-                       return indexOf.call( checkContext, elem ) > -1;
-               }, implicitRelative, true ),
-               matchers = [ function( elem, context, xml ) {
-                       return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
-                               (checkContext = context).nodeType ?
-                                       matchContext( elem, context, xml ) :
-                                       matchAnyContext( elem, context, xml ) );
-               } ];
-
-       for ( ; i < len; i++ ) {
-               if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
-                       matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];
-               } else {
-                       matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
-
-                       // Return special upon seeing a positional matcher
-                       if ( matcher[ expando ] ) {
-                               // Find the next relative operator (if any) for proper handling
-                               j = ++i;
-                               for ( ; j < len; j++ ) {
-                                       if ( Expr.relative[ tokens[j].type ] ) {
-                                               break;
-                                       }
-                               }
-                               return setMatcher(
-                                       i > 1 && elementMatcher( matchers ),
-                                       i > 1 && tokens.slice( 0, i - 1 ).join("").replace( rtrim, "$1" ),
-                                       matcher,
-                                       i < j && matcherFromTokens( tokens.slice( i, j ) ),
-                                       j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
-                                       j < len && tokens.join("")
-                               );
-                       }
-                       matchers.push( matcher );
-               }
-       }
-
-       return elementMatcher( matchers );
-}
-
-function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
-       var bySet = setMatchers.length > 0,
-               byElement = elementMatchers.length > 0,
-               superMatcher = function( seed, context, xml, results, expandContext ) {
-                       var elem, j, matcher,
-                               setMatched = [],
-                               matchedCount = 0,
-                               i = "0",
-                               unmatched = seed && [],
-                               outermost = expandContext != null,
-                               contextBackup = outermostContext,
-                               // We must always have either seed elements or context
-                               elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ),
-                               // Nested matchers should use non-integer dirruns
-                               dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.E);
-
-                       if ( outermost ) {
-                               outermostContext = context !== document && context;
-                               cachedruns = superMatcher.el;
-                       }
-
-                       // Add elements passing elementMatchers directly to results
-                       for ( ; (elem = elems[i]) != null; i++ ) {
-                               if ( byElement && elem ) {
-                                       for ( j = 0; (matcher = elementMatchers[j]); j++ ) {
-                                               if ( matcher( elem, context, xml ) ) {
-                                                       results.push( elem );
-                                                       break;
-                                               }
-                                       }
-                                       if ( outermost ) {
-                                               dirruns = dirrunsUnique;
-                                               cachedruns = ++superMatcher.el;
-                                       }
-                               }
-
-                               // Track unmatched elements for set filters
-                               if ( bySet ) {
-                                       // They will have gone through all possible matchers
-                                       if ( (elem = !matcher && elem) ) {
-                                               matchedCount--;
-                                       }
-
-                                       // Lengthen the array for every element, matched or not
-                                       if ( seed ) {
-                                               unmatched.push( elem );
-                                       }
-                               }
-                       }
-
-                       // Apply set filters to unmatched elements
-                       matchedCount += i;
-                       if ( bySet && i !== matchedCount ) {
-                               for ( j = 0; (matcher = setMatchers[j]); j++ ) {
-                                       matcher( unmatched, setMatched, context, xml );
-                               }
-
-                               if ( seed ) {
-                                       // Reintegrate element matches to eliminate the need for sorting
-                                       if ( matchedCount > 0 ) {
-                                               while ( i-- ) {
-                                                       if ( !(unmatched[i] || setMatched[i]) ) {
-                                                               setMatched[i] = pop.call( results );
-                                                       }
-                                               }
-                                       }
-
-                                       // Discard index placeholder values to get only actual matches
-                                       setMatched = condense( setMatched );
-                               }
-
-                               // Add matches to results
-                               push.apply( results, setMatched );
-
-                               // Seedless set matches succeeding multiple successful matchers stipulate sorting
-                               if ( outermost && !seed && setMatched.length > 0 &&
-                                       ( matchedCount + setMatchers.length ) > 1 ) {
-
-                                       Sizzle.uniqueSort( results );
-                               }
-                       }
-
-                       // Override manipulation of globals by nested matchers
-                       if ( outermost ) {
-                               dirruns = dirrunsUnique;
-                               outermostContext = contextBackup;
-                       }
-
-                       return unmatched;
-               };
-
-       superMatcher.el = 0;
-       return bySet ?
-               markFunction( superMatcher ) :
-               superMatcher;
-}
-
-compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
-       var i,
-               setMatchers = [],
-               elementMatchers = [],
-               cached = compilerCache[ expando ][ selector + " " ];
-
-       if ( !cached ) {
-               // Generate a function of recursive functions that can be used to check each element
-               if ( !group ) {
-                       group = tokenize( selector );
-               }
-               i = group.length;
-               while ( i-- ) {
-                       cached = matcherFromTokens( group[i] );
-                       if ( cached[ expando ] ) {
-                               setMatchers.push( cached );
-                       } else {
-                               elementMatchers.push( cached );
-                       }
-               }
-
-               // Cache the compiled function
-               cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
-       }
-       return cached;
-};
-
-function multipleContexts( selector, contexts, results ) {
-       var i = 0,
-               len = contexts.length;
-       for ( ; i < len; i++ ) {
-               Sizzle( selector, contexts[i], results );
-       }
-       return results;
-}
-
-function select( selector, context, results, seed, xml ) {
-       var i, tokens, token, type, find,
-               match = tokenize( selector ),
-               j = match.length;
-
-       if ( !seed ) {
-               // Try to minimize operations if there is only one group
-               if ( match.length === 1 ) {
-
-                       // Take a shortcut and set the context if the root selector is an ID
-                       tokens = match[0] = match[0].slice( 0 );
-                       if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
-                                       context.nodeType === 9 && !xml &&
-                                       Expr.relative[ tokens[1].type ] ) {
-
-                               context = Expr.find["ID"]( token.matches[0].replace( rbackslash, "" ), context, xml )[0];
-                               if ( !context ) {
-                                       return results;
-                               }
-
-                               selector = selector.slice( tokens.shift().length );
-                       }
-
-                       // Fetch a seed set for right-to-left matching
-                       for ( i = matchExpr["POS"].test( selector ) ? -1 : tokens.length - 1; i >= 0; i-- ) {
-                               token = tokens[i];
-
-                               // Abort if we hit a combinator
-                               if ( Expr.relative[ (type = token.type) ] ) {
-                                       break;
-                               }
-                               if ( (find = Expr.find[ type ]) ) {
-                                       // Search, expanding context for leading sibling combinators
-                                       if ( (seed = find(
-                                               token.matches[0].replace( rbackslash, "" ),
-                                               rsibling.test( tokens[0].type ) && context.parentNode || context,
-                                               xml
-                                       )) ) {
-
-                                               // If seed is empty or no tokens remain, we can return early
-                                               tokens.splice( i, 1 );
-                                               selector = seed.length && tokens.join("");
-                                               if ( !selector ) {
-                                                       push.apply( results, slice.call( seed, 0 ) );
-                                                       return results;
-                                               }
-
-                                               break;
-                                       }
-                               }
-                       }
-               }
-       }
-
-       // Compile and execute a filtering function
-       // Provide `match` to avoid retokenization if we modified the selector above
-       compile( selector, match )(
-               seed,
-               context,
-               xml,
-               results,
-               rsibling.test( selector )
-       );
-       return results;
-}
-
-if ( document.querySelectorAll ) {
-       (function() {
-               var disconnectedMatch,
-                       oldSelect = select,
-                       rescape = /'|\\/g,
-                       rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,
-
-                       // qSa(:focus) reports false when true (Chrome 21), no need to also add to buggyMatches since matches checks buggyQSA
-                       // A support test would require too much code (would include document ready)
-                       rbuggyQSA = [ ":focus" ],
-
-                       // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
-                       // A support test would require too much code (would include document ready)
-                       // just skip matchesSelector for :active
-                       rbuggyMatches = [ ":active" ],
-                       matches = docElem.matchesSelector ||
-                               docElem.mozMatchesSelector ||
-                               docElem.webkitMatchesSelector ||
-                               docElem.oMatchesSelector ||
-                               docElem.msMatchesSelector;
-
-               // Build QSA regex
-               // Regex strategy adopted from Diego Perini
-               assert(function( div ) {
-                       // Select is set to empty string on purpose
-                       // This is to test IE's treatment of not explictly
-                       // setting a boolean content attribute,
-                       // since its presence should be enough
-                       // http://bugs.jquery.com/ticket/12359
-                       div.innerHTML = "<select><option selected=''></option></select>";
-
-                       // IE8 - Some boolean attributes are not treated correctly
-                       if ( !div.querySelectorAll("[selected]").length ) {
-                               rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" );
-                       }
-
-                       // Webkit/Opera - :checked should return selected option elements
-                       // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
-                       // IE8 throws error here (do not put tests after this one)
-                       if ( !div.querySelectorAll(":checked").length ) {
-                               rbuggyQSA.push(":checked");
-                       }
-               });
-
-               assert(function( div ) {
-
-                       // Opera 10-12/IE9 - ^= $= *= and empty values
-                       // Should not select anything
-                       div.innerHTML = "<p test=''></p>";
-                       if ( div.querySelectorAll("[test^='']").length ) {
-                               rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" );
-                       }
-
-                       // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
-                       // IE8 throws error here (do not put tests after this one)
-                       div.innerHTML = "<input type='hidden'/>";
-                       if ( !div.querySelectorAll(":enabled").length ) {
-                               rbuggyQSA.push(":enabled", ":disabled");
-                       }
-               });
-
-               // rbuggyQSA always contains :focus, so no need for a length check
-               rbuggyQSA = /* rbuggyQSA.length && */ new RegExp( rbuggyQSA.join("|") );
-
-               select = function( selector, context, results, seed, xml ) {
-                       // Only use querySelectorAll when not filtering,
-                       // when this is not xml,
-                       // and when no QSA bugs apply
-                       if ( !seed && !xml && !rbuggyQSA.test( selector ) ) {
-                               var groups, i,
-                                       old = true,
-                                       nid = expando,
-                                       newContext = context,
-                                       newSelector = context.nodeType === 9 && selector;
-
-                               // qSA works strangely on Element-rooted queries
-                               // We can work around this by specifying an extra ID on the root
-                               // and working up from there (Thanks to Andrew Dupont for the technique)
-                               // IE 8 doesn't work on object elements
-                               if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
-                                       groups = tokenize( selector );
-
-                                       if ( (old = context.getAttribute("id")) ) {
-                                               nid = old.replace( rescape, "\\$&" );
-                                       } else {
-                                               context.setAttribute( "id", nid );
-                                       }
-                                       nid = "[id='" + nid + "'] ";
-
-                                       i = groups.length;
-                                       while ( i-- ) {
-                                               groups[i] = nid + groups[i].join("");
-                                       }
-                                       newContext = rsibling.test( selector ) && context.parentNode || context;
-                                       newSelector = groups.join(",");
-                               }
-
-                               if ( newSelector ) {
-                                       try {
-                                               push.apply( results, slice.call( newContext.querySelectorAll(
-                                                       newSelector
-                                               ), 0 ) );
-                                               return results;
-                                       } catch(qsaError) {
-                                       } finally {
-                                               if ( !old ) {
-                                                       context.removeAttribute("id");
-                                               }
-                                       }
-                               }
-                       }
-
-                       return oldSelect( selector, context, results, seed, xml );
-               };
-
-               if ( matches ) {
-                       assert(function( div ) {
-                               // Check to see if it's possible to do matchesSelector
-                               // on a disconnected node (IE 9)
-                               disconnectedMatch = matches.call( div, "div" );
-
-                               // This should fail with an exception
-                               // Gecko does not error, returns false instead
-                               try {
-                                       matches.call( div, "[test!='']:sizzle" );
-                                       rbuggyMatches.push( "!=", pseudos );
-                               } catch ( e ) {}
-                       });
-
-                       // rbuggyMatches always contains :active and :focus, so no need for a length check
-                       rbuggyMatches = /* rbuggyMatches.length && */ new RegExp( rbuggyMatches.join("|") );
-
-                       Sizzle.matchesSelector = function( elem, expr ) {
-                               // Make sure that attribute selectors are quoted
-                               expr = expr.replace( rattributeQuotes, "='$1']" );
-
-                               // rbuggyMatches always contains :active, so no need for an existence check
-                               if ( !isXML( elem ) && !rbuggyMatches.test( expr ) && !rbuggyQSA.test( expr ) ) {
-                                       try {
-                                               var ret = matches.call( elem, expr );
-
-                                               // IE 9's matchesSelector returns false on disconnected nodes
-                                               if ( ret || disconnectedMatch ||
-                                                               // As well, disconnected nodes are said to be in a document
-                                                               // fragment in IE 9
-                                                               elem.document && elem.document.nodeType !== 11 ) {
-                                                       return ret;
-                                               }
-                                       } catch(e) {}
-                               }
-
-                               return Sizzle( expr, null, null, [ elem ] ).length > 0;
-                       };
-               }
-       })();
-}
-
-// Deprecated
-Expr.pseudos["nth"] = Expr.pseudos["eq"];
-
-// Back-compat
-function setFilters() {}
-Expr.filters = setFilters.prototype = Expr.pseudos;
-Expr.setFilters = new setFilters();
-
-// Override sizzle attribute retrieval
-Sizzle.attr = jQuery.attr;
-jQuery.find = Sizzle;
-jQuery.expr = Sizzle.selectors;
-jQuery.expr[":"] = jQuery.expr.pseudos;
-jQuery.unique = Sizzle.uniqueSort;
-jQuery.text = Sizzle.getText;
-jQuery.isXMLDoc = Sizzle.isXML;
-jQuery.contains = Sizzle.contains;
-
-
-})( window );
-var runtil = /Until$/,
-       rparentsprev = /^(?:parents|prev(?:Until|All))/,
-       isSimple = /^.[^:#\[\.,]*$/,
-       rneedsContext = jQuery.expr.match.needsContext,
-       // methods guaranteed to produce a unique set when starting from a unique set
-       guaranteedUnique = {
-               children: true,
-               contents: true,
-               next: true,
-               prev: true
-       };
-
-jQuery.fn.extend({
-       find: function( selector ) {
-               var i, l, length, n, r, ret,
-                       self = this;
-
-               if ( typeof selector !== "string" ) {
-                       return jQuery( selector ).filter(function() {
-                               for ( i = 0, l = self.length; i < l; i++ ) {
-                                       if ( jQuery.contains( self[ i ], this ) ) {
-                                               return true;
-                                       }
-                               }
-                       });
-               }
-
-               ret = this.pushStack( "", "find", selector );
-
-               for ( i = 0, l = this.length; i < l; i++ ) {
-                       length = ret.length;
-                       jQuery.find( selector, this[i], ret );
-
-                       if ( i > 0 ) {
-                               // Make sure that the results are unique
-                               for ( n = length; n < ret.length; n++ ) {
-                                       for ( r = 0; r < length; r++ ) {
-                                               if ( ret[r] === ret[n] ) {
-                                                       ret.splice(n--, 1);
-                                                       break;
-                                               }
-                                       }
-                               }
-                       }
-               }
-
-               return ret;
-       },
-
-       has: function( target ) {
-               var i,
-                       targets = jQuery( target, this ),
-                       len = targets.length;
-
-               return this.filter(function() {
-                       for ( i = 0; i < len; i++ ) {
-                               if ( jQuery.contains( this, targets[i] ) ) {
-                                       return true;
-                               }
-                       }
-               });
-       },
-
-       not: function( selector ) {
-               return this.pushStack( winnow(this, selector, false), "not", selector);
-       },
-
-       filter: function( selector ) {
-               return this.pushStack( winnow(this, selector, true), "filter", selector );
-       },
-
-       is: function( selector ) {
-               return !!selector && (
-                       typeof selector === "string" ?
-                               // If this is a positional/relative selector, check membership in the returned set
-                               // so $("p:first").is("p:last") won't return true for a doc with two "p".
-                               rneedsContext.test( selector ) ?
-                                       jQuery( selector, this.context ).index( this[0] ) >= 0 :
-                                       jQuery.filter( selector, this ).length > 0 :
-                               this.filter( selector ).length > 0 );
-       },
-
-       closest: function( selectors, context ) {
-               var cur,
-                       i = 0,
-                       l = this.length,
-                       ret = [],
-                       pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
-                               jQuery( selectors, context || this.context ) :
-                               0;
-
-               for ( ; i < l; i++ ) {
-                       cur = this[i];
-
-                       while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) {
-                               if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
-                                       ret.push( cur );
-                                       break;
-                               }
-                               cur = cur.parentNode;
-                       }
-               }
-
-               ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
-
-               return this.pushStack( ret, "closest", selectors );
-       },
-
-       // Determine the position of an element within
-       // the matched set of elements
-       index: function( elem ) {
-
-               // No argument, return index in parent
-               if ( !elem ) {
-                       return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
-               }
-
-               // index in selector
-               if ( typeof elem === "string" ) {
-                       return jQuery.inArray( this[0], jQuery( elem ) );
-               }
-
-               // Locate the position of the desired element
-               return jQuery.inArray(
-                       // If it receives a jQuery object, the first element is used
-                       elem.jquery ? elem[0] : elem, this );
-       },
-
-       add: function( selector, context ) {
-               var set = typeof selector === "string" ?
-                               jQuery( selector, context ) :
-                               jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
-                       all = jQuery.merge( this.get(), set );
-
-               return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
-                       all :
-                       jQuery.unique( all ) );
-       },
-
-       addBack: function( selector ) {
-               return this.add( selector == null ?
-                       this.prevObject : this.prevObject.filter(selector)
-               );
-       }
-});
-
-jQuery.fn.andSelf = jQuery.fn.addBack;
-
-// A painfully simple check to see if an element is disconnected
-// from a document (should be improved, where feasible).
-function isDisconnected( node ) {
-       return !node || !node.parentNode || node.parentNode.nodeType === 11;
-}
-
-function sibling( cur, dir ) {
-       do {
-               cur = cur[ dir ];
-       } while ( cur && cur.nodeType !== 1 );
-
-       return cur;
-}
-
-jQuery.each({
-       parent: function( elem ) {
-               var parent = elem.parentNode;
-               return parent && parent.nodeType !== 11 ? parent : null;
-       },
-       parents: function( elem ) {
-               return jQuery.dir( elem, "parentNode" );
-       },
-       parentsUntil: function( elem, i, until ) {
-               return jQuery.dir( elem, "parentNode", until );
-       },
-       next: function( elem ) {
-               return sibling( elem, "nextSibling" );
-       },
-       prev: function( elem ) {
-               return sibling( elem, "previousSibling" );
-       },
-       nextAll: function( elem ) {
-               return jQuery.dir( elem, "nextSibling" );
-       },
-       prevAll: function( elem ) {
-               return jQuery.dir( elem, "previousSibling" );
-       },
-       nextUntil: function( elem, i, until ) {
-               return jQuery.dir( elem, "nextSibling", until );
-       },
-       prevUntil: function( elem, i, until ) {
-               return jQuery.dir( elem, "previousSibling", until );
-       },
-       siblings: function( elem ) {
-               return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
-       },
-       children: function( elem ) {
-               return jQuery.sibling( elem.firstChild );
-       },
-       contents: function( elem ) {
-               return jQuery.nodeName( elem, "iframe" ) ?
-                       elem.contentDocument || elem.contentWindow.document :
-                       jQuery.merge( [], elem.childNodes );
-       }
-}, function( name, fn ) {
-       jQuery.fn[ name ] = function( until, selector ) {
-               var ret = jQuery.map( this, fn, until );
-
-               if ( !runtil.test( name ) ) {
-                       selector = until;
-               }
-
-               if ( selector && typeof selector === "string" ) {
-                       ret = jQuery.filter( selector, ret );
-               }
-
-               ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
-
-               if ( this.length > 1 && rparentsprev.test( name ) ) {
-                       ret = ret.reverse();
-               }
-
-               return this.pushStack( ret, name, core_slice.call( arguments ).join(",") );
-       };
-});
-
-jQuery.extend({
-       filter: function( expr, elems, not ) {
-               if ( not ) {
-                       expr = ":not(" + expr + ")";
-               }
-
-               return elems.length === 1 ?
-                       jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
-                       jQuery.find.matches(expr, elems);
-       },
-
-       dir: function( elem, dir, until ) {
-               var matched = [],
-                       cur = elem[ dir ];
-
-               while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
-                       if ( cur.nodeType === 1 ) {
-                               matched.push( cur );
-                       }
-                       cur = cur[dir];
-               }
-               return matched;
-       },
-
-       sibling: function( n, elem ) {
-               var r = [];
-
-               for ( ; n; n = n.nextSibling ) {
-                       if ( n.nodeType === 1 && n !== elem ) {
-                               r.push( n );
-                       }
-               }
-
-               return r;
-       }
-});
-
-// Implement the identical functionality for filter and not
-function winnow( elements, qualifier, keep ) {
-
-       // Can't pass null or undefined to indexOf in Firefox 4
-       // Set to 0 to skip string check
-       qualifier = qualifier || 0;
-
-       if ( jQuery.isFunction( qualifier ) ) {
-               return jQuery.grep(elements, function( elem, i ) {
-                       var retVal = !!qualifier.call( elem, i, elem );
-                       return retVal === keep;
-               });
-
-       } else if ( qualifier.nodeType ) {
-               return jQuery.grep(elements, function( elem, i ) {
-                       return ( elem === qualifier ) === keep;
-               });
-
-       } else if ( typeof qualifier === "string" ) {
-               var filtered = jQuery.grep(elements, function( elem ) {
-                       return elem.nodeType === 1;
-               });
-
-               if ( isSimple.test( qualifier ) ) {
-                       return jQuery.filter(qualifier, filtered, !keep);
-               } else {
-                       qualifier = jQuery.filter( qualifier, filtered );
-               }
-       }
-
-       return jQuery.grep(elements, function( elem, i ) {
-               return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
-       });
-}
-function createSafeFragment( document ) {
-       var list = nodeNames.split( "|" ),
-       safeFrag = document.createDocumentFragment();
-
-       if ( safeFrag.createElement ) {
-               while ( list.length ) {
-                       safeFrag.createElement(
-                               list.pop()
-                       );
-               }
-       }
-       return safeFrag;
-}
-
-var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
-               "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
-       rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
-       rleadingWhitespace = /^\s+/,
-       rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
-       rtagName = /<([\w:]+)/,
-       rtbody = /<tbody/i,
-       rhtml = /<|&#?\w+;/,
-       rnoInnerhtml = /<(?:script|style|link)/i,
-       rnocache = /<(?:script|object|embed|option|style)/i,
-       rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
-       rcheckableType = /^(?:checkbox|radio)$/,
-       // checked="checked" or checked
-       rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
-       rscriptType = /\/(java|ecma)script/i,
-       rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,
-       wrapMap = {
-               option: [ 1, "<select multiple='multiple'>", "</select>" ],
-               legend: [ 1, "<fieldset>", "</fieldset>" ],
-               thead: [ 1, "<table>", "</table>" ],
-               tr: [ 2, "<table><tbody>", "</tbody></table>" ],
-               td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
-               col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
-               area: [ 1, "<map>", "</map>" ],
-               _default: [ 0, "", "" ]
-       },
-       safeFragment = createSafeFragment( document ),
-       fragmentDiv = safeFragment.appendChild( document.createElement("div") );
-
-wrapMap.optgroup = wrapMap.option;
-wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
-wrapMap.th = wrapMap.td;
-
-// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
-// unless wrapped in a div with non-breaking characters in front of it.
-if ( !jQuery.support.htmlSerialize ) {
-       wrapMap._default = [ 1, "X<div>", "</div>" ];
-}
-
-jQuery.fn.extend({
-       text: function( value ) {
-               return jQuery.access( this, function( value ) {
-                       return value === undefined ?
-                               jQuery.text( this ) :
-                               this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
-               }, null, value, arguments.length );
-       },
-
-       wrapAll: function( html ) {
-               if ( jQuery.isFunction( html ) ) {
-                       return this.each(function(i) {
-                               jQuery(this).wrapAll( html.call(this, i) );
-                       });
-               }
-
-               if ( this[0] ) {
-                       // The elements to wrap the target around
-                       var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
-
-                       if ( this[0].parentNode ) {
-                               wrap.insertBefore( this[0] );
-                       }
-
-                       wrap.map(function() {
-                               var elem = this;
-
-                               while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
-                                       elem = elem.firstChild;
-                               }
-
-                               return elem;
-                       }).append( this );
-               }
-
-               return this;
-       },
-
-       wrapInner: function( html ) {
-               if ( jQuery.isFunction( html ) ) {
-                       return this.each(function(i) {
-                               jQuery(this).wrapInner( html.call(this, i) );
-                       });
-               }
-
-               return this.each(function() {
-                       var self = jQuery( this ),
-                               contents = self.contents();
-
-                       if ( contents.length ) {
-                               contents.wrapAll( html );
-
-                       } else {
-                               self.append( html );
-                       }
-               });
-       },
-
-       wrap: function( html ) {
-               var isFunction = jQuery.isFunction( html );
-
-               return this.each(function(i) {
-                       jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
-               });
-       },
-
-       unwrap: function() {
-               return this.parent().each(function() {
-                       if ( !jQuery.nodeName( this, "body" ) ) {
-                               jQuery( this ).replaceWith( this.childNodes );
-                       }
-               }).end();
-       },
-
-       append: function() {
-               return this.domManip(arguments, true, function( elem ) {
-                       if ( this.nodeType === 1 || this.nodeType === 11 ) {
-                               this.appendChild( elem );
-                       }
-               });
-       },
-
-       prepend: function() {
-               return this.domManip(arguments, true, function( elem ) {
-                       if ( this.nodeType === 1 || this.nodeType === 11 ) {
-                               this.insertBefore( elem, this.firstChild );
-                       }
-               });
-       },
-
-       before: function() {
-               if ( !isDisconnected( this[0] ) ) {
-                       return this.domManip(arguments, false, function( elem ) {
-                               this.parentNode.insertBefore( elem, this );
-                       });
-               }
-
-               if ( arguments.length ) {
-                       var set = jQuery.clean( arguments );
-                       return this.pushStack( jQuery.merge( set, this ), "before", this.selector );
-               }
-       },
-
-       after: function() {
-               if ( !isDisconnected( this[0] ) ) {
-                       return this.domManip(arguments, false, function( elem ) {
-                               this.parentNode.insertBefore( elem, this.nextSibling );
-                       });
-               }
-
-               if ( arguments.length ) {
-                       var set = jQuery.clean( arguments );
-                       return this.pushStack( jQuery.merge( this, set ), "after", this.selector );
-               }
-       },
-
-       // keepData is for internal use only--do not document
-       remove: function( selector, keepData ) {
-               var elem,
-                       i = 0;
-
-               for ( ; (elem = this[i]) != null; i++ ) {
-                       if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
-                               if ( !keepData && elem.nodeType === 1 ) {
-                                       jQuery.cleanData( elem.getElementsByTagName("*") );
-                                       jQuery.cleanData( [ elem ] );
-                               }
-
-                               if ( elem.parentNode ) {
-                                       elem.parentNode.removeChild( elem );
-                               }
-                       }
-               }
-
-               return this;
-       },
-
-       empty: function() {
-               var elem,
-                       i = 0;
-
-               for ( ; (elem = this[i]) != null; i++ ) {
-                       // Remove element nodes and prevent memory leaks
-                       if ( elem.nodeType === 1 ) {
-                               jQuery.cleanData( elem.getElementsByTagName("*") );
-                       }
-
-                       // Remove any remaining nodes
-                       while ( elem.firstChild ) {
-                               elem.removeChild( elem.firstChild );
-                       }
-               }
-
-               return this;
-       },
-
-       clone: function( dataAndEvents, deepDataAndEvents ) {
-               dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
-               deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
-
-               return this.map( function () {
-                       return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
-               });
-       },
-
-       html: function( value ) {
-               return jQuery.access( this, function( value ) {
-                       var elem = this[0] || {},
-                               i = 0,
-                               l = this.length;
-
-                       if ( value === undefined ) {
-                               return elem.nodeType === 1 ?
-                                       elem.innerHTML.replace( rinlinejQuery, "" ) :
-                                       undefined;
-                       }
-
-                       // See if we can take a shortcut and just use innerHTML
-                       if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
-                               ( jQuery.support.htmlSerialize || !rnoshimcache.test( value )  ) &&
-                               ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
-                               !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
-
-                               value = value.replace( rxhtmlTag, "<$1></$2>" );
-
-                               try {
-                                       for (; i < l; i++ ) {
-                                               // Remove element nodes and prevent memory leaks
-                                               elem = this[i] || {};
-                                               if ( elem.nodeType === 1 ) {
-                                                       jQuery.cleanData( elem.getElementsByTagName( "*" ) );
-                                                       elem.innerHTML = value;
-                                               }
-                                       }
-
-                                       elem = 0;
-
-                               // If using innerHTML throws an exception, use the fallback method
-                               } catch(e) {}
-                       }
-
-                       if ( elem ) {
-                               this.empty().append( value );
-                       }
-               }, null, value, arguments.length );
-       },
-
-       replaceWith: function( value ) {
-               if ( !isDisconnected( this[0] ) ) {
-                       // Make sure that the elements are removed from the DOM before they are inserted
-                       // this can help fix replacing a parent with child elements
-                       if ( jQuery.isFunction( value ) ) {
-                               return this.each(function(i) {
-                                       var self = jQuery(this), old = self.html();
-                                       self.replaceWith( value.call( this, i, old ) );
-                               });
-                       }
-
-                       if ( typeof value !== "string" ) {
-                               value = jQuery( value ).detach();
-                       }
-
-                       return this.each(function() {
-                               var next = this.nextSibling,
-                                       parent = this.parentNode;
-
-                               jQuery( this ).remove();
-
-                               if ( next ) {
-                                       jQuery(next).before( value );
-                               } else {
-                                       jQuery(parent).append( value );
-                               }
-                       });
-               }
-
-               return this.length ?
-                       this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
-                       this;
-       },
-
-       detach: function( selector ) {
-               return this.remove( selector, true );
-       },
-
-       domManip: function( args, table, callback ) {
-
-               // Flatten any nested arrays
-               args = [].concat.apply( [], args );
-
-               var results, first, fragment, iNoClone,
-                       i = 0,
-                       value = args[0],
-                       scripts = [],
-                       l = this.length;
-
-               // We can't cloneNode fragments that contain checked, in WebKit
-               if ( !jQuery.support.checkClone && l > 1 && typeof value === "string" && rchecked.test( value ) ) {
-                       return this.each(function() {
-                               jQuery(this).domManip( args, table, callback );
-                       });
-               }
-
-               if ( jQuery.isFunction(value) ) {
-                       return this.each(function(i) {
-                               var self = jQuery(this);
-                               args[0] = value.call( this, i, table ? self.html() : undefined );
-                               self.domManip( args, table, callback );
-                       });
-               }
-
-               if ( this[0] ) {
-                       results = jQuery.buildFragment( args, this, scripts );
-                       fragment = results.fragment;
-                       first = fragment.firstChild;
-
-                       if ( fragment.childNodes.length === 1 ) {
-                               fragment = first;
-                       }
-
-                       if ( first ) {
-                               table = table && jQuery.nodeName( first, "tr" );
-
-                               // Use the original fragment for the last item instead of the first because it can end up
-                               // being emptied incorrectly in certain situations (#8070).
-                               // Fragments from the fragment cache must always be cloned and never used in place.
-                               for ( iNoClone = results.cacheable || l - 1; i < l; i++ ) {
-                                       callback.call(
-                                               table && jQuery.nodeName( this[i], "table" ) ?
-                                                       findOrAppend( this[i], "tbody" ) :
-                                                       this[i],
-                                               i === iNoClone ?
-                                                       fragment :
-                                                       jQuery.clone( fragment, true, true )
-                                       );
-                               }
-                       }
-
-                       // Fix #11809: Avoid leaking memory
-                       fragment = first = null;
-
-                       if ( scripts.length ) {
-                               jQuery.each( scripts, function( i, elem ) {
-                                       if ( elem.src ) {
-                                               if ( jQuery.ajax ) {
-                                                       jQuery.ajax({
-                                                               url: elem.src,
-                                                               type: "GET",
-                                                               dataType: "script",
-                                                               async: false,
-                                                               global: false,
-                                                               "throws": true
-                                                       });
-                                               } else {
-                                                       jQuery.error("no ajax");
-                                               }
-                                       } else {
-                                               jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "" ) );
-                                       }
-
-                                       if ( elem.parentNode ) {
-                                               elem.parentNode.removeChild( elem );
-                                       }
-                               });
-                       }
-               }
-
-               return this;
-       }
-});
-
-function findOrAppend( elem, tag ) {
-       return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) );
-}
-
-function cloneCopyEvent( src, dest ) {
-
-       if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
-               return;
-       }
-
-       var type, i, l,
-               oldData = jQuery._data( src ),
-               curData = jQuery._data( dest, oldData ),
-               events = oldData.events;
-
-       if ( events ) {
-               delete curData.handle;
-               curData.events = {};
-
-               for ( type in events ) {
-                       for ( i = 0, l = events[ type ].length; i < l; i++ ) {
-                               jQuery.event.add( dest, type, events[ type ][ i ] );
-                       }
-               }
-       }
-
-       // make the cloned public data object a copy from the original
-       if ( curData.data ) {
-               curData.data = jQuery.extend( {}, curData.data );
-       }
-}
-
-function cloneFixAttributes( src, dest ) {
-       var nodeName;
-
-       // We do not need to do anything for non-Elements
-       if ( dest.nodeType !== 1 ) {
-               return;
-       }
-
-       // clearAttributes removes the attributes, which we don't want,
-       // but also removes the attachEvent events, which we *do* want
-       if ( dest.clearAttributes ) {
-               dest.clearAttributes();
-       }
-
-       // mergeAttributes, in contrast, only merges back on the
-       // original attributes, not the events
-       if ( dest.mergeAttributes ) {
-               dest.mergeAttributes( src );
-       }
-
-       nodeName = dest.nodeName.toLowerCase();
-
-       if ( nodeName === "object" ) {
-               // IE6-10 improperly clones children of object elements using classid.
-               // IE10 throws NoModificationAllowedError if parent is null, #12132.
-               if ( dest.parentNode ) {
-                       dest.outerHTML = src.outerHTML;
-               }
-
-               // This path appears unavoidable for IE9. When cloning an object
-               // element in IE9, the outerHTML strategy above is not sufficient.
-               // If the src has innerHTML and the destination does not,
-               // copy the src.innerHTML into the dest.innerHTML. #10324
-               if ( jQuery.support.html5Clone && (src.innerHTML && !jQuery.trim(dest.innerHTML)) ) {
-                       dest.innerHTML = src.innerHTML;
-               }
-
-       } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
-               // IE6-8 fails to persist the checked state of a cloned checkbox
-               // or radio button. Worse, IE6-7 fail to give the cloned element
-               // a checked appearance if the defaultChecked value isn't also set
-
-               dest.defaultChecked = dest.checked = src.checked;
-
-               // IE6-7 get confused and end up setting the value of a cloned
-               // checkbox/radio button to an empty string instead of "on"
-               if ( dest.value !== src.value ) {
-                       dest.value = src.value;
-               }
-
-       // IE6-8 fails to return the selected option to the default selected
-       // state when cloning options
-       } else if ( nodeName === "option" ) {
-               dest.selected = src.defaultSelected;
-
-       // IE6-8 fails to set the defaultValue to the correct value when
-       // cloning other types of input fields
-       } else if ( nodeName === "input" || nodeName === "textarea" ) {
-               dest.defaultValue = src.defaultValue;
-
-       // IE blanks contents when cloning scripts
-       } else if ( nodeName === "script" && dest.text !== src.text ) {
-               dest.text = src.text;
-       }
-
-       // Event data gets referenced instead of copied if the expando
-       // gets copied too
-       dest.removeAttribute( jQuery.expando );
-}
-
-jQuery.buildFragment = function( args, context, scripts ) {
-       var fragment, cacheable, cachehit,
-               first = args[ 0 ];
-
-       // Set context from what may come in as undefined or a jQuery collection or a node
-       // Updated to fix #12266 where accessing context[0] could throw an exception in IE9/10 &
-       // also doubles as fix for #8950 where plain objects caused createDocumentFragment exception
-       context = context || document;
-       context = !context.nodeType && context[0] || context;
-       context = context.ownerDocument || context;
-
-       // Only cache "small" (1/2 KB) HTML strings that are associated with the main document
-       // Cloning options loses the selected state, so don't cache them
-       // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
-       // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
-       // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501
-       if ( args.length === 1 && typeof first === "string" && first.length < 512 && context === document &&
-               first.charAt(0) === "<" && !rnocache.test( first ) &&
-               (jQuery.support.checkClone || !rchecked.test( first )) &&
-               (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {
-
-               // Mark cacheable and look for a hit
-               cacheable = true;
-               fragment = jQuery.fragments[ first ];
-               cachehit = fragment !== undefined;
-       }
-
-       if ( !fragment ) {
-               fragment = context.createDocumentFragment();
-               jQuery.clean( args, context, fragment, scripts );
-
-               // Update the cache, but only store false
-               // unless this is a second parsing of the same content
-               if ( cacheable ) {
-                       jQuery.fragments[ first ] = cachehit && fragment;
-               }
-       }
-
-       return { fragment: fragment, cacheable: cacheable };
-};
-
-jQuery.fragments = {};
-
-jQuery.each({
-       appendTo: "append",
-       prependTo: "prepend",
-       insertBefore: "before",
-       insertAfter: "after",
-       replaceAll: "replaceWith"
-}, function( name, original ) {
-       jQuery.fn[ name ] = function( selector ) {
-               var elems,
-                       i = 0,
-                       ret = [],
-                       insert = jQuery( selector ),
-                       l = insert.length,
-                       parent = this.length === 1 && this[0].parentNode;
-
-               if ( (parent == null || parent && parent.nodeType === 11 && parent.childNodes.length === 1) && l === 1 ) {
-                       insert[ original ]( this[0] );
-                       return this;
-               } else {
-                       for ( ; i < l; i++ ) {
-                               elems = ( i > 0 ? this.clone(true) : this ).get();
-                               jQuery( insert[i] )[ original ]( elems );
-                               ret = ret.concat( elems );
-                       }
-
-                       return this.pushStack( ret, name, insert.selector );
-               }
-       };
-});
-
-function getAll( elem ) {
-       if ( typeof elem.getElementsByTagName !== "undefined" ) {
-               return elem.getElementsByTagName( "*" );
-
-       } else if ( typeof elem.querySelectorAll !== "undefined" ) {
-               return elem.querySelectorAll( "*" );
-
-       } else {
-               return [];
-       }
-}
-
-// Used in clean, fixes the defaultChecked property
-function fixDefaultChecked( elem ) {
-       if ( rcheckableType.test( elem.type ) ) {
-               elem.defaultChecked = elem.checked;
-       }
-}
-
-jQuery.extend({
-       clone: function( elem, dataAndEvents, deepDataAndEvents ) {
-               var srcElements,
-                       destElements,
-                       i,
-                       clone;
-
-               if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {
-                       clone = elem.cloneNode( true );
-
-               // IE<=8 does not properly clone detached, unknown element nodes
-               } else {
-                       fragmentDiv.innerHTML = elem.outerHTML;
-                       fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
-               }
-
-               if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
-                               (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
-                       // IE copies events bound via attachEvent when using cloneNode.
-                       // Calling detachEvent on the clone will also remove the events
-                       // from the original. In order to get around this, we use some
-                       // proprietary methods to clear the events. Thanks to MooTools
-                       // guys for this hotness.
-
-                       cloneFixAttributes( elem, clone );
-
-                       // Using Sizzle here is crazy slow, so we use getElementsByTagName instead
-                       srcElements = getAll( elem );
-                       destElements = getAll( clone );
-
-                       // Weird iteration because IE will replace the length property
-                       // with an element if you are cloning the body and one of the
-                       // elements on the page has a name or id of "length"
-                       for ( i = 0; srcElements[i]; ++i ) {
-                               // Ensure that the destination node is not null; Fixes #9587
-                               if ( destElements[i] ) {
-                                       cloneFixAttributes( srcElements[i], destElements[i] );
-                               }
-                       }
-               }
-
-               // Copy the events from the original to the clone
-               if ( dataAndEvents ) {
-                       cloneCopyEvent( elem, clone );
-
-                       if ( deepDataAndEvents ) {
-                               srcElements = getAll( elem );
-                               destElements = getAll( clone );
-
-                               for ( i = 0; srcElements[i]; ++i ) {
-                                       cloneCopyEvent( srcElements[i], destElements[i] );
-                               }
-                       }
-               }
-
-               srcElements = destElements = null;
-
-               // Return the cloned set
-               return clone;
-       },
-
-       clean: function( elems, context, fragment, scripts ) {
-               var i, j, elem, tag, wrap, depth, div, hasBody, tbody, len, handleScript, jsTags,
-                       safe = context === document && safeFragment,
-                       ret = [];
-
-               // Ensure that context is a document
-               if ( !context || typeof context.createDocumentFragment === "undefined" ) {
-                       context = document;
-               }
-
-               // Use the already-created safe fragment if context permits
-               for ( i = 0; (elem = elems[i]) != null; i++ ) {
-                       if ( typeof elem === "number" ) {
-                               elem += "";
-                       }
-
-                       if ( !elem ) {
-                               continue;
-                       }
-
-                       // Convert html string into DOM nodes
-                       if ( typeof elem === "string" ) {
-                               if ( !rhtml.test( elem ) ) {
-                                       elem = context.createTextNode( elem );
-                               } else {
-                                       // Ensure a safe container in which to render the html
-                                       safe = safe || createSafeFragment( context );
-                                       div = context.createElement("div");
-                                       safe.appendChild( div );
-
-                                       // Fix "XHTML"-style tags in all browsers
-                                       elem = elem.replace(rxhtmlTag, "<$1></$2>");
-
-                                       // Go to html and back, then peel off extra wrappers
-                                       tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase();
-                                       wrap = wrapMap[ tag ] || wrapMap._default;
-                                       depth = wrap[0];
-                                       div.innerHTML = wrap[1] + elem + wrap[2];
-
-                                       // Move to the right depth
-                                       while ( depth-- ) {
-                                               div = div.lastChild;
-                                       }
-
-                                       // Remove IE's autoinserted <tbody> from table fragments
-                                       if ( !jQuery.support.tbody ) {
-
-                                               // String was a <table>, *may* have spurious <tbody>
-                                               hasBody = rtbody.test(elem);
-                                                       tbody = tag === "table" && !hasBody ?
-                                                               div.firstChild && div.firstChild.childNodes :
-
-                                                               // String was a bare <thead> or <tfoot>
-                                                               wrap[1] === "<table>" && !hasBody ?
-                                                                       div.childNodes :
-                                                                       [];
-
-                                               for ( j = tbody.length - 1; j >= 0 ; --j ) {
-                                                       if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
-                                                               tbody[ j ].parentNode.removeChild( tbody[ j ] );
-                                                       }
-                                               }
-                                       }
-
-                                       // IE completely kills leading whitespace when innerHTML is used
-                                       if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
-                                               div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
-                                       }
-
-                                       elem = div.childNodes;
-
-                                       // Take out of fragment container (we need a fresh div each time)
-                                       div.parentNode.removeChild( div );
-                               }
-                       }
-
-                       if ( elem.nodeType ) {
-                               ret.push( elem );
-                       } else {
-                               jQuery.merge( ret, elem );
-                       }
-               }
-
-               // Fix #11356: Clear elements from safeFragment
-               if ( div ) {
-                       elem = div = safe = null;
-               }
-
-               // Reset defaultChecked for any radios and checkboxes
-               // about to be appended to the DOM in IE 6/7 (#8060)
-               if ( !jQuery.support.appendChecked ) {
-                       for ( i = 0; (elem = ret[i]) != null; i++ ) {
-                               if ( jQuery.nodeName( elem, "input" ) ) {
-                                       fixDefaultChecked( elem );
-                               } else if ( typeof elem.getElementsByTagName !== "undefined" ) {
-                                       jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
-                               }
-                       }
-               }
-
-               // Append elements to a provided document fragment
-               if ( fragment ) {
-                       // Special handling of each script element
-                       handleScript = function( elem ) {
-                               // Check if we consider it executable
-                               if ( !elem.type || rscriptType.test( elem.type ) ) {
-                                       // Detach the script and store it in the scripts array (if provided) or the fragment
-                                       // Return truthy to indicate that it has been handled
-                                       return scripts ?
-                                               scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) :
-                                               fragment.appendChild( elem );
-                               }
-                       };
-
-                       for ( i = 0; (elem = ret[i]) != null; i++ ) {
-                               // Check if we're done after handling an executable script
-                               if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) {
-                                       // Append to fragment and handle embedded scripts
-                                       fragment.appendChild( elem );
-                                       if ( typeof elem.getElementsByTagName !== "undefined" ) {
-                                               // handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration
-                                               jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript );
-
-                                               // Splice the scripts into ret after their former ancestor and advance our index beyond them
-                                               ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
-                                               i += jsTags.length;
-                                       }
-                               }
-                       }
-               }
-
-               return ret;
-       },
-
-       cleanData: function( elems, /* internal */ acceptData ) {
-               var data, id, elem, type,
-                       i = 0,
-                       internalKey = jQuery.expando,
-                       cache = jQuery.cache,
-                       deleteExpando = jQuery.support.deleteExpando,
-                       special = jQuery.event.special;
-
-               for ( ; (elem = elems[i]) != null; i++ ) {
-
-                       if ( acceptData || jQuery.acceptData( elem ) ) {
-
-                               id = elem[ internalKey ];
-                               data = id && cache[ id ];
-
-                               if ( data ) {
-                                       if ( data.events ) {
-                                               for ( type in data.events ) {
-                                                       if ( special[ type ] ) {
-                                                               jQuery.event.remove( elem, type );
-
-                                                       // This is a shortcut to avoid jQuery.event.remove's overhead
-                                                       } else {
-                                                               jQuery.removeEvent( elem, type, data.handle );
-                                                       }
-                                               }
-                                       }
-
-                                       // Remove cache only if it was not already removed by jQuery.event.remove
-                                       if ( cache[ id ] ) {
-
-                                               delete cache[ id ];
-
-                                               // IE does not allow us to delete expando properties from nodes,
-                                               // nor does it have a removeAttribute function on Document nodes;
-                                               // we must handle all of these cases
-                                               if ( deleteExpando ) {
-                                                       delete elem[ internalKey ];
-
-                                               } else if ( elem.removeAttribute ) {
-                                                       elem.removeAttribute( internalKey );
-
-                                               } else {
-                                                       elem[ internalKey ] = null;
-                                               }
-
-                                               jQuery.deletedIds.push( id );
-                                       }
-                               }
-                       }
-               }
-       }
-});
-// Limit scope pollution from any deprecated API
-(function() {
-
-var matched, browser;
-
-// Use of jQuery.browser is frowned upon.
-// More details: http://api.jquery.com/jQuery.browser
-// jQuery.uaMatch maintained for back-compat
-jQuery.uaMatch = function( ua ) {
-       ua = ua.toLowerCase();
-
-       var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
-               /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
-               /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
-               /(msie) ([\w.]+)/.exec( ua ) ||
-               ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
-               [];
-
-       return {
-               browser: match[ 1 ] || "",
-               version: match[ 2 ] || "0"
-       };
-};
-
-matched = jQuery.uaMatch( navigator.userAgent );
-browser = {};
-
-if ( matched.browser ) {
-       browser[ matched.browser ] = true;
-       browser.version = matched.version;
-}
-
-// Chrome is Webkit, but Webkit is also Safari.
-if ( browser.chrome ) {
-       browser.webkit = true;
-} else if ( browser.webkit ) {
-       browser.safari = true;
-}
-
-jQuery.browser = browser;
-
-jQuery.sub = function() {
-       function jQuerySub( selector, context ) {
-               return new jQuerySub.fn.init( selector, context );
-       }
-       jQuery.extend( true, jQuerySub, this );
-       jQuerySub.superclass = this;
-       jQuerySub.fn = jQuerySub.prototype = this();
-       jQuerySub.fn.constructor = jQuerySub;
-       jQuerySub.sub = this.sub;
-       jQuerySub.fn.init = function init( selector, context ) {
-               if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
-                       context = jQuerySub( context );
-               }
-
-               return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
-       };
-       jQuerySub.fn.init.prototype = jQuerySub.fn;
-       var rootjQuerySub = jQuerySub(document);
-       return jQuerySub;
-};
-
-})();
-var curCSS, iframe, iframeDoc,
-       ralpha = /alpha\([^)]*\)/i,
-       ropacity = /opacity=([^)]*)/,
-       rposition = /^(top|right|bottom|left)$/,
-       // swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
-       // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
-       rdisplayswap = /^(none|table(?!-c[ea]).+)/,
-       rmargin = /^margin/,
-       rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
-       rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
-       rrelNum = new RegExp( "^([-+])=(" + core_pnum + ")", "i" ),
-       elemdisplay = { BODY: "block" },
-
-       cssShow = { position: "absolute", visibility: "hidden", display: "block" },
-       cssNormalTransform = {
-               letterSpacing: 0,
-               fontWeight: 400
-       },
-
-       cssExpand = [ "Top", "Right", "Bottom", "Left" ],
-       cssPrefixes = [ "Webkit", "O", "Moz", "ms" ],
-
-       eventsToggle = jQuery.fn.toggle;
-
-// return a css property mapped to a potentially vendor prefixed property
-function vendorPropName( style, name ) {
-
-       // shortcut for names that are not vendor prefixed
-       if ( name in style ) {
-               return name;
-       }
-
-       // check for vendor prefixed names
-       var capName = name.charAt(0).toUpperCase() + name.slice(1),
-               origName = name,
-               i = cssPrefixes.length;
-
-       while ( i-- ) {
-               name = cssPrefixes[ i ] + capName;
-               if ( name in style ) {
-                       return name;
-               }
-       }
-
-       return origName;
-}
-
-function isHidden( elem, el ) {
-       elem = el || elem;
-       return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
-}
-
-function showHide( elements, show ) {
-       var elem, display,
-               values = [],
-               index = 0,
-               length = elements.length;
-
-       for ( ; index < length; index++ ) {
-               elem = elements[ index ];
-               if ( !elem.style ) {
-                       continue;
-               }
-               values[ index ] = jQuery._data( elem, "olddisplay" );
-               if ( show ) {
-                       // Reset the inline display of this element to learn if it is
-                       // being hidden by cascaded rules or not
-                       if ( !values[ index ] && elem.style.display === "none" ) {
-                               elem.style.display = "";
-                       }
-
-                       // Set elements which have been overridden with display: none
-                       // in a stylesheet to whatever the default browser style is
-                       // for such an element
-                       if ( elem.style.display === "" && isHidden( elem ) ) {
-                               values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) );
-                       }
-               } else {
-                       display = curCSS( elem, "display" );
-
-                       if ( !values[ index ] && display !== "none" ) {
-                               jQuery._data( elem, "olddisplay", display );
-                       }
-               }
-       }
-
-       // Set the display of most of the elements in a second loop
-       // to avoid the constant reflow
-       for ( index = 0; index < length; index++ ) {
-               elem = elements[ index ];
-               if ( !elem.style ) {
-                       continue;
-               }
-               if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
-                       elem.style.display = show ? values[ index ] || "" : "none";
-               }
-       }
-
-       return elements;
-}
-
-jQuery.fn.extend({
-       css: function( name, value ) {
-               return jQuery.access( this, function( elem, name, value ) {
-                       return value !== undefined ?
-                               jQuery.style( elem, name, value ) :
-                               jQuery.css( elem, name );
-               }, name, value, arguments.length > 1 );
-       },
-       show: function() {
-               return showHide( this, true );
-       },
-       hide: function() {
-               return showHide( this );
-       },
-       toggle: function( state, fn2 ) {
-               var bool = typeof state === "boolean";
-
-               if ( jQuery.isFunction( state ) && jQuery.isFunction( fn2 ) ) {
-                       return eventsToggle.apply( this, arguments );
-               }
-
-               return this.each(function() {
-                       if ( bool ? state : isHidden( this ) ) {
-                               jQuery( this ).show();
-                       } else {
-                               jQuery( this ).hide();
-                       }
-               });
-       }
-});
-
-jQuery.extend({
-       // Add in style property hooks for overriding the default
-       // behavior of getting and setting a style property
-       cssHooks: {
-               opacity: {
-                       get: function( elem, computed ) {
-                               if ( computed ) {
-                                       // We should always get a number back from opacity
-                                       var ret = curCSS( elem, "opacity" );
-                                       return ret === "" ? "1" : ret;
-
-                               }
-                       }
-               }
-       },
-
-       // Exclude the following css properties to add px
-       cssNumber: {
-               "fillOpacity": true,
-               "fontWeight": true,
-               "lineHeight": true,
-               "opacity": true,
-               "orphans": true,
-               "widows": true,
-               "zIndex": true,
-               "zoom": true
-       },
-
-       // Add in properties whose names you wish to fix before
-       // setting or getting the value
-       cssProps: {
-               // normalize float css property
-               "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
-       },
-
-       // Get and set the style property on a DOM Node
-       style: function( elem, name, value, extra ) {
-               // Don't set styles on text and comment nodes
-               if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
-                       return;
-               }
-
-               // Make sure that we're working with the right name
-               var ret, type, hooks,
-                       origName = jQuery.camelCase( name ),
-                       style = elem.style;
-
-               name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
-
-               // gets hook for the prefixed version
-               // followed by the unprefixed version
-               hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
-
-               // Check if we're setting a value
-               if ( value !== undefined ) {
-                       type = typeof value;
-
-                       // convert relative number strings (+= or -=) to relative numbers. #7345
-                       if ( type === "string" && (ret = rrelNum.exec( value )) ) {
-                               value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
-                               // Fixes bug #9237
-                               type = "number";
-                       }
-
-                       // Make sure that NaN and null values aren't set. See: #7116
-                       if ( value == null || type === "number" && isNaN( value ) ) {
-                               return;
-                       }
-
-                       // If a number was passed in, add 'px' to the (except for certain CSS properties)
-                       if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
-                               value += "px";
-                       }
-
-                       // If a hook was provided, use that value, otherwise just set the specified value
-                       if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
-                               // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
-                               // Fixes bug #5509
-                               try {
-                                       style[ name ] = value;
-                               } catch(e) {}
-                       }
-
-               } else {
-                       // If a hook was provided get the non-computed value from there
-                       if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
-                               return ret;
-                       }
-
-                       // Otherwise just get the value from the style object
-                       return style[ name ];
-               }
-       },
-
-       css: function( elem, name, numeric, extra ) {
-               var val, num, hooks,
-                       origName = jQuery.camelCase( name );
-
-               // Make sure that we're working with the right name
-               name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
-
-               // gets hook for the prefixed version
-               // followed by the unprefixed version
-               hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
-
-               // If a hook was provided get the computed value from there
-               if ( hooks && "get" in hooks ) {
-                       val = hooks.get( elem, true, extra );
-               }
-
-               // Otherwise, if a way to get the computed value exists, use that
-               if ( val === undefined ) {
-                       val = curCSS( elem, name );
-               }
-
-               //convert "normal" to computed value
-               if ( val === "normal" && name in cssNormalTransform ) {
-                       val = cssNormalTransform[ name ];
-               }
-
-               // Return, converting to number if forced or a qualifier was provided and val looks numeric
-               if ( numeric || extra !== undefined ) {
-                       num = parseFloat( val );
-                       return numeric || jQuery.isNumeric( num ) ? num || 0 : val;
-               }
-               return val;
-       },
-
-       // A method for quickly swapping in/out CSS properties to get correct calculations
-       swap: function( elem, options, callback ) {
-               var ret, name,
-                       old = {};
-
-               // Remember the old values, and insert the new ones
-               for ( name in options ) {
-                       old[ name ] = elem.style[ name ];
-                       elem.style[ name ] = options[ name ];
-               }
-
-               ret = callback.call( elem );
-
-               // Revert the old values
-               for ( name in options ) {
-                       elem.style[ name ] = old[ name ];
-               }
-
-               return ret;
-       }
-});
-
-// NOTE: To any future maintainer, we've window.getComputedStyle
-// because jsdom on node.js will break without it.
-if ( window.getComputedStyle ) {
-       curCSS = function( elem, name ) {
-               var ret, width, minWidth, maxWidth,
-                       computed = window.getComputedStyle( elem, null ),
-                       style = elem.style;
-
-               if ( computed ) {
-
-                       // getPropertyValue is only needed for .css('filter') in IE9, see #12537
-                       ret = computed.getPropertyValue( name ) || computed[ name ];
-
-                       if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
-                               ret = jQuery.style( elem, name );
-                       }
-
-                       // A tribute to the "awesome hack by Dean Edwards"
-                       // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right
-                       // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
-                       // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
-                       if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
-                               width = style.width;
-                               minWidth = style.minWidth;
-                               maxWidth = style.maxWidth;
-
-                               style.minWidth = style.maxWidth = style.width = ret;
-                               ret = computed.width;
-
-                               style.width = width;
-                               style.minWidth = minWidth;
-                               style.maxWidth = maxWidth;
-                       }
-               }
-
-               return ret;
-       };
-} else if ( document.documentElement.currentStyle ) {
-       curCSS = function( elem, name ) {
-               var left, rsLeft,
-                       ret = elem.currentStyle && elem.currentStyle[ name ],
-                       style = elem.style;
-
-               // Avoid setting ret to empty string here
-               // so we don't default to auto
-               if ( ret == null && style && style[ name ] ) {
-                       ret = style[ name ];
-               }
-
-               // From the awesome hack by Dean Edwards
-               // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
-
-               // If we're not dealing with a regular pixel number
-               // but a number that has a weird ending, we need to convert it to pixels
-               // but not position css attributes, as those are proportional to the parent element instead
-               // and we can't measure the parent instead because it might trigger a "stacking dolls" problem
-               if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
-
-                       // Remember the original values
-                       left = style.left;
-                       rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;
-
-                       // Put in the new values to get a computed value out
-                       if ( rsLeft ) {
-                               elem.runtimeStyle.left = elem.currentStyle.left;
-                       }
-                       style.left = name === "fontSize" ? "1em" : ret;
-                       ret = style.pixelLeft + "px";
-
-                       // Revert the changed values
-                       style.left = left;
-                       if ( rsLeft ) {
-                               elem.runtimeStyle.left = rsLeft;
-                       }
-               }
-
-               return ret === "" ? "auto" : ret;
-       };
-}
-
-function setPositiveNumber( elem, value, subtract ) {
-       var matches = rnumsplit.exec( value );
-       return matches ?
-                       Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
-                       value;
-}
-
-function augmentWidthOrHeight( elem, name, extra, isBorderBox ) {
-       var i = extra === ( isBorderBox ? "border" : "content" ) ?
-               // If we already have the right measurement, avoid augmentation
-               4 :
-               // Otherwise initialize for horizontal or vertical properties
-               name === "width" ? 1 : 0,
-
-               val = 0;
-
-       for ( ; i < 4; i += 2 ) {
-               // both box models exclude margin, so add it if we want it
-               if ( extra === "margin" ) {
-                       // we use jQuery.css instead of curCSS here
-                       // because of the reliableMarginRight CSS hook!
-                       val += jQuery.css( elem, extra + cssExpand[ i ], true );
-               }
-
-               // From this point on we use curCSS for maximum performance (relevant in animations)
-               if ( isBorderBox ) {
-                       // border-box includes padding, so remove it if we want content
-                       if ( extra === "content" ) {
-                               val -= parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
-                       }
-
-                       // at this point, extra isn't border nor margin, so remove border
-                       if ( extra !== "margin" ) {
-                               val -= parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
-                       }
-               } else {
-                       // at this point, extra isn't content, so add padding
-                       val += parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
-
-                       // at this point, extra isn't content nor padding, so add border
-                       if ( extra !== "padding" ) {
-                               val += parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
-                       }
-               }
-       }
-
-       return val;
-}
-
-function getWidthOrHeight( elem, name, extra ) {
-
-       // Start with offset property, which is equivalent to the border-box value
-       var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
-               valueIsBorderBox = true,
-               isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box";
-
-       // some non-html elements return undefined for offsetWidth, so check for null/undefined
-       // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
-       // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
-       if ( val <= 0 || val == null ) {
-               // Fall back to computed then uncomputed css if necessary
-               val = curCSS( elem, name );
-               if ( val < 0 || val == null ) {
-                       val = elem.style[ name ];
-               }
-
-               // Computed unit is not pixels. Stop here and return.
-               if ( rnumnonpx.test(val) ) {
-                       return val;
-               }
-
-               // we need the check for style in case a browser which returns unreliable values
-               // for getComputedStyle silently falls back to the reliable elem.style
-               valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );
-
-               // Normalize "", auto, and prepare for extra
-               val = parseFloat( val ) || 0;
-       }
-
-       // use the active box-sizing model to add/subtract irrelevant styles
-       return ( val +
-               augmentWidthOrHeight(
-                       elem,
-                       name,
-                       extra || ( isBorderBox ? "border" : "content" ),
-                       valueIsBorderBox
-               )
-       ) + "px";
-}
-
-
-// Try to determine the default display value of an element
-function css_defaultDisplay( nodeName ) {
-       if ( elemdisplay[ nodeName ] ) {
-               return elemdisplay[ nodeName ];
-       }
-
-       var elem = jQuery( "<" + nodeName + ">" ).appendTo( document.body ),
-               display = elem.css("display");
-       elem.remove();
-
-       // If the simple way fails,
-       // get element's real default display by attaching it to a temp iframe
-       if ( display === "none" || display === "" ) {
-               // Use the already-created iframe if possible
-               iframe = document.body.appendChild(
-                       iframe || jQuery.extend( document.createElement("iframe"), {
-                               frameBorder: 0,
-                               width: 0,
-                               height: 0
-                       })
-               );
-
-               // Create a cacheable copy of the iframe document on first call.
-               // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
-               // document to it; WebKit & Firefox won't allow reusing the iframe document.
-               if ( !iframeDoc || !iframe.createElement ) {
-                       iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
-                       iframeDoc.write("<!doctype html><html><body>");
-                       iframeDoc.close();
-               }
-
-               elem = iframeDoc.body.appendChild( iframeDoc.createElement(nodeName) );
-
-               display = curCSS( elem, "display" );
-               document.body.removeChild( iframe );
-       }
-
-       // Store the correct default display
-       elemdisplay[ nodeName ] = display;
-
-       return display;
-}
-
-jQuery.each([ "height", "width" ], function( i, name ) {
-       jQuery.cssHooks[ name ] = {
-               get: function( elem, computed, extra ) {
-                       if ( computed ) {
-                               // certain elements can have dimension info if we invisibly show them
-                               // however, it must have a current display style that would benefit from this
-                               if ( elem.offsetWidth === 0 && rdisplayswap.test( curCSS( elem, "display" ) ) ) {
-                                       return jQuery.swap( elem, cssShow, function() {
-                                               return getWidthOrHeight( elem, name, extra );
-                                       });
-                               } else {
-                                       return getWidthOrHeight( elem, name, extra );
-                               }
-                       }
-               },
-
-               set: function( elem, value, extra ) {
-                       return setPositiveNumber( elem, value, extra ?
-                               augmentWidthOrHeight(
-                                       elem,
-                                       name,
-                                       extra,
-                                       jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box"
-                               ) : 0
-                       );
-               }
-       };
-});
-
-if ( !jQuery.support.opacity ) {
-       jQuery.cssHooks.opacity = {
-               get: function( elem, computed ) {
-                       // IE uses filters for opacity
-                       return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
-                               ( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
-                               computed ? "1" : "";
-               },
-
-               set: function( elem, value ) {
-                       var style = elem.style,
-                               currentStyle = elem.currentStyle,
-                               opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
-                               filter = currentStyle && currentStyle.filter || style.filter || "";
-
-                       // IE has trouble with opacity if it does not have layout
-                       // Force it by setting the zoom level
-                       style.zoom = 1;
-
-                       // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
-                       if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
-                               style.removeAttribute ) {
-
-                               // Setting style.filter to null, "" & " " still leave "filter:" in the cssText
-                               // if "filter:" is present at all, clearType is disabled, we want to avoid this
-                               // style.removeAttribute is IE Only, but so apparently is this code path...
-                               style.removeAttribute( "filter" );
-
-                               // if there there is no filter style applied in a css rule, we are done
-                               if ( currentStyle && !currentStyle.filter ) {
-                                       return;
-                               }
-                       }
-
-                       // otherwise, set new filter values
-                       style.filter = ralpha.test( filter ) ?
-                               filter.replace( ralpha, opacity ) :
-                               filter + " " + opacity;
-               }
-       };
-}
-
-// These hooks cannot be added until DOM ready because the support test
-// for it is not run until after DOM ready
-jQuery(function() {
-       if ( !jQuery.support.reliableMarginRight ) {
-               jQuery.cssHooks.marginRight = {
-                       get: function( elem, computed ) {
-                               // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
-                               // Work around by temporarily setting element display to inline-block
-                               return jQuery.swap( elem, { "display": "inline-block" }, function() {
-                                       if ( computed ) {
-                                               return curCSS( elem, "marginRight" );
-                                       }
-                               });
-                       }
-               };
-       }
-
-       // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
-       // getComputedStyle returns percent when specified for top/left/bottom/right
-       // rather than make the css module depend on the offset module, we just check for it here
-       if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
-               jQuery.each( [ "top", "left" ], function( i, prop ) {
-                       jQuery.cssHooks[ prop ] = {
-                               get: function( elem, computed ) {
-                                       if ( computed ) {
-                                               var ret = curCSS( elem, prop );
-                                               // if curCSS returns percentage, fallback to offset
-                                               return rnumnonpx.test( ret ) ? jQuery( elem ).position()[ prop ] + "px" : ret;
-                                       }
-                               }
-                       };
-               });
-       }
-
-});
-
-if ( jQuery.expr && jQuery.expr.filters ) {
-       jQuery.expr.filters.hidden = function( elem ) {
-               return ( elem.offsetWidth === 0 && elem.offsetHeight === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || curCSS( elem, "display" )) === "none");
-       };
-
-       jQuery.expr.filters.visible = function( elem ) {
-               return !jQuery.expr.filters.hidden( elem );
-       };
-}
-
-// These hooks are used by animate to expand properties
-jQuery.each({
-       margin: "",
-       padding: "",
-       border: "Width"
-}, function( prefix, suffix ) {
-       jQuery.cssHooks[ prefix + suffix ] = {
-               expand: function( value ) {
-                       var i,
-
-                               // assumes a single number if not a string
-                               parts = typeof value === "string" ? value.split(" ") : [ value ],
-                               expanded = {};
-
-                       for ( i = 0; i < 4; i++ ) {
-                               expanded[ prefix + cssExpand[ i ] + suffix ] =
-                                       parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
-                       }
-
-                       return expanded;
-               }
-       };
-
-       if ( !rmargin.test( prefix ) ) {
-               jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
-       }
-});
-var r20 = /%20/g,
-       rbracket = /\[\]$/,
-       rCRLF = /\r?\n/g,
-       rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
-       rselectTextarea = /^(?:select|textarea)/i;
-
-jQuery.fn.extend({
-       serialize: function() {
-               return jQuery.param( this.serializeArray() );
-       },
-       serializeArray: function() {
-               return this.map(function(){
-                       return this.elements ? jQuery.makeArray( this.elements ) : this;
-               })
-               .filter(function(){
-                       return this.name && !this.disabled &&
-                               ( this.checked || rselectTextarea.test( this.nodeName ) ||
-                                       rinput.test( this.type ) );
-               })
-               .map(function( i, elem ){
-                       var val = jQuery( this ).val();
-
-                       return val == null ?
-                               null :
-                               jQuery.isArray( val ) ?
-                                       jQuery.map( val, function( val, i ){
-                                               return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
-                                       }) :
-                                       { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
-               }).get();
-       }
-});
-
-//Serialize an array of form elements or a set of
-//key/values into a query string
-jQuery.param = function( a, traditional ) {
-       var prefix,
-               s = [],
-               add = function( key, value ) {
-                       // If value is a function, invoke it and return its value
-                       value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
-                       s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
-               };
-
-       // Set traditional to true for jQuery <= 1.3.2 behavior.
-       if ( traditional === undefined ) {
-               traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
-       }
-
-       // If an array was passed in, assume that it is an array of form elements.
-       if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
-               // Serialize the form elements
-               jQuery.each( a, function() {
-                       add( this.name, this.value );
-               });
-
-       } else {
-               // If traditional, encode the "old" way (the way 1.3.2 or older
-               // did it), otherwise encode params recursively.
-               for ( prefix in a ) {
-                       buildParams( prefix, a[ prefix ], traditional, add );
-               }
-       }
-
-       // Return the resulting serialization
-       return s.join( "&" ).replace( r20, "+" );
-};
-
-function buildParams( prefix, obj, traditional, add ) {
-       var name;
-
-       if ( jQuery.isArray( obj ) ) {
-               // Serialize array item.
-               jQuery.each( obj, function( i, v ) {
-                       if ( traditional || rbracket.test( prefix ) ) {
-                               // Treat each array item as a scalar.
-                               add( prefix, v );
-
-                       } else {
-                               // If array item is non-scalar (array or object), encode its
-                               // numeric index to resolve deserialization ambiguity issues.
-                               // Note that rack (as of 1.0.0) can't currently deserialize
-                               // nested arrays properly, and attempting to do so may cause
-                               // a server error. Possible fixes are to modify rack's
-                               // deserialization algorithm or to provide an option or flag
-                               // to force array serialization to be shallow.
-                               buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
-                       }
-               });
-
-       } else if ( !traditional && jQuery.type( obj ) === "object" ) {
-               // Serialize object item.
-               for ( name in obj ) {
-                       buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
-               }
-
-       } else {
-               // Serialize scalar item.
-               add( prefix, obj );
-       }
-}
-var
-       // Document location
-       ajaxLocParts,
-       ajaxLocation,
-
-       rhash = /#.*$/,
-       rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
-       // #7653, #8125, #8152: local protocol detection
-       rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
-       rnoContent = /^(?:GET|HEAD)$/,
-       rprotocol = /^\/\//,
-       rquery = /\?/,
-       rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
-       rts = /([?&])_=[^&]*/,
-       rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,
-
-       // Keep a copy of the old load method
-       _load = jQuery.fn.load,
-
-       /* Prefilters
-        * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
-        * 2) These are called:
-        *    - BEFORE asking for a transport
-        *    - AFTER param serialization (s.data is a string if s.processData is true)
-        * 3) key is the dataType
-        * 4) the catchall symbol "*" can be used
-        * 5) execution will start with transport dataType and THEN continue down to "*" if needed
-        */
-       prefilters = {},
-
-       /* Transports bindings
-        * 1) key is the dataType
-        * 2) the catchall symbol "*" can be used
-        * 3) selection will start with transport dataType and THEN go to "*" if needed
-        */
-       transports = {},
-
-       // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
-       allTypes = ["*/"] + ["*"];
-
-// #8138, IE may throw an exception when accessing
-// a field from window.location if document.domain has been set
-try {
-       ajaxLocation = location.href;
-} catch( e ) {
-       // Use the href attribute of an A element
-       // since IE will modify it given document.location
-       ajaxLocation = document.createElement( "a" );
-       ajaxLocation.href = "";
-       ajaxLocation = ajaxLocation.href;
-}
-
-// Segment location into parts
-ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
-
-// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
-function addToPrefiltersOrTransports( structure ) {
-
-       // dataTypeExpression is optional and defaults to "*"
-       return function( dataTypeExpression, func ) {
-
-               if ( typeof dataTypeExpression !== "string" ) {
-                       func = dataTypeExpression;
-                       dataTypeExpression = "*";
-               }
-
-               var dataType, list, placeBefore,
-                       dataTypes = dataTypeExpression.toLowerCase().split( core_rspace ),
-                       i = 0,
-                       length = dataTypes.length;
-
-               if ( jQuery.isFunction( func ) ) {
-                       // For each dataType in the dataTypeExpression
-                       for ( ; i < length; i++ ) {
-                               dataType = dataTypes[ i ];
-                               // We control if we're asked to add before
-                               // any existing element
-                               placeBefore = /^\+/.test( dataType );
-                               if ( placeBefore ) {
-                                       dataType = dataType.substr( 1 ) || "*";
-                               }
-                               list = structure[ dataType ] = structure[ dataType ] || [];
-                               // then we add to the structure accordingly
-                               list[ placeBefore ? "unshift" : "push" ]( func );
-                       }
-               }
-       };
-}
-
-// Base inspection function for prefilters and transports
-function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
-               dataType /* internal */, inspected /* internal */ ) {
-
-       dataType = dataType || options.dataTypes[ 0 ];
-       inspected = inspected || {};
-
-       inspected[ dataType ] = true;
-
-       var selection,
-               list = structure[ dataType ],
-               i = 0,
-               length = list ? list.length : 0,
-               executeOnly = ( structure === prefilters );
-
-       for ( ; i < length && ( executeOnly || !selection ); i++ ) {
-               selection = list[ i ]( options, originalOptions, jqXHR );
-               // If we got redirected to another dataType
-               // we try there if executing only and not done already
-               if ( typeof selection === "string" ) {
-                       if ( !executeOnly || inspected[ selection ] ) {
-                               selection = undefined;
-                       } else {
-                               options.dataTypes.unshift( selection );
-                               selection = inspectPrefiltersOrTransports(
-                                               structure, options, originalOptions, jqXHR, selection, inspected );
-                       }
-               }
-       }
-       // If we're only executing or nothing was selected
-       // we try the catchall dataType if not done already
-       if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
-               selection = inspectPrefiltersOrTransports(
-                               structure, options, originalOptions, jqXHR, "*", inspected );
-       }
-       // unnecessary when only executing (prefilters)
-       // but it'll be ignored by the caller in that case
-       return selection;
-}
-
-// A special extend for ajax options
-// that takes "flat" options (not to be deep extended)
-// Fixes #9887
-function ajaxExtend( target, src ) {
-       var key, deep,
-               flatOptions = jQuery.ajaxSettings.flatOptions || {};
-       for ( key in src ) {
-               if ( src[ key ] !== undefined ) {
-                       ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
-               }
-       }
-       if ( deep ) {
-               jQuery.extend( true, target, deep );
-       }
-}
-
-jQuery.fn.load = function( url, params, callback ) {
-       if ( typeof url !== "string" && _load ) {
-               return _load.apply( this, arguments );
-       }
-
-       // Don't do a request if no elements are being requested
-       if ( !this.length ) {
-               return this;
-       }
-
-       var selector, type, response,
-               self = this,
-               off = url.indexOf(" ");
-
-       if ( off >= 0 ) {
-               selector = url.slice( off, url.length );
-               url = url.slice( 0, off );
-       }
-
-       // If it's a function
-       if ( jQuery.isFunction( params ) ) {
-
-               // We assume that it's the callback
-               callback = params;
-               params = undefined;
-
-       // Otherwise, build a param string
-       } else if ( params && typeof params === "object" ) {
-               type = "POST";
-       }
-
-       // Request the remote document
-       jQuery.ajax({
-               url: url,
-
-               // if "type" variable is undefined, then "GET" method will be used
-               type: type,
-               dataType: "html",
-               data: params,
-               complete: function( jqXHR, status ) {
-                       if ( callback ) {
-                               self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
-                       }
-               }
-       }).done(function( responseText ) {
-
-               // Save response for use in complete callback
-               response = arguments;
-
-               // See if a selector was specified
-               self.html( selector ?
-
-                       // Create a dummy div to hold the results
-                       jQuery("<div>")
-
-                               // inject the contents of the document in, removing the scripts
-                               // to avoid any 'Permission Denied' errors in IE
-                               .append( responseText.replace( rscript, "" ) )
-
-                               // Locate the specified elements
-                               .find( selector ) :
-
-                       // If not, just inject the full result
-                       responseText );
-
-       });
-
-       return this;
-};
-
-// Attach a bunch of functions for handling common AJAX events
-jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
-       jQuery.fn[ o ] = function( f ){
-               return this.on( o, f );
-       };
-});
-
-jQuery.each( [ "get", "post" ], function( i, method ) {
-       jQuery[ method ] = function( url, data, callback, type ) {
-               // shift arguments if data argument was omitted
-               if ( jQuery.isFunction( data ) ) {
-                       type = type || callback;
-                       callback = data;
-                       data = undefined;
-               }
-
-               return jQuery.ajax({
-                       type: method,
-                       url: url,
-                       data: data,
-                       success: callback,
-                       dataType: type
-               });
-       };
-});
-
-jQuery.extend({
-
-       getScript: function( url, callback ) {
-               return jQuery.get( url, undefined, callback, "script" );
-       },
-
-       getJSON: function( url, data, callback ) {
-               return jQuery.get( url, data, callback, "json" );
-       },
-
-       // Creates a full fledged settings object into target
-       // with both ajaxSettings and settings fields.
-       // If target is omitted, writes into ajaxSettings.
-       ajaxSetup: function( target, settings ) {
-               if ( settings ) {
-                       // Building a settings object
-                       ajaxExtend( target, jQuery.ajaxSettings );
-               } else {
-                       // Extending ajaxSettings
-                       settings = target;
-                       target = jQuery.ajaxSettings;
-               }
-               ajaxExtend( target, settings );
-               return target;
-       },
-
-       ajaxSettings: {
-               url: ajaxLocation,
-               isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
-               global: true,
-               type: "GET",
-               contentType: "application/x-www-form-urlencoded; charset=UTF-8",
-               processData: true,
-               async: true,
-               /*
-               timeout: 0,
-               data: null,
-               dataType: null,
-               username: null,
-               password: null,
-               cache: null,
-               throws: false,
-               traditional: false,
-               headers: {},
-               */
-
-               accepts: {
-                       xml: "application/xml, text/xml",
-                       html: "text/html",
-                       text: "text/plain",
-                       json: "application/json, text/javascript",
-                       "*": allTypes
-               },
-
-               contents: {
-                       xml: /xml/,
-                       html: /html/,
-                       json: /json/
-               },
-
-               responseFields: {
-                       xml: "responseXML",
-                       text: "responseText"
-               },
-
-               // List of data converters
-               // 1) key format is "source_type destination_type" (a single space in-between)
-               // 2) the catchall symbol "*" can be used for source_type
-               converters: {
-
-                       // Convert anything to text
-                       "* text": window.String,
-
-                       // Text to html (true = no transformation)
-                       "text html": true,
-
-                       // Evaluate text as a json expression
-                       "text json": jQuery.parseJSON,
-
-                       // Parse text as xml
-                       "text xml": jQuery.parseXML
-               },
-
-               // For options that shouldn't be deep extended:
-               // you can add your own custom options here if
-               // and when you create one that shouldn't be
-               // deep extended (see ajaxExtend)
-               flatOptions: {
-                       context: true,
-                       url: true
-               }
-       },
-
-       ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
-       ajaxTransport: addToPrefiltersOrTransports( transports ),
-
-       // Main method
-       ajax: function( url, options ) {
-
-               // If url is an object, simulate pre-1.5 signature
-               if ( typeof url === "object" ) {
-                       options = url;
-                       url = undefined;
-               }
-
-               // Force options to be an object
-               options = options || {};
-
-               var // ifModified key
-                       ifModifiedKey,
-                       // Response headers
-                       responseHeadersString,
-                       responseHeaders,
-                       // transport
-                       transport,
-                       // timeout handle
-                       timeoutTimer,
-                       // Cross-domain detection vars
-                       parts,
-                       // To know if global events are to be dispatched
-                       fireGlobals,
-                       // Loop variable
-                       i,
-                       // Create the final options object
-                       s = jQuery.ajaxSetup( {}, options ),
-                       // Callbacks context
-                       callbackContext = s.context || s,
-                       // Context for global events
-                       // It's the callbackContext if one was provided in the options
-                       // and if it's a DOM node or a jQuery collection
-                       globalEventContext = callbackContext !== s &&
-                               ( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
-                                               jQuery( callbackContext ) : jQuery.event,
-                       // Deferreds
-                       deferred = jQuery.Deferred(),
-                       completeDeferred = jQuery.Callbacks( "once memory" ),
-                       // Status-dependent callbacks
-                       statusCode = s.statusCode || {},
-                       // Headers (they are sent all at once)
-                       requestHeaders = {},
-                       requestHeadersNames = {},
-                       // The jqXHR state
-                       state = 0,
-                       // Default abort message
-                       strAbort = "canceled",
-                       // Fake xhr
-                       jqXHR = {
-
-                               readyState: 0,
-
-                               // Caches the header
-                               setRequestHeader: function( name, value ) {
-                                       if ( !state ) {
-                                               var lname = name.toLowerCase();
-                                               name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
-                                               requestHeaders[ name ] = value;
-                                       }
-                                       return this;
-                               },
-
-                               // Raw string
-                               getAllResponseHeaders: function() {
-                                       return state === 2 ? responseHeadersString : null;
-                               },
-
-                               // Builds headers hashtable if needed
-                               getResponseHeader: function( key ) {
-                                       var match;
-                                       if ( state === 2 ) {
-                                               if ( !responseHeaders ) {
-                                                       responseHeaders = {};
-                                                       while( ( match = rheaders.exec( responseHeadersString ) ) ) {
-                                                               responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
-                                                       }
-                                               }
-                                               match = responseHeaders[ key.toLowerCase() ];
-                                       }
-                                       return match === undefined ? null : match;
-                               },
-
-                               // Overrides response content-type header
-                               overrideMimeType: function( type ) {
-                                       if ( !state ) {
-                                               s.mimeType = type;
-                                       }
-                                       return this;
-                               },
-
-                               // Cancel the request
-                               abort: function( statusText ) {
-                                       statusText = statusText || strAbort;
-                                       if ( transport ) {
-                                               transport.abort( statusText );
-                                       }
-                                       done( 0, statusText );
-                                       return this;
-                               }
-                       };
-
-               // Callback for when everything is done
-               // It is defined here because jslint complains if it is declared
-               // at the end of the function (which would be more logical and readable)
-               function done( status, nativeStatusText, responses, headers ) {
-                       var isSuccess, success, error, response, modified,
-                               statusText = nativeStatusText;
-
-                       // Called once
-                       if ( state === 2 ) {
-                               return;
-                       }
-
-                       // State is "done" now
-                       state = 2;
-
-                       // Clear timeout if it exists
-                       if ( timeoutTimer ) {
-                               clearTimeout( timeoutTimer );
-                       }
-
-                       // Dereference transport for early garbage collection
-                       // (no matter how long the jqXHR object will be used)
-                       transport = undefined;
-
-                       // Cache response headers
-                       responseHeadersString = headers || "";
-
-                       // Set readyState
-                       jqXHR.readyState = status > 0 ? 4 : 0;
-
-                       // Get response data
-                       if ( responses ) {
-                               response = ajaxHandleResponses( s, jqXHR, responses );
-                       }
-
-                       // If successful, handle type chaining
-                       if ( status >= 200 && status < 300 || status === 304 ) {
-
-                               // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
-                               if ( s.ifModified ) {
-
-                                       modified = jqXHR.getResponseHeader("Last-Modified");
-                                       if ( modified ) {
-                                               jQuery.lastModified[ ifModifiedKey ] = modified;
-                                       }
-                                       modified = jqXHR.getResponseHeader("Etag");
-                                       if ( modified ) {
-                                               jQuery.etag[ ifModifiedKey ] = modified;
-                                       }
-                               }
-
-                               // If not modified
-                               if ( status === 304 ) {
-
-                                       statusText = "notmodified";
-                                       isSuccess = true;
-
-                               // If we have data
-                               } else {
-
-                                       isSuccess = ajaxConvert( s, response );
-                                       statusText = isSuccess.state;
-                                       success = isSuccess.data;
-                                       error = isSuccess.error;
-                                       isSuccess = !error;
-                               }
-                       } else {
-                               // We extract error from statusText
-                               // then normalize statusText and status for non-aborts
-                               error = statusText;
-                               if ( !statusText || status ) {
-                                       statusText = "error";
-                                       if ( status < 0 ) {
-                                               status = 0;
-                                       }
-                               }
-                       }
-
-                       // Set data for the fake xhr object
-                       jqXHR.status = status;
-                       jqXHR.statusText = ( nativeStatusText || statusText ) + "";
-
-                       // Success/Error
-                       if ( isSuccess ) {
-                               deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
-                       } else {
-                               deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
-                       }
-
-                       // Status-dependent callbacks
-                       jqXHR.statusCode( statusCode );
-                       statusCode = undefined;
-
-                       if ( fireGlobals ) {
-                               globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
-                                               [ jqXHR, s, isSuccess ? success : error ] );
-                       }
-
-                       // Complete
-                       completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
-
-                       if ( fireGlobals ) {
-                               globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
-                               // Handle the global AJAX counter
-                               if ( !( --jQuery.active ) ) {
-                                       jQuery.event.trigger( "ajaxStop" );
-                               }
-                       }
-               }
-
-               // Attach deferreds
-               deferred.promise( jqXHR );
-               jqXHR.success = jqXHR.done;
-               jqXHR.error = jqXHR.fail;
-               jqXHR.complete = completeDeferred.add;
-
-               // Status-dependent callbacks
-               jqXHR.statusCode = function( map ) {
-                       if ( map ) {
-                               var tmp;
-                               if ( state < 2 ) {
-                                       for ( tmp in map ) {
-                                               statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
-                                       }
-                               } else {
-                                       tmp = map[ jqXHR.status ];
-                                       jqXHR.always( tmp );
-                               }
-                       }
-                       return this;
-               };
-
-               // Remove hash character (#7531: and string promotion)
-               // Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
-               // We also use the url parameter if available
-               s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
-
-               // Extract dataTypes list
-               s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( core_rspace );
-
-               // A cross-domain request is in order when we have a protocol:host:port mismatch
-               if ( s.crossDomain == null ) {
-                       parts = rurl.exec( s.url.toLowerCase() );
-                       s.crossDomain = !!( parts &&
-                               ( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
-                                       ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
-                                               ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
-                       );
-               }
-
-               // Convert data if not already a string
-               if ( s.data && s.processData && typeof s.data !== "string" ) {
-                       s.data = jQuery.param( s.data, s.traditional );
-               }
-
-               // Apply prefilters
-               inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
-
-               // If request was aborted inside a prefilter, stop there
-               if ( state === 2 ) {
-                       return jqXHR;
-               }
-
-               // We can fire global events as of now if asked to
-               fireGlobals = s.global;
-
-               // Uppercase the type
-               s.type = s.type.toUpperCase();
-
-               // Determine if request has content
-               s.hasContent = !rnoContent.test( s.type );
-
-               // Watch for a new set of requests
-               if ( fireGlobals && jQuery.active++ === 0 ) {
-                       jQuery.event.trigger( "ajaxStart" );
-               }
-
-               // More options handling for requests with no content
-               if ( !s.hasContent ) {
-
-                       // If data is available, append data to url
-                       if ( s.data ) {
-                               s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
-                               // #9682: remove data so that it's not used in an eventual retry
-                               delete s.data;
-                       }
-
-                       // Get ifModifiedKey before adding the anti-cache parameter
-                       ifModifiedKey = s.url;
-
-                       // Add anti-cache in url if needed
-                       if ( s.cache === false ) {
-
-                               var ts = jQuery.now(),
-                                       // try replacing _= if it is there
-                                       ret = s.url.replace( rts, "$1_=" + ts );
-
-                               // if nothing was replaced, add timestamp to the end
-                               s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
-                       }
-               }
-
-               // Set the correct header, if data is being sent
-               if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
-                       jqXHR.setRequestHeader( "Content-Type", s.contentType );
-               }
-
-               // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
-               if ( s.ifModified ) {
-                       ifModifiedKey = ifModifiedKey || s.url;
-                       if ( jQuery.lastModified[ ifModifiedKey ] ) {
-                               jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] );
-                       }
-                       if ( jQuery.etag[ ifModifiedKey ] ) {
-                               jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] );
-                       }
-               }
-
-               // Set the Accepts header for the server, depending on the dataType
-               jqXHR.setRequestHeader(
-                       "Accept",
-                       s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
-                               s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
-                               s.accepts[ "*" ]
-               );
-
-               // Check for headers option
-               for ( i in s.headers ) {
-                       jqXHR.setRequestHeader( i, s.headers[ i ] );
-               }
-
-               // Allow custom headers/mimetypes and early abort
-               if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
-                               // Abort if not done already and return
-                               return jqXHR.abort();
-
-               }
-
-               // aborting is no longer a cancellation
-               strAbort = "abort";
-
-               // Install callbacks on deferreds
-               for ( i in { success: 1, error: 1, complete: 1 } ) {
-                       jqXHR[ i ]( s[ i ] );
-               }
-
-               // Get transport
-               transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
-
-               // If no transport, we auto-abort
-               if ( !transport ) {
-                       done( -1, "No Transport" );
-               } else {
-                       jqXHR.readyState = 1;
-                       // Send global event
-                       if ( fireGlobals ) {
-                               globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
-                       }
-                       // Timeout
-                       if ( s.async && s.timeout > 0 ) {
-                               timeoutTimer = setTimeout( function(){
-                                       jqXHR.abort( "timeout" );
-                               }, s.timeout );
-                       }
-
-                       try {
-                               state = 1;
-                               transport.send( requestHeaders, done );
-                       } catch (e) {
-                               // Propagate exception as error if not done
-                               if ( state < 2 ) {
-                                       done( -1, e );
-                               // Simply rethrow otherwise
-                               } else {
-                                       throw e;
-                               }
-                       }
-               }
-
-               return jqXHR;
-       },
-
-       // Counter for holding the number of active queries
-       active: 0,
-
-       // Last-Modified header cache for next request
-       lastModified: {},
-       etag: {}
-
-});
-
-/* Handles responses to an ajax request:
- * - sets all responseXXX fields accordingly
- * - finds the right dataType (mediates between content-type and expected dataType)
- * - returns the corresponding response
- */
-function ajaxHandleResponses( s, jqXHR, responses ) {
-
-       var ct, type, finalDataType, firstDataType,
-               contents = s.contents,
-               dataTypes = s.dataTypes,
-               responseFields = s.responseFields;
-
-       // Fill responseXXX fields
-       for ( type in responseFields ) {
-               if ( type in responses ) {
-                       jqXHR[ responseFields[type] ] = responses[ type ];
-               }
-       }
-
-       // Remove auto dataType and get content-type in the process
-       while( dataTypes[ 0 ] === "*" ) {
-               dataTypes.shift();
-               if ( ct === undefined ) {
-                       ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
-               }
-       }
-
-       // Check if we're dealing with a known content-type
-       if ( ct ) {
-               for ( type in contents ) {
-                       if ( contents[ type ] && contents[ type ].test( ct ) ) {
-                               dataTypes.unshift( type );
-                               break;
-                       }
-               }
-       }
-
-       // Check to see if we have a response for the expected dataType
-       if ( dataTypes[ 0 ] in responses ) {
-               finalDataType = dataTypes[ 0 ];
-       } else {
-               // Try convertible dataTypes
-               for ( type in responses ) {
-                       if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
-                               finalDataType = type;
-                               break;
-                       }
-                       if ( !firstDataType ) {
-                               firstDataType = type;
-                       }
-               }
-               // Or just use first one
-               finalDataType = finalDataType || firstDataType;
-       }
-
-       // If we found a dataType
-       // We add the dataType to the list if needed
-       // and return the corresponding response
-       if ( finalDataType ) {
-               if ( finalDataType !== dataTypes[ 0 ] ) {
-                       dataTypes.unshift( finalDataType );
-               }
-               return responses[ finalDataType ];
-       }
-}
-
-// Chain conversions given the request and the original response
-function ajaxConvert( s, response ) {
-
-       var conv, conv2, current, tmp,
-               // Work with a copy of dataTypes in case we need to modify it for conversion
-               dataTypes = s.dataTypes.slice(),
-               prev = dataTypes[ 0 ],
-               converters = {},
-               i = 0;
-
-       // Apply the dataFilter if provided
-       if ( s.dataFilter ) {
-               response = s.dataFilter( response, s.dataType );
-       }
-
-       // Create converters map with lowercased keys
-       if ( dataTypes[ 1 ] ) {
-               for ( conv in s.converters ) {
-                       converters[ conv.toLowerCase() ] = s.converters[ conv ];
-               }
-       }
-
-       // Convert to each sequential dataType, tolerating list modification
-       for ( ; (current = dataTypes[++i]); ) {
-
-               // There's only work to do if current dataType is non-auto
-               if ( current !== "*" ) {
-
-                       // Convert response if prev dataType is non-auto and differs from current
-                       if ( prev !== "*" && prev !== current ) {
-
-                               // Seek a direct converter
-                               conv = converters[ prev + " " + current ] || converters[ "* " + current ];
-
-                               // If none found, seek a pair
-                               if ( !conv ) {
-                                       for ( conv2 in converters ) {
-
-                                               // If conv2 outputs current
-                                               tmp = conv2.split(" ");
-                                               if ( tmp[ 1 ] === current ) {
-
-                                                       // If prev can be converted to accepted input
-                                                       conv = converters[ prev + " " + tmp[ 0 ] ] ||
-                                                               converters[ "* " + tmp[ 0 ] ];
-                                                       if ( conv ) {
-                                                               // Condense equivalence converters
-                                                               if ( conv === true ) {
-                                                                       conv = converters[ conv2 ];
-
-                                                               // Otherwise, insert the intermediate dataType
-                                                               } else if ( converters[ conv2 ] !== true ) {
-                                                                       current = tmp[ 0 ];
-                                                                       dataTypes.splice( i--, 0, current );
-                                                               }
-
-                                                               break;
-                                                       }
-                                               }
-                                       }
-                               }
-
-                               // Apply converter (if not an equivalence)
-                               if ( conv !== true ) {
-
-                                       // Unless errors are allowed to bubble, catch and return them
-                                       if ( conv && s["throws"] ) {
-                                               response = conv( response );
-                                       } else {
-                                               try {
-                                                       response = conv( response );
-                                               } catch ( e ) {
-                                                       return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
-                                               }
-                                       }
-                               }
-                       }
-
-                       // Update prev for next iteration
-                       prev = current;
-               }
-       }
-
-       return { state: "success", data: response };
-}
-var oldCallbacks = [],
-       rquestion = /\?/,
-       rjsonp = /(=)\?(?=&|$)|\?\?/,
-       nonce = jQuery.now();
-
-// Default jsonp settings
-jQuery.ajaxSetup({
-       jsonp: "callback",
-       jsonpCallback: function() {
-               var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
-               this[ callback ] = true;
-               return callback;
-       }
-});
-
-// Detect, normalize options and install callbacks for jsonp requests
-jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
-
-       var callbackName, overwritten, responseContainer,
-               data = s.data,
-               url = s.url,
-               hasCallback = s.jsonp !== false,
-               replaceInUrl = hasCallback && rjsonp.test( url ),
-               replaceInData = hasCallback && !replaceInUrl && typeof data === "string" &&
-                       !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") &&
-                       rjsonp.test( data );
-
-       // Handle iff the expected data type is "jsonp" or we have a parameter to set
-       if ( s.dataTypes[ 0 ] === "jsonp" || replaceInUrl || replaceInData ) {
-
-               // Get callback name, remembering preexisting value associated with it
-               callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
-                       s.jsonpCallback() :
-                       s.jsonpCallback;
-               overwritten = window[ callbackName ];
-
-               // Insert callback into url or form data
-               if ( replaceInUrl ) {
-                       s.url = url.replace( rjsonp, "$1" + callbackName );
-               } else if ( replaceInData ) {
-                       s.data = data.replace( rjsonp, "$1" + callbackName );
-               } else if ( hasCallback ) {
-                       s.url += ( rquestion.test( url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
-               }
-
-               // Use data converter to retrieve json after script execution
-               s.converters["script json"] = function() {
-                       if ( !responseContainer ) {
-                               jQuery.error( callbackName + " was not called" );
-                       }
-                       return responseContainer[ 0 ];
-               };
-
-               // force json dataType
-               s.dataTypes[ 0 ] = "json";
-
-               // Install callback
-               window[ callbackName ] = function() {
-                       responseContainer = arguments;
-               };
-
-               // Clean-up function (fires after converters)
-               jqXHR.always(function() {
-                       // Restore preexisting value
-                       window[ callbackName ] = overwritten;
-
-                       // Save back as free
-                       if ( s[ callbackName ] ) {
-                               // make sure that re-using the options doesn't screw things around
-                               s.jsonpCallback = originalSettings.jsonpCallback;
-
-                               // save the callback name for future use
-                               oldCallbacks.push( callbackName );
-                       }
-
-                       // Call if it was a function and we have a response
-                       if ( responseContainer && jQuery.isFunction( overwritten ) ) {
-                               overwritten( responseContainer[ 0 ] );
-                       }
-
-                       responseContainer = overwritten = undefined;
-               });
-
-               // Delegate to script
-               return "script";
-       }
-});
-// Install script dataType
-jQuery.ajaxSetup({
-       accepts: {
-               script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
-       },
-       contents: {
-               script: /javascript|ecmascript/
-       },
-       converters: {
-               "text script": function( text ) {
-                       jQuery.globalEval( text );
-                       return text;
-               }
-       }
-});
-
-// Handle cache's special case and global
-jQuery.ajaxPrefilter( "script", function( s ) {
-       if ( s.cache === undefined ) {
-               s.cache = false;
-       }
-       if ( s.crossDomain ) {
-               s.type = "GET";
-               s.global = false;
-       }
-});
-
-// Bind script tag hack transport
-jQuery.ajaxTransport( "script", function(s) {
-
-       // This transport only deals with cross domain requests
-       if ( s.crossDomain ) {
-
-               var script,
-                       head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
-
-               return {
-
-                       send: function( _, callback ) {
-
-                               script = document.createElement( "script" );
-
-                               script.async = "async";
-
-                               if ( s.scriptCharset ) {
-                                       script.charset = s.scriptCharset;
-                               }
-
-                               script.src = s.url;
-
-                               // Attach handlers for all browsers
-                               script.onload = script.onreadystatechange = function( _, isAbort ) {
-
-                                       if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
-
-                                               // Handle memory leak in IE
-                                               script.onload = script.onreadystatechange = null;
-
-                                               // Remove the script
-                                               if ( head && script.parentNode ) {
-                                                       head.removeChild( script );
-                                               }
-
-                                               // Dereference the script
-                                               script = undefined;
-
-                                               // Callback if not abort
-                                               if ( !isAbort ) {
-                                                       callback( 200, "success" );
-                                               }
-                                       }
-                               };
-                               // Use insertBefore instead of appendChild  to circumvent an IE6 bug.
-                               // This arises when a base node is used (#2709 and #4378).
-                               head.insertBefore( script, head.firstChild );
-                       },
-
-                       abort: function() {
-                               if ( script ) {
-                                       script.onload( 0, 1 );
-                               }
-                       }
-               };
-       }
-});
-var xhrCallbacks,
-       // #5280: Internet Explorer will keep connections alive if we don't abort on unload
-       xhrOnUnloadAbort = window.ActiveXObject ? function() {
-               // Abort all pending requests
-               for ( var key in xhrCallbacks ) {
-                       xhrCallbacks[ key ]( 0, 1 );
-               }
-       } : false,
-       xhrId = 0;
-
-// Functions to create xhrs
-function createStandardXHR() {
-       try {
-               return new window.XMLHttpRequest();
-       } catch( e ) {}
-}
-
-function createActiveXHR() {
-       try {
-               return new window.ActiveXObject( "Microsoft.XMLHTTP" );
-       } catch( e ) {}
-}
-
-// Create the request object
-// (This is still attached to ajaxSettings for backward compatibility)
-jQuery.ajaxSettings.xhr = window.ActiveXObject ?
-       /* Microsoft failed to properly
-        * implement the XMLHttpRequest in IE7 (can't request local files),
-        * so we use the ActiveXObject when it is available
-        * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
-        * we need a fallback.
-        */
-       function() {
-               return !this.isLocal && createStandardXHR() || createActiveXHR();
-       } :
-       // For all other browsers, use the standard XMLHttpRequest object
-       createStandardXHR;
-
-// Determine support properties
-(function( xhr ) {
-       jQuery.extend( jQuery.support, {
-               ajax: !!xhr,
-               cors: !!xhr && ( "withCredentials" in xhr )
-       });
-})( jQuery.ajaxSettings.xhr() );
-
-// Create transport if the browser can provide an xhr
-if ( jQuery.support.ajax ) {
-
-       jQuery.ajaxTransport(function( s ) {
-               // Cross domain only allowed if supported through XMLHttpRequest
-               if ( !s.crossDomain || jQuery.support.cors ) {
-
-                       var callback;
-
-                       return {
-                               send: function( headers, complete ) {
-
-                                       // Get a new xhr
-                                       var handle, i,
-                                               xhr = s.xhr();
-
-                                       // Open the socket
-                                       // Passing null username, generates a login popup on Opera (#2865)
-                                       if ( s.username ) {
-                                               xhr.open( s.type, s.url, s.async, s.username, s.password );
-                                       } else {
-                                               xhr.open( s.type, s.url, s.async );
-                                       }
-
-                                       // Apply custom fields if provided
-                                       if ( s.xhrFields ) {
-                                               for ( i in s.xhrFields ) {
-                                                       xhr[ i ] = s.xhrFields[ i ];
-                                               }
-                                       }
-
-                                       // Override mime type if needed
-                                       if ( s.mimeType && xhr.overrideMimeType ) {
-                                               xhr.overrideMimeType( s.mimeType );
-                                       }
-
-                                       // X-Requested-With header
-                                       // For cross-domain requests, seeing as conditions for a preflight are
-                                       // akin to a jigsaw puzzle, we simply never set it to be sure.
-                                       // (it can always be set on a per-request basis or even using ajaxSetup)
-                                       // For same-domain requests, won't change header if already provided.
-                                       if ( !s.crossDomain && !headers["X-Requested-With"] ) {
-                                               headers[ "X-Requested-With" ] = "XMLHttpRequest";
-                                       }
-
-                                       // Need an extra try/catch for cross domain requests in Firefox 3
-                                       try {
-                                               for ( i in headers ) {
-                                                       xhr.setRequestHeader( i, headers[ i ] );
-                                               }
-                                       } catch( _ ) {}
-
-                                       // Do send the request
-                                       // This may raise an exception which is actually
-                                       // handled in jQuery.ajax (so no try/catch here)
-                                       xhr.send( ( s.hasContent && s.data ) || null );
-
-                                       // Listener
-                                       callback = function( _, isAbort ) {
-
-                                               var status,
-                                                       statusText,
-                                                       responseHeaders,
-                                                       responses,
-                                                       xml;
-
-                                               // Firefox throws exceptions when accessing properties
-                                               // of an xhr when a network error occurred
-                                               // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
-                                               try {
-
-                                                       // Was never called and is aborted or complete
-                                                       if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
-
-                                                               // Only called once
-                                                               callback = undefined;
-
-                                                               // Do not keep as active anymore
-                                                               if ( handle ) {
-                                                                       xhr.onreadystatechange = jQuery.noop;
-                                                                       if ( xhrOnUnloadAbort ) {
-                                                                               delete xhrCallbacks[ handle ];
-                                                                       }
-                                                               }
-
-                                                               // If it's an abort
-                                                               if ( isAbort ) {
-                                                                       // Abort it manually if needed
-                                                                       if ( xhr.readyState !== 4 ) {
-                                                                               xhr.abort();
-                                                                       }
-                                                               } else {
-                                                                       status = xhr.status;
-                                                                       responseHeaders = xhr.getAllResponseHeaders();
-                                                                       responses = {};
-                                                                       xml = xhr.responseXML;
-
-                                                                       // Construct response list
-                                                                       if ( xml && xml.documentElement /* #4958 */ ) {
-                                                                               responses.xml = xml;
-                                                                       }
-
-                                                                       // When requesting binary data, IE6-9 will throw an exception
-                                                                       // on any attempt to access responseText (#11426)
-                                                                       try {
-                                                                               responses.text = xhr.responseText;
-                                                                       } catch( e ) {
-                                                                       }
-
-                                                                       // Firefox throws an exception when accessing
-                                                                       // statusText for faulty cross-domain requests
-                                                                       try {
-                                                                               statusText = xhr.statusText;
-                                                                       } catch( e ) {
-                                                                               // We normalize with Webkit giving an empty statusText
-                                                                               statusText = "";
-                                                                       }
-
-                                                                       // Filter status for non standard behaviors
-
-                                                                       // If the request is local and we have data: assume a success
-                                                                       // (success with no data won't get notified, that's the best we
-                                                                       // can do given current implementations)
-                                                                       if ( !status && s.isLocal && !s.crossDomain ) {
-                                                                               status = responses.text ? 200 : 404;
-                                                                       // IE - #1450: sometimes returns 1223 when it should be 204
-                                                                       } else if ( status === 1223 ) {
-                                                                               status = 204;
-                                                                       }
-                                                               }
-                                                       }
-                                               } catch( firefoxAccessException ) {
-                                                       if ( !isAbort ) {
-                                                               complete( -1, firefoxAccessException );
-                                                       }
-                                               }
-
-                                               // Call complete if needed
-                                               if ( responses ) {
-                                                       complete( status, statusText, responses, responseHeaders );
-                                               }
-                                       };
-
-                                       if ( !s.async ) {
-                                               // if we're in sync mode we fire the callback
-                                               callback();
-                                       } else if ( xhr.readyState === 4 ) {
-                                               // (IE6 & IE7) if it's in cache and has been
-                                               // retrieved directly we need to fire the callback
-                                               setTimeout( callback, 0 );
-                                       } else {
-                                               handle = ++xhrId;
-                                               if ( xhrOnUnloadAbort ) {
-                                                       // Create the active xhrs callbacks list if needed
-                                                       // and attach the unload handler
-                                                       if ( !xhrCallbacks ) {
-                                                               xhrCallbacks = {};
-                                                               jQuery( window ).unload( xhrOnUnloadAbort );
-                                                       }
-                                                       // Add to list of active xhrs callbacks
-                                                       xhrCallbacks[ handle ] = callback;
-                                               }
-                                               xhr.onreadystatechange = callback;
-                                       }
-                               },
-
-                               abort: function() {
-                                       if ( callback ) {
-                                               callback(0,1);
-                                       }
-                               }
-                       };
-               }
-       });
-}
-var fxNow, timerId,
-       rfxtypes = /^(?:toggle|show|hide)$/,
-       rfxnum = new RegExp( "^(?:([-+])=|)(" + core_pnum + ")([a-z%]*)$", "i" ),
-       rrun = /queueHooks$/,
-       animationPrefilters = [ defaultPrefilter ],
-       tweeners = {
-               "*": [function( prop, value ) {
-                       var end, unit,
-                               tween = this.createTween( prop, value ),
-                               parts = rfxnum.exec( value ),
-                               target = tween.cur(),
-                               start = +target || 0,
-                               scale = 1,
-                               maxIterations = 20;
-
-                       if ( parts ) {
-                               end = +parts[2];
-                               unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" );
-
-                               // We need to compute starting value
-                               if ( unit !== "px" && start ) {
-                                       // Iteratively approximate from a nonzero starting point
-                                       // Prefer the current property, because this process will be trivial if it uses the same units
-                                       // Fallback to end or a simple constant
-                                       start = jQuery.css( tween.elem, prop, true ) || end || 1;
-
-                                       do {
-                                               // If previous iteration zeroed out, double until we get *something*
-                                               // Use a string for doubling factor so we don't accidentally see scale as unchanged below
-                                               scale = scale || ".5";
-
-                                               // Adjust and apply
-                                               start = start / scale;
-                                               jQuery.style( tween.elem, prop, start + unit );
-
-                                       // Update scale, tolerating zero or NaN from tween.cur()
-                                       // And breaking the loop if scale is unchanged or perfect, or if we've just had enough
-                                       } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
-                               }
-
-                               tween.unit = unit;
-                               tween.start = start;
-                               // If a +=/-= token was provided, we're doing a relative animation
-                               tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end;
-                       }
-                       return tween;
-               }]
-       };
-
-// Animations created synchronously will run synchronously
-function createFxNow() {
-       setTimeout(function() {
-               fxNow = undefined;
-       }, 0 );
-       return ( fxNow = jQuery.now() );
-}
-
-function createTweens( animation, props ) {
-       jQuery.each( props, function( prop, value ) {
-               var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
-                       index = 0,
-                       length = collection.length;
-               for ( ; index < length; index++ ) {
-                       if ( collection[ index ].call( animation, prop, value ) ) {
-
-                               // we're done with this property
-                               return;
-                       }
-               }
-       });
-}
-
-function Animation( elem, properties, options ) {
-       var result,
-               index = 0,
-               tweenerIndex = 0,
-               length = animationPrefilters.length,
-               deferred = jQuery.Deferred().always( function() {
-                       // don't match elem in the :animated selector
-                       delete tick.elem;
-               }),
-               tick = function() {
-                       var currentTime = fxNow || createFxNow(),
-                               remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
-                               // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
-                               temp = remaining / animation.duration || 0,
-                               percent = 1 - temp,
-                               index = 0,
-                               length = animation.tweens.length;
-
-                       for ( ; index < length ; index++ ) {
-                               animation.tweens[ index ].run( percent );
-                       }
-
-                       deferred.notifyWith( elem, [ animation, percent, remaining ]);
-
-                       if ( percent < 1 && length ) {
-                               return remaining;
-                       } else {
-                               deferred.resolveWith( elem, [ animation ] );
-                               return false;
-                       }
-               },
-               animation = deferred.promise({
-                       elem: elem,
-                       props: jQuery.extend( {}, properties ),
-                       opts: jQuery.extend( true, { specialEasing: {} }, options ),
-                       originalProperties: properties,
-                       originalOptions: options,
-                       startTime: fxNow || createFxNow(),
-                       duration: options.duration,
-                       tweens: [],
-                       createTween: function( prop, end, easing ) {
-                               var tween = jQuery.Tween( elem, animation.opts, prop, end,
-                                               animation.opts.specialEasing[ prop ] || animation.opts.easing );
-                               animation.tweens.push( tween );
-                               return tween;
-                       },
-                       stop: function( gotoEnd ) {
-                               var index = 0,
-                                       // if we are going to the end, we want to run all the tweens
-                                       // otherwise we skip this part
-                                       length = gotoEnd ? animation.tweens.length : 0;
-
-                               for ( ; index < length ; index++ ) {
-                                       animation.tweens[ index ].run( 1 );
-                               }
-
-                               // resolve when we played the last frame
-                               // otherwise, reject
-                               if ( gotoEnd ) {
-                                       deferred.resolveWith( elem, [ animation, gotoEnd ] );
-                               } else {
-                                       deferred.rejectWith( elem, [ animation, gotoEnd ] );
-                               }
-                               return this;
-                       }
-               }),
-               props = animation.props;
-
-       propFilter( props, animation.opts.specialEasing );
-
-       for ( ; index < length ; index++ ) {
-               result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
-               if ( result ) {
-                       return result;
-               }
-       }
-
-       createTweens( animation, props );
-
-       if ( jQuery.isFunction( animation.opts.start ) ) {
-               animation.opts.start.call( elem, animation );
-       }
-
-       jQuery.fx.timer(
-               jQuery.extend( tick, {
-                       anim: animation,
-                       queue: animation.opts.queue,
-                       elem: elem
-               })
-       );
-
-       // attach callbacks from options
-       return animation.progress( animation.opts.progress )
-               .done( animation.opts.done, animation.opts.complete )
-               .fail( animation.opts.fail )
-               .always( animation.opts.always );
-}
-
-function propFilter( props, specialEasing ) {
-       var index, name, easing, value, hooks;
-
-       // camelCase, specialEasing and expand cssHook pass
-       for ( index in props ) {
-               name = jQuery.camelCase( index );
-               easing = specialEasing[ name ];
-               value = props[ index ];
-               if ( jQuery.isArray( value ) ) {
-                       easing = value[ 1 ];
-                       value = props[ index ] = value[ 0 ];
-               }
-
-               if ( index !== name ) {
-                       props[ name ] = value;
-                       delete props[ index ];
-               }
-
-               hooks = jQuery.cssHooks[ name ];
-               if ( hooks && "expand" in hooks ) {
-                       value = hooks.expand( value );
-                       delete props[ name ];
-
-                       // not quite $.extend, this wont overwrite keys already present.
-                       // also - reusing 'index' from above because we have the correct "name"
-                       for ( index in value ) {
-                               if ( !( index in props ) ) {
-                                       props[ index ] = value[ index ];
-                                       specialEasing[ index ] = easing;
-                               }
-                       }
-               } else {
-                       specialEasing[ name ] = easing;
-               }
-       }
-}
-
-jQuery.Animation = jQuery.extend( Animation, {
-
-       tweener: function( props, callback ) {
-               if ( jQuery.isFunction( props ) ) {
-                       callback = props;
-                       props = [ "*" ];
-               } else {
-                       props = props.split(" ");
-               }
-
-               var prop,
-                       index = 0,
-                       length = props.length;
-
-               for ( ; index < length ; index++ ) {
-                       prop = props[ index ];
-                       tweeners[ prop ] = tweeners[ prop ] || [];
-                       tweeners[ prop ].unshift( callback );
-               }
-       },
-
-       prefilter: function( callback, prepend ) {
-               if ( prepend ) {
-                       animationPrefilters.unshift( callback );
-               } else {
-                       animationPrefilters.push( callback );
-               }
-       }
-});
-
-function defaultPrefilter( elem, props, opts ) {
-       var index, prop, value, length, dataShow, toggle, tween, hooks, oldfire,
-               anim = this,
-               style = elem.style,
-               orig = {},
-               handled = [],
-               hidden = elem.nodeType && isHidden( elem );
-
-       // handle queue: false promises
-       if ( !opts.queue ) {
-               hooks = jQuery._queueHooks( elem, "fx" );
-               if ( hooks.unqueued == null ) {
-                       hooks.unqueued = 0;
-                       oldfire = hooks.empty.fire;
-                       hooks.empty.fire = function() {
-                               if ( !hooks.unqueued ) {
-                                       oldfire();
-                               }
-                       };
-               }
-               hooks.unqueued++;
-
-               anim.always(function() {
-                       // doing this makes sure that the complete handler will be called
-                       // before this completes
-                       anim.always(function() {
-                               hooks.unqueued--;
-                               if ( !jQuery.queue( elem, "fx" ).length ) {
-                                       hooks.empty.fire();
-                               }
-                       });
-               });
-       }
-
-       // height/width overflow pass
-       if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
-               // Make sure that nothing sneaks out
-               // Record all 3 overflow attributes because IE does not
-               // change the overflow attribute when overflowX and
-               // overflowY are set to the same value
-               opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
-
-               // Set display property to inline-block for height/width
-               // animations on inline elements that are having width/height animated
-               if ( jQuery.css( elem, "display" ) === "inline" &&
-                               jQuery.css( elem, "float" ) === "none" ) {
-
-                       // inline-level elements accept inline-block;
-                       // block-level elements need to be inline with layout
-                       if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) {
-                               style.display = "inline-block";
-
-                       } else {
-                               style.zoom = 1;
-                       }
-               }
-       }
-
-       if ( opts.overflow ) {
-               style.overflow = "hidden";
-               if ( !jQuery.support.shrinkWrapBlocks ) {
-                       anim.done(function() {
-                               style.overflow = opts.overflow[ 0 ];
-                               style.overflowX = opts.overflow[ 1 ];
-                               style.overflowY = opts.overflow[ 2 ];
-                       });
-               }
-       }
-
-
-       // show/hide pass
-       for ( index in props ) {
-               value = props[ index ];
-               if ( rfxtypes.exec( value ) ) {
-                       delete props[ index ];
-                       toggle = toggle || value === "toggle";
-                       if ( value === ( hidden ? "hide" : "show" ) ) {
-                               continue;
-                       }
-                       handled.push( index );
-               }
-       }
-
-       length = handled.length;
-       if ( length ) {
-               dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} );
-               if ( "hidden" in dataShow ) {
-                       hidden = dataShow.hidden;
-               }
-
-               // store state if its toggle - enables .stop().toggle() to "reverse"
-               if ( toggle ) {
-                       dataShow.hidden = !hidden;
-               }
-               if ( hidden ) {
-                       jQuery( elem ).show();
-               } else {
-                       anim.done(function() {
-                               jQuery( elem ).hide();
-                       });
-               }
-               anim.done(function() {
-                       var prop;
-                       jQuery.removeData( elem, "fxshow", true );
-                       for ( prop in orig ) {
-                               jQuery.style( elem, prop, orig[ prop ] );
-                       }
-               });
-               for ( index = 0 ; index < length ; index++ ) {
-                       prop = handled[ index ];
-                       tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 );
-                       orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop );
-
-                       if ( !( prop in dataShow ) ) {
-                               dataShow[ prop ] = tween.start;
-                               if ( hidden ) {
-                                       tween.end = tween.start;
-                                       tween.start = prop === "width" || prop === "height" ? 1 : 0;
-                               }
-                       }
-               }
-       }
-}
-
-function Tween( elem, options, prop, end, easing ) {
-       return new Tween.prototype.init( elem, options, prop, end, easing );
-}
-jQuery.Tween = Tween;
-
-Tween.prototype = {
-       constructor: Tween,
-       init: function( elem, options, prop, end, easing, unit ) {
-               this.elem = elem;
-               this.prop = prop;
-               this.easing = easing || "swing";
-               this.options = options;
-               this.start = this.now = this.cur();
-               this.end = end;
-               this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
-       },
-       cur: function() {
-               var hooks = Tween.propHooks[ this.prop ];
-
-               return hooks && hooks.get ?
-                       hooks.get( this ) :
-                       Tween.propHooks._default.get( this );
-       },
-       run: function( percent ) {
-               var eased,
-                       hooks = Tween.propHooks[ this.prop ];
-
-               if ( this.options.duration ) {
-                       this.pos = eased = jQuery.easing[ this.easing ](
-                               percent, this.options.duration * percent, 0, 1, this.options.duration
-                       );
-               } else {
-                       this.pos = eased = percent;
-               }
-               this.now = ( this.end - this.start ) * eased + this.start;
-
-               if ( this.options.step ) {
-                       this.options.step.call( this.elem, this.now, this );
-               }
-
-               if ( hooks && hooks.set ) {
-                       hooks.set( this );
-               } else {
-                       Tween.propHooks._default.set( this );
-               }
-               return this;
-       }
-};
-
-Tween.prototype.init.prototype = Tween.prototype;
-
-Tween.propHooks = {
-       _default: {
-               get: function( tween ) {
-                       var result;
-
-                       if ( tween.elem[ tween.prop ] != null &&
-                               (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
-                               return tween.elem[ tween.prop ];
-                       }
-
-                       // passing any value as a 4th parameter to .css will automatically
-                       // attempt a parseFloat and fallback to a string if the parse fails
-                       // so, simple values such as "10px" are parsed to Float.
-                       // complex values such as "rotate(1rad)" are returned as is.
-                       result = jQuery.css( tween.elem, tween.prop, false, "" );
-                       // Empty strings, null, undefined and "auto" are converted to 0.
-                       return !result || result === "auto" ? 0 : result;
-               },
-               set: function( tween ) {
-                       // use step hook for back compat - use cssHook if its there - use .style if its
-                       // available and use plain properties where available
-                       if ( jQuery.fx.step[ tween.prop ] ) {
-                               jQuery.fx.step[ tween.prop ]( tween );
-                       } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
-                               jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
-                       } else {
-                               tween.elem[ tween.prop ] = tween.now;
-                       }
-               }
-       }
-};
-
-// Remove in 2.0 - this supports IE8's panic based approach
-// to setting things on disconnected nodes
-
-Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
-       set: function( tween ) {
-               if ( tween.elem.nodeType && tween.elem.parentNode ) {
-                       tween.elem[ tween.prop ] = tween.now;
-               }
-       }
-};
-
-jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
-       var cssFn = jQuery.fn[ name ];
-       jQuery.fn[ name ] = function( speed, easing, callback ) {
-               return speed == null || typeof speed === "boolean" ||
-                       // special check for .toggle( handler, handler, ... )
-                       ( !i && jQuery.isFunction( speed ) && jQuery.isFunction( easing ) ) ?
-                       cssFn.apply( this, arguments ) :
-                       this.animate( genFx( name, true ), speed, easing, callback );
-       };
-});
-
-jQuery.fn.extend({
-       fadeTo: function( speed, to, easing, callback ) {
-
-               // show any hidden elements after setting opacity to 0
-               return this.filter( isHidden ).css( "opacity", 0 ).show()
-
-                       // animate to the value specified
-                       .end().animate({ opacity: to }, speed, easing, callback );
-       },
-       animate: function( prop, speed, easing, callback ) {
-               var empty = jQuery.isEmptyObject( prop ),
-                       optall = jQuery.speed( speed, easing, callback ),
-                       doAnimation = function() {
-                               // Operate on a copy of prop so per-property easing won't be lost
-                               var anim = Animation( this, jQuery.extend( {}, prop ), optall );
-
-                               // Empty animations resolve immediately
-                               if ( empty ) {
-                                       anim.stop( true );
-                               }
-                       };
-
-               return empty || optall.queue === false ?
-                       this.each( doAnimation ) :
-                       this.queue( optall.queue, doAnimation );
-       },
-       stop: function( type, clearQueue, gotoEnd ) {
-               var stopQueue = function( hooks ) {
-                       var stop = hooks.stop;
-                       delete hooks.stop;
-                       stop( gotoEnd );
-               };
-
-               if ( typeof type !== "string" ) {
-                       gotoEnd = clearQueue;
-                       clearQueue = type;
-                       type = undefined;
-               }
-               if ( clearQueue && type !== false ) {
-                       this.queue( type || "fx", [] );
-               }
-
-               return this.each(function() {
-                       var dequeue = true,
-                               index = type != null && type + "queueHooks",
-                               timers = jQuery.timers,
-                               data = jQuery._data( this );
-
-                       if ( index ) {
-                               if ( data[ index ] && data[ index ].stop ) {
-                                       stopQueue( data[ index ] );
-                               }
-                       } else {
-                               for ( index in data ) {
-                                       if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
-                                               stopQueue( data[ index ] );
-                                       }
-                               }
-                       }
-
-                       for ( index = timers.length; index--; ) {
-                               if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
-                                       timers[ index ].anim.stop( gotoEnd );
-                                       dequeue = false;
-                                       timers.splice( index, 1 );
-                               }
-                       }
-
-                       // start the next in the queue if the last step wasn't forced
-                       // timers currently will call their complete callbacks, which will dequeue
-                       // but only if they were gotoEnd
-                       if ( dequeue || !gotoEnd ) {
-                               jQuery.dequeue( this, type );
-                       }
-               });
-       }
-});
-
-// Generate parameters to create a standard animation
-function genFx( type, includeWidth ) {
-       var which,
-               attrs = { height: type },
-               i = 0;
-
-       // if we include width, step value is 1 to do all cssExpand values,
-       // if we don't include width, step value is 2 to skip over Left and Right
-       includeWidth = includeWidth? 1 : 0;
-       for( ; i < 4 ; i += 2 - includeWidth ) {
-               which = cssExpand[ i ];
-               attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
-       }
-
-       if ( includeWidth ) {
-               attrs.opacity = attrs.width = type;
-       }
-
-       return attrs;
-}
-
-// Generate shortcuts for custom animations
-jQuery.each({
-       slideDown: genFx("show"),
-       slideUp: genFx("hide"),
-       slideToggle: genFx("toggle"),
-       fadeIn: { opacity: "show" },
-       fadeOut: { opacity: "hide" },
-       fadeToggle: { opacity: "toggle" }
-}, function( name, props ) {
-       jQuery.fn[ name ] = function( speed, easing, callback ) {
-               return this.animate( props, speed, easing, callback );
-       };
-});
-
-jQuery.speed = function( speed, easing, fn ) {
-       var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
-               complete: fn || !fn && easing ||
-                       jQuery.isFunction( speed ) && speed,
-               duration: speed,
-               easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
-       };
-
-       opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
-               opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
-
-       // normalize opt.queue - true/undefined/null -> "fx"
-       if ( opt.queue == null || opt.queue === true ) {
-               opt.queue = "fx";
-       }
-
-       // Queueing
-       opt.old = opt.complete;
-
-       opt.complete = function() {
-               if ( jQuery.isFunction( opt.old ) ) {
-                       opt.old.call( this );
-               }
-
-               if ( opt.queue ) {
-                       jQuery.dequeue( this, opt.queue );
-               }
-       };
-
-       return opt;
-};
-
-jQuery.easing = {
-       linear: function( p ) {
-               return p;
-       },
-       swing: function( p ) {
-               return 0.5 - Math.cos( p*Math.PI ) / 2;
-       }
-};
-
-jQuery.timers = [];
-jQuery.fx = Tween.prototype.init;
-jQuery.fx.tick = function() {
-       var timer,
-               timers = jQuery.timers,
-               i = 0;
-
-       fxNow = jQuery.now();
-
-       for ( ; i < timers.length; i++ ) {
-               timer = timers[ i ];
-               // Checks the timer has not already been removed
-               if ( !timer() && timers[ i ] === timer ) {
-                       timers.splice( i--, 1 );
-               }
-       }
-
-       if ( !timers.length ) {
-               jQuery.fx.stop();
-       }
-       fxNow = undefined;
-};
-
-jQuery.fx.timer = function( timer ) {
-       if ( timer() && jQuery.timers.push( timer ) && !timerId ) {
-               timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
-       }
-};
-
-jQuery.fx.interval = 13;
-
-jQuery.fx.stop = function() {
-       clearInterval( timerId );
-       timerId = null;
-};
-
-jQuery.fx.speeds = {
-       slow: 600,
-       fast: 200,
-       // Default speed
-       _default: 400
-};
-
-// Back Compat <1.8 extension point
-jQuery.fx.step = {};
-
-if ( jQuery.expr && jQuery.expr.filters ) {
-       jQuery.expr.filters.animated = function( elem ) {
-               return jQuery.grep(jQuery.timers, function( fn ) {
-                       return elem === fn.elem;
-               }).length;
-       };
-}
-var rroot = /^(?:body|html)$/i;
-
-jQuery.fn.offset = function( options ) {
-       if ( arguments.length ) {
-               return options === undefined ?
-                       this :
-                       this.each(function( i ) {
-                               jQuery.offset.setOffset( this, options, i );
-                       });
-       }
-
-       var docElem, body, win, clientTop, clientLeft, scrollTop, scrollLeft,
-               box = { top: 0, left: 0 },
-               elem = this[ 0 ],
-               doc = elem && elem.ownerDocument;
-
-       if ( !doc ) {
-               return;
-       }
-
-       if ( (body = doc.body) === elem ) {
-               return jQuery.offset.bodyOffset( elem );
-       }
-
-       docElem = doc.documentElement;
-
-       // Make sure it's not a disconnected DOM node
-       if ( !jQuery.contains( docElem, elem ) ) {
-               return box;
-       }
-
-       // If we don't have gBCR, just use 0,0 rather than error
-       // BlackBerry 5, iOS 3 (original iPhone)
-       if ( typeof elem.getBoundingClientRect !== "undefined" ) {
-               box = elem.getBoundingClientRect();
-       }
-       win = getWindow( doc );
-       clientTop  = docElem.clientTop  || body.clientTop  || 0;
-       clientLeft = docElem.clientLeft || body.clientLeft || 0;
-       scrollTop  = win.pageYOffset || docElem.scrollTop;
-       scrollLeft = win.pageXOffset || docElem.scrollLeft;
-       return {
-               top: box.top  + scrollTop  - clientTop,
-               left: box.left + scrollLeft - clientLeft
-       };
-};
-
-jQuery.offset = {
-
-       bodyOffset: function( body ) {
-               var top = body.offsetTop,
-                       left = body.offsetLeft;
-
-               if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) {
-                       top  += parseFloat( jQuery.css(body, "marginTop") ) || 0;
-                       left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
-               }
-
-               return { top: top, left: left };
-       },
-
-       setOffset: function( elem, options, i ) {
-               var position = jQuery.css( elem, "position" );
-
-               // set position first, in-case top/left are set even on static elem
-               if ( position === "static" ) {
-                       elem.style.position = "relative";
-               }
-
-               var curElem = jQuery( elem ),
-                       curOffset = curElem.offset(),
-                       curCSSTop = jQuery.css( elem, "top" ),
-                       curCSSLeft = jQuery.css( elem, "left" ),
-                       calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
-                       props = {}, curPosition = {}, curTop, curLeft;
-
-               // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
-               if ( calculatePosition ) {
-                       curPosition = curElem.position();
-                       curTop = curPosition.top;
-                       curLeft = curPosition.left;
-               } else {
-                       curTop = parseFloat( curCSSTop ) || 0;
-                       curLeft = parseFloat( curCSSLeft ) || 0;
-               }
-
-               if ( jQuery.isFunction( options ) ) {
-                       options = options.call( elem, i, curOffset );
-               }
-
-               if ( options.top != null ) {
-                       props.top = ( options.top - curOffset.top ) + curTop;
-               }
-               if ( options.left != null ) {
-                       props.left = ( options.left - curOffset.left ) + curLeft;
-               }
-
-               if ( "using" in options ) {
-                       options.using.call( elem, props );
-               } else {
-                       curElem.css( props );
-               }
-       }
-};
-
-
-jQuery.fn.extend({
-
-       position: function() {
-               if ( !this[0] ) {
-                       return;
-               }
-
-               var elem = this[0],
-
-               // Get *real* offsetParent
-               offsetParent = this.offsetParent(),
-
-               // Get correct offsets
-               offset       = this.offset(),
-               parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
-
-               // Subtract element margins
-               // note: when an element has margin: auto the offsetLeft and marginLeft
-               // are the same in Safari causing offset.left to incorrectly be 0
-               offset.top  -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
-               offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
-
-               // Add offsetParent borders
-               parentOffset.top  += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
-               parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
-
-               // Subtract the two offsets
-               return {
-                       top:  offset.top  - parentOffset.top,
-                       left: offset.left - parentOffset.left
-               };
-       },
-
-       offsetParent: function() {
-               return this.map(function() {
-                       var offsetParent = this.offsetParent || document.body;
-                       while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
-                               offsetParent = offsetParent.offsetParent;
-                       }
-                       return offsetParent || document.body;
-               });
-       }
-});
-
-
-// Create scrollLeft and scrollTop methods
-jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
-       var top = /Y/.test( prop );
-
-       jQuery.fn[ method ] = function( val ) {
-               return jQuery.access( this, function( elem, method, val ) {
-                       var win = getWindow( elem );
-
-                       if ( val === undefined ) {
-                               return win ? (prop in win) ? win[ prop ] :
-                                       win.document.documentElement[ method ] :
-                                       elem[ method ];
-                       }
-
-                       if ( win ) {
-                               win.scrollTo(
-                                       !top ? val : jQuery( win ).scrollLeft(),
-                                        top ? val : jQuery( win ).scrollTop()
-                               );
-
-                       } else {
-                               elem[ method ] = val;
-                       }
-               }, method, val, arguments.length, null );
-       };
-});
-
-function getWindow( elem ) {
-       return jQuery.isWindow( elem ) ?
-               elem :
-               elem.nodeType === 9 ?
-                       elem.defaultView || elem.parentWindow :
-                       false;
-}
-// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
-jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
-       jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
-               // margin is only for outerHeight, outerWidth
-               jQuery.fn[ funcName ] = function( margin, value ) {
-                       var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
-                               extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
-
-                       return jQuery.access( this, function( elem, type, value ) {
-                               var doc;
-
-                               if ( jQuery.isWindow( elem ) ) {
-                                       // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
-                                       // isn't a whole lot we can do. See pull request at this URL for discussion:
-                                       // https://github.com/jquery/jquery/pull/764
-                                       return elem.document.documentElement[ "client" + name ];
-                               }
-
-                               // Get document width or height
-                               if ( elem.nodeType === 9 ) {
-                                       doc = elem.documentElement;
-
-                                       // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
-                                       // unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
-                                       return Math.max(
-                                               elem.body[ "scroll" + name ], doc[ "scroll" + name ],
-                                               elem.body[ "offset" + name ], doc[ "offset" + name ],
-                                               doc[ "client" + name ]
-                                       );
-                               }
-
-                               return value === undefined ?
-                                       // Get width or height on the element, requesting but not forcing parseFloat
-                                       jQuery.css( elem, type, value, extra ) :
-
-                                       // Set width or height on the element
-                                       jQuery.style( elem, type, value, extra );
-                       }, type, chainable ? margin : undefined, chainable, null );
-               };
-       });
-});
-// Expose jQuery to the global object
-window.jQuery = window.$ = jQuery;
-
-// Expose jQuery as an AMD module, but only for AMD loaders that
-// understand the issues with loading multiple versions of jQuery
-// in a page that all might call define(). The loader will indicate
-// they have special allowances for multiple jQuery versions by
-// specifying define.amd.jQuery = true. Register as a named module,
-// since jQuery can be concatenated with other files that may use define,
-// but not use a proper concatenation script that understands anonymous
-// AMD modules. A named AMD is safest and most robust way to register.
-// Lowercase jquery is used because AMD module names are derived from
-// file names, and jQuery is normally delivered in a lowercase file name.
-// Do this after creating the global so that if an AMD module wants to call
-// noConflict to hide this version of jQuery, it will work.
-if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
-       define( "jquery", [], function () { return jQuery; } );
-}
-
-})( window );
diff --git a/pdns/recursordist/html/js/jquery-1.8.3.min.js b/pdns/recursordist/html/js/jquery-1.8.3.min.js
deleted file mode 100644 (file)
index 83589da..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/*! jQuery v1.8.3 jquery.com | jquery.org/license */\r
-(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(P,"-$1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t==="data"&&v.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function et(){return!1}function tt(){return!0}function ut(e){return!e||!e.parentNode||e.parentNode.nodeType===11}function at(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ft(e,t,n){t=t||0;if(v.isFunction(t))return v.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return v.grep(e,function(e,r){return e===t===n});if(typeof t=="string"){var r=v.grep(e,function(e){return e.nodeType===1});if(it.test(t))return v.filter(t,r,!n);t=v.filter(t,r)}return v.grep(e,function(e,r){return v.inArray(e,t)>=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r<i;r++)v.event.add(t,n,u[n][r])}o.data&&(o.data=v.extend({},o.data))}function Ot(e,t){var n;if(t.nodeType!==1)return;t.clearAttributes&&t.clearAttributes(),t.mergeAttributes&&t.mergeAttributes(e),n=t.nodeName.toLowerCase(),n==="object"?(t.parentNode&&(t.outerHTML=e.outerHTML),v.support.html5Clone&&e.innerHTML&&!v.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):n==="input"&&Et.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):n==="option"?t.selected=e.defaultSelected:n==="input"||n==="textarea"?t.defaultValue=e.defaultValue:n==="script"&&t.text!==e.text&&(t.text=e.text),t.removeAttribute(v.expando)}function Mt(e){return typeof e.getElementsByTagName!="undefined"?e.getElementsByTagName("*"):typeof e.querySelectorAll!="undefined"?e.querySelectorAll("*"):[]}function _t(e){Et.test(e.type)&&(e.defaultChecked=e.checked)}function Qt(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Jt.length;while(i--){t=Jt[i]+n;if(t in e)return t}return r}function Gt(e,t){return e=t||e,v.css(e,"display")==="none"||!v.contains(e.ownerDocument,e)}function Yt(e,t){var n,r,i=[],s=0,o=e.length;for(;s<o;s++){n=e[s];if(!n.style)continue;i[s]=v._data(n,"olddisplay"),t?(!i[s]&&n.style.display==="none"&&(n.style.display=""),n.style.display===""&&Gt(n)&&(i[s]=v._data(n,"olddisplay",nn(n.nodeName)))):(r=Dt(n,"display"),!i[s]&&r!=="none"&&v._data(n,"olddisplay",r))}for(s=0;s<o;s++){n=e[s];if(!n.style)continue;if(!t||n.style.display==="none"||n.style.display==="")n.style.display=t?i[s]||"":"none"}return e}function Zt(e,t,n){var r=Rt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function en(e,t,n,r){var i=n===(r?"border":"content")?4:t==="width"?1:0,s=0;for(;i<4;i+=2)n==="margin"&&(s+=v.css(e,n+$t[i],!0)),r?(n==="content"&&(s-=parseFloat(Dt(e,"padding"+$t[i]))||0),n!=="margin"&&(s-=parseFloat(Dt(e,"border"+$t[i]+"Width"))||0)):(s+=parseFloat(Dt(e,"padding"+$t[i]))||0,n!=="padding"&&(s+=parseFloat(Dt(e,"border"+$t[i]+"Width"))||0));return s}function tn(e,t,n){var r=t==="width"?e.offsetWidth:e.offsetHeight,i=!0,s=v.support.boxSizing&&v.css(e,"boxSizing")==="border-box";if(r<=0||r==null){r=Dt(e,t);if(r<0||r==null)r=e.style[t];if(Ut.test(r))return r;i=s&&(v.support.boxSizingReliable||r===e.style[t]),r=parseFloat(r)||0}return r+en(e,t,n||(s?"border":"content"),i)+"px"}function nn(e){if(Wt[e])return Wt[e];var t=v("<"+e+">").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write("<!doctype html><html><body>"),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u<a;u++)r=o[u],s=/^\+/.test(r),s&&(r=r.substr(1)||"*"),i=e[r]=e[r]||[],i[s?"unshift":"push"](n)}}function kn(e,n,r,i,s,o){s=s||n.dataTypes[0],o=o||{},o[s]=!0;var u,a=e[s],f=0,l=a?a.length:0,c=e===Sn;for(;f<l&&(c||!u);f++)u=a[f](n,r,i),typeof u=="string"&&(!c||o[u]?u=t:(n.dataTypes.unshift(u),u=kn(e,n,r,i,u,o)));return(c||!u)&&!o["*"]&&(u=kn(e,n,r,i,"*",o)),u}function Ln(e,n){var r,i,s=v.ajaxSettings.flatOptions||{};for(r in n)n[r]!==t&&((s[r]?e:i||(i={}))[r]=n[r]);i&&v.extend(!0,e,i)}function An(e,n,r){var i,s,o,u,a=e.contents,f=e.dataTypes,l=e.responseFields;for(s in l)s in r&&(n[l[s]]=r[s]);while(f[0]==="*")f.shift(),i===t&&(i=e.mimeType||n.getResponseHeader("content-type"));if(i)for(s in a)if(a[s]&&a[s].test(i)){f.unshift(s);break}if(f[0]in r)o=f[0];else{for(s in r){if(!f[0]||e.converters[s+" "+f[0]]){o=s;break}u||(u=s)}o=o||u}if(o)return o!==f[0]&&f.unshift(o),r[o]}function On(e,t){var n,r,i,s,o=e.dataTypes.slice(),u=o[0],a={},f=0;e.dataFilter&&(t=e.dataFilter(t,e.dataType));if(o[1])for(n in e.converters)a[n.toLowerCase()]=e.converters[n];for(;i=o[++f];)if(i!=="*"){if(u!=="*"&&u!==i){n=a[u+" "+i]||a["* "+i];if(!n)for(r in a){s=r.split(" ");if(s[1]===i){n=a[u+" "+s[0]]||a["* "+s[0]];if(n){n===!0?n=a[r]:a[r]!==!0&&(i=s[0],o.splice(f--,0,i));break}}}if(n!==!0)if(n&&e["throws"])t=n(t);else try{t=n(t)}catch(l){return{state:"parsererror",error:n?l:"No conversion from "+u+" to "+i}}}u=i}return{state:"success",data:t}}function Fn(){try{return new e.XMLHttpRequest}catch(t){}}function In(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}function $n(){return setTimeout(function(){qn=t},0),qn=v.now()}function Jn(e,t){v.each(t,function(t,n){var r=(Vn[t]||[]).concat(Vn["*"]),i=0,s=r.length;for(;i<s;i++)if(r[i].call(e,t,n))return})}function Kn(e,t,n){var r,i=0,s=0,o=Xn.length,u=v.Deferred().always(function(){delete a.elem}),a=function(){var t=qn||$n(),n=Math.max(0,f.startTime+f.duration-t),r=n/f.duration||0,i=1-r,s=0,o=f.tweens.length;for(;s<o;s++)f.tweens[s].run(i);return u.notifyWith(e,[f,i,n]),i<1&&o?n:(u.resolveWith(e,[f]),!1)},f=u.promise({elem:e,props:v.extend({},t),opts:v.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:qn||$n(),duration:n.duration,tweens:[],createTween:function(t,n,r){var i=v.Tween(e,f.opts,t,n,f.opts.specialEasing[t]||f.opts.easing);return f.tweens.push(i),i},stop:function(t){var n=0,r=t?f.tweens.length:0;for(;n<r;n++)f.tweens[n].run(1);return t?u.resolveWith(e,[f,t]):u.rejectWith(e,[f,t]),this}}),l=f.props;Qn(l,f.opts.specialEasing);for(;i<o;i++){r=Xn[i].call(f,e,l,f.opts);if(r)return r}return Jn(f,l),v.isFunction(f.opts.start)&&f.opts.start.call(e,f),v.fx.timer(v.extend(a,{anim:f,queue:f.opts.queue,elem:e})),f.progress(f.opts.progress).done(f.opts.done,f.opts.complete).fail(f.opts.fail).always(f.opts.always)}function Qn(e,t){var n,r,i,s,o;for(n in e){r=v.camelCase(n),i=t[r],s=e[n],v.isArray(s)&&(i=s[1],s=e[n]=s[0]),n!==r&&(e[r]=s,delete e[n]),o=v.cssHooks[r];if(o&&"expand"in o){s=o.expand(s),delete e[r];for(n in s)n in e||(e[n]=s[n],t[n]=i)}else t[r]=i}}function Gn(e,t,n){var r,i,s,o,u,a,f,l,c,h=this,p=e.style,d={},m=[],g=e.nodeType&&Gt(e);n.queue||(l=v._queueHooks(e,"fx"),l.unqueued==null&&(l.unqueued=0,c=l.empty.fire,l.empty.fire=function(){l.unqueued||c()}),l.unqueued++,h.always(function(){h.always(function(){l.unqueued--,v.queue(e,"fx").length||l.empty.fire()})})),e.nodeType===1&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],v.css(e,"display")==="inline"&&v.css(e,"float")==="none"&&(!v.support.inlineBlockNeedsLayout||nn(e.nodeName)==="inline"?p.display="inline-block":p.zoom=1)),n.overflow&&(p.overflow="hidden",v.support.shrinkWrapBlocks||h.done(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t){s=t[r];if(Un.exec(s)){delete t[r],a=a||s==="toggle";if(s===(g?"hide":"show"))continue;m.push(r)}}o=m.length;if(o){u=v._data(e,"fxshow")||v._data(e,"fxshow",{}),"hidden"in u&&(g=u.hidden),a&&(u.hidden=!g),g?v(e).show():h.done(function(){v(e).hide()}),h.done(function(){var t;v.removeData(e,"fxshow",!0);for(t in d)v.style(e,t,d[t])});for(r=0;r<o;r++)i=m[r],f=h.createTween(i,g?u[i]:0),d[i]=u[i]||v.style(e,i),i in u||(u[i]=f.start,g&&(f.end=f.start,f.start=i==="width"||i==="height"?1:0))}}function Yn(e,t,n,r,i){return new Yn.prototype.init(e,t,n,r,i)}function Zn(e,t){var n,r={height:e},i=0;t=t?1:0;for(;i<4;i+=2-t)n=$t[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}function tr(e){return v.isWindow(e)?e:e.nodeType===9?e.defaultView||e.parentWindow:!1}var n,r,i=e.document,s=e.location,o=e.navigator,u=e.jQuery,a=e.$,f=Array.prototype.push,l=Array.prototype.slice,c=Array.prototype.indexOf,h=Object.prototype.toString,p=Object.prototype.hasOwnProperty,d=String.prototype.trim,v=function(e,t){return new v.fn.init(e,t,n)},m=/[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,g=/\S/,y=/\s+/,b=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,w=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMContentLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=arguments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a<f;a++)if((e=arguments[a])!=null)for(n in e){r=u[n],i=e[n];if(u===i)continue;l&&i&&(v.isPlainObject(i)||(s=v.isArray(i)))?(s?(s=!1,o=r&&v.isArray(r)?r:[]):o=r&&v.isPlainObject(r)?r:{},u[n]=v.extend(l,o,i)):i!==t&&(u[n]=i)}return u},v.extend({noConflict:function(t){return e.$===v&&(e.$=a),t&&e.jQuery===v&&(e.jQuery=u),v},isReady:!1,readyWait:1,holdReady:function(e){e?v.readyWait++:v.ready(!0)},ready:function(e){if(e===!0?--v.readyWait:v.isReady)return;if(!i.body)return setTimeout(v.ready,1);v.isReady=!0;if(e!==!0&&--v.readyWait>0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s<o;)if(n.apply(e[s++],r)===!1)break}else if(u){for(i in e)if(n.call(e[i],i,e[i])===!1)break}else for(;s<o;)if(n.call(e[s],s,e[s++])===!1)break;return e},trim:d&&!d.call("\ufeff\u00a0")?function(e){return e==null?"":d.call(e)}:function(e){return e==null?"":(e+"").replace(b,"")},makeArray:function(e,t){var n,r=t||[];return e!=null&&(n=v.type(e),e.length==null||n==="string"||n==="function"||n==="regexp"||v.isWindow(e)?f.call(r,e):v.merge(r,e)),r},inArray:function(e,t,n){var r;if(t){if(c)return c.call(t,e,n);r=t.length,n=n?n<0?Math.max(0,r+n):n:0;for(;n<r;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,s=0;if(typeof r=="number")for(;s<r;s++)e[i++]=n[s];else while(n[s]!==t)e[i++]=n[s++];return e.length=i,e},grep:function(e,t,n){var r,i=[],s=0,o=e.length;n=!!n;for(;s<o;s++)r=!!t(e[s],s),n!==r&&i.push(e[s]);return i},map:function(e,n,r){var i,s,o=[],u=0,a=e.length,f=e instanceof v||a!==t&&typeof a=="number"&&(a>0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u<a;u++)i=n(e[u],u,r),i!=null&&(o[o.length]=i);else for(s in e)i=n(e[s],s,r),i!=null&&(o[o.length]=i);return o.concat.apply([],o)},guid:1,proxy:function(e,n){var r,i,s;return typeof n=="string"&&(r=e[n],n=e,e=r),v.isFunction(e)?(i=l.call(arguments,2),s=function(){return e.apply(n,i.concat(l.call(arguments)))},s.guid=e.guid=e.guid||v.guid++,s):t},access:function(e,n,r,i,s,o,u){var a,f=r==null,l=0,c=e.length;if(r&&typeof r=="object"){for(l in r)v.access(e,n,l,r[l],1,o,i);s=1}else if(i!==t){a=u===t&&v.isFunction(i),f&&(a?(a=n,n=function(e,t,n){return a.call(v(e),n)}):(n.call(e,i),n=null));if(n)for(;l<c;l++)n(e[l],r,a?i.call(e[l],l,n(e[l],r)):i,u);s=1}return s?e:f?n.call(e):c?n(e[0],r):o},now:function(){return(new Date).getTime()}}),v.ready.promise=function(t){if(!r){r=v.Deferred();if(i.readyState==="complete")setTimeout(v.ready,1);else if(i.addEventListener)i.addEventListener("DOMContentLoaded",A,!1),e.addEventListener("load",v.ready,!1);else{i.attachEvent("onreadystatechange",A),e.attachEvent("onload",v.ready);var n=!1;try{n=e.frameElement==null&&i.documentElement}catch(s){}n&&n.doScroll&&function o(){if(!v.isReady){try{n.doScroll("left")}catch(e){return setTimeout(o,50)}v.ready()}}()}}return r.promise(t)},v.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(e,t){O["[object "+t+"]"]=t.toLowerCase()}),n=v(i);var M={};v.Callbacks=function(e){e=typeof e=="string"?M[e]||_(e):v.extend({},e);var n,r,i,s,o,u,a=[],f=!e.once&&[],l=function(t){n=e.memory&&t,r=!0,u=s||0,s=0,o=a.length,i=!0;for(;a&&u<o;u++)if(a[u].apply(t[0],t[1])===!1&&e.stopOnFalse){n=!1;break}i=!1,a&&(f?f.length&&l(f.shift()):n?a=[]:c.disable())},c={add:function(){if(a){var t=a.length;(function r(t){v.each(t,function(t,n){var i=v.type(n);i==="function"?(!e.unique||!c.has(n))&&a.push(n):n&&n.length&&i!=="string"&&r(n)})})(arguments),i?o=a.length:n&&(s=t,l(n))}return this},remove:function(){return a&&v.each(arguments,function(e,t){var n;while((n=v.inArray(t,a,n))>-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t<r;t++)n[t]&&v.isFunction(n[t].promise)?n[t].promise().done(o(t,f,n)).fail(s.reject).progress(o(t,a,u)):--i}return i||s.resolveWith(f,n),s.promise()}}),v.support=function(){var t,n,r,s,o,u,a,f,l,c,h,p=i.createElement("div");p.setAttribute("className","t"),p.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.setAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="<table><tr><td></td><td>t</td></tr></table>",s=r.getElementsByTagName("td"),s[0].style.cssText="padding:0;margin:0;border:0;display:none",c=s[0].offsetHeight===0,s[0].style.display="",s[1].style.display="none",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML="",r.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!=="1%",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:"4px"}).width==="4px",o=i.createElement("div"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width="0",r.style.width="1px",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!="undefined"&&(r.innerHTML="",r.style.cssText=u+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display="block",r.style.overflow="visible",r.innerHTML="<div></div>",r.firstChild.style.width="5px",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(v.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n=="string",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n=="object"||typeof n=="function")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(!v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(" ")));for(i=0,s=t.length;i<s;i++)delete r[t[i]];if(!(n?B:v.isEmptyObject)(r))return}}if(!n){delete u[a].data;if(!B(u[a]))return}o?v.cleanData([e],!0):v.support.deleteExpando||u!=u.window?delete u[a]:u[a]=null},_data:function(e,t,n){return v.data(e,t,n,!0)},acceptData:function(e){var t=e.nodeName&&v.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),v.fn.extend({data:function(e,n){var r,i,s,o,u,a=this[0],f=0,l=null;if(e===t){if(this.length){l=v.data(a);if(a.nodeType===1&&!v._data(a,"parsedAttrs")){s=a.attributes;for(u=s.length;f<u;f++)o=s[f].name,o.indexOf("data-")||(o=v.camelCase(o.substring(5)),H(a,o,l[o]));v._data(a,"parsedAttrs",!0)}}return l}return typeof e=="object"?this.each(function(){v.data(this,e)}):(r=e.split(".",2),r[1]=r[1]?"."+r[1]:"",i=r[1]+"!",v.access(this,function(n){if(n===t)return l=this.triggerHandler("getData"+i,[r[0]]),l===t&&a&&(l=v.data(a,e),l=H(a,e,l)),l===t&&r[1]?this.data(r[0]):l;r[1]=n,this.each(function(){var t=v(this);t.triggerHandler("setData"+i,r),v.data(this,e,n),t.triggerHandler("changeData"+i,r)})},null,n,arguments.length>1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks("once memory").add(function(){v.removeData(e,t+"queue",!0),v.removeData(e,n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!="string"&&(n=e,e="fx",r--),arguments.length<r?v.queue(this[0],e):n===t?this:this.each(function(){var t=v.queue(this,e,n);v._queueHooks(this,e),e==="fx"&&t[0]!=="inprogress"&&v.dequeue(this,e)})},dequeue:function(e){return this.each(function(){v.dequeue(this,e)})},delay:function(e,t){return e=v.fx?v.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,s=v.Deferred(),o=this,u=this.length,a=function(){--i||s.resolveWith(o,[o])};typeof e!="string"&&(n=e,e=t),e=e||"fx";while(u--)r=v._data(o[u],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(a));return a(),s.promise(n)}});var j,F,I,q=/[\t\r\n]/g,R=/\r/g,U=/^(?:button|input)$/i,z=/^(?:button|input|object|select|textarea)$/i,W=/^a(?:rea|)$/i,X=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,V=v.support.getSetAttribute;v.fn.extend({attr:function(e,t){return v.access(this,v.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e=="string"){t=e.split(y);for(n=0,r=this.length;n<r;n++){i=this[n];if(i.nodeType===1)if(!i.className&&t.length===1)i.className=e;else{s=" "+i.className+" ";for(o=0,u=t.length;o<u;o++)s.indexOf(" "+t[o]+" ")<0&&(s+=t[o]+" ");i.className=v.trim(s)}}}return this},removeClass:function(e){var n,r,i,s,o,u,a;if(v.isFunction(e))return this.each(function(t){v(this).removeClass(e.call(this,t,this.className))});if(e&&typeof e=="string"||e===t){n=(e||"").split(y);for(u=0,a=this.length;u<a;u++){i=this[u];if(i.nodeType===1&&i.className){r=(" "+i.className+" ").replace(q," ");for(s=0,o=n.length;s<o;s++)while(r.indexOf(" "+n[s]+" ")>=0)r=r.replace(" "+n[s]+" "," ");i.className=e?v.trim(r):""}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t=="boolean";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?"addClass":"removeClass"](i)}else if(n==="undefined"||n==="boolean")this.className&&v._data(this,"__className__",this.className),this.className=this.className||e===!1?"":v._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n<r;n++)if(this[n].nodeType===1&&(" "+this[n].className+" ").replace(q," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.length){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(s,"value"))!==t?r:(r=s.value,typeof r=="string"?r.replace(R,""):r==null?"":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s="":typeof s=="number"?s+="":v.isArray(s)&&(s=v.map(s,function(e){return e==null?"":e+""})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!("set"in n)||n.set(this,s,"value")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a<u;a++){n=r[a];if((n.selected||a===i)&&(v.support.optDisabled?!n.disabled:n.getAttribute("disabled")===null)&&(!n.parentNode.disabled||!v.nodeName(n.parentNode,"optgroup"))){t=v(n).val();if(s)return t;o.push(t)}}return o},set:function(e,t){var n=v.makeArray(t);return v(e).find("option").each(function(){this.selected=v.inArray(v(this).val(),n)>=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute=="undefined")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&"set"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r)}return o&&"get"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o<r.length;o++)i=r[o],i&&(n=v.propFix[i]||i,s=X.test(i),s||v.attr(e,i,""),e.removeAttribute(V?i:n),s&&n in e&&(e[n]=!1))}},attrHooks:{type:{set:function(e,t){if(U.test(e.nodeName)&&e.parentNode)v.error("type property can't be changed");else if(!v.support.radioValue&&t==="radio"&&v.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}},value:{get:function(e,t){return j&&v.nodeName(e,"button")?j.get(e,t):t in e?e.value:null},set:function(e,t,n){if(j&&v.nodeName(e,"button"))return j.set(e,t,n);e.value=t}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(e,n,r){var i,s,o,u=e.nodeType;if(!e||u===3||u===8||u===2)return;return o=u!==1||!v.isXMLDoc(e),o&&(n=v.propFix[n]||n,s=v.propHooks[n]),r!==t?s&&"set"in s&&(i=s.set(e,r,n))!==t?i:e[n]=r:s&&"get"in s&&(i=s.get(e,n))!==null?i:e[n]},propHooks:{tabIndex:{get:function(e){var n=e.getAttributeNode("tabindex");return n&&n.specified?parseInt(n.value,10):z.test(e.nodeName)||W.test(e.nodeName)&&e.href?0:t}}}}),F={get:function(e,n){var r,i=v.prop(e,n);return i===!0||typeof i!="boolean"&&(r=e.getAttributeNode(n))&&r.nodeValue!==!1?n.toLowerCase():t},set:function(e,t,n){var r;return t===!1?v.removeAttr(e,n):(r=v.propFix[n]||n,r in e&&(e[r]=!0),e.setAttribute(n,n.toLowerCase())),n}},V||(I={name:!0,id:!0,coords:!0},j=v.valHooks.button={get:function(e,n){var r;return r=e.getAttributeNode(n),r&&(I[n]?r.value!=="":r.specified)?r.value:t},set:function(e,t,n){var r=e.getAttributeNode(n);return r||(r=i.createAttribute(n),e.setAttributeNode(r)),r.value=t+""}},v.each(["width","height"],function(e,t){v.attrHooks[t]=v.extend(v.attrHooks[t],{set:function(e,n){if(n==="")return e.setAttribute(t,"auto"),n}})}),v.attrHooks.contenteditable={get:j.get,set:function(e,t,n){t===""&&(t="false"),j.set(e,t,n)}}),v.support.hrefNormalized||v.each(["href","src","width","height"],function(e,n){v.attrHooks[n]=v.extend(v.attrHooks[n],{get:function(e){var r=e.getAttribute(n,2);return r===null?t:r}})}),v.support.style||(v.attrHooks.style={get:function(e){return e.style.cssText.toLowerCase()||t},set:function(e,t){return e.style.cssText=t+""}}),v.support.optSelected||(v.propHooks.selected=v.extend(v.propHooks.selected,{get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}})),v.support.enctype||(v.propFix.enctype="encoding"),v.support.checkOn||v.each(["radio","checkbox"],function(){v.valHooks[this]={get:function(e){return e.getAttribute("value")===null?"on":e.value}}}),v.each(["radio","checkbox"],function(){v.valHooks[this]=v.extend(v.valHooks[this],{set:function(e,t){if(v.isArray(t))return e.checked=v.inArray(v(e).val(),t)>=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\.]*|)(?:\.(.+)|)$/,K=/(?:^|\s)hover(\.\S+|)\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,"mouseenter$1 mouseleave$1")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v=="undefined"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(" ");for(f=0;f<n.length;f++){l=J.exec(n[f])||[],c=l[1],h=(l[2]||"").split(".").sort(),g=v.event.special[c]||{},c=(s?g.delegateType:g.bindType)||c,g=v.event.special[c]||{},p=v.extend({type:c,origType:l[1],data:i,handler:r,guid:r.guid,selector:s,needsContext:s&&v.expr.match.needsContext.test(s),namespace:h.join(".")},d),m=a[c];if(!m){m=a[c]=[],m.delegateCount=0;if(!g.setup||g.setup.call(e,i,h,u)===!1)e.addEventListener?e.addEventListener(c,u,!1):e.attachEvent&&e.attachEvent("on"+c,u)}g.add&&(g.add.call(e,p),p.handler.guid||(p.handler.guid=r.guid)),s?m.splice(m.delegateCount++,0,p):m.push(p),v.event.global[c]=!0}e=null},global:{},remove:function(e,t,n,r,i){var s,o,u,a,f,l,c,h,p,d,m,g=v.hasData(e)&&v._data(e);if(!g||!(h=g.events))return;t=v.trim(Z(t||"")).split(" ");for(s=0;s<t.length;s++){o=J.exec(t[s])||[],u=a=o[1],f=o[2];if(!u){for(u in h)v.event.remove(e,u+t[s],n,r,!0);continue}p=v.event.special[u]||{},u=(r?p.delegateType:p.bindType)||u,d=h[u]||[],l=d.length,f=f?new RegExp("(^|\\.)"+f.split(".").sort().join("\\.(?:.*\\.|)")+"(\\.|$)"):null;for(c=0;c<d.length;c++)m=d[c],(i||a===m.origType)&&(!n||n.guid===m.guid)&&(!f||f.test(m.namespace))&&(!r||r===m.selector||r==="**"&&m.selector)&&(d.splice(c--,1),m.selector&&d.delegateCount--,p.remove&&p.remove.call(e,m));d.length===0&&l!==d.length&&((!p.teardown||p.teardown.call(e,f,g.handle)===!1)&&v.removeEvent(e,u,g.handle),delete h[u])}v.isEmptyObject(h)&&(delete g.handle,v.removeData(e,"events",!0))},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(n,r,s,o){if(!s||s.nodeType!==3&&s.nodeType!==8){var u,a,f,l,c,h,p,d,m,g,y=n.type||n,b=[];if(Y.test(y+v.event.triggered))return;y.indexOf("!")>=0&&(y=y.slice(0,-1),a=!0),y.indexOf(".")>=0&&(b=y.split("."),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n=="object"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join("."),n.namespace_re=n.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,h=y.indexOf(":")<0?"on"+y:"";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f<m.length&&!n.isPropagationStopped();f++)l=m[f][0],n.type=m[f][1],d=(v._data(l,"events")||{})[n.type]&&v._data(l,"handle"),d&&d.apply(l,r),d=h&&l[h],d&&v.acceptData(l)&&d.apply&&d.apply(l,r)===!1&&n.preventDefault();return n.type=y,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(s.ownerDocument,r)===!1)&&(y!=="click"||!v.nodeName(s,"a"))&&v.acceptData(s)&&h&&s[y]&&(y!=="focus"&&y!=="blur"||n.target.offsetWidth!==0)&&!v.isWindow(s)&&(c=s[h],c&&(s[h]=null),v.event.triggered=y,s[y](),v.event.triggered=t,c&&(s[h]=c)),n.result}return},dispatch:function(n){n=v.event.fix(n||e.event);var r,i,s,o,u,a,f,c,h,p,d=(v._data(this,"events")||{})[n.type]||[],m=d.delegateCount,g=l.call(arguments),y=!n.exclusive&&!n.namespace,b=v.event.special[n.type]||{},w=[];g[0]=n,n.delegateTarget=this;if(b.preDispatch&&b.preDispatch.call(this,n)===!1)return;if(m&&(!n.button||n.type!=="click"))for(s=n.target;s!=this;s=s.parentNode||this)if(s.disabled!==!0||n.type!=="click"){u={},f=[];for(r=0;r<m;r++)c=d[r],h=c.selector,u[h]===t&&(u[h]=c.needsContext?v(h,this).index(s)>=0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r<w.length&&!n.isPropagationStopped();r++){a=w[r],n.currentTarget=a.elem;for(i=0;i<a.matches.length&&!n.isImmediatePropagationStopped();i++){c=a.matches[i];if(y||!n.namespace&&!c.namespace||n.namespace_re&&n.namespace_re.test(c.namespace))n.data=c.data,n.handleObj=c,o=((v.event.special[c.origType]||{}).handle||c.handler).apply(a.elem,g),o!==t&&(n.result=o,o===!1&&(n.preventDefault(),n.stopPropagation()))}}return b.postDispatch&&b.postDispatch.call(this,n),n.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return e.which==null&&(e.which=t.charCode!=null?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,s,o,u=n.button,a=n.fromElement;return e.pageX==null&&n.clientX!=null&&(r=e.target.ownerDocument||i,s=r.documentElement,o=r.body,e.pageX=n.clientX+(s&&s.scrollLeft||o&&o.scrollLeft||0)-(s&&s.clientLeft||o&&o.clientLeft||0),e.pageY=n.clientY+(s&&s.scrollTop||o&&o.scrollTop||0)-(s&&s.clientTop||o&&o.clientTop||0)),!e.relatedTarget&&a&&(e.relatedTarget=a===e.target?n.toElement:a),!e.which&&u!==t&&(e.which=u&1?1:u&2?3:u&4?2:0),e}},fix:function(e){if(e[v.expando])return e;var t,n,r=e,s=v.event.fixHooks[e.type]||{},o=s.props?this.props.concat(s.props):this.props;e=v.Event(r);for(t=o.length;t;)n=o[--t],e[n]=r[n];return e.target||(e.target=r.srcElement||i),e.target.nodeType===3&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,r):e},special:{load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(e,t,n){v.isWindow(this)&&(this.onbeforeunload=n)},teardown:function(e,t){this.onbeforeunload===t&&(this.onbeforeunload=null)}}},simulate:function(e,t,n,r){var i=v.extend(new v.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?v.event.trigger(i,null,t):v.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},v.event.handle=v.event.dispatch,v.removeEvent=i.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]=="undefined"&&(e[r]=null),e.detachEvent(r,n))},v.Event=function(e,t){if(!(this instanceof v.Event))return new v.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?tt:et):this.type=e,t&&v.extend(this,t),this.timeStamp=e&&e.timeStamp||v.now(),this[v.expando]=!0},v.Event.prototype={preventDefault:function(){this.isDefaultPrevented=tt;var e=this.originalEvent;if(!e)return;e.preventDefault?e.preventDefault():e.returnValue=!1},stopPropagation:function(){this.isPropagationStopped=tt;var e=this.originalEvent;if(!e)return;e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=tt,this.stopPropagation()},isDefaultPrevented:et,isPropagationStopped:et,isImmediatePropagationStopped:et},v.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){v.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,s=e.handleObj,o=s.selector;if(!i||i!==r&&!v.contains(r,i))e.type=s.origType,n=s.handler.apply(this,arguments),e.type=t;return n}}}),v.support.submitBubbles||(v.event.special.submit={setup:function(){if(v.nodeName(this,"form"))return!1;v.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=v.nodeName(n,"input")||v.nodeName(n,"button")?n.form:t;r&&!v._data(r,"_submit_attached")&&(v.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),v._data(r,"_submit_attached",!0))})},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&v.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){if(v.nodeName(this,"form"))return!1;v.event.remove(this,"._submit")}}),v.support.changeBubbles||(v.event.special.change={setup:function(){if($.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")v.event.add(this,"propertychange._change",function(e){e.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),v.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),v.event.simulate("change",this,e,!0)});return!1}v.event.add(this,"beforeactivate._change",function(e){var t=e.target;$.test(t.nodeName)&&!v._data(t,"_change_attached")&&(v.event.add(t,"change._change",function(e){this.parentNode&&!e.isSimulated&&!e.isTrigger&&v.event.simulate("change",this.parentNode,e,!0)}),v._data(t,"_change_attached",!0))})},handle:function(e){var t=e.target;if(this!==t||e.isSimulated||e.isTrigger||t.type!=="radio"&&t.type!=="checkbox")return e.handleObj.handler.apply(this,arguments)},teardown:function(){return v.event.remove(this,"._change"),!$.test(this.nodeName)}}),v.support.focusinBubbles||v.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){v.event.simulate(t,e.target,v.event.fix(e),!0)};v.event.special[t]={setup:function(){n++===0&&i.addEventListener(e,r,!0)},teardown:function(){--n===0&&i.removeEventListener(e,r,!0)}}}),v.fn.extend({on:function(e,n,r,i,s){var o,u;if(typeof e=="object"){typeof n!="string"&&(r=r||n,n=t);for(u in e)this.on(u,n,r,e[u],s);return this}r==null&&i==null?(i=n,r=n=t):i==null&&(typeof n=="string"?(i=r,r=t):(i=r,r=n,n=t));if(i===!1)i=et;else if(!i)return this;return s===1&&(o=i,i=function(e){return v().off(e),o.apply(this,arguments)},i.guid=o.guid||(o.guid=v.guid++)),this.each(function(){v.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,s;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,v(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if(typeof e=="object"){for(s in e)this.off(s,n,e[s]);return this}if(n===!1||typeof n=="function")r=n,n=t;return r===!1&&(r=et),this.each(function(){v.event.remove(this,e,r,n)})},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},live:function(e,t,n){return v(this.context).on(e,this.selector,t,n),this},die:function(e,t){return v(this.context).off(e,this.selector||"**",t),this},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return arguments.length===1?this.off(e,"**"):this.off(t,e||"**",n)},trigger:function(e,t){return this.each(function(){v.event.trigger(e,t,this)})},triggerHandler:function(e,t){if(this[0])return v.event.trigger(e,t,this[0],!0)},toggle:function(e){var t=arguments,n=e.guid||v.guid++,r=0,i=function(n){var i=(v._data(this,"lastToggle"+e.guid)||0)%r;return v._data(this,"lastToggle"+e.guid,i+1),n.preventDefault(),t[i].apply(this,arguments)||!1};i.guid=n;while(r<t.length)t[r++].guid=n;return this.click(i)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),v.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){v.fn[t]=function(e,n){return n==null&&(n=e,e=null),arguments.length>0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!="string")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,"$1"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.slice(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j," ");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir==="parentNode",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+" "+o+" ",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a=="string"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u<a;u++)if(s=e[u])if(!n||n(s,r,i))o.push(s),f&&t.push(u);return o}function ct(e,t,n,r,i,s){return r&&!r[d]&&(r=ct(r)),i&&!i[d]&&(i=ct(i,s)),N(function(s,o,u,a){var f,l,c,h=[],p=[],d=o.length,v=s||dt(t||"*",u.nodeType?[u]:u,[]),m=e&&(s||!t)?lt(v,h,e,u,a):v,g=n?i||(s?e:d||r)?[]:o:m;n&&n(m,g,u,a);if(r){f=lt(g,p),r(f,[],u,a),l=f.length;while(l--)if(c=f[l])g[p[l]]=!(m[p[l]]=c)}if(s){if(i||e){if(i){f=[],l=g.length;while(l--)(c=g[l])&&f.push(m[l]=c);i(null,g=[],f,a)}l=g.length;while(l--)(c=g[l])&&(f=i?T.call(s,c):h[l])>-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[" "],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a<s;a++)if(n=i.relative[e[a].type])h=[at(ft(h),n)];else{n=i.filter[e[a].type].apply(null,e[a].matches);if(n[d]){r=++a;for(;r<s;r++)if(i.relative[e[r].type])break;return ct(a>1&&ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a<r&&ht(e.slice(a,r)),r<s&&ht(e=e.slice(r)),r<s&&e.join(""))}h.push(n)}return ft(h)}function pt(e,t){var r=t.length>0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w="0",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG("*",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r<i;r++)nt(e,t[r],n);return n}function vt(e,t,n,r,s){var o,u,f,l,c,h=ut(e),p=h.length;if(!r&&h.length===1){u=h[0]=h[0].slice(0);if(u.length>2&&(f=u[0]).type==="ID"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,""),t,s)[0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,""),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join("");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p="undefined",d=("sizcache"+Math.random()).replace(".",""),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;t<n;t++)if(this[t]===e)return t;return-1},N=function(e,t){return e[d]=t==null||t,e},C=function(){var e={},t=[];return N(function(n,r){return t.push(n)>i.cacheLength&&delete e[t.shift()],e[n+" "]=r},e)},k=C(),L=C(),A=C(),O="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",_=M.replace("w","w#"),D="([*^$|!~]?=)",P="\\["+O+"*("+M+")"+O+"*(?:"+D+O+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+_+")|)|)"+O+"*\\]",H=":("+M+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+P+")|[^:]|\\\\.)*|.*))\\)|)",B=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)",j=new RegExp("^"+O+"+|((?:^|[^\\\\])(?:\\\\.)*)"+O+"+$","g"),F=new RegExp("^"+O+"*,"+O+"*"),I=new RegExp("^"+O+"*([\\x20\\t\\r\\n\\f>+~])"+O+"*"),q=new RegExp(H),R=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,U=/^:not/,z=/[\x20\t\r\n\f]*[+~]/,W=/:not\($/,X=/h\d/i,V=/input|select|textarea|button/i,$=/\\(?!\\)/g,J={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),NAME:new RegExp("^\\[name=['\"]?("+M+")['\"]?\\]"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+H),POS:new RegExp(B,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),needsContext:new RegExp("^"+O+"*[>+~]|"+B,"i")},K=function(e){var t=g.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}},Q=K(function(e){return e.appendChild(g.createComment("")),!e.getElementsByTagName("*").length}),G=K(function(e){return e.innerHTML="<a href='#'></a>",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="<select></select>";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",!e.getElementsByClassName||!e.getElementsByClassName("e").length?!1:(e.lastChild.className="e",e.getElementsByClassName("e").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML="<a name='"+d+"'></a><div name='"+d+"'></div>",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.matches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]=="boolean"?e[t]?t:null:n.specified?n.value:null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode("id").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e==="*"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,""),e[3]=(e[4]||e[5]||"").replace($,""),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]==="nth"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]==="even"||e[2]==="odd")),e[4]=+(e[6]+e[7]||e[2]==="odd")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(")",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,""),function(t){return t.getAttribute("id")===e}}:function(e){return e=e.replace($,""),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode("id");return n&&n.value===e}},TAG:function(e){return e==="*"?function(){return!0}:(e=e.replace($,"").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[d][e+" "];return t||(t=new RegExp("(^|"+O+")"+e+"("+O+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t==="!=":t?(s+="",t==="="?s===n:t==="!="?s!==n:t==="^="?n&&s.indexOf(n)===0:t==="*="?n&&s.indexOf(n)>-1:t==="$="?n&&s.substr(s.length-n.length)===n:t==="~="?(" "+s+" ").indexOf(n)>-1:t==="|="?s===n||s.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r){return e==="nth"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case"only":case"first":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e==="first")return!0;n=t;case"last":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||nt.error("unsupported pseudo: "+e);return r[d]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,"$1"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty:function(e){var t;e=e.firstChild;while(e){if(e.nodeName>"@"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()==="input"&&(t=e.type)==="text"&&((n=e.getAttribute("type"))==null||n.toLowerCase()===t)},radio:rt("radio"),checkbox:rt("checkbox"),file:rt("file"),password:rt("password"),image:rt("image"),submit:it("submit"),reset:it("reset"),button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:st(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:st(function(e,t,n){for(var r=n<0?n+t:n;--r>=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}},f=y.compareDocumentPosition?function(e,t){return e===t?(l=!0,0):(!e.compareDocumentPosition||!t.compareDocumentPosition?e.compareDocumentPosition:e.compareDocumentPosition(t)&4)?-1:1}:function(e,t){if(e===t)return l=!0,0;if(e.sourceIndex&&t.sourceIndex)return e.sourceIndex-t.sourceIndex;var n,r,i=[],s=[],o=e.parentNode,u=t.parentNode,a=o;if(o===u)return ot(e,t);if(!o)return-1;if(!u)return 1;while(a)i.unshift(a),a=a.parentNode;a=u;while(a)s.unshift(a),a=a.parentNode;n=i.length,r=s.length;for(var f=0;f<n&&f<r;f++)if(i[f]!==s[f])return ot(i[f],s[f]);return f===n?ot(e,s[f],-1):ot(i[f],t,1)},[0,0].sort(f),h=!l,nt.uniqueSort=function(e){var t,n=[],r=1,i=0;l=h,e.sort(f);if(l){for(;t=e[r];r++)t===e[r-1]&&(i=n.push(r));while(i--)e.splice(n[i],1)}return e},nt.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},a=nt.compile=function(e,t){var n,r=[],i=[],s=A[d][e+" "];if(!s){t||(t=ut(e)),n=t.length;while(n--)s=ht(t[n]),s[d]?r.push(s):i.push(s);s=A(e,pt(i,r))}return s},g.querySelectorAll&&function(){var e,t=vt,n=/'|\\/g,r=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,i=[":focus"],s=[":active"],u=y.matchesSelector||y.mozMatchesSelector||y.webkitMatchesSelector||y.oMatchesSelector||y.msMatchesSelector;K(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="<p test=''></p>",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="<input type='hidden'/>",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t<n;t++)if(v.contains(u[t],this))return!0});o=this.pushStack("","find",e);for(t=0,n=this.length;t<n;t++){r=o.length,v.find(e,this[t],o);if(t>0)for(i=r;i<o.length;i++)for(s=0;s<r;s++)if(o[s]===o[i]){o.splice(i--,1);break}}return o},has:function(e){var t,n=v(e,this),r=n.length;return this.filter(function(){for(t=0;t<r;t++)if(v.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e,!1),"not",e)},filter:function(e){return this.pushStack(ft(this,e,!0),"filter",e)},is:function(e){return!!e&&(typeof e=="string"?st.test(e)?v(e,this.context).index(this[0])>=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t||this.context):0;for(;r<i;r++){n=this[r];while(n&&n.ownerDocument&&n!==t&&n.nodeType!==11){if(o?o.index(n)>-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir(e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/<tbody/i,gt=/<|&#?\w+;/,yt=/<(?:script|style|link)/i,bt=/<(?:script|object|embed|option|style)/i,wt=new RegExp("<(?:"+ct+")[\\s/>]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,Nt={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X<div>","</div>"]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1></$2>");try{for(;r<i;r++)n=this[r]||{},n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),n.innerHTML=e);n=0}catch(s){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(e){return ut(this[0])?this.length?this.pushStack(v(v.isFunction(e)?e():e),"replaceWith",e):this:v.isFunction(e)?this.each(function(t){var n=v(this),r=n.html();n.replaceWith(e.call(this,t,r))}):(typeof e!="string"&&(e=v(e).detach()),this.each(function(){var t=this.nextSibling,n=this.parentNode;v(this).remove(),t?v(t).before(e):v(n).append(e)}))},detach:function(e){return this.remove(e,!0)},domManip:function(e,n,r){e=[].concat.apply([],e);var i,s,o,u,a=0,f=e[0],l=[],c=this.length;if(!v.support.checkClone&&c>1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a<c;a++)r.call(n&&v.nodeName(this[a],"table")?Lt(this[a],"tbody"):this[a],a===u?o:v.clone(o,!0,!0))}o=s=null,l.length&&v.each(l,function(e,t){t.src?v.ajax?v.ajax({url:t.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):v.error("no ajax"):v.globalEval((t.text||t.textContent||t.innerHTML||"").replace(Tt,"")),t.parentNode&&t.parentNode.removeChild(t)})}return this}}),v.buildFragment=function(e,n,r){var s,o,u,a=e[0];return n=n||i,n=!n.nodeType&&n[0]||n,n=n.ownerDocument||n,e.length===1&&typeof a=="string"&&a.length<512&&n===i&&a.charAt(0)==="<"&&!bt.test(a)&&(v.support.checkClone||!St.test(a))&&(v.support.html5Clone||!wt.test(a))&&(o=!0,s=v.fragments[a],u=s!==t),s||(s=n.createDocumentFragment(),v.clean(e,n,s,r),o&&(v.fragments[a]=u&&s)),{fragment:s,cacheable:o}},v.fragments={},v.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){v.fn[e]=function(n){var r,i=0,s=[],o=v(n),u=o.length,a=this.length===1&&this[0].parentNode;if((a==null||a&&a.nodeType===11&&a.childNodes.length===1)&&u===1)return o[t](this[0]),this;for(;i<u;i++)r=(i>0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1></$2>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]==="<table>"&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=["*/"]+["*"];try{cn=s.href}catch(Nn){cn=i.createElement("a"),cn.href="",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!="string"&&En)return En.apply(this,arguments);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(" ");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n=="object"&&(s="POST"),v.ajax({url:e,type:s,dataType:"html",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v("<div>").append(e.replace(yn,"")).find(i):e)}),this},v.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each(["get","post"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,"script")},getJSON:function(e,t,n){return v.get(e,t,n,"json")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":v.parseJSON,"text xml":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||"",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader("Last-Modified"),S&&(v.lastModified[r]=S),S=x.getResponseHeader("Etag"),S&&(v.etag[r]=S)),e===304?(T="notmodified",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T="error",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+"",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger("ajax"+(l?"Success":"Error"),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger("ajaxComplete",[x,c]),--v.active||v.event.trigger("ajaxStop"))}typeof e=="object"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks("once memory"),g=c.statusCode||{},b={},w={},E=0,S="canceled",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+"").replace(hn,"").replace(mn,ln[1]+"//"),c.dataTypes=v.trim(c.dataType||"*").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]==="http:"?80:443))==(ln[3]||(ln[1]==="http:"?80:443)))),c.data&&c.processData&&typeof c.data!="string"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger("ajaxStart");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?"&":"?")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,"$1_="+N);c.url=C+(C===c.url?(gn.test(c.url)?"&":"?")+"_="+N:"")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader("Content-Type",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader("If-Modified-Since",v.lastModified[r]),v.etag[r]&&x.setRequestHeader("If-None-Match",v.etag[r])),x.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!=="*"?", "+Tn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S="abort";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,"No Transport");else{x.readyState=1,f&&p.trigger("ajaxSend",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort("timeout")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\?/,Dn=/(=)\?(?=&|$)|\?\?/,Pn=v.now();v.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Mn.pop()||v.expando+"_"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter("json jsonp",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a=="string"&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Dn.test(a);if(n.dataTypes[0]==="jsonp"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,"$1"+s):h?n.data=a.replace(Dn,"$1"+s):l&&(n.url+=(_n.test(f)?"&":"?")+n.jsonp+"="+s),n.converters["script json"]=function(){return u||v.error(s+" was not called"),u[0]},n.dataTypes[0]="json",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),"script"}),v.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),v.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName("head")[0]||i.documentElement;return{send:function(s,o){n=i.createElement("script"),n.async="async",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,"success")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&"withCredentials"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i["X-Requested-With"]&&(i["X-Requested-With"]="XMLHttpRequest");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=""}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp("^(?:([-+])=|)("+m+")([a-z%]*)$","i"),Wn=/queueHooks$/,Xn=[Gn],Vn={"*":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?"":"px");if(r!=="px"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a||".5",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r<i;r++)n=e[r],Vn[n]=Vn[n]||[],Vn[n].unshift(t)},prefilter:function(e,t){t?Xn.unshift(e):Xn.push(e)}}),v.Tween=Yn,Yn.prototype={constructor:Yn,init:function(e,t,n,r,i,s){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=s||(v.cssNumber[n]?"":"px")},cur:function(){var e=Yn.propHooks[this.prop];return e&&e.get?e.get(this):Yn.propHooks._default.get(this)},run:function(e){var t,n=Yn.propHooks[this.prop];return this.options.duration?this.pos=t=v.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):Yn.propHooks._default.set(this),this}},Yn.prototype.init.prototype=Yn.prototype,Yn.propHooks={_default:{get:function(e){var t;return e.elem[e.prop]==null||!!e.elem.style&&e.elem.style[e.prop]!=null?(t=v.css(e.elem,e.prop,!1,""),!t||t==="auto"?0:t):e.elem[e.prop]},set:function(e){v.fx.step[e.prop]?v.fx.step[e.prop](e):e.elem.style&&(e.elem.style[v.cssProps[e.prop]]!=null||v.cssHooks[e.prop])?v.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},Yn.propHooks.scrollTop=Yn.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},v.each(["toggle","show","hide"],function(e,t){var n=v.fn[t];v.fn[t]=function(r,i,s){return r==null||typeof r=="boolean"||!e&&v.isFunction(r)&&v.isFunction(i)?n.apply(this,arguments):this.animate(Zn(t,!0),r,i,s)}}),v.fn.extend({fadeTo:function(e,t,n,r){return this.filter(Gt).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=v.isEmptyObject(e),s=v.speed(t,n,r),o=function(){var t=Kn(this,v.extend({},e),s);i&&t.stop(!0)};return i||s.queue===!1?this.each(o):this.queue(s.queue,o)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return typeof e!="string"&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=e!=null&&e+"queueHooks",s=v.timers,o=v._data(this);if(n)o[n]&&o[n].stop&&i(o[n]);else for(n in o)o[n]&&o[n].stop&&Wn.test(n)&&i(o[n]);for(n=s.length;n--;)s[n].elem===this&&(e==null||s[n].queue===e)&&(s[n].anim.stop(r),t=!1,s.splice(n,1));(t||!r)&&v.dequeue(this,e)})}}),v.each({slideDown:Zn("show"),slideUp:Zn("hide"),slideToggle:Zn("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){v.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),v.speed=function(e,t,n){var r=e&&typeof e=="object"?v.extend({},e):{complete:n||!n&&t||v.isFunction(e)&&e,duration:e,easing:n&&t||t&&!v.isFunction(t)&&t};r.duration=v.fx.off?0:typeof r.duration=="number"?r.duration:r.duration in v.fx.speeds?v.fx.speeds[r.duration]:v.fx.speeds._default;if(r.queue==null||r.queue===!0)r.queue="fx";return r.old=r.complete,r.complete=function(){v.isFunction(r.old)&&r.old.call(this),r.queue&&v.dequeue(this,r.queue)},r},v.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},v.timers=[],v.fx=Yn.prototype.init,v.fx.tick=function(){var e,n=v.timers,r=0;qn=v.now();for(;r<n.length;r++)e=n[r],!e()&&n[r]===e&&n.splice(r--,1);n.length||v.fx.stop(),qn=t},v.fx.timer=function(e){e()&&v.timers.push(e)&&!Rn&&(Rn=setInterval(v.fx.tick,v.fx.interval))},v.fx.interval=13,v.fx.stop=function(){clearInterval(Rn),Rn=null},v.fx.speeds={slow:600,fast:200,_default:400},v.fx.step={},v.expr&&v.expr.filters&&(v.expr.filters.animated=function(e){return v.grep(v.timers,function(t){return e===t.elem}).length});var er=/^(?:body|html)$/i;v.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){v.offset.setOffset(this,e,t)});var n,r,i,s,o,u,a,f={top:0,left:0},l=this[0],c=l&&l.ownerDocument;if(!c)return;return(r=c.body)===l?v.offset.bodyOffset(l):(n=c.documentElement,v.contains(n,l)?(typeof l.getBoundingClientRect!="undefined"&&(f=l.getBoundingClientRect()),i=tr(c),s=n.clientTop||r.clientTop||0,o=n.clientLeft||r.clientLeft||0,u=i.pageYOffset||n.scrollTop,a=i.pageXOffset||n.scrollLeft,{top:f.top+u-s,left:f.left+a-o}):f)},v.offset={bodyOffset:function(e){var t=e.offsetTop,n=e.offsetLeft;return v.support.doesNotIncludeMarginInBodyOffset&&(t+=parseFloat(v.css(e,"marginTop"))||0,n+=parseFloat(v.css(e,"marginLeft"))||0),{top:t,left:n}},setOffset:function(e,t,n){var r=v.css(e,"position");r==="static"&&(e.style.position="relative");var i=v(e),s=i.offset(),o=v.css(e,"top"),u=v.css(e,"left"),a=(r==="absolute"||r==="fixed")&&v.inArray("auto",[o,u])>-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=null&&(f.left=t.left-s.left+h),"using"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,"marginTop"))||0,n.left-=parseFloat(v.css(e,"marginLeft"))||0,r.top+=parseFloat(v.css(t[0],"borderTopWidth"))||0,r.left+=parseFloat(v.css(t[0],"borderLeftWidth"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,"position")==="static")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:"height",Width:"width"},function(e,n){v.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!="boolean"),u=r||(i===!0||s===!0?"margin":"border");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement["client"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body["scroll"+e],s["scroll"+e],n.body["offset"+e],s["offset"+e],s["client"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return v})})(window);
\ No newline at end of file
diff --git a/pdns/recursordist/html/js/jsrender.js b/pdns/recursordist/html/js/jsrender.js
deleted file mode 100644 (file)
index 8f21545..0000000
+++ /dev/null
@@ -1,1283 +0,0 @@
-/*! JsRender v1.0pre: http://github.com/BorisMoore/jsrender */
-/*
-* Optimized version of jQuery Templates, for rendering to string.
-* Does not require jQuery, or HTML DOM
-* Integrates with JsViews (http://github.com/BorisMoore/jsviews)
-* Copyright 2012, Boris Moore
-* Released under the MIT License.
-*/
-// informal pre beta commit counter: 24
-
-(function(global, jQuery, undefined) {
-       // global is the this object, which is window when running in the usual browser environment.
-       "use strict";
-
-       if (jQuery && jQuery.views || global.jsviews) { return; } // JsRender is already loaded
-
-       //========================== Top-level vars ==========================
-
-       var versionNumber = "v1.0pre",
-
-               $, jsvStoreName, rTag, rTmplString,
-//TODO tmplFnsCache = {},
-               delimOpenChar0 = "{", delimOpenChar1 = "{", delimCloseChar0 = "}", delimCloseChar1 = "}", linkChar = "^",
-               FALSE = false, TRUE = true,
-
-               rPath = /^(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,
-               //                                     object     helper    view  viewProperty pathTokens      leafToken
-
-               rParams = /(\()(?=|\s*\()|(?:([([])\s*)?(?:([#~]?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*!:?\/]|(=))\s*|([#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*([)\]])([([]?))|(\s+)/g,
-               //          lftPrn        lftPrn2                 path    operator err                                                eq          path2       prn    comma   lftPrn2   apos quot        rtPrn   prn2   space
-               // (left paren? followed by (path? followed by operator) or (path followed by paren?)) or comma or apos or quot or right paren or space
-
-               rNewLine = /\r?\n/g,
-               rUnescapeQuotes = /\\(['"])/g,
-               // escape quotes and \ character
-               rEscapeQuotes = /([\\'"])/g,
-               rBuildHash = /\x08(~)?([^\x08]+)\x08/g,
-               rTestElseIf = /^if\s/,
-               rFirstElem = /<(\w+)[>\s]/,
-               rPrevElem = /<(\w+)[^>\/]*>[^>]*$/,
-               autoTmplName = 0,
-               viewId = 0,
-               escapeMapForHtml = {
-                       "&": "&amp;",
-                       "<": "&lt;",
-                       ">": "&gt;"
-               },
-        attrEncodeChars = /[<"'&]/g,
-               htmlEncodeChars = /[\x00<>"'&]/g,
-               tmplAttr = "data-jsv-tmpl",
-               fnDeclStr = "var j=j||" + (jQuery ? "jQuery." : "js") + "views,",
-               slice = [].slice,
-
-               $render = {},
-               jsvStores = {
-                       template: {
-                               compile: compileTmpl
-                       },
-                       tag: {
-                               compile: compileTag
-                       },
-                       helper: {},
-                       converter: {}
-               },
-
-               // jsviews object ($.views if jQuery is loaded)
-               $views = {
-                       jsviews: versionNumber,
-                       render: $render,
-                       View: View,
-                       settings: {
-                               delimiters: $viewsDelimiters,
-                               debugMode: TRUE,
-                               tryCatch: TRUE
-                       },
-                       sub: {
-                               // subscription, e.g. JsViews integration
-                               Error: JsViewsError,
-                               tmplFn: tmplFn,
-                               parse: parseParams,
-                               extend: $extend,
-                               error: error
-//TODO                 invoke: $invoke
-                       },
-                       _cnvt: convertVal,
-                       _tag: renderTag,
-
-                       // TODO provide better debug experience - e.g. support $.views.onError callback
-                       _err: function(e) {
-                               // Place a breakpoint here to intercept template rendering errors
-                               return $viewsSettings.debugMode ? ("Error: " + (e.message || e)) + ". " : '';
-                       }
-               };
-
-               function JsViewsError(message, object) {
-                       // Error exception type for JsViews/JsRender
-                       // Override of $.views.sub.Error is possible
-                       if (object && object.onError) {
-                               if (object.onError(message) === FALSE) {
-                                       return;
-                               }
-                       }
-                       this.name = "JsRender Error";
-                       this.message = message || "JsRender error";
-               }
-
-               function $extend(target, source) {
-                       var name;
-                       target = target || {};
-                       for (name in source) {
-                               target[name] = source[name];
-                       }
-                       return target;
-               }
-
-//TODO         function $invoke() {
-//                     try {
-//                             return arguments[1].apply(arguments[0], arguments[2]);
-//                     }
-//                     catch(e) {
-//                             throw new $views.sub.Error(e, arguments[0]);
-//                     }
-//             }
-
-               (JsViewsError.prototype = new Error()).constructor = JsViewsError;
-
-       //========================== Top-level functions ==========================
-
-       //===================
-       // jsviews.delimiters
-       //===================
-
-       function $viewsDelimiters(openChars, closeChars, link) {
-               // Set the tag opening and closing delimiters and 'link' character. Default is "{{", "}}" and "^"
-               // openChars, closeChars: opening and closing strings, each with two characters
-
-               if (!$viewsSub.rTag || arguments.length) {
-                       delimOpenChar0 = openChars ? openChars.charAt(0) : delimOpenChar0; // Escape the characters - since they could be regex special characters
-                       delimOpenChar1 = openChars ? openChars.charAt(1) : delimOpenChar1;
-                       delimCloseChar0 = closeChars ? closeChars.charAt(0) : delimCloseChar0;
-                       delimCloseChar1 = closeChars ? closeChars.charAt(1) : delimCloseChar1;
-                       linkChar = link || linkChar;
-                       openChars = "\\" + delimOpenChar0 + "(\\" + linkChar + ")?\\" + delimOpenChar1;  // Default is "{^{"
-                       closeChars = "\\" + delimCloseChar0 + "\\" + delimCloseChar1;                   // Default is "}}"
-                       // Build regex with new delimiters
-                       //          tag    (followed by / space or })   or cvtr+colon or html or code
-                       rTag = "(?:(?:(\\w+(?=[\\/\\s\\" + delimCloseChar0 + "]))|(?:(\\w+)?(:)|(>)|!--((?:[^-]|-(?!-))*)--|(\\*)))"
-                               + "\\s*((?:[^\\" + delimCloseChar0 + "]|\\" + delimCloseChar0 + "(?!\\" + delimCloseChar1 + "))*?)";
-
-                       // make rTag available to JsViews (or other components) for parsing binding expressions
-                       $viewsSub.rTag = rTag + ")";
-
-                       rTag = new RegExp(openChars + rTag + "(\\/)?|(?:\\/(\\w+)))" + closeChars, "g");
-
-                       // Default:    bind           tag       converter colon html     comment            code      params            slash   closeBlock
-                       //           /{(\^)?{(?:(?:(\w+(?=[\/\s}]))|(?:(\w+)?(:)|(>)|!--((?:[^-]|-(?!-))*)--|(\*)))\s*((?:[^}]|}(?!}))*?)(\/)?|(?:\/(\w+)))}}/g
-
-                       rTmplString = new RegExp("<.*>|([^\\\\]|^)[{}]|" + openChars + ".*" + closeChars);
-                       // rTmplString looks for html tags or { or } char not preceded by \\, or JsRender tags {{xxx}}. Each of these strings are considered NOT to be jQuery selectors
-               }
-               return [delimOpenChar0, delimOpenChar1, delimCloseChar0, delimCloseChar1, linkChar];
-       }
-
-       //=========
-       // View.get
-       //=========
-
-       function getView(type) {
-               // TODO complete/test/provide samples for this
-               // If type is undefined, returns root view (view under top view).
-               var view = this,
-                       root = !type || type === "root";
-               while (root && view.parent.parent || view && view.type !== type) {
-                       view = view.parent;
-               }
-               return view;
-       }
-
-       function getIndex() {
-               var view = this.get("item");
-               return view ? view.index : undefined;
-       }
-
-       getIndex.depends = function(view) {
-               return [view.get("item"), "index"];
-       }
-       //==========
-       // View._hlp
-       //==========
-
-       function getHelper(helper) {
-               // Helper method called as view._hlp(key) from compiled template, for helper functions or template parameters ~foo
-               var wrapped,
-                       view = this;
-               if (helper = (view.ctx || {})[helper] || view.getRsc("helpers", helper)) {
-                       if (typeof helper === "function") {
-                               wrapped = function() {
-                                       // If it is of type function, we will wrap it so it gets called with view as 'this' context.
-                                       // If the helper ~foo() was in a data-link expression, the view will have a 'temporary' linkCtx property too.
-                                       // However note that helper functions on deeper paths will not have access to view and tagCtx.
-                                       // For example, ~util.foo() will have the ~util object as 'this' pointer
-                                       return helper.apply(view, arguments);
-                               };
-                               $extend(wrapped, helper);
-                       }
-               }
-               return wrapped || helper;
-       }
-
-       //==============
-       // jsviews._cnvt
-       //==============
-
-       function convertVal(converter, view, self, tagCtx, bindingPaths, text) {
-               // self is template object or linkCtx object
-               if (converter || bindingPaths) {
-                       var tmplConverter,
-                               linkCtx = !self.markup && self,
-                               tag = {
-                                       tagName:  converter + ":",
-                                       tagCtx: tagCtx
-                               },
-                               args = tagCtx.args = slice.call(arguments, 5);
-
-                       tagCtx.view = view;
-                       tagCtx.bind = !!(linkCtx || bindingPaths);
-
-                       if (linkCtx) {
-                               linkCtx.tag = tag;
-                               tag.linkCtx = linkCtx;
-                               tagCtx.ctx = extendCtx(tagCtx.ctx, linkCtx.view.ctx);
-                       }
-                       tag.ctx = tagCtx.ctx || {};
-                       tagCtx.props = tagCtx.props || {};
-                       delete tagCtx.ctx;
-
-                       if (converter && ((tmplConverter = view.getRsc("converters", converter)) || error("Unknown converter: {{"+ converter + ":"))) {
-                               // A call to {{cnvt: ... }} or {^{cnvt: ... }} or data-link="{cnvt: ... }"
-                               text = tmplConverter.apply(tag, args);
-                       }
-                       if (bindingPaths) {
-                               // A call to {^{: ... }} or {^{cnvt: ... }}
-                               bindingPaths = view.tmpl.bnds[bindingPaths-1];
-                               linkCtx.paths = bindingPaths;
-                               // Consider being able to switch off binding if parent view is not currently bound.
-                               view._.tag = tag; // Provide this tag on view, for markerNode on bound tags, and for getting the tagCtx and linkCtx during rendering.
-                               // Provide this tag on view, for addMarkerNode on bound tags to add the tag to view._.bnds, associated with the tag id,
-                               // and so when rendering subsequent {{else}}, will be associated with this tag
-                               //TODO does this work with nested elses and with {^{foo:...}} which also adds tag to view, for markerNodes.
-                               text = view._.onRender(text, view, TRUE);
-       //Example:  text = '<script type="jsv123"></script>' + text + '<script type="jsv123/"></script>';
-                       }
-               }
-               return text;
-       }
-
-       //=============
-       // jsviews._tag
-       //=============
-
-       function getResource(storeName, item) {
-               var res,
-                       view = this,
-                       store = $views[storeName];
-
-               res = store && store[item];
-               while (!res && view) {
-                       store = view.tmpl[storeName];
-                       res = store && store[item];
-                       view = view.parent;
-               }
-               return res;
-       }
-
-       function getResource2(storeName, item, root) {
-               var view = this,
-                       store = !root && $views[storeName];
-               return store && store[item]
-                       || (store = view.tmpl[storeName], store && store[item])
-                       || view.parent && view.parent.getRsc(storeName, item, TRUE);
-       }
-
-       function renderTag(tagName, parentView, self, content, tagCtx, bind) {
-               // Called from within compiled template function, to render a template tag
-               // Returns the rendered tag
-
-               var ret, render, ctx, elses, tag, tags,
-                       tmpl = self.markup && self,
-                       // self is either a template object (if rendering a tag) or a linkCtx object (if linking using a link tag)
-                       linkCtx = !tmpl && self,
-                       parentView_ = parentView._,
-                       parentTmpl = tmpl || parentView.tmpl,
-                       childTemplates = parentTmpl.templates,
-                       tagDef = parentView.getRsc("tags", tagName) || error("Unknown tag: {{"+ tagName + "}}"),
-                       args = tagCtx.args = arguments.length > 6 ? slice.call(arguments, 6) : [],
-                       props = tagCtx.props = tagCtx.props || {};
-
-               tagCtx.view = parentView;
-               tagCtx.ctx = extendCtx(tagCtx.ctx, parentView.ctx); // Extend parentView.ctx
-               ctx = tagCtx.ctx || {};
-               delete tagCtx.ctx;
-
-               // Set the tmpl property to the content of the block tag, unless set as an override property on the tag
-               tmpl = props.tmpl;
-               content = content && parentTmpl.tmpls[content - 1];
-               tmpl = tmpl || content || tagDef.template || undefined;
-               tmpl = "" + tmpl === tmpl // if a string
-                       ? parentView.getRsc("templates", tmpl) || $templates(tmpl)
-                       : tmpl;
-
-               if (tagName === "else") {
-                       tag = parentView._.tag;
-                       // Switch current tagCtx of tag instance to this {{else ...}}
-                       elses = tag._elses = tag._elses || [];
-                       elses.push(tmpl);
-                       tagCtx.isElse = elses.length;
-                       render = tag.render;
-               }
-               if (tagDef.init) {
-                       // init is the constructor for the tag/control instance
-
-                       // tags hash: tag.ctx.tags, merged with parentView.ctx.tags,
-                       tags = ctx.tags = parentView.ctx && extendCtx(ctx.tags, parentView.ctx.tags) || {};
-
-                       tag = tag || linkCtx.tag;
-                       if (tag) {
-                               // tag has already been instantiated, so keep it, but attach the current context, which may have changed
-                               // Add tag to tags hash
-                               tags[tagName] = tag;
-                       } else {
-                               // If the tag has not already been instantiated, we will create a new instance and add to the tags hash,
-                               // so ~tags.tagName will access the tag, even within the rendering of the template content of this tag
-//     TODO provide error handling owned by the tag - using tag.onError
-//                     try {
-                                       tag = tags[tagName] = new tagDef.init(tagCtx, linkCtx, ctx);
-//                             }
-//                             catch(e) {
-//                                     tagDef.onError(e);
-//                             }
-                               tag.tmpl = tmpl;
-
-                               if (linkCtx) {
-                                       tag.attr =
-                                               // Setting attr on tag so renderContent knows whether to include script node markers.
-                                               linkCtx.attr =
-                                                       // Setting attr on self to ensure outputting to the correct target attribute.
-                                                       linkCtx.attr || tagDef.attr || "";
-                               }
-                       }
-                       ctx.tag = tag;
-               } else {
-                       // This is a simple tag declared as a function. We won't instantiate a specific tag constructor - just a standard instance object.
-                       tag = tag || {
-                               // tag instance object if no init constructor
-                               render: tagDef.render,
-                               renderContent: renderContent,
-                               tmpl: tmpl,
-                               tagName: tagName
-                       };
-               }
-
-               // Provide tagCtx, linkCtx and ctx access from tag
-               tag.tagCtx = tagCtx;
-               tag.ctx = ctx;
-               if (linkCtx) {
-                       linkCtx.tag = tag;
-                       tag.linkCtx = linkCtx;
-               }
-
-               tag._is = "tag";
-               tag._done = tagCtx.isElse ? tag._done : FALSE; // If not an {{else}} this is a new
-               tmpl = tmpl || tag.tmpl;
-               elses = tag._elses;
-
-//TODO The above works for initial rendering, but when refreshing {^{foo}} need also to associate with {{else}} tags. Use compilation to bind else content templates and expressions with the primary tag template and expression.
-
-               parentView_.tag = tag;
-               // Provide this tag on view, for addMarkerNode on bound tags to add the tag to view._.bnds, associated with the tag id,
-               // for getting the tagCtx and linkCtx during rendering, and so when rendering subsequent {{else}}, will be associated with this tag
-               //TODO does this work with nested elses and with {^{foo:...}} which also adds tag to view, for markerNodes.
-
-//             while (tmpl) {
-                       // If tagDef has a 'render' function, call it.
-                       // If the return result is undefined, return "", or, if a template (or content) is provided,
-                       // return the rendered template(using the current data or the first parameter as data);
-                       if (render = render || tag.render) {
-                               ret = render.apply(tag, args);
-
-//     TODO            ret = $invoke(tag, render, args);
-                       }
-                       ret = ret !== undefined
-                               ? ret    // Return result of render function unless it is undefined, in which case return rendered template
-                               : tmpl
-                                       // render template on args[0] if defined, or otherwise on the current data item
-                                       ? tag.renderContent(tagCtx.data !== undefined ? tagCtx.data : parentView.data, undefined, parentView)
-                                       : ""; // No return value from render, and no template defined, so return ::
-
-//                     tmpl = (tag !== "else" && elses) ? (tagCtx.isElse = tagCtx.isElse || 0, elses[tagCtx.isElse++]) : undefined;
-//}
-
-               // If bind, for {^{tag ... }}, insert script marker nodes
-               return bind ? parentView_.onRender(ret, parentView, bind) : ret;
-       }
-
-       //=================
-       // View constructor
-       //=================
-
-       function View(context, type, parentView, data, template, key, onRender) {
-               // Constructor for view object in view hierarchy. (Augmented by JsViews if JsViews is loaded)
-               var views, parentView_,
-                       isArray = type === "array",
-                       self_ = {
-                               key: 0,
-                               useKey: isArray ? 0 : 1,
-                               id: "" + viewId++,
-                               onRender: onRender,
-                               bnd: {}
-                       },
-                       self = {
-                               data: data,
-                               tmpl: template,
-                               views: isArray ? [] : {},
-                               parent: parentView,
-                               ctx: context,
-                               type: type,
-                               // If the data is an array, this is an 'array view' with a views array for each child 'item view'
-                               // If the data is not an array, this is an 'item view' with a views 'map' object for any child nested views
-                               // ._.useKey is non zero if is not an 'array view' (owning a data array). Uuse this as next key for adding to child views map
-                               get: getView,
-                               getIndex: getIndex,
-                               getRsc: getResource,
-                               _hlp: getHelper,
-                               _: self_
-               };
-
-               if (parentView) {
-                       views = parentView.views;
-                       parentView_ = parentView._;
-                       if (parentView_.useKey) {
-                               // Parent is an 'item view'. Add this view to its views object
-                               // self._key = is the key in the parent view map
-                               views[self_.key = "_" + parentView_.useKey++] = self;
-                       } else {
-                               // Parent is an 'array view'. Add this view to its views array
-                               views.splice(
-                                       // self._.key = self.index - the index in the parent view array
-                                       self_.key = self.index =
-                                               key !== undefined
-                                                       ? key
-                                                       : views.length,
-                               0, self);
-                       }
-                       // If no context was passed in, use parent context
-                       // If context was passed in, it should have been merged already with parent context
-                       self.ctx = context || parentView.ctx;
-               }
-               return self;
-       }
-
-       //=============
-       // Registration
-       //=============
-
-       function compileChildResources(parentTmpl) {
-               var storeName, resources, resourceName, settings, compile;
-               for (storeName in jsvStores) {
-                       settings = jsvStores[storeName];
-                       if ((compile = settings.compile) && (resources = parentTmpl[storeName + "s"])) {
-                               for (resourceName in resources) {
-                                       // compile child resource declarations (templates, tags, converters or helpers)
-                                       resources[resourceName] = compile(resourceName, resources[resourceName], parentTmpl, storeName, settings);
-                               }
-                       }
-               }
-       }
-
-       function compileTag(name, item, parentTmpl) {
-               var init, tmpl;
-               if (typeof item === "function") {
-                       // Simple tag declared as function. No presenter instantation.
-                       item = {
-                               tagName: name,
-                               render: item,
-                               depends: item.depends
-                       };
-               } else {
-                       // Tag declared as object, used as the prototype for tag instantiation (control/presenter)
-                       item.tagName = name;
-                       if (tmpl = item.template) {
-                               item.template = "" + tmpl === tmpl ? ($templates[tmpl] || $templates(tmpl)) : tmpl;
-                       }
-                       if (item.init !== FALSE) {
-                               init = item.init = item.init || function(tagCtx) {};
-                               init.prototype = item;
-                               (init.prototype = item).constructor = init;
-                       }
-               }
-               item.renderContent = renderContent;
-               item.attr = "html";
-               if (parentTmpl) {
-                       item._parentTmpl = parentTmpl;
-               }
-//TODO item.onError = function(e) {
-//                     var error;
-//                     if (error = this.prototype.onError) {
-//                             error.call(this, e);
-//                     } else {
-//                             throw e;
-//                     }
-//             }
-               return item;
-       }
-
-       function compileTmpl(name, tmpl, parentTmpl, storeName, storeSettings, options) {
-               // tmpl is either a template object, a selector for a template script block, the name of a compiled template, or a template object
-
-               //==== nested functions ====
-               function tmplOrMarkupFromStr(value) {
-                       // If value is of type string - treat as selector, or name of compiled template
-                       // Return the template object, if already compiled, or the markup string
-
-                       if (("" + value === value) || value.nodeType > 0) {
-                               try {
-                                       elem = value.nodeType > 0
-                                       ? value
-                                       : !rTmplString.test(value)
-                                       // If value is a string and does not contain HTML or tag content, then test as selector
-                                               && jQuery && jQuery(value)[0];
-                                       // If selector is valid and returns at least one element, get first element
-                                       // If invalid, jQuery will throw. We will stay with the original string.
-                               } catch (e) { }
-
-                               if (elem) {
-                                       // Generally this is a script element.
-                                       // However we allow it to be any element, so you can for example take the content of a div,
-                                       // use it as a template, and replace it by the same content rendered against data.
-                                       // e.g. for linking the content of a div to a container, and using the initial content as template:
-                                       // $.link("#content", model, {tmpl: "#content"});
-
-                                       value = elem.getAttribute(tmplAttr);
-                                       name = name || value;
-                                       value = $templates[value];
-                                       if (!value) {
-                                               // Not already compiled and cached, so compile and cache the name
-                                               // Create a name for compiled template if none provided
-                                               name = name || "_" + autoTmplName++;
-                                               elem.setAttribute(tmplAttr, name);
-                                               value = $templates[name] = compileTmpl(name, elem.innerHTML, parentTmpl, storeName, storeSettings, options); // Use tmpl as options
-                                       }
-                               }
-                               return value;
-                       }
-                       // If value is not a string, return undefined
-               }
-
-               var tmplOrMarkup, elem;
-
-               //==== Compile the template ====
-               tmpl = tmpl || "";
-               tmplOrMarkup = tmplOrMarkupFromStr(tmpl);
-
-               // If options, then this was already compiled from a (script) element template declaration.
-               // If not, then if tmpl is a template object, use it for options
-               options = options || (tmpl.markup ? tmpl : {});
-               options.tmplName = name;
-               if (parentTmpl) {
-                       options._parentTmpl = parentTmpl;
-               }
-               // If tmpl is not a markup string or a selector string, then it must be a template object
-               // In that case, get it from the markup property of the object
-               if (!tmplOrMarkup && tmpl.markup && (tmplOrMarkup = tmplOrMarkupFromStr(tmpl.markup))) {
-                       if (tmplOrMarkup.fn && (tmplOrMarkup.debug !== tmpl.debug || tmplOrMarkup.allowCode !== tmpl.allowCode)) {
-                               // if the string references a compiled template object, but the debug or allowCode props are different, need to recompile
-                               tmplOrMarkup = tmplOrMarkup.markup;
-                       }
-               }
-               if (tmplOrMarkup !== undefined) {
-                       if (name && !parentTmpl) {
-                               $render[name] = function() {
-                                       return tmpl.render.apply(tmpl, arguments);
-                               };
-                       }
-                       if (tmplOrMarkup.fn || tmpl.fn) {
-                               // tmpl is already compiled, so use it, or if different name is provided, clone it
-                               if (tmplOrMarkup.fn) {
-                                       if (name && name !== tmplOrMarkup.tmplName) {
-                                               tmpl = extendCtx(options, tmplOrMarkup);
-                                       } else {
-                                               tmpl = tmplOrMarkup;
-                                       }
-                               }
-                       } else {
-                               // tmplOrMarkup is a markup string, not a compiled template
-                               // Create template object
-                               tmpl = TmplObject(tmplOrMarkup, options);
-                               // Compile to AST and then to compiled function
-                               tmplFn(tmplOrMarkup, tmpl);
-                       }
-                       compileChildResources(options);
-                       return tmpl;
-               }
-       }
-       //==== /end of function compile ====
-
-       function TmplObject(markup, options) {
-               // Template object constructor
-               var htmlTag,
-                       wrapMap = $viewsSettings.wrapMap || {},
-                       tmpl = $extend(
-                               {
-                                       markup: markup,
-                                       tmpls: [],
-                                       links: {},
-                                       bnds: [],
-                                       render: renderContent
-                               },
-                               options
-                       );
-
-               if (!options.htmlTag) {
-                       // Set tmpl.tag to the top-level HTML tag used in the template, if any...
-                       htmlTag = rFirstElem.exec(markup);
-                       tmpl.htmlTag = htmlTag ? htmlTag[1].toLowerCase() : "";
-               }
-               htmlTag = wrapMap[tmpl.htmlTag];
-               if (htmlTag && htmlTag !== wrapMap.div) {
-                       // When using JsViews, we trim templates which are inserted into HTML contexts where text nodes are not rendered (i.e. not 'Phrasing Content').
-                       tmpl.markup = $.trim(tmpl.markup);
-                       tmpl._elCnt = TRUE; // element content model (no rendered text nodes), not phrasing content model
-               }
-
-               return tmpl;
-       }
-
-       function registerStore(storeName, storeSettings) {
-
-               function theStore(name, item, parentTmpl) {
-                       // The store is also the function used to add items to the store. e.g. $.templates, or $.views.tags
-
-                       // For store of name 'thing', Call as:
-                       //    $.views.things(items[, parentTmpl]),
-                       // or $.views.things(name, item[, parentTmpl])
-
-                       var onStore, compile, items, itemName, childTemplates, childTemplate, thisStore, childStoreName;
-
-                       if (name && "" + name !== name && !name.nodeType && !name.markup) {
-                               // Call to $.views.things(items[, parentTmpl]),
-
-                               // Adding items to the store
-                               // If name is a map, then item is parentTmpl. Iterate over map and call store for key.
-                               for (itemName in name) {
-                                       theStore(itemName, name[itemName], item);
-                               }
-                               return $views;
-                       }
-                       thisStore = parentTmpl ? parentTmpl[storeNames] = parentTmpl[storeNames] || {} : theStore;
-
-                       // Adding a single unnamed item to the store
-                       if (item === undefined) {
-                               item = name;
-                               name = undefined;
-                       }
-                       compile = storeSettings.compile;
-                       if (onStore = $viewsSub.onBeforeStoreItem) {
-                               // e.g. provide an external compiler or preprocess the item.
-                               compile = onStore(thisStore, name, item, compile) || compile;
-                       }
-                       if (!name) {
-                               item = compile(undefined, item);
-                       } else if ("" + name === name) { // name must be a string
-                               if (item === null) {
-                                       // If item is null, delete this entry
-                                       delete thisStore[name];
-                               } else {
-                                       thisStore[name] = compile ? (item = compile(name, item, parentTmpl, storeName, storeSettings)) : item;
-                               }
-                       }
-                       if (item) {
-                               item._is = storeName;
-                       }
-                       if (onStore = $viewsSub.onStoreItem) {
-                               // e.g. JsViews integration
-                               onStore(thisStore, name, item, compile);
-                       }
-                       return item;
-               }
-
-               var storeNames = storeName + "s";
-
-               $views[storeNames] = theStore;
-               jsvStores[storeName] = storeSettings;
-       }
-
-       //==============
-       // renderContent
-       //==============
-
-       function renderContent(data, context, parentView, key, isLayout, onRender) {
-               // Render template against data as a tree of subviews (nested rendered template instances), or as a string (top-level template).
-               // If the data is the parent view, treat as layout template, re-render with the same data context.
-               var i, l, dataItem, newView, childView, itemResult, parentContext, props, swapContent, tagCtx, isTag, outerOnRender,
-                       self = this,
-                       tmpl = self,
-                       allowDataLink = self.attr === undefined || self.attr === "html",
-                       result = "";
-
-               if (key === TRUE) {
-                       swapContent = TRUE;
-                       key = 0;
-               }
-               if (isTag = self._is === "tag") {
-                       tagCtx = self.tagCtx;
-                       // This is a call from renderTag
-                       tmpl = tagCtx.isElse ? self._elses[tagCtx.isElse-1] : self.tmpl;
-                       context = extendCtx(context, self.ctx);
-                       props = tagCtx.props;
-                       if ( props.link === FALSE ) {
-                               // link=false setting on block tag
-                               // We will override inherited value of link by the explicit setting link=false taken from props
-                               // The child views of an unlinked view are also unlinked. So setting child back to true will not have any effect.
-                               context =  context || {};
-                               context.link = FALSE;
-                       }
-                       parentView = parentView || tagCtx.view;
-               } else {
-                       tmpl = self.jquery && (self[0] || error('Unknown template: "' + self.selector + '"')) // This is a call from $(selector).render
-                               || self;
-               }
-               if (tmpl) {
-                       if (parentView) {
-                               onRender = onRender || parentView._.onRender;
-                               parentContext = parentView.ctx;
-                               if (data === parentView) {
-                                       // Inherit the data from the parent view.
-                                       // This may be the contents of an {{if}} block
-                                       // Set isLayout = true so we don't iterate the if block if the data is an array.
-                                       data = parentView.data;
-                                       isLayout = TRUE;
-                               }
-                       }
-
-                       // Set additional context on views created here, (as modified context inherited from the parent, and to be inherited by child views)
-                       // Note: If no jQuery, $extend does not support chained copies - so limit extend() to two parameters
-                       context = extendCtx(context, parentContext);
-
-                       if (!tmpl.fn) {
-                               tmpl = $templates[tmpl] || $templates(tmpl);
-                       }
-
-                       if (tmpl) {
-                               onRender = (context && context.link) !== FALSE && allowDataLink && onRender;
-                               // If link===false, do not call onRender, so no data-linking marker nodes
-                               outerOnRender = onRender;
-                               if (onRender === TRUE) {
-                                       // Used by view.refresh(). Don't create a new wrapper view.
-                                       outerOnRender = undefined;
-                                       onRender = parentView._.onRender;
-                               }
-                               if ($.isArray(data) && !isLayout) {
-                                       // Create a view for the array, whose child views correspond to each data item. (Note: if key and parentView are passed in
-                                       // along with parent view, treat as insert -e.g. from view.addViews - so parentView is already the view item for array)
-                                       newView = swapContent ? parentView : (key !== undefined && parentView) || View(context, "array", parentView, data, tmpl, key, onRender);
-                                       for (i = 0, l = data.length; i < l; i++) {
-                                               // Create a view for each data item.
-                                               dataItem = data[i];
-                                               childView = View(context, "item", newView, dataItem, tmpl, (key || 0) + i, onRender);
-                                               itemResult = tmpl.fn(dataItem, childView, $views);
-                                               result += newView._.onRender ? newView._.onRender(itemResult, childView) : itemResult;
-                                       }
-                               } else {
-                                       // Create a view for singleton data object. The type of the view will be the tag name, e.g. "if" or "myTag" except for
-                                       // "item", "array" and "data" views. A "data" view is from programatic render(object) against a 'singleton'. 
-                                       newView = swapContent ? parentView : View(context, self.tagName||"data", parentView, data, tmpl, key, onRender);
-                                       result += tmpl.fn(data, newView, $views);
-                               }
-                               return outerOnRender ? outerOnRender(result, newView) : result;
-                       }
-               }
-               return "";
-       }
-
-       //===========================
-       // Build and compile template
-       //===========================
-
-       // Generate a reusable function that will serve to render a template against data
-       // (Compile AST then build template function)
-
-       function error(message) {
-               if ($viewsSettings.debugMode) {
-                       throw new $views.sub.Error(message);
-               }
-       }
-
-       function syntaxError(message) {
-               error("Syntax error\n" + message);
-       }
-
-       function tmplFn(markup, tmpl, isLinkExpression) {
-               // Compile markup to AST (abtract syntax tree) then build the template function code from the AST nodes
-               // Used for compiling templates, and also by JsViews to build functions for data link expressions
-
-
-               //==== nested functions ====
-               function pushprecedingContent(shift) {
-                       shift -= loc;
-                       if (shift) {
-                               content.push(markup.substr(loc, shift).replace(rNewLine, "\\n"));
-                       }
-               }
-
-               function blockTagCheck(tagName) {
-                       tagName && syntaxError('Unmatched or missing tag: "{{/' + tagName + '}}" in template:\n' + markup);
-               }
-
-               function parseTag(all, bind, tagName, converter, colon, html, comment, codeTag, params, slash, closeBlock, index) {
-
-                       //    bind         tag        converter colon html     comment            code      params            slash   closeBlock
-                       // /{(\^)?{(?:(?:(\w+(?=[\/\s}]))|(?:(\w+)?(:)|(>)|!--((?:[^-]|-(?!-))*)--|(\*)))\s*((?:[^}]|}(?!}))*?)(\/)?|(?:\/(\w+)))}}/g
-                       // Build abstract syntax tree (AST): [ tagName, converter, params, content, hash, bindings, contentMarkup ]
-                       if (html) {
-                               colon = ":";
-                               converter = "html";
-                       }
-                       var noError, current0,
-                               pathBindings = [],
-                               code = "",
-                               hash = "",
-                               passedCtx = "",
-                               // Block tag if not self-closing and not {{:}} or {{>}} (special case) and not a data-link expression
-                               block = !slash && !colon && !comment && !isLinkExpression;
-
-                       //==== nested helper function ====
-                       tagName = tagName || colon;
-                       pushprecedingContent(index);
-                       loc = index + all.length; // location marker - parsed up to here
-                       if (codeTag) {
-                               if (allowCode) {
-                                       content.push(["*", "\n" + params.replace(rUnescapeQuotes, "$1") + "\n"]);
-                               }
-                       } else if (tagName) {
-                               if (tagName === "else") {
-                                       if (rTestElseIf.test(params)) {
-                                               syntaxError('for "{{else if expr}}" use "{{else expr}}"');
-                                       }
-                                       current[7] = markup.substring(current[7], index); // contentMarkup for block tag
-                                       current = stack.pop();
-                                       content = current[3];
-                                       block = TRUE;
-                               }
-                               if (params) {
-                                       params = params.replace(/\s*\n\s*/g, " "); // remove newlines from the params string, to avoid compiled code errors for unterminated strings
-                                       code = parseParams(params, pathBindings)
-                                               .replace(rBuildHash, function(all, isCtx, keyValue) {
-                                                       if (isCtx) {
-                                                               passedCtx += keyValue + ",";
-                                                       } else {
-                                                               hash += keyValue + ",";
-                                                       }
-                                                       return "";
-                                               });
-                               }
-                               hash = hash.slice(0, -1);
-                               code = code.slice(0, -1);
-                               noError = hash && (hash.indexOf("noerror:true") + 1) && hash || "";
-
-                               newNode = [
-                                               tagName,
-                                               converter || "",
-                                               code,
-                                               block && [],
-                                               "{" + (hash ? ("props:" + (noError ? "hsh": "{" + hash + "}")
-                                                       + ",") : "") + 'params:"' + params + '"' + (passedCtx ? ",ctx:{" + passedCtx.slice(0, -1) + "}" : "") + "},",
-                                               noError,
-                                               //"{" + (hash ? ("props:{" + hash + "},") : "") + 'params:"' + params + '"' + (passedCtx ? ",ctx:{" + passedCtx.slice(0, -1) + "}" : "") + "},",
-                                               bind && pathBindings || 0,
-                                       ];
-                               content.push(newNode);
-                               if (block) {
-                                       stack.push(current);
-                                       current = newNode;
-                                       current[7] = loc; // Store current location of open tag, to be able to add contentMarkup when we reach closing tag
-                               }
-                       } else if (closeBlock) {
-                               current0 = current[0];
-                               blockTagCheck(closeBlock !== current0 && current0 && current0 !== "else");
-                               current[7] = markup.substring(current[7], index); // contentMarkup for block tag
-                               current = stack.pop();
-                       }
-                       blockTagCheck(!current && closeBlock);
-                       content = current[3];
-               }
-               //==== /end of nested functions ====
-
-               var newNode,
-                       allowCode = tmpl && tmpl.allowCode,
-                       astTop = [],
-                       loc = 0,
-                       stack = [],
-                       content = astTop,
-                       current = [, , , astTop];
-
-               markup = markup.replace(rEscapeQuotes, "\\$1");
-
-//TODO result = tmplFnsCache[markup];  // Only cache if template is not named and markup length < ..., and there are no bindings or subtemplates?? Consider standard optimization for data-link="a.b.c"
-//             if (result) {
-//                     tmpl.fn = result;
-//             } else {
-
-//             result = markup;
-
-               blockTagCheck(stack[0] && stack[0][3].pop()[0]);
-
-               // Build the AST (abstract syntax tree) under astTop
-               markup.replace(rTag, parseTag);
-
-               pushprecedingContent(markup.length);
-
-               if (loc = astTop[astTop.length-1]) {
-                       blockTagCheck("" + loc !== loc && (+loc[7] === loc[7]) && loc[0]);
-               }
-//                     result = tmplFnsCache[markup] = buildCode(astTop, tmpl);
-//             }
-               return buildCode(astTop, tmpl);
-       }
-
-       function buildCode(ast, tmpl) {
-               // Build the template function code from the AST nodes, and set as property on the passed-in template object
-               // Used for compiling templates, and also by JsViews to build functions for data link expressions
-               var ret, i, node, hasTag, noError, hasEncoder, getsValue, hasConverter, hasViewPath, tagName, converter, params, hash, bindings, bindingPaths, nestedTmpls, nestedTmpl, allowCode, content, markup,
-                       code = "",
-                       tmplOptions = {},
-                       l = ast.length;
-
-               if (tmpl) {
-                       if (allowCode = tmpl.allowCode) {
-                               tmplOptions.allowCode = TRUE;
-                       }
-                       if (tmpl.debug) {
-                               tmplOptions.debug = TRUE;
-                       }
-                       bindings = tmpl.bnds;
-                       nestedTmpls = tmpl.tmpls;
-               }
-
-               for (i = 0; i < l; i++) {
-                       // AST nodes: [ tagName, converter, params, content, hash, bindings, contentMarkup, link ]
-                       node = ast[i];
-
-                       // Add newline for each callout to t() c() etc. and each markup string
-                       ret = "";
-                       if ("" + node === node) {
-                               // a markup string to be inserted
-                               ret = 'ret+="' + node + '";';
-                       } else {
-                               // a compiled tag expression to be inserted
-                               tagName = node[0];
-                               if (tagName === "*") {
-                                       // Code tag: {{* }}
-                                       ret = "" + node[1];
-                               } else {
-                                       converter = node[1];
-                                       params = node[2];
-                                       content = node[3];
-                                       hash = node[4];
-                                       noError = node[5];
-                                       bindingPaths = node[6];
-                                       markup = node[7];
-
-                                       if (content) {
-                                               // Create template object for nested template
-                                               nestedTmpl = TmplObject(markup, tmplOptions);
-                                               // Compile to AST and then to compiled function
-                                               buildCode(content, nestedTmpl);
-                                               nestedTmpls.push(nestedTmpl);
-                                       }
-                                       if (bindingPaths) {
-                                               // Add leaf binding paths to template
-                                               bindings.push(bindingPaths);
-                                               bindingPaths = bindings.length;
-                                       }
-                                       hasViewPath = hasViewPath || hash.indexOf("view") > -1;
-                                       // Add newline for each callout to t() c() etc.
-
-                                       //TODO consider passing in ret to c() and t() so they can look at the previous ret, and detect whether this is a jsrender tag _within_an_HTML_element_tag_
-                                       // and if so, don't insert marker nodes, add data-link attributes to the HTML element markup... No need for people to set link=false.
-
-                                       if (noError) {
-                                               // If the tag includes noerror=true, we will do a try catch around expressions for named or unnamed parameters
-                                               // passed to the tag, and return the empty string for each expression if it throws during evaluation
-                                               // TODO perhaps support noerror=xxx and return the value of the expression xxx||'', rather than always the empty string
-                                               noError = "try{prm=" + params + ";hsh={" + noError + '};}catch(e){prm="";hsh={};}\n';
-                                               params = "prm";
-                                       }
-
-                                       ret += noError + "ret+=" + (tagName === ":"
-                                               ? (converter === "html" && !bindingPaths
-                                                       ? (hasEncoder = TRUE, "h(" + params+ ");")
-                                                       : converter || bindingPaths // Call _cnvt if there is a converter, or binding: {{cnvt: ... }}, {^{: ... }} or {^{cnvt: ... }}
-                                                               ? (hasConverter = TRUE, 'c("' + converter + '",view,this,' + hash + bindingPaths + "," + params + ");")
-                                                               : (getsValue = TRUE, "(v=" + params + ')!=u?v:"";')
-                                               )
-                                               : (hasTag = TRUE, 't("' + tagName + '",view,this,'
-                                                       + (content ? nestedTmpls.length : '""') // For block tags, pass in the key (nestedTmpls.length) to the nested content template
-                                                       + "," + hash + bindingPaths + (params ? "," : "") + params) + ");");
-                               }
-                       }
-                       code += "\n" + ret;
-               }
-
-               // Include only the var references that are needed in the code
-               code = fnDeclStr
-               + (noError? "prm,hsh," : "")
-               + (getsValue ? "v," : "")
-               + (hasTag ? "t=j._tag," : "")
-               + (hasConverter ? "c=j._cnvt," : "")
-               + (hasEncoder ? "h=j.converters.html," : "")
-               + 'ret="";\n'
-               + ($viewsSettings.tryCatch ? "try{\n" : "")
-               + (tmplOptions.debug ? "debugger;" : "")
-               + code + "\nreturn ret;\n"
-               + ($viewsSettings.tryCatch ? "\n}catch(e){return j._err(e);}" : "");
-
-               try {
-                       code = new Function("data, view, j, u", code);
-               } catch (e) {
-                       syntaxError("Compiled template code:\n\n" + code, e);
-               }
-
-               if (tmpl) {
-                       tmpl.fn = code;
-               }
-               return code;
-       }
-
-       function parseParams(params, bindings) {
-
-               function parseTokens(all, lftPrn0, lftPrn, path, operator, err, eq, path2, prn, comma, lftPrn2, apos, quot, rtPrn, prn2, space) {
-                       // rParams = /(\()(?=|\s*\()|(?:([([])\s*)?(?:([#~]?[\w$^.]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*!:?\/]|(=))\s*|([#~]?[\w$^.]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*([)\]])([([]?))|(\s+)
-                       //          lftPrn0-flwed by (- lftPrn               path    operator err                                                eq         path2       prn    comma   lftPrn3   apos quot        rtPrn   prn2   space
-                       // (left paren? followed by (path? followed by operator) or (path followed by paren?)) or comma or apos or quot or right paren or space
-                       operator = operator || "";
-                       lftPrn = lftPrn || lftPrn0 || lftPrn2;
-                       path = path || path2;
-                       prn = prn || prn2 || "";
-
-                       function parsePath(all, object, helper, view, viewProperty, pathTokens, leafToken) {
-                               // rPath = /^(?:null|true|false|\d[\d.]*|([\w$]+|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,
-                               //                                        object   helper    view  viewProperty pathTokens       leafToken
-
-                               if (object) {
-                                       bindings.push(path);
-                                       if (object !== ".") {
-                                               var leaf,
-                                                       ret = (helper
-                                                               ? 'view._hlp("' + helper + '")'
-                                                               : view
-                                                                       ? "view"
-                                                                       : "data")
-                                                       + (leafToken
-                                                               ? (viewProperty
-                                                                       ? "." + viewProperty
-                                                                       : helper
-                                                                               ? ""
-                                                                               : (view ? "" : "." + object)
-                                                                       ) + (pathTokens || "")
-                                                               : (leafToken = helper ? "" : view ? viewProperty || "" : object, ""));
-
-                                               leaf = (leafToken ? "." + leafToken : "");
-                                               ret = ret + leaf;
-                                               ret = ret.slice(0, 9) === "view.data"
-                                               ? ret.slice(5) // convert #view.data... to data...
-                                               : ret;
-                                               return ret;
-                                       }
-                               }
-                               return all;
-                       }
-
-                       if (err) {
-                               syntaxError(params);
-                       } else {
-                               return (aposed
-                                       // within single-quoted string
-                                       ? (aposed = !apos, (aposed ? all : '"'))
-                                       : quoted
-                                       // within double-quoted string
-                                               ? (quoted = !quot, (quoted ? all : '"'))
-                                               :
-                                       (
-                                               (lftPrn
-                                                               ? (parenDepth++, lftPrn)
-                                                               : "")
-                                               + (space
-                                                       ? (parenDepth
-                                                               ? ""
-                                                               : named
-                                                                       ? (named = FALSE, "\b")
-                                                                       : ","
-                                                       )
-                                                       : eq
-                                       // named param
-                                       // Insert backspace \b (\x08) as separator for named params, used subsequently by rBuildHash
-                                                               ? (parenDepth && syntaxError(params), named = TRUE, '\b' + path + ':')
-                                                               : path
-                                       // path
-                                                                       ? (path.split("^").join(".").replace(rPath, parsePath)
-                                                                               + (prn
-                                                                                       ? (fnCall[++parenDepth] = TRUE, prn)
-                                                                                       : operator)
-                                                                       )
-                                                                       : operator
-                                                                               ? operator
-                                                                               : rtPrn
-                                       // function
-                                                                                       ? ((fnCall[parenDepth--] = FALSE, rtPrn)
-                                                                                               + (prn
-                                                                                                       ? (fnCall[++parenDepth] = TRUE, prn)
-                                                                                                       : "")
-                                                                                       )
-                                                                                       : comma
-                                                                                               ? (fnCall[parenDepth] || syntaxError(params), ",") // We don't allow top-level literal arrays or objects
-                                                                                               : lftPrn0
-                                                                                                       ? ""
-                                                                                                       : (aposed = apos, quoted = quot, '"')
-                                       ))
-                               );
-                       }
-               }
-               var named,
-                       fnCall = {},
-                       parenDepth = 0,
-                       quoted = FALSE, // boolean for string content in double quotes
-                       aposed = FALSE; // or in single quotes
-
-               bindings.expr = params.replace(rUnescapeQuotes, "$1");
-               return (params + " ").replace(rParams, parseTokens);
-       }
-
-       //==========
-       // Utilities
-       //==========
-
-       // HTML encoding helper
-       function replacerForHtml(ch) {
-               // Original code from Mike Samuel <msamuel@google.com>
-               return escapeMapForHtml[ch]
-                       // Intentional assignment that caches the result of encoding ch.
-                       || (escapeMapForHtml[ch] = "&#" + ch.charCodeAt(0) + ";");
-       }
-
-       // Merge objects, in particular contexts which inherit from parent contexts
-       function extendCtx(context, parentContext) {
-               // Return copy of parentContext, unless context is defined and is different, in which case return a new merged context
-               // If neither context nor parentContext are undefined, return undefined
-               return context && context !== parentContext
-                       ? (parentContext
-                               ? $extend($extend({}, parentContext), context)
-                               : context)
-                       : parentContext && $extend({}, parentContext);
-       }
-
-       //========================== Initialize ==========================
-
-       for (jsvStoreName in jsvStores) {
-               registerStore(jsvStoreName, jsvStores[jsvStoreName]);
-       }
-
-       var $templates = $views.templates,
-               $converters = $views.converters,
-               $helpers = $views.helpers,
-               $tags = $views.tags,
-               $viewsSub = $views.sub,
-               $viewsSettings = $views.settings;
-
-       if (jQuery) {
-               ////////////////////////////////////////////////////////////////////////////////////////////////
-               // jQuery is loaded, so make $ the jQuery object
-               $ = jQuery;
-               $.render = $render;
-               $.views = $views;
-               $.templates = $templates = $views.templates;
-               $.fn.render = renderContent;
-
-       } else {
-               ////////////////////////////////////////////////////////////////////////////////////////////////
-               // jQuery is not loaded.
-
-               $ = global.jsviews = $views;
-
-               $.isArray = Array && Array.isArray || function(obj) {
-                       return Object.prototype.toString.call(obj) === "[object Array]";
-               };
-       }
-
-       //========================== Register tags ==========================
-
-       $tags({
-               "if": function(val) {
-                       var self = this;
-                               // If not done and val is truey, set done=true on tag instance and render content. Otherwise return ""
-                               // On else will call this function again on the same tag instance.
-                       return (self._done || arguments.length && !val)
-                               ? ""
-                               : (self._done = true,
-                                       // Test is satisfied, so render content on current context. Rather than return undefined
-                                       // (which will render the tmpl/content on the current context but will iterate if it is an array),
-                                       // we pass in the view. This ensures treating as a layout template - with no iteration
-                                       self.renderContent(self.tagCtx.view));
-               },
-// Temporary fix for binding to {{if}}
-//     "if": {
-//             render: function(val) {
-//                     var self = this;
-//                     return (self._done || arguments.length && !val) ? "" : (self._done = true, self.renderContent(self.tagCtx.view));
-//             }
-//     },
-               "else": function() {}, // Does nothing but ensures {{else}} tags are recognized as valid
-               "for": function() {
-                       var i, arg, undef,
-                               self = this,
-                               tagCtx = self.tagCtx,
-                               result = "",
-                               args = arguments,
-                               done = 0,
-                               l = args.length;
-
-                       if (!l) {
-                               return tagCtx.done
-                                       ? ""
-                                       : (tagCtx.done = TRUE,
-                                               // Test is satisfied, so render content on current context. Rather than return undefined
-                                               // (which will render the tmpl/content on the current context but will iterate if it is an array),
-                                               // we pass in the view. This ensures treating as a layout template - with no iteration
-                                               self.renderContent(tagCtx.view));
-                       }
-                       for (i = 0; i < l; i++) {
-                               arg = args[i];
-                               undef = arg === undefined;
-                               if (!undef) {
-                                       done += $.isArray(arg) ? arg.length : 1;
-                                       result += self.renderContent(arg);
-                               } else {
-                                       return "";
-                               }
-                       }
-                       tagCtx.done = done;
-                       return result;
-               },
-               "*": function(value) {
-                       return value;
-               }
-       });
-
-       //========================== Register global helpers ==========================
-
-       //      $helpers({ // Global helper functions
-       //              // TODO add any useful built-in helper functions
-       //      });
-
-       //========================== Register converters ==========================
-
-       $converters({
-               html: function(text) {
-                       // HTML encoding helper: Replace < > & and ' and " by corresponding entities.
-                       return text != undefined ? String(text).replace(htmlEncodeChars, replacerForHtml) : "";
-               },
-               attr: function(text) {
-                       // Attribute encoding helper: Replace < & ' and " by corresponding entities.
-                       return text != undefined ? String(text).replace(attrEncodeChars, replacerForHtml) : "";
-               },
-               url: function(text) {
-                       // TODO - support chaining {{attr|url:....}} to protect against injection attacks from url parameters containing " or '.
-                       // URL encoding helper.
-                       return text != undefined ? encodeURI(String(text)) : "";
-               }
-       });
-
-       //========================== Define default delimiters ==========================
-       $viewsDelimiters();
-
-})(this, this.jQuery);
index c635ec0b356079d988d10ecba7db39ad4559dd97..2e559835a0acf9bc03ffef58fc824739bbb1f7f1 100644 (file)
 //! moment.js
-//! version : 2.9.0
+//! version : 2.29.2
 //! authors : Tim Wood, Iskren Chernev, Moment.js contributors
 //! license : MIT
 //! momentjs.com
 
-(function (undefined) {
-    /************************************
-        Constants
-    ************************************/
-
-    var moment,
-        VERSION = '2.9.0',
-        // the global-scope this is NOT the global object in Node.js
-        globalScope = (typeof global !== 'undefined' && (typeof window === 'undefined' || window === global.window)) ? global : this,
-        oldGlobalMoment,
-        round = Math.round,
-        hasOwnProperty = Object.prototype.hasOwnProperty,
-        i,
-
-        YEAR = 0,
-        MONTH = 1,
-        DATE = 2,
-        HOUR = 3,
-        MINUTE = 4,
-        SECOND = 5,
-        MILLISECOND = 6,
-
-        // internal storage for locale config files
-        locales = {},
-
-        // extra moment internal properties (plugins register props here)
-        momentProperties = [],
-
-        // check for nodeJS
-        hasModule = (typeof module !== 'undefined' && module && module.exports),
-
-        // ASP.NET json date format regex
-        aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
-        aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,
-
-        // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
-        // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
-        isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,
-
-        // format tokens
-        formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|x|X|zz?|ZZ?|.)/g,
-        localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,
-
-        // parsing token regexes
-        parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99
-        parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999
-        parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999
-        parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999
-        parseTokenDigits = /\d+/, // nonzero number of digits
-        parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic.
-        parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z
-        parseTokenT = /T/i, // T (ISO separator)
-        parseTokenOffsetMs = /[\+\-]?\d+/, // 1234567890123
-        parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
-
-        //strict parsing regexes
-        parseTokenOneDigit = /\d/, // 0 - 9
-        parseTokenTwoDigits = /\d\d/, // 00 - 99
-        parseTokenThreeDigits = /\d{3}/, // 000 - 999
-        parseTokenFourDigits = /\d{4}/, // 0000 - 9999
-        parseTokenSixDigits = /[+-]?\d{6}/, // -999,999 - 999,999
-        parseTokenSignedNumber = /[+-]?\d+/, // -inf - inf
-
-        // iso 8601 regex
-        // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
-        isoRegex = /^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,
-
-        isoFormat = 'YYYY-MM-DDTHH:mm:ssZ',
+;(function (global, factory) {
+    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+    typeof define === 'function' && define.amd ? define(factory) :
+    global.moment = factory()
+}(this, (function () { 'use strict';
 
-        isoDates = [
-            ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/],
-            ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/],
-            ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/],
-            ['GGGG-[W]WW', /\d{4}-W\d{2}/],
-            ['YYYY-DDD', /\d{4}-\d{3}/]
-        ],
-
-        // iso time formats and regexes
-        isoTimes = [
-            ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/],
-            ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/],
-            ['HH:mm', /(T| )\d\d:\d\d/],
-            ['HH', /(T| )\d\d/]
-        ],
-
-        // timezone chunker '+10:00' > ['10', '00'] or '-1530' > ['-', '15', '30']
-        parseTimezoneChunker = /([\+\-]|\d\d)/gi,
-
-        // getter and setter names
-        proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'),
-        unitMillisecondFactors = {
-            'Milliseconds' : 1,
-            'Seconds' : 1e3,
-            'Minutes' : 6e4,
-            'Hours' : 36e5,
-            'Days' : 864e5,
-            'Months' : 2592e6,
-            'Years' : 31536e6
-        },
+    var hookCallback;
 
-        unitAliases = {
-            ms : 'millisecond',
-            s : 'second',
-            m : 'minute',
-            h : 'hour',
-            d : 'day',
-            D : 'date',
-            w : 'week',
-            W : 'isoWeek',
-            M : 'month',
-            Q : 'quarter',
-            y : 'year',
-            DDD : 'dayOfYear',
-            e : 'weekday',
-            E : 'isoWeekday',
-            gg: 'weekYear',
-            GG: 'isoWeekYear'
-        },
+    function hooks() {
+        return hookCallback.apply(null, arguments);
+    }
 
-        camelFunctions = {
-            dayofyear : 'dayOfYear',
-            isoweekday : 'isoWeekday',
-            isoweek : 'isoWeek',
-            weekyear : 'weekYear',
-            isoweekyear : 'isoWeekYear'
-        },
+    // This is done to register the method called with moment()
+    // without creating circular dependencies.
+    function setHookCallback(callback) {
+        hookCallback = callback;
+    }
 
-        // format function strings
-        formatFunctions = {},
+    function isArray(input) {
+        return (
+            input instanceof Array ||
+            Object.prototype.toString.call(input) === '[object Array]'
+        );
+    }
 
-        // default relative time thresholds
-        relativeTimeThresholds = {
-            s: 45,  // seconds to minute
-            m: 45,  // minutes to hour
-            h: 22,  // hours to day
-            d: 26,  // days to month
-            M: 11   // months to year
-        },
+    function isObject(input) {
+        // IE8 will treat undefined and null as object if it wasn't for
+        // input != null
+        return (
+            input != null &&
+            Object.prototype.toString.call(input) === '[object Object]'
+        );
+    }
 
-        // tokens to ordinalize and pad
-        ordinalizeTokens = 'DDD w W M D d'.split(' '),
-        paddedTokens = 'M D H h m s w W'.split(' '),
+    function hasOwnProp(a, b) {
+        return Object.prototype.hasOwnProperty.call(a, b);
+    }
 
-        formatTokenFunctions = {
-            M    : function () {
-                return this.month() + 1;
-            },
-            MMM  : function (format) {
-                return this.localeData().monthsShort(this, format);
-            },
-            MMMM : function (format) {
-                return this.localeData().months(this, format);
-            },
-            D    : function () {
-                return this.date();
-            },
-            DDD  : function () {
-                return this.dayOfYear();
-            },
-            d    : function () {
-                return this.day();
-            },
-            dd   : function (format) {
-                return this.localeData().weekdaysMin(this, format);
-            },
-            ddd  : function (format) {
-                return this.localeData().weekdaysShort(this, format);
-            },
-            dddd : function (format) {
-                return this.localeData().weekdays(this, format);
-            },
-            w    : function () {
-                return this.week();
-            },
-            W    : function () {
-                return this.isoWeek();
-            },
-            YY   : function () {
-                return leftZeroFill(this.year() % 100, 2);
-            },
-            YYYY : function () {
-                return leftZeroFill(this.year(), 4);
-            },
-            YYYYY : function () {
-                return leftZeroFill(this.year(), 5);
-            },
-            YYYYYY : function () {
-                var y = this.year(), sign = y >= 0 ? '+' : '-';
-                return sign + leftZeroFill(Math.abs(y), 6);
-            },
-            gg   : function () {
-                return leftZeroFill(this.weekYear() % 100, 2);
-            },
-            gggg : function () {
-                return leftZeroFill(this.weekYear(), 4);
-            },
-            ggggg : function () {
-                return leftZeroFill(this.weekYear(), 5);
-            },
-            GG   : function () {
-                return leftZeroFill(this.isoWeekYear() % 100, 2);
-            },
-            GGGG : function () {
-                return leftZeroFill(this.isoWeekYear(), 4);
-            },
-            GGGGG : function () {
-                return leftZeroFill(this.isoWeekYear(), 5);
-            },
-            e : function () {
-                return this.weekday();
-            },
-            E : function () {
-                return this.isoWeekday();
-            },
-            a    : function () {
-                return this.localeData().meridiem(this.hours(), this.minutes(), true);
-            },
-            A    : function () {
-                return this.localeData().meridiem(this.hours(), this.minutes(), false);
-            },
-            H    : function () {
-                return this.hours();
-            },
-            h    : function () {
-                return this.hours() % 12 || 12;
-            },
-            m    : function () {
-                return this.minutes();
-            },
-            s    : function () {
-                return this.seconds();
-            },
-            S    : function () {
-                return toInt(this.milliseconds() / 100);
-            },
-            SS   : function () {
-                return leftZeroFill(toInt(this.milliseconds() / 10), 2);
-            },
-            SSS  : function () {
-                return leftZeroFill(this.milliseconds(), 3);
-            },
-            SSSS : function () {
-                return leftZeroFill(this.milliseconds(), 3);
-            },
-            Z    : function () {
-                var a = this.utcOffset(),
-                    b = '+';
-                if (a < 0) {
-                    a = -a;
-                    b = '-';
-                }
-                return b + leftZeroFill(toInt(a / 60), 2) + ':' + leftZeroFill(toInt(a) % 60, 2);
-            },
-            ZZ   : function () {
-                var a = this.utcOffset(),
-                    b = '+';
-                if (a < 0) {
-                    a = -a;
-                    b = '-';
+    function isObjectEmpty(obj) {
+        if (Object.getOwnPropertyNames) {
+            return Object.getOwnPropertyNames(obj).length === 0;
+        } else {
+            var k;
+            for (k in obj) {
+                if (hasOwnProp(obj, k)) {
+                    return false;
                 }
-                return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2);
-            },
-            z : function () {
-                return this.zoneAbbr();
-            },
-            zz : function () {
-                return this.zoneName();
-            },
-            x    : function () {
-                return this.valueOf();
-            },
-            X    : function () {
-                return this.unix();
-            },
-            Q : function () {
-                return this.quarter();
             }
-        },
-
-        deprecations = {},
-
-        lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin'],
-
-        updateInProgress = false;
-
-    // Pick the first defined of two or three arguments. dfl comes from
-    // default.
-    function dfl(a, b, c) {
-        switch (arguments.length) {
-            case 2: return a != null ? a : b;
-            case 3: return a != null ? a : b != null ? b : c;
-            default: throw new Error('Implement me');
+            return true;
         }
     }
 
-    function hasOwnProp(a, b) {
-        return hasOwnProperty.call(a, b);
-    }
-
-    function defaultParsingFlags() {
-        // We need to deep clone this object, and es5 standard is not very
-        // helpful.
-        return {
-            empty : false,
-            unusedTokens : [],
-            unusedInput : [],
-            overflow : -2,
-            charsLeftOver : 0,
-            nullInput : false,
-            invalidMonth : null,
-            invalidFormat : false,
-            userInvalidated : false,
-            iso: false
-        };
+    function isUndefined(input) {
+        return input === void 0;
     }
 
-    function printMsg(msg) {
-        if (moment.suppressDeprecationWarnings === false &&
-                typeof console !== 'undefined' && console.warn) {
-            console.warn('Deprecation warning: ' + msg);
-        }
+    function isNumber(input) {
+        return (
+            typeof input === 'number' ||
+            Object.prototype.toString.call(input) === '[object Number]'
+        );
     }
 
-    function deprecate(msg, fn) {
-        var firstTime = true;
-        return extend(function () {
-            if (firstTime) {
-                printMsg(msg);
-                firstTime = false;
-            }
-            return fn.apply(this, arguments);
-        }, fn);
+    function isDate(input) {
+        return (
+            input instanceof Date ||
+            Object.prototype.toString.call(input) === '[object Date]'
+        );
     }
 
-    function deprecateSimple(name, msg) {
-        if (!deprecations[name]) {
-            printMsg(msg);
-            deprecations[name] = true;
+    function map(arr, fn) {
+        var res = [],
+            i,
+            arrLen = arr.length;
+        for (i = 0; i < arrLen; ++i) {
+            res.push(fn(arr[i], i));
         }
+        return res;
     }
 
-    function padToken(func, count) {
-        return function (a) {
-            return leftZeroFill(func.call(this, a), count);
-        };
-    }
-    function ordinalizeToken(func, period) {
-        return function (a) {
-            return this.localeData().ordinal(func.call(this, a), period);
-        };
-    }
-
-    function monthDiff(a, b) {
-        // difference in months
-        var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()),
-            // b is in (anchor - 1 month, anchor + 1 month)
-            anchor = a.clone().add(wholeMonthDiff, 'months'),
-            anchor2, adjust;
+    function extend(a, b) {
+        for (var i in b) {
+            if (hasOwnProp(b, i)) {
+                a[i] = b[i];
+            }
+        }
 
-        if (b - anchor < 0) {
-            anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
-            // linear across the month
-            adjust = (b - anchor) / (anchor - anchor2);
-        } else {
-            anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
-            // linear across the month
-            adjust = (b - anchor) / (anchor2 - anchor);
+        if (hasOwnProp(b, 'toString')) {
+            a.toString = b.toString;
         }
 
-        return -(wholeMonthDiff + adjust);
-    }
+        if (hasOwnProp(b, 'valueOf')) {
+            a.valueOf = b.valueOf;
+        }
 
-    while (ordinalizeTokens.length) {
-        i = ordinalizeTokens.pop();
-        formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i);
-    }
-    while (paddedTokens.length) {
-        i = paddedTokens.pop();
-        formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2);
+        return a;
     }
-    formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3);
-
-
-    function meridiemFixWrap(locale, hour, meridiem) {
-        var isPm;
 
-        if (meridiem == null) {
-            // nothing to do
-            return hour;
-        }
-        if (locale.meridiemHour != null) {
-            return locale.meridiemHour(hour, meridiem);
-        } else if (locale.isPM != null) {
-            // Fallback
-            isPm = locale.isPM(meridiem);
-            if (isPm && hour < 12) {
-                hour += 12;
-            }
-            if (!isPm && hour === 12) {
-                hour = 0;
-            }
-            return hour;
-        } else {
-            // thie is not supposed to happen
-            return hour;
-        }
+    function createUTC(input, format, locale, strict) {
+        return createLocalOrUTC(input, format, locale, strict, true).utc();
     }
 
-    /************************************
-        Constructors
-    ************************************/
-
-    function Locale() {
+    function defaultParsingFlags() {
+        // We need to deep clone this object.
+        return {
+            empty: false,
+            unusedTokens: [],
+            unusedInput: [],
+            overflow: -2,
+            charsLeftOver: 0,
+            nullInput: false,
+            invalidEra: null,
+            invalidMonth: null,
+            invalidFormat: false,
+            userInvalidated: false,
+            iso: false,
+            parsedDateParts: [],
+            era: null,
+            meridiem: null,
+            rfc2822: false,
+            weekdayMismatch: false,
+        };
     }
 
-    // Moment prototype object
-    function Moment(config, skipOverflow) {
-        if (skipOverflow !== false) {
-            checkOverflow(config);
-        }
-        copyConfig(this, config);
-        this._d = new Date(+config._d);
-        // Prevent infinite loop in case updateOffset creates new moment
-        // objects.
-        if (updateInProgress === false) {
-            updateInProgress = true;
-            moment.updateOffset(this);
-            updateInProgress = false;
+    function getParsingFlags(m) {
+        if (m._pf == null) {
+            m._pf = defaultParsingFlags();
         }
+        return m._pf;
     }
 
-    // Duration Constructor
-    function Duration(duration) {
-        var normalizedInput = normalizeObjectUnits(duration),
-            years = normalizedInput.year || 0,
-            quarters = normalizedInput.quarter || 0,
-            months = normalizedInput.month || 0,
-            weeks = normalizedInput.week || 0,
-            days = normalizedInput.day || 0,
-            hours = normalizedInput.hour || 0,
-            minutes = normalizedInput.minute || 0,
-            seconds = normalizedInput.second || 0,
-            milliseconds = normalizedInput.millisecond || 0;
-
-        // representation for dateAddRemove
-        this._milliseconds = +milliseconds +
-            seconds * 1e3 + // 1000
-            minutes * 6e4 + // 1000 * 60
-            hours * 36e5; // 1000 * 60 * 60
-        // Because of dateAddRemove treats 24 hours as different from a
-        // day when working around DST, we need to store them separately
-        this._days = +days +
-            weeks * 7;
-        // It is impossible translate months into days without knowing
-        // which months you are are talking about, so we have to store
-        // it separately.
-        this._months = +months +
-            quarters * 3 +
-            years * 12;
-
-        this._data = {};
-
-        this._locale = moment.localeData();
+    var some;
+    if (Array.prototype.some) {
+        some = Array.prototype.some;
+    } else {
+        some = function (fun) {
+            var t = Object(this),
+                len = t.length >>> 0,
+                i;
+
+            for (i = 0; i < len; i++) {
+                if (i in t && fun.call(this, t[i], i, t)) {
+                    return true;
+                }
+            }
 
-        this._bubble();
+            return false;
+        };
     }
 
-    /************************************
-        Helpers
-    ************************************/
-
+    function isValid(m) {
+        if (m._isValid == null) {
+            var flags = getParsingFlags(m),
+                parsedParts = some.call(flags.parsedDateParts, function (i) {
+                    return i != null;
+                }),
+                isNowValid =
+                    !isNaN(m._d.getTime()) &&
+                    flags.overflow < 0 &&
+                    !flags.empty &&
+                    !flags.invalidEra &&
+                    !flags.invalidMonth &&
+                    !flags.invalidWeekday &&
+                    !flags.weekdayMismatch &&
+                    !flags.nullInput &&
+                    !flags.invalidFormat &&
+                    !flags.userInvalidated &&
+                    (!flags.meridiem || (flags.meridiem && parsedParts));
 
-    function extend(a, b) {
-        for (var i in b) {
-            if (hasOwnProp(b, i)) {
-                a[i] = b[i];
+            if (m._strict) {
+                isNowValid =
+                    isNowValid &&
+                    flags.charsLeftOver === 0 &&
+                    flags.unusedTokens.length === 0 &&
+                    flags.bigHour === undefined;
             }
-        }
 
-        if (hasOwnProp(b, 'toString')) {
-            a.toString = b.toString;
+            if (Object.isFrozen == null || !Object.isFrozen(m)) {
+                m._isValid = isNowValid;
+            } else {
+                return isNowValid;
+            }
         }
+        return m._isValid;
+    }
 
-        if (hasOwnProp(b, 'valueOf')) {
-            a.valueOf = b.valueOf;
+    function createInvalid(flags) {
+        var m = createUTC(NaN);
+        if (flags != null) {
+            extend(getParsingFlags(m), flags);
+        } else {
+            getParsingFlags(m).userInvalidated = true;
         }
 
-        return a;
+        return m;
     }
 
+    // Plugins that add properties should also add the key here (null value),
+    // so we can properly clone ourselves.
+    var momentProperties = (hooks.momentProperties = []),
+        updateInProgress = false;
+
     function copyConfig(to, from) {
-        var i, prop, val;
+        var i,
+            prop,
+            val,
+            momentPropertiesLen = momentProperties.length;
 
-        if (typeof from._isAMomentObject !== 'undefined') {
+        if (!isUndefined(from._isAMomentObject)) {
             to._isAMomentObject = from._isAMomentObject;
         }
-        if (typeof from._i !== 'undefined') {
+        if (!isUndefined(from._i)) {
             to._i = from._i;
         }
-        if (typeof from._f !== 'undefined') {
+        if (!isUndefined(from._f)) {
             to._f = from._f;
         }
-        if (typeof from._l !== 'undefined') {
+        if (!isUndefined(from._l)) {
             to._l = from._l;
         }
-        if (typeof from._strict !== 'undefined') {
+        if (!isUndefined(from._strict)) {
             to._strict = from._strict;
         }
-        if (typeof from._tzm !== 'undefined') {
+        if (!isUndefined(from._tzm)) {
             to._tzm = from._tzm;
         }
-        if (typeof from._isUTC !== 'undefined') {
+        if (!isUndefined(from._isUTC)) {
             to._isUTC = from._isUTC;
         }
-        if (typeof from._offset !== 'undefined') {
+        if (!isUndefined(from._offset)) {
             to._offset = from._offset;
         }
-        if (typeof from._pf !== 'undefined') {
-            to._pf = from._pf;
+        if (!isUndefined(from._pf)) {
+            to._pf = getParsingFlags(from);
         }
-        if (typeof from._locale !== 'undefined') {
+        if (!isUndefined(from._locale)) {
             to._locale = from._locale;
         }
 
-        if (momentProperties.length > 0) {
-            for (i in momentProperties) {
+        if (momentPropertiesLen > 0) {
+            for (i = 0; i < momentPropertiesLen; i++) {
                 prop = momentProperties[i];
                 val = from[prop];
-                if (typeof val !== 'undefined') {
+                if (!isUndefined(val)) {
                     to[prop] = val;
                 }
             }
         return to;
     }
 
-    function absRound(number) {
-        if (number < 0) {
-            return Math.ceil(number);
-        } else {
-            return Math.floor(number);
+    // Moment prototype object
+    function Moment(config) {
+        copyConfig(this, config);
+        this._d = new Date(config._d != null ? config._d.getTime() : NaN);
+        if (!this.isValid()) {
+            this._d = new Date(NaN);
         }
-    }
-
-    // left zero fill a number
-    // see http://jsperf.com/left-zero-filling for performance comparison
-    function leftZeroFill(number, targetLength, forceSign) {
-        var output = '' + Math.abs(number),
-            sign = number >= 0;
-
-        while (output.length < targetLength) {
-            output = '0' + output;
+        // Prevent infinite loop in case updateOffset creates new moment
+        // objects.
+        if (updateInProgress === false) {
+            updateInProgress = true;
+            hooks.updateOffset(this);
+            updateInProgress = false;
         }
-        return (sign ? (forceSign ? '+' : '') : '-') + output;
     }
 
-    function positiveMomentsDifference(base, other) {
-        var res = {milliseconds: 0, months: 0};
+    function isMoment(obj) {
+        return (
+            obj instanceof Moment || (obj != null && obj._isAMomentObject != null)
+        );
+    }
 
-        res.months = other.month() - base.month() +
-            (other.year() - base.year()) * 12;
-        if (base.clone().add(res.months, 'M').isAfter(other)) {
-            --res.months;
+    function warn(msg) {
+        if (
+            hooks.suppressDeprecationWarnings === false &&
+            typeof console !== 'undefined' &&
+            console.warn
+        ) {
+            console.warn('Deprecation warning: ' + msg);
         }
-
-        res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
-
-        return res;
     }
 
-    function momentsDifference(base, other) {
-        var res;
-        other = makeAs(other, base);
-        if (base.isBefore(other)) {
-            res = positiveMomentsDifference(base, other);
-        } else {
-            res = positiveMomentsDifference(other, base);
-            res.milliseconds = -res.milliseconds;
-            res.months = -res.months;
-        }
-
-        return res;
-    }
+    function deprecate(msg, fn) {
+        var firstTime = true;
 
-    // TODO: remove 'name' arg after deprecation is removed
-    function createAdder(direction, name) {
-        return function (val, period) {
-            var dur, tmp;
-            //invert the arguments, but complain about it
-            if (period !== null && !isNaN(+period)) {
-                deprecateSimple(name, 'moment().' + name  + '(period, number) is deprecated. Please use moment().' + name + '(number, period).');
-                tmp = val; val = period; period = tmp;
+        return extend(function () {
+            if (hooks.deprecationHandler != null) {
+                hooks.deprecationHandler(null, msg);
             }
-
-            val = typeof val === 'string' ? +val : val;
-            dur = moment.duration(val, period);
-            addOrSubtractDurationFromMoment(this, dur, direction);
-            return this;
-        };
+            if (firstTime) {
+                var args = [],
+                    arg,
+                    i,
+                    key,
+                    argLen = arguments.length;
+                for (i = 0; i < argLen; i++) {
+                    arg = '';
+                    if (typeof arguments[i] === 'object') {
+                        arg += '\n[' + i + '] ';
+                        for (key in arguments[0]) {
+                            if (hasOwnProp(arguments[0], key)) {
+                                arg += key + ': ' + arguments[0][key] + ', ';
+                            }
+                        }
+                        arg = arg.slice(0, -2); // Remove trailing comma and space
+                    } else {
+                        arg = arguments[i];
+                    }
+                    args.push(arg);
+                }
+                warn(
+                    msg +
+                        '\nArguments: ' +
+                        Array.prototype.slice.call(args).join('') +
+                        '\n' +
+                        new Error().stack
+                );
+                firstTime = false;
+            }
+            return fn.apply(this, arguments);
+        }, fn);
     }
 
-    function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) {
-        var milliseconds = duration._milliseconds,
-            days = duration._days,
-            months = duration._months;
-        updateOffset = updateOffset == null ? true : updateOffset;
+    var deprecations = {};
 
-        if (milliseconds) {
-            mom._d.setTime(+mom._d + milliseconds * isAdding);
-        }
-        if (days) {
-            rawSetter(mom, 'Date', rawGetter(mom, 'Date') + days * isAdding);
-        }
-        if (months) {
-            rawMonthSetter(mom, rawGetter(mom, 'Month') + months * isAdding);
+    function deprecateSimple(name, msg) {
+        if (hooks.deprecationHandler != null) {
+            hooks.deprecationHandler(name, msg);
         }
-        if (updateOffset) {
-            moment.updateOffset(mom, days || months);
+        if (!deprecations[name]) {
+            warn(msg);
+            deprecations[name] = true;
         }
     }
 
-    // check if is an array
-    function isArray(input) {
-        return Object.prototype.toString.call(input) === '[object Array]';
-    }
+    hooks.suppressDeprecationWarnings = false;
+    hooks.deprecationHandler = null;
 
-    function isDate(input) {
-        return Object.prototype.toString.call(input) === '[object Date]' ||
-            input instanceof Date;
+    function isFunction(input) {
+        return (
+            (typeof Function !== 'undefined' && input instanceof Function) ||
+            Object.prototype.toString.call(input) === '[object Function]'
+        );
     }
 
-    // compare two arrays, return the number of differences
-    function compareArrays(array1, array2, dontConvert) {
-        var len = Math.min(array1.length, array2.length),
-            lengthDiff = Math.abs(array1.length - array2.length),
-            diffs = 0,
-            i;
-        for (i = 0; i < len; i++) {
-            if ((dontConvert && array1[i] !== array2[i]) ||
-                (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
-                diffs++;
+    function set(config) {
+        var prop, i;
+        for (i in config) {
+            if (hasOwnProp(config, i)) {
+                prop = config[i];
+                if (isFunction(prop)) {
+                    this[i] = prop;
+                } else {
+                    this['_' + i] = prop;
+                }
             }
         }
-        return diffs + lengthDiff;
-    }
-
-    function normalizeUnits(units) {
-        if (units) {
-            var lowered = units.toLowerCase().replace(/(.)s$/, '$1');
-            units = unitAliases[units] || camelFunctions[lowered] || lowered;
-        }
-        return units;
+        this._config = config;
+        // Lenient ordinal parsing accepts just a number in addition to
+        // number + (possibly) stuff coming from _dayOfMonthOrdinalParse.
+        // TODO: Remove "ordinalParse" fallback in next major release.
+        this._dayOfMonthOrdinalParseLenient = new RegExp(
+            (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) +
+                '|' +
+                /\d{1,2}/.source
+        );
     }
 
-    function normalizeObjectUnits(inputObject) {
-        var normalizedInput = {},
-            normalizedProp,
+    function mergeConfigs(parentConfig, childConfig) {
+        var res = extend({}, parentConfig),
             prop;
-
-        for (prop in inputObject) {
-            if (hasOwnProp(inputObject, prop)) {
-                normalizedProp = normalizeUnits(prop);
-                if (normalizedProp) {
-                    normalizedInput[normalizedProp] = inputObject[prop];
+        for (prop in childConfig) {
+            if (hasOwnProp(childConfig, prop)) {
+                if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {
+                    res[prop] = {};
+                    extend(res[prop], parentConfig[prop]);
+                    extend(res[prop], childConfig[prop]);
+                } else if (childConfig[prop] != null) {
+                    res[prop] = childConfig[prop];
+                } else {
+                    delete res[prop];
                 }
             }
         }
-
-        return normalizedInput;
+        for (prop in parentConfig) {
+            if (
+                hasOwnProp(parentConfig, prop) &&
+                !hasOwnProp(childConfig, prop) &&
+                isObject(parentConfig[prop])
+            ) {
+                // make sure changes to properties don't modify parent config
+                res[prop] = extend({}, res[prop]);
+            }
+        }
+        return res;
     }
 
-    function makeList(field) {
-        var count, setter;
-
-        if (field.indexOf('week') === 0) {
-            count = 7;
-            setter = 'day';
-        }
-        else if (field.indexOf('month') === 0) {
-            count = 12;
-            setter = 'month';
-        }
-        else {
-            return;
+    function Locale(config) {
+        if (config != null) {
+            this.set(config);
         }
+    }
 
-        moment[field] = function (format, index) {
-            var i, getter,
-                method = moment._locale[field],
-                results = [];
-
-            if (typeof format === 'number') {
-                index = format;
-                format = undefined;
-            }
-
-            getter = function (i) {
-                var m = moment().utc().set(setter, i);
-                return method.call(moment._locale, m, format || '');
-            };
+    var keys;
 
-            if (index != null) {
-                return getter(index);
-            }
-            else {
-                for (i = 0; i < count; i++) {
-                    results.push(getter(i));
+    if (Object.keys) {
+        keys = Object.keys;
+    } else {
+        keys = function (obj) {
+            var i,
+                res = [];
+            for (i in obj) {
+                if (hasOwnProp(obj, i)) {
+                    res.push(i);
                 }
-                return results;
             }
+            return res;
         };
     }
 
-    function toInt(argumentForCoercion) {
-        var coercedNumber = +argumentForCoercion,
-            value = 0;
-
-        if (coercedNumber !== 0 && isFinite(coercedNumber)) {
-            if (coercedNumber >= 0) {
-                value = Math.floor(coercedNumber);
-            } else {
-                value = Math.ceil(coercedNumber);
-            }
-        }
-
-        return value;
-    }
-
-    function daysInMonth(year, month) {
-        return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
-    }
-
-    function weeksInYear(year, dow, doy) {
-        return weekOfYear(moment([year, 11, 31 + dow - doy]), dow, doy).week;
-    }
-
-    function daysInYear(year) {
-        return isLeapYear(year) ? 366 : 365;
-    }
+    var defaultCalendar = {
+        sameDay: '[Today at] LT',
+        nextDay: '[Tomorrow at] LT',
+        nextWeek: 'dddd [at] LT',
+        lastDay: '[Yesterday at] LT',
+        lastWeek: '[Last] dddd [at] LT',
+        sameElse: 'L',
+    };
 
-    function isLeapYear(year) {
-        return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+    function calendar(key, mom, now) {
+        var output = this._calendar[key] || this._calendar['sameElse'];
+        return isFunction(output) ? output.call(mom, now) : output;
     }
 
-    function checkOverflow(m) {
-        var overflow;
-        if (m._a && m._pf.overflow === -2) {
-            overflow =
-                m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH :
-                m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE :
-                m._a[HOUR] < 0 || m._a[HOUR] > 24 ||
-                    (m._a[HOUR] === 24 && (m._a[MINUTE] !== 0 ||
-                                           m._a[SECOND] !== 0 ||
-                                           m._a[MILLISECOND] !== 0)) ? HOUR :
-                m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE :
-                m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND :
-                m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND :
-                -1;
-
-            if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
-                overflow = DATE;
-            }
-
-            m._pf.overflow = overflow;
-        }
+    function zeroFill(number, targetLength, forceSign) {
+        var absNumber = '' + Math.abs(number),
+            zerosToFill = targetLength - absNumber.length,
+            sign = number >= 0;
+        return (
+            (sign ? (forceSign ? '+' : '') : '-') +
+            Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) +
+            absNumber
+        );
     }
 
-    function isValid(m) {
-        if (m._isValid == null) {
-            m._isValid = !isNaN(m._d.getTime()) &&
-                m._pf.overflow < 0 &&
-                !m._pf.empty &&
-                !m._pf.invalidMonth &&
-                !m._pf.nullInput &&
-                !m._pf.invalidFormat &&
-                !m._pf.userInvalidated;
-
-            if (m._strict) {
-                m._isValid = m._isValid &&
-                    m._pf.charsLeftOver === 0 &&
-                    m._pf.unusedTokens.length === 0 &&
-                    m._pf.bigHour === undefined;
-            }
+    var formattingTokens =
+            /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,
+        localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,
+        formatFunctions = {},
+        formatTokenFunctions = {};
+
+    // token:    'M'
+    // padded:   ['MM', 2]
+    // ordinal:  'Mo'
+    // callback: function () { this.month() + 1 }
+    function addFormatToken(token, padded, ordinal, callback) {
+        var func = callback;
+        if (typeof callback === 'string') {
+            func = function () {
+                return this[callback]();
+            };
         }
-        return m._isValid;
-    }
-
-    function normalizeLocale(key) {
-        return key ? key.toLowerCase().replace('_', '-') : key;
-    }
-
-    // pick the locale from the array
-    // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
-    // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
-    function chooseLocale(names) {
-        var i = 0, j, next, locale, split;
-
-        while (i < names.length) {
-            split = normalizeLocale(names[i]).split('-');
-            j = split.length;
-            next = normalizeLocale(names[i + 1]);
-            next = next ? next.split('-') : null;
-            while (j > 0) {
-                locale = loadLocale(split.slice(0, j).join('-'));
-                if (locale) {
-                    return locale;
-                }
-                if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
-                    //the next array item is better than a shallower substring of this one
-                    break;
-                }
-                j--;
-            }
-            i++;
+        if (token) {
+            formatTokenFunctions[token] = func;
         }
-        return null;
-    }
-
-    function loadLocale(name) {
-        var oldLocale = null;
-        if (!locales[name] && hasModule) {
-            try {
-                oldLocale = moment.locale();
-                require('./locale/' + name);
-                // because defineLocale currently also sets the global locale, we want to undo that for lazy loaded locales
-                moment.locale(oldLocale);
-            } catch (e) { }
+        if (padded) {
+            formatTokenFunctions[padded[0]] = function () {
+                return zeroFill(func.apply(this, arguments), padded[1], padded[2]);
+            };
         }
-        return locales[name];
-    }
-
-    // Return a moment from input, that is local/utc/utcOffset equivalent to
-    // model.
-    function makeAs(input, model) {
-        var res, diff;
-        if (model._isUTC) {
-            res = model.clone();
-            diff = (moment.isMoment(input) || isDate(input) ?
-                    +input : +moment(input)) - (+res);
-            // Use low-level api, because this fn is low-level api.
-            res._d.setTime(+res._d + diff);
-            moment.updateOffset(res, false);
-            return res;
-        } else {
-            return moment(input).local();
+        if (ordinal) {
+            formatTokenFunctions[ordinal] = function () {
+                return this.localeData().ordinal(
+                    func.apply(this, arguments),
+                    token
+                );
+            };
         }
     }
 
-    /************************************
-        Locale
-    ************************************/
-
-
-    extend(Locale.prototype, {
-
-        set : function (config) {
-            var prop, i;
-            for (i in config) {
-                prop = config[i];
-                if (typeof prop === 'function') {
-                    this[i] = prop;
-                } else {
-                    this['_' + i] = prop;
-                }
-            }
-            // Lenient ordinal parsing accepts just a number in addition to
-            // number + (possibly) stuff coming from _ordinalParseLenient.
-            this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + /\d{1,2}/.source);
-        },
-
-        _months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
-        months : function (m) {
-            return this._months[m.month()];
-        },
-
-        _monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),
-        monthsShort : function (m) {
-            return this._monthsShort[m.month()];
-        },
-
-        monthsParse : function (monthName, format, strict) {
-            var i, mom, regex;
-
-            if (!this._monthsParse) {
-                this._monthsParse = [];
-                this._longMonthsParse = [];
-                this._shortMonthsParse = [];
-            }
-
-            for (i = 0; i < 12; i++) {
-                // make the regex if we don't have it already
-                mom = moment.utc([2000, i]);
-                if (strict && !this._longMonthsParse[i]) {
-                    this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i');
-                    this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i');
-                }
-                if (!strict && !this._monthsParse[i]) {
-                    regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
-                    this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
-                }
-                // test the regex
-                if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) {
-                    return i;
-                } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) {
-                    return i;
-                } else if (!strict && this._monthsParse[i].test(monthName)) {
-                    return i;
-                }
-            }
-        },
-
-        _weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),
-        weekdays : function (m) {
-            return this._weekdays[m.day()];
-        },
-
-        _weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
-        weekdaysShort : function (m) {
-            return this._weekdaysShort[m.day()];
-        },
-
-        _weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
-        weekdaysMin : function (m) {
-            return this._weekdaysMin[m.day()];
-        },
-
-        weekdaysParse : function (weekdayName) {
-            var i, mom, regex;
-
-            if (!this._weekdaysParse) {
-                this._weekdaysParse = [];
-            }
-
-            for (i = 0; i < 7; i++) {
-                // make the regex if we don't have it already
-                if (!this._weekdaysParse[i]) {
-                    mom = moment([2000, 1]).day(i);
-                    regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
-                    this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
-                }
-                // test the regex
-                if (this._weekdaysParse[i].test(weekdayName)) {
-                    return i;
-                }
-            }
-        },
-
-        _longDateFormat : {
-            LTS : 'h:mm:ss A',
-            LT : 'h:mm A',
-            L : 'MM/DD/YYYY',
-            LL : 'MMMM D, YYYY',
-            LLL : 'MMMM D, YYYY LT',
-            LLLL : 'dddd, MMMM D, YYYY LT'
-        },
-        longDateFormat : function (key) {
-            var output = this._longDateFormat[key];
-            if (!output && this._longDateFormat[key.toUpperCase()]) {
-                output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) {
-                    return val.slice(1);
-                });
-                this._longDateFormat[key] = output;
-            }
-            return output;
-        },
-
-        isPM : function (input) {
-            // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
-            // Using charAt should be more compatible.
-            return ((input + '').toLowerCase().charAt(0) === 'p');
-        },
-
-        _meridiemParse : /[ap]\.?m?\.?/i,
-        meridiem : function (hours, minutes, isLower) {
-            if (hours > 11) {
-                return isLower ? 'pm' : 'PM';
-            } else {
-                return isLower ? 'am' : 'AM';
-            }
-        },
-
-
-        _calendar : {
-            sameDay : '[Today at] LT',
-            nextDay : '[Tomorrow at] LT',
-            nextWeek : 'dddd [at] LT',
-            lastDay : '[Yesterday at] LT',
-            lastWeek : '[Last] dddd [at] LT',
-            sameElse : 'L'
-        },
-        calendar : function (key, mom, now) {
-            var output = this._calendar[key];
-            return typeof output === 'function' ? output.apply(mom, [now]) : output;
-        },
-
-        _relativeTime : {
-            future : 'in %s',
-            past : '%s ago',
-            s : 'a few seconds',
-            m : 'a minute',
-            mm : '%d minutes',
-            h : 'an hour',
-            hh : '%d hours',
-            d : 'a day',
-            dd : '%d days',
-            M : 'a month',
-            MM : '%d months',
-            y : 'a year',
-            yy : '%d years'
-        },
-
-        relativeTime : function (number, withoutSuffix, string, isFuture) {
-            var output = this._relativeTime[string];
-            return (typeof output === 'function') ?
-                output(number, withoutSuffix, string, isFuture) :
-                output.replace(/%d/i, number);
-        },
-
-        pastFuture : function (diff, output) {
-            var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
-            return typeof format === 'function' ? format(output) : format.replace(/%s/i, output);
-        },
-
-        ordinal : function (number) {
-            return this._ordinal.replace('%d', number);
-        },
-        _ordinal : '%d',
-        _ordinalParse : /\d{1,2}/,
-
-        preparse : function (string) {
-            return string;
-        },
-
-        postformat : function (string) {
-            return string;
-        },
-
-        week : function (mom) {
-            return weekOfYear(mom, this._week.dow, this._week.doy).week;
-        },
-
-        _week : {
-            dow : 0, // Sunday is the first day of the week.
-            doy : 6  // The week that contains Jan 1st is the first week of the year.
-        },
-
-        firstDayOfWeek : function () {
-            return this._week.dow;
-        },
-
-        firstDayOfYear : function () {
-            return this._week.doy;
-        },
-
-        _invalidDate: 'Invalid date',
-        invalidDate: function () {
-            return this._invalidDate;
-        }
-    });
-
-    /************************************
-        Formatting
-    ************************************/
-
-
     function removeFormattingTokens(input) {
         if (input.match(/\[[\s\S]/)) {
             return input.replace(/^\[|\]$/g, '');
     }
 
     function makeFormatFunction(format) {
-        var array = format.match(formattingTokens), i, length;
+        var array = format.match(formattingTokens),
+            i,
+            length;
 
         for (i = 0, length = array.length; i < length; i++) {
             if (formatTokenFunctions[array[i]]) {
         }
 
         return function (mom) {
-            var output = '';
+            var output = '',
+                i;
             for (i = 0; i < length; i++) {
-                output += array[i] instanceof Function ? array[i].call(mom, format) : array[i];
+                output += isFunction(array[i])
+                    ? array[i].call(mom, format)
+                    : array[i];
             }
             return output;
         };
         }
 
         format = expandFormat(format, m.localeData());
-
-        if (!formatFunctions[format]) {
-            formatFunctions[format] = makeFormatFunction(format);
-        }
+        formatFunctions[format] =
+            formatFunctions[format] || makeFormatFunction(format);
 
         return formatFunctions[format](m);
     }
 
         localFormattingTokens.lastIndex = 0;
         while (i >= 0 && localFormattingTokens.test(format)) {
-            format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
+            format = format.replace(
+                localFormattingTokens,
+                replaceLongDateFormatTokens
+            );
             localFormattingTokens.lastIndex = 0;
             i -= 1;
         }
         return format;
     }
 
+    var defaultLongDateFormat = {
+        LTS: 'h:mm:ss A',
+        LT: 'h:mm A',
+        L: 'MM/DD/YYYY',
+        LL: 'MMMM D, YYYY',
+        LLL: 'MMMM D, YYYY h:mm A',
+        LLLL: 'dddd, MMMM D, YYYY h:mm A',
+    };
 
-    /************************************
-        Parsing
-    ************************************/
+    function longDateFormat(key) {
+        var format = this._longDateFormat[key],
+            formatUpper = this._longDateFormat[key.toUpperCase()];
 
+        if (format || !formatUpper) {
+            return format;
+        }
 
-    // get the regex to find the next token
-    function getParseRegexForToken(token, config) {
-        var a, strict = config._strict;
-        switch (token) {
-        case 'Q':
-            return parseTokenOneDigit;
-        case 'DDDD':
-            return parseTokenThreeDigits;
-        case 'YYYY':
-        case 'GGGG':
-        case 'gggg':
-            return strict ? parseTokenFourDigits : parseTokenOneToFourDigits;
-        case 'Y':
-        case 'G':
-        case 'g':
-            return parseTokenSignedNumber;
-        case 'YYYYYY':
-        case 'YYYYY':
-        case 'GGGGG':
-        case 'ggggg':
-            return strict ? parseTokenSixDigits : parseTokenOneToSixDigits;
-        case 'S':
-            if (strict) {
-                return parseTokenOneDigit;
+        this._longDateFormat[key] = formatUpper
+            .match(formattingTokens)
+            .map(function (tok) {
+                if (
+                    tok === 'MMMM' ||
+                    tok === 'MM' ||
+                    tok === 'DD' ||
+                    tok === 'dddd'
+                ) {
+                    return tok.slice(1);
+                }
+                return tok;
+            })
+            .join('');
+
+        return this._longDateFormat[key];
+    }
+
+    var defaultInvalidDate = 'Invalid date';
+
+    function invalidDate() {
+        return this._invalidDate;
+    }
+
+    var defaultOrdinal = '%d',
+        defaultDayOfMonthOrdinalParse = /\d{1,2}/;
+
+    function ordinal(number) {
+        return this._ordinal.replace('%d', number);
+    }
+
+    var defaultRelativeTime = {
+        future: 'in %s',
+        past: '%s ago',
+        s: 'a few seconds',
+        ss: '%d seconds',
+        m: 'a minute',
+        mm: '%d minutes',
+        h: 'an hour',
+        hh: '%d hours',
+        d: 'a day',
+        dd: '%d days',
+        w: 'a week',
+        ww: '%d weeks',
+        M: 'a month',
+        MM: '%d months',
+        y: 'a year',
+        yy: '%d years',
+    };
+
+    function relativeTime(number, withoutSuffix, string, isFuture) {
+        var output = this._relativeTime[string];
+        return isFunction(output)
+            ? output(number, withoutSuffix, string, isFuture)
+            : output.replace(/%d/i, number);
+    }
+
+    function pastFuture(diff, output) {
+        var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
+        return isFunction(format) ? format(output) : format.replace(/%s/i, output);
+    }
+
+    var aliases = {};
+
+    function addUnitAlias(unit, shorthand) {
+        var lowerCase = unit.toLowerCase();
+        aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
+    }
+
+    function normalizeUnits(units) {
+        return typeof units === 'string'
+            ? aliases[units] || aliases[units.toLowerCase()]
+            : undefined;
+    }
+
+    function normalizeObjectUnits(inputObject) {
+        var normalizedInput = {},
+            normalizedProp,
+            prop;
+
+        for (prop in inputObject) {
+            if (hasOwnProp(inputObject, prop)) {
+                normalizedProp = normalizeUnits(prop);
+                if (normalizedProp) {
+                    normalizedInput[normalizedProp] = inputObject[prop];
+                }
             }
-            /* falls through */
-        case 'SS':
-            if (strict) {
-                return parseTokenTwoDigits;
+        }
+
+        return normalizedInput;
+    }
+
+    var priorities = {};
+
+    function addUnitPriority(unit, priority) {
+        priorities[unit] = priority;
+    }
+
+    function getPrioritizedUnits(unitsObj) {
+        var units = [],
+            u;
+        for (u in unitsObj) {
+            if (hasOwnProp(unitsObj, u)) {
+                units.push({ unit: u, priority: priorities[u] });
             }
-            /* falls through */
-        case 'SSS':
-            if (strict) {
-                return parseTokenThreeDigits;
-            }
-            /* falls through */
-        case 'DDD':
-            return parseTokenOneToThreeDigits;
-        case 'MMM':
-        case 'MMMM':
-        case 'dd':
-        case 'ddd':
-        case 'dddd':
-            return parseTokenWord;
-        case 'a':
-        case 'A':
-            return config._locale._meridiemParse;
-        case 'x':
-            return parseTokenOffsetMs;
-        case 'X':
-            return parseTokenTimestampMs;
-        case 'Z':
-        case 'ZZ':
-            return parseTokenTimezone;
-        case 'T':
-            return parseTokenT;
-        case 'SSSS':
-            return parseTokenDigits;
-        case 'MM':
-        case 'DD':
-        case 'YY':
-        case 'GG':
-        case 'gg':
-        case 'HH':
-        case 'hh':
-        case 'mm':
-        case 'ss':
-        case 'ww':
-        case 'WW':
-            return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits;
-        case 'M':
-        case 'D':
-        case 'd':
-        case 'H':
-        case 'h':
-        case 'm':
-        case 's':
-        case 'w':
-        case 'W':
-        case 'e':
-        case 'E':
-            return parseTokenOneOrTwoDigits;
-        case 'Do':
-            return strict ? config._locale._ordinalParse : config._locale._ordinalParseLenient;
-        default :
-            a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), 'i'));
-            return a;
         }
+        units.sort(function (a, b) {
+            return a.priority - b.priority;
+        });
+        return units;
     }
 
-    function utcOffsetFromString(string) {
-        string = string || '';
-        var possibleTzMatches = (string.match(parseTokenTimezone) || []),
-            tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [],
-            parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0],
-            minutes = +(parts[1] * 60) + toInt(parts[2]);
+    function isLeapYear(year) {
+        return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+    }
 
-        return parts[0] === '+' ? minutes : -minutes;
+    function absFloor(number) {
+        if (number < 0) {
+            // -0 -> 0
+            return Math.ceil(number) || 0;
+        } else {
+            return Math.floor(number);
+        }
     }
 
-    // function to convert string input to date
-    function addTimeToArrayFromToken(token, input, config) {
-        var a, datePartArray = config._a;
-
-        switch (token) {
-        // QUARTER
-        case 'Q':
-            if (input != null) {
-                datePartArray[MONTH] = (toInt(input) - 1) * 3;
-            }
-            break;
-        // MONTH
-        case 'M' : // fall through to MM
-        case 'MM' :
-            if (input != null) {
-                datePartArray[MONTH] = toInt(input) - 1;
-            }
-            break;
-        case 'MMM' : // fall through to MMMM
-        case 'MMMM' :
-            a = config._locale.monthsParse(input, token, config._strict);
-            // if we didn't find a month name, mark the date as invalid.
-            if (a != null) {
-                datePartArray[MONTH] = a;
+    function toInt(argumentForCoercion) {
+        var coercedNumber = +argumentForCoercion,
+            value = 0;
+
+        if (coercedNumber !== 0 && isFinite(coercedNumber)) {
+            value = absFloor(coercedNumber);
+        }
+
+        return value;
+    }
+
+    function makeGetSet(unit, keepTime) {
+        return function (value) {
+            if (value != null) {
+                set$1(this, unit, value);
+                hooks.updateOffset(this, keepTime);
+                return this;
             } else {
-                config._pf.invalidMonth = input;
-            }
-            break;
-        // DAY OF MONTH
-        case 'D' : // fall through to DD
-        case 'DD' :
-            if (input != null) {
-                datePartArray[DATE] = toInt(input);
-            }
-            break;
-        case 'Do' :
-            if (input != null) {
-                datePartArray[DATE] = toInt(parseInt(
-                            input.match(/\d{1,2}/)[0], 10));
-            }
-            break;
-        // DAY OF YEAR
-        case 'DDD' : // fall through to DDDD
-        case 'DDDD' :
-            if (input != null) {
-                config._dayOfYear = toInt(input);
-            }
-
-            break;
-        // YEAR
-        case 'YY' :
-            datePartArray[YEAR] = moment.parseTwoDigitYear(input);
-            break;
-        case 'YYYY' :
-        case 'YYYYY' :
-        case 'YYYYYY' :
-            datePartArray[YEAR] = toInt(input);
-            break;
-        // AM / PM
-        case 'a' : // fall through to A
-        case 'A' :
-            config._meridiem = input;
-            // config._isPm = config._locale.isPM(input);
-            break;
-        // HOUR
-        case 'h' : // fall through to hh
-        case 'hh' :
-            config._pf.bigHour = true;
-            /* falls through */
-        case 'H' : // fall through to HH
-        case 'HH' :
-            datePartArray[HOUR] = toInt(input);
-            break;
-        // MINUTE
-        case 'm' : // fall through to mm
-        case 'mm' :
-            datePartArray[MINUTE] = toInt(input);
-            break;
-        // SECOND
-        case 's' : // fall through to ss
-        case 'ss' :
-            datePartArray[SECOND] = toInt(input);
-            break;
-        // MILLISECOND
-        case 'S' :
-        case 'SS' :
-        case 'SSS' :
-        case 'SSSS' :
-            datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000);
-            break;
-        // UNIX OFFSET (MILLISECONDS)
-        case 'x':
-            config._d = new Date(toInt(input));
-            break;
-        // UNIX TIMESTAMP WITH MS
-        case 'X':
-            config._d = new Date(parseFloat(input) * 1000);
-            break;
-        // TIMEZONE
-        case 'Z' : // fall through to ZZ
-        case 'ZZ' :
-            config._useUTC = true;
-            config._tzm = utcOffsetFromString(input);
-            break;
-        // WEEKDAY - human
-        case 'dd':
-        case 'ddd':
-        case 'dddd':
-            a = config._locale.weekdaysParse(input);
-            // if we didn't get a weekday name, mark the date as invalid
-            if (a != null) {
-                config._w = config._w || {};
-                config._w['d'] = a;
+                return get(this, unit);
+            }
+        };
+    }
+
+    function get(mom, unit) {
+        return mom.isValid()
+            ? mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]()
+            : NaN;
+    }
+
+    function set$1(mom, unit, value) {
+        if (mom.isValid() && !isNaN(value)) {
+            if (
+                unit === 'FullYear' &&
+                isLeapYear(mom.year()) &&
+                mom.month() === 1 &&
+                mom.date() === 29
+            ) {
+                value = toInt(value);
+                mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](
+                    value,
+                    mom.month(),
+                    daysInMonth(value, mom.month())
+                );
             } else {
-                config._pf.invalidWeekday = input;
-            }
-            break;
-        // WEEK, WEEK DAY - numeric
-        case 'w':
-        case 'ww':
-        case 'W':
-        case 'WW':
-        case 'd':
-        case 'e':
-        case 'E':
-            token = token.substr(0, 1);
-            /* falls through */
-        case 'gggg':
-        case 'GGGG':
-        case 'GGGGG':
-            token = token.substr(0, 2);
-            if (input) {
-                config._w = config._w || {};
-                config._w[token] = toInt(input);
-            }
-            break;
-        case 'gg':
-        case 'GG':
-            config._w = config._w || {};
-            config._w[token] = moment.parseTwoDigitYear(input);
+                mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
+            }
         }
     }
 
-    function dayOfYearFromWeekInfo(config) {
-        var w, weekYear, week, weekday, dow, doy, temp;
+    // MOMENTS
 
-        w = config._w;
-        if (w.GG != null || w.W != null || w.E != null) {
-            dow = 1;
-            doy = 4;
+    function stringGet(units) {
+        units = normalizeUnits(units);
+        if (isFunction(this[units])) {
+            return this[units]();
+        }
+        return this;
+    }
 
-            // TODO: We need to take the current isoWeekYear, but that depends on
-            // how we interpret now (local, utc, fixed offset). So create
-            // a now version of current config (take local/utc/offset flags, and
-            // create now).
-            weekYear = dfl(w.GG, config._a[YEAR], weekOfYear(moment(), 1, 4).year);
-            week = dfl(w.W, 1);
-            weekday = dfl(w.E, 1);
+    function stringSet(units, value) {
+        if (typeof units === 'object') {
+            units = normalizeObjectUnits(units);
+            var prioritized = getPrioritizedUnits(units),
+                i,
+                prioritizedLen = prioritized.length;
+            for (i = 0; i < prioritizedLen; i++) {
+                this[prioritized[i].unit](units[prioritized[i].unit]);
+            }
         } else {
-            dow = config._locale._week.dow;
-            doy = config._locale._week.doy;
+            units = normalizeUnits(units);
+            if (isFunction(this[units])) {
+                return this[units](value);
+            }
+        }
+        return this;
+    }
 
-            weekYear = dfl(w.gg, config._a[YEAR], weekOfYear(moment(), dow, doy).year);
-            week = dfl(w.w, 1);
+    var match1 = /\d/, //       0 - 9
+        match2 = /\d\d/, //      00 - 99
+        match3 = /\d{3}/, //     000 - 999
+        match4 = /\d{4}/, //    0000 - 9999
+        match6 = /[+-]?\d{6}/, // -999999 - 999999
+        match1to2 = /\d\d?/, //       0 - 99
+        match3to4 = /\d\d\d\d?/, //     999 - 9999
+        match5to6 = /\d\d\d\d\d\d?/, //   99999 - 999999
+        match1to3 = /\d{1,3}/, //       0 - 999
+        match1to4 = /\d{1,4}/, //       0 - 9999
+        match1to6 = /[+-]?\d{1,6}/, // -999999 - 999999
+        matchUnsigned = /\d+/, //       0 - inf
+        matchSigned = /[+-]?\d+/, //    -inf - inf
+        matchOffset = /Z|[+-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z
+        matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi, // +00 -00 +00:00 -00:00 +0000 -0000 or Z
+        matchTimestamp = /[+-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
+        // any word (or two) characters or numbers including two/three word month in arabic.
+        // includes scottish gaelic two word and hyphenated months
+        matchWord =
+            /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i,
+        regexes;
+
+    regexes = {};
+
+    function addRegexToken(token, regex, strictRegex) {
+        regexes[token] = isFunction(regex)
+            ? regex
+            : function (isStrict, localeData) {
+                  return isStrict && strictRegex ? strictRegex : regex;
+              };
+    }
 
-            if (w.d != null) {
-                // weekday -- low day numbers are considered next week
-                weekday = w.d;
-                if (weekday < dow) {
-                    ++week;
-                }
-            } else if (w.e != null) {
-                // local weekday -- counting starts from begining of week
-                weekday = w.e + dow;
-            } else {
-                // default to begining of week
-                weekday = dow;
-            }
+    function getParseRegexForToken(token, config) {
+        if (!hasOwnProp(regexes, token)) {
+            return new RegExp(unescapeFormat(token));
         }
-        temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow);
 
-        config._a[YEAR] = temp.year;
-        config._dayOfYear = temp.dayOfYear;
+        return regexes[token](config._strict, config._locale);
     }
 
-    // convert an array to a date.
-    // the array should mirror the parameters below
-    // note: all values past the year are optional and will default to the lowest possible value.
-    // [year, month, day , hour, minute, second, millisecond]
-    function dateFromConfig(config) {
-        var i, date, input = [], currentDate, yearToUse;
+    // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
+    function unescapeFormat(s) {
+        return regexEscape(
+            s
+                .replace('\\', '')
+                .replace(
+                    /\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,
+                    function (matched, p1, p2, p3, p4) {
+                        return p1 || p2 || p3 || p4;
+                    }
+                )
+        );
+    }
 
-        if (config._d) {
-            return;
+    function regexEscape(s) {
+        return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+    }
+
+    var tokens = {};
+
+    function addParseToken(token, callback) {
+        var i,
+            func = callback,
+            tokenLen;
+        if (typeof token === 'string') {
+            token = [token];
+        }
+        if (isNumber(callback)) {
+            func = function (input, array) {
+                array[callback] = toInt(input);
+            };
+        }
+        tokenLen = token.length;
+        for (i = 0; i < tokenLen; i++) {
+            tokens[token[i]] = func;
         }
+    }
 
-        currentDate = currentDateArray(config);
+    function addWeekParseToken(token, callback) {
+        addParseToken(token, function (input, array, config, token) {
+            config._w = config._w || {};
+            callback(input, config._w, config, token);
+        });
+    }
 
-        //compute day of the year from weeks and weekdays
-        if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
-            dayOfYearFromWeekInfo(config);
+    function addTimeToArrayFromToken(token, input, config) {
+        if (input != null && hasOwnProp(tokens, token)) {
+            tokens[token](input, config._a, config, token);
         }
+    }
 
-        //if the day of the year is set, figure out what it is
-        if (config._dayOfYear) {
-            yearToUse = dfl(config._a[YEAR], currentDate[YEAR]);
+    var YEAR = 0,
+        MONTH = 1,
+        DATE = 2,
+        HOUR = 3,
+        MINUTE = 4,
+        SECOND = 5,
+        MILLISECOND = 6,
+        WEEK = 7,
+        WEEKDAY = 8;
+
+    function mod(n, x) {
+        return ((n % x) + x) % x;
+    }
+
+    var indexOf;
 
-            if (config._dayOfYear > daysInYear(yearToUse)) {
-                config._pf._overflowDayOfYear = true;
+    if (Array.prototype.indexOf) {
+        indexOf = Array.prototype.indexOf;
+    } else {
+        indexOf = function (o) {
+            // I know
+            var i;
+            for (i = 0; i < this.length; ++i) {
+                if (this[i] === o) {
+                    return i;
+                }
             }
+            return -1;
+        };
+    }
 
-            date = makeUTCDate(yearToUse, 0, config._dayOfYear);
-            config._a[MONTH] = date.getUTCMonth();
-            config._a[DATE] = date.getUTCDate();
+    function daysInMonth(year, month) {
+        if (isNaN(year) || isNaN(month)) {
+            return NaN;
         }
+        var modMonth = mod(month, 12);
+        year += (month - modMonth) / 12;
+        return modMonth === 1
+            ? isLeapYear(year)
+                ? 29
+                : 28
+            : 31 - ((modMonth % 7) % 2);
+    }
 
-        // Default to current date.
-        // * if no year, month, day of month are given, default to today
-        // * if day of month is given, default month and year
-        // * if month is given, default only year
-        // * if year is given, don't default anything
-        for (i = 0; i < 3 && config._a[i] == null; ++i) {
-            config._a[i] = input[i] = currentDate[i];
+    // FORMATTING
+
+    addFormatToken('M', ['MM', 2], 'Mo', function () {
+        return this.month() + 1;
+    });
+
+    addFormatToken('MMM', 0, 0, function (format) {
+        return this.localeData().monthsShort(this, format);
+    });
+
+    addFormatToken('MMMM', 0, 0, function (format) {
+        return this.localeData().months(this, format);
+    });
+
+    // ALIASES
+
+    addUnitAlias('month', 'M');
+
+    // PRIORITY
+
+    addUnitPriority('month', 8);
+
+    // PARSING
+
+    addRegexToken('M', match1to2);
+    addRegexToken('MM', match1to2, match2);
+    addRegexToken('MMM', function (isStrict, locale) {
+        return locale.monthsShortRegex(isStrict);
+    });
+    addRegexToken('MMMM', function (isStrict, locale) {
+        return locale.monthsRegex(isStrict);
+    });
+
+    addParseToken(['M', 'MM'], function (input, array) {
+        array[MONTH] = toInt(input) - 1;
+    });
+
+    addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {
+        var month = config._locale.monthsParse(input, token, config._strict);
+        // if we didn't find a month name, mark the date as invalid.
+        if (month != null) {
+            array[MONTH] = month;
+        } else {
+            getParsingFlags(config).invalidMonth = input;
         }
+    });
 
-        // Zero out whatever was not defaulted, including time
-        for (; i < 7; i++) {
-            config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
+    // LOCALES
+
+    var defaultLocaleMonths =
+            'January_February_March_April_May_June_July_August_September_October_November_December'.split(
+                '_'
+            ),
+        defaultLocaleMonthsShort =
+            'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),
+        MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/,
+        defaultMonthsShortRegex = matchWord,
+        defaultMonthsRegex = matchWord;
+
+    function localeMonths(m, format) {
+        if (!m) {
+            return isArray(this._months)
+                ? this._months
+                : this._months['standalone'];
         }
+        return isArray(this._months)
+            ? this._months[m.month()]
+            : this._months[
+                  (this._months.isFormat || MONTHS_IN_FORMAT).test(format)
+                      ? 'format'
+                      : 'standalone'
+              ][m.month()];
+    }
 
-        // Check for 24:00:00.000
-        if (config._a[HOUR] === 24 &&
-                config._a[MINUTE] === 0 &&
-                config._a[SECOND] === 0 &&
-                config._a[MILLISECOND] === 0) {
-            config._nextDay = true;
-            config._a[HOUR] = 0;
+    function localeMonthsShort(m, format) {
+        if (!m) {
+            return isArray(this._monthsShort)
+                ? this._monthsShort
+                : this._monthsShort['standalone'];
         }
+        return isArray(this._monthsShort)
+            ? this._monthsShort[m.month()]
+            : this._monthsShort[
+                  MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'
+              ][m.month()];
+    }
 
-        config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input);
-        // Apply timezone offset from input. The actual utcOffset can be changed
-        // with parseZone.
-        if (config._tzm != null) {
-            config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
+    function handleStrictParse(monthName, format, strict) {
+        var i,
+            ii,
+            mom,
+            llc = monthName.toLocaleLowerCase();
+        if (!this._monthsParse) {
+            // this is not used
+            this._monthsParse = [];
+            this._longMonthsParse = [];
+            this._shortMonthsParse = [];
+            for (i = 0; i < 12; ++i) {
+                mom = createUTC([2000, i]);
+                this._shortMonthsParse[i] = this.monthsShort(
+                    mom,
+                    ''
+                ).toLocaleLowerCase();
+                this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();
+            }
         }
 
-        if (config._nextDay) {
-            config._a[HOUR] = 24;
+        if (strict) {
+            if (format === 'MMM') {
+                ii = indexOf.call(this._shortMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._longMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            }
+        } else {
+            if (format === 'MMM') {
+                ii = indexOf.call(this._shortMonthsParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._longMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._longMonthsParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._shortMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            }
         }
     }
 
-    function dateFromObject(config) {
-        var normalizedInput;
+    function localeMonthsParse(monthName, format, strict) {
+        var i, mom, regex;
 
-        if (config._d) {
-            return;
+        if (this._monthsParseExact) {
+            return handleStrictParse.call(this, monthName, format, strict);
         }
 
-        normalizedInput = normalizeObjectUnits(config._i);
-        config._a = [
-            normalizedInput.year,
-            normalizedInput.month,
-            normalizedInput.day || normalizedInput.date,
-            normalizedInput.hour,
-            normalizedInput.minute,
-            normalizedInput.second,
-            normalizedInput.millisecond
-        ];
+        if (!this._monthsParse) {
+            this._monthsParse = [];
+            this._longMonthsParse = [];
+            this._shortMonthsParse = [];
+        }
 
-        dateFromConfig(config);
+        // TODO: add sorting
+        // Sorting makes sure if one month (or abbr) is a prefix of another
+        // see sorting in computeMonthsParse
+        for (i = 0; i < 12; i++) {
+            // make the regex if we don't have it already
+            mom = createUTC([2000, i]);
+            if (strict && !this._longMonthsParse[i]) {
+                this._longMonthsParse[i] = new RegExp(
+                    '^' + this.months(mom, '').replace('.', '') + '$',
+                    'i'
+                );
+                this._shortMonthsParse[i] = new RegExp(
+                    '^' + this.monthsShort(mom, '').replace('.', '') + '$',
+                    'i'
+                );
+            }
+            if (!strict && !this._monthsParse[i]) {
+                regex =
+                    '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
+                this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
+            }
+            // test the regex
+            if (
+                strict &&
+                format === 'MMMM' &&
+                this._longMonthsParse[i].test(monthName)
+            ) {
+                return i;
+            } else if (
+                strict &&
+                format === 'MMM' &&
+                this._shortMonthsParse[i].test(monthName)
+            ) {
+                return i;
+            } else if (!strict && this._monthsParse[i].test(monthName)) {
+                return i;
+            }
+        }
     }
 
-    function currentDateArray(config) {
-        var now = new Date();
-        if (config._useUTC) {
-            return [
-                now.getUTCFullYear(),
-                now.getUTCMonth(),
-                now.getUTCDate()
-            ];
+    // MOMENTS
+
+    function setMonth(mom, value) {
+        var dayOfMonth;
+
+        if (!mom.isValid()) {
+            // No op
+            return mom;
+        }
+
+        if (typeof value === 'string') {
+            if (/^\d+$/.test(value)) {
+                value = toInt(value);
+            } else {
+                value = mom.localeData().monthsParse(value);
+                // TODO: Another silent failure?
+                if (!isNumber(value)) {
+                    return mom;
+                }
+            }
+        }
+
+        dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));
+        mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
+        return mom;
+    }
+
+    function getSetMonth(value) {
+        if (value != null) {
+            setMonth(this, value);
+            hooks.updateOffset(this, true);
+            return this;
+        } else {
+            return get(this, 'Month');
+        }
+    }
+
+    function getDaysInMonth() {
+        return daysInMonth(this.year(), this.month());
+    }
+
+    function monthsShortRegex(isStrict) {
+        if (this._monthsParseExact) {
+            if (!hasOwnProp(this, '_monthsRegex')) {
+                computeMonthsParse.call(this);
+            }
+            if (isStrict) {
+                return this._monthsShortStrictRegex;
+            } else {
+                return this._monthsShortRegex;
+            }
+        } else {
+            if (!hasOwnProp(this, '_monthsShortRegex')) {
+                this._monthsShortRegex = defaultMonthsShortRegex;
+            }
+            return this._monthsShortStrictRegex && isStrict
+                ? this._monthsShortStrictRegex
+                : this._monthsShortRegex;
+        }
+    }
+
+    function monthsRegex(isStrict) {
+        if (this._monthsParseExact) {
+            if (!hasOwnProp(this, '_monthsRegex')) {
+                computeMonthsParse.call(this);
+            }
+            if (isStrict) {
+                return this._monthsStrictRegex;
+            } else {
+                return this._monthsRegex;
+            }
         } else {
-            return [now.getFullYear(), now.getMonth(), now.getDate()];
+            if (!hasOwnProp(this, '_monthsRegex')) {
+                this._monthsRegex = defaultMonthsRegex;
+            }
+            return this._monthsStrictRegex && isStrict
+                ? this._monthsStrictRegex
+                : this._monthsRegex;
+        }
+    }
+
+    function computeMonthsParse() {
+        function cmpLenRev(a, b) {
+            return b.length - a.length;
+        }
+
+        var shortPieces = [],
+            longPieces = [],
+            mixedPieces = [],
+            i,
+            mom;
+        for (i = 0; i < 12; i++) {
+            // make the regex if we don't have it already
+            mom = createUTC([2000, i]);
+            shortPieces.push(this.monthsShort(mom, ''));
+            longPieces.push(this.months(mom, ''));
+            mixedPieces.push(this.months(mom, ''));
+            mixedPieces.push(this.monthsShort(mom, ''));
+        }
+        // Sorting makes sure if one month (or abbr) is a prefix of another it
+        // will match the longer piece.
+        shortPieces.sort(cmpLenRev);
+        longPieces.sort(cmpLenRev);
+        mixedPieces.sort(cmpLenRev);
+        for (i = 0; i < 12; i++) {
+            shortPieces[i] = regexEscape(shortPieces[i]);
+            longPieces[i] = regexEscape(longPieces[i]);
+        }
+        for (i = 0; i < 24; i++) {
+            mixedPieces[i] = regexEscape(mixedPieces[i]);
         }
+
+        this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+        this._monthsShortRegex = this._monthsRegex;
+        this._monthsStrictRegex = new RegExp(
+            '^(' + longPieces.join('|') + ')',
+            'i'
+        );
+        this._monthsShortStrictRegex = new RegExp(
+            '^(' + shortPieces.join('|') + ')',
+            'i'
+        );
+    }
+
+    // FORMATTING
+
+    addFormatToken('Y', 0, 0, function () {
+        var y = this.year();
+        return y <= 9999 ? zeroFill(y, 4) : '+' + y;
+    });
+
+    addFormatToken(0, ['YY', 2], 0, function () {
+        return this.year() % 100;
+    });
+
+    addFormatToken(0, ['YYYY', 4], 0, 'year');
+    addFormatToken(0, ['YYYYY', 5], 0, 'year');
+    addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');
+
+    // ALIASES
+
+    addUnitAlias('year', 'y');
+
+    // PRIORITIES
+
+    addUnitPriority('year', 1);
+
+    // PARSING
+
+    addRegexToken('Y', matchSigned);
+    addRegexToken('YY', match1to2, match2);
+    addRegexToken('YYYY', match1to4, match4);
+    addRegexToken('YYYYY', match1to6, match6);
+    addRegexToken('YYYYYY', match1to6, match6);
+
+    addParseToken(['YYYYY', 'YYYYYY'], YEAR);
+    addParseToken('YYYY', function (input, array) {
+        array[YEAR] =
+            input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input);
+    });
+    addParseToken('YY', function (input, array) {
+        array[YEAR] = hooks.parseTwoDigitYear(input);
+    });
+    addParseToken('Y', function (input, array) {
+        array[YEAR] = parseInt(input, 10);
+    });
+
+    // HELPERS
+
+    function daysInYear(year) {
+        return isLeapYear(year) ? 366 : 365;
+    }
+
+    // HOOKS
+
+    hooks.parseTwoDigitYear = function (input) {
+        return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
+    };
+
+    // MOMENTS
+
+    var getSetYear = makeGetSet('FullYear', true);
+
+    function getIsLeapYear() {
+        return isLeapYear(this.year());
+    }
+
+    function createDate(y, m, d, h, M, s, ms) {
+        // can't just apply() to create a date:
+        // https://stackoverflow.com/q/181348
+        var date;
+        // the date constructor remaps years 0-99 to 1900-1999
+        if (y < 100 && y >= 0) {
+            // preserve leap years using a full 400 year cycle, then reset
+            date = new Date(y + 400, m, d, h, M, s, ms);
+            if (isFinite(date.getFullYear())) {
+                date.setFullYear(y);
+            }
+        } else {
+            date = new Date(y, m, d, h, M, s, ms);
+        }
+
+        return date;
+    }
+
+    function createUTCDate(y) {
+        var date, args;
+        // the Date.UTC function remaps years 0-99 to 1900-1999
+        if (y < 100 && y >= 0) {
+            args = Array.prototype.slice.call(arguments);
+            // preserve leap years using a full 400 year cycle, then reset
+            args[0] = y + 400;
+            date = new Date(Date.UTC.apply(null, args));
+            if (isFinite(date.getUTCFullYear())) {
+                date.setUTCFullYear(y);
+            }
+        } else {
+            date = new Date(Date.UTC.apply(null, arguments));
+        }
+
+        return date;
+    }
+
+    // start-of-first-week - start-of-year
+    function firstWeekOffset(year, dow, doy) {
+        var // first-week day -- which january is always in the first week (4 for iso, 1 for other)
+            fwd = 7 + dow - doy,
+            // first-week day local weekday -- which local weekday is fwd
+            fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;
+
+        return -fwdlw + fwd - 1;
+    }
+
+    // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
+    function dayOfYearFromWeeks(year, week, weekday, dow, doy) {
+        var localWeekday = (7 + weekday - dow) % 7,
+            weekOffset = firstWeekOffset(year, dow, doy),
+            dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,
+            resYear,
+            resDayOfYear;
+
+        if (dayOfYear <= 0) {
+            resYear = year - 1;
+            resDayOfYear = daysInYear(resYear) + dayOfYear;
+        } else if (dayOfYear > daysInYear(year)) {
+            resYear = year + 1;
+            resDayOfYear = dayOfYear - daysInYear(year);
+        } else {
+            resYear = year;
+            resDayOfYear = dayOfYear;
+        }
+
+        return {
+            year: resYear,
+            dayOfYear: resDayOfYear,
+        };
+    }
+
+    function weekOfYear(mom, dow, doy) {
+        var weekOffset = firstWeekOffset(mom.year(), dow, doy),
+            week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
+            resWeek,
+            resYear;
+
+        if (week < 1) {
+            resYear = mom.year() - 1;
+            resWeek = week + weeksInYear(resYear, dow, doy);
+        } else if (week > weeksInYear(mom.year(), dow, doy)) {
+            resWeek = week - weeksInYear(mom.year(), dow, doy);
+            resYear = mom.year() + 1;
+        } else {
+            resYear = mom.year();
+            resWeek = week;
+        }
+
+        return {
+            week: resWeek,
+            year: resYear,
+        };
+    }
+
+    function weeksInYear(year, dow, doy) {
+        var weekOffset = firstWeekOffset(year, dow, doy),
+            weekOffsetNext = firstWeekOffset(year + 1, dow, doy);
+        return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;
+    }
+
+    // FORMATTING
+
+    addFormatToken('w', ['ww', 2], 'wo', 'week');
+    addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');
+
+    // ALIASES
+
+    addUnitAlias('week', 'w');
+    addUnitAlias('isoWeek', 'W');
+
+    // PRIORITIES
+
+    addUnitPriority('week', 5);
+    addUnitPriority('isoWeek', 5);
+
+    // PARSING
+
+    addRegexToken('w', match1to2);
+    addRegexToken('ww', match1to2, match2);
+    addRegexToken('W', match1to2);
+    addRegexToken('WW', match1to2, match2);
+
+    addWeekParseToken(
+        ['w', 'ww', 'W', 'WW'],
+        function (input, week, config, token) {
+            week[token.substr(0, 1)] = toInt(input);
+        }
+    );
+
+    // HELPERS
+
+    // LOCALES
+
+    function localeWeek(mom) {
+        return weekOfYear(mom, this._week.dow, this._week.doy).week;
+    }
+
+    var defaultLocaleWeek = {
+        dow: 0, // Sunday is the first day of the week.
+        doy: 6, // The week that contains Jan 6th is the first week of the year.
+    };
+
+    function localeFirstDayOfWeek() {
+        return this._week.dow;
+    }
+
+    function localeFirstDayOfYear() {
+        return this._week.doy;
+    }
+
+    // MOMENTS
+
+    function getSetWeek(input) {
+        var week = this.localeData().week(this);
+        return input == null ? week : this.add((input - week) * 7, 'd');
+    }
+
+    function getSetISOWeek(input) {
+        var week = weekOfYear(this, 1, 4).week;
+        return input == null ? week : this.add((input - week) * 7, 'd');
+    }
+
+    // FORMATTING
+
+    addFormatToken('d', 0, 'do', 'day');
+
+    addFormatToken('dd', 0, 0, function (format) {
+        return this.localeData().weekdaysMin(this, format);
+    });
+
+    addFormatToken('ddd', 0, 0, function (format) {
+        return this.localeData().weekdaysShort(this, format);
+    });
+
+    addFormatToken('dddd', 0, 0, function (format) {
+        return this.localeData().weekdays(this, format);
+    });
+
+    addFormatToken('e', 0, 0, 'weekday');
+    addFormatToken('E', 0, 0, 'isoWeekday');
+
+    // ALIASES
+
+    addUnitAlias('day', 'd');
+    addUnitAlias('weekday', 'e');
+    addUnitAlias('isoWeekday', 'E');
+
+    // PRIORITY
+    addUnitPriority('day', 11);
+    addUnitPriority('weekday', 11);
+    addUnitPriority('isoWeekday', 11);
+
+    // PARSING
+
+    addRegexToken('d', match1to2);
+    addRegexToken('e', match1to2);
+    addRegexToken('E', match1to2);
+    addRegexToken('dd', function (isStrict, locale) {
+        return locale.weekdaysMinRegex(isStrict);
+    });
+    addRegexToken('ddd', function (isStrict, locale) {
+        return locale.weekdaysShortRegex(isStrict);
+    });
+    addRegexToken('dddd', function (isStrict, locale) {
+        return locale.weekdaysRegex(isStrict);
+    });
+
+    addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {
+        var weekday = config._locale.weekdaysParse(input, token, config._strict);
+        // if we didn't get a weekday name, mark the date as invalid
+        if (weekday != null) {
+            week.d = weekday;
+        } else {
+            getParsingFlags(config).invalidWeekday = input;
+        }
+    });
+
+    addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
+        week[token] = toInt(input);
+    });
+
+    // HELPERS
+
+    function parseWeekday(input, locale) {
+        if (typeof input !== 'string') {
+            return input;
+        }
+
+        if (!isNaN(input)) {
+            return parseInt(input, 10);
+        }
+
+        input = locale.weekdaysParse(input);
+        if (typeof input === 'number') {
+            return input;
+        }
+
+        return null;
+    }
+
+    function parseIsoWeekday(input, locale) {
+        if (typeof input === 'string') {
+            return locale.weekdaysParse(input) % 7 || 7;
+        }
+        return isNaN(input) ? null : input;
+    }
+
+    // LOCALES
+    function shiftWeekdays(ws, n) {
+        return ws.slice(n, 7).concat(ws.slice(0, n));
+    }
+
+    var defaultLocaleWeekdays =
+            'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),
+        defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
+        defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
+        defaultWeekdaysRegex = matchWord,
+        defaultWeekdaysShortRegex = matchWord,
+        defaultWeekdaysMinRegex = matchWord;
+
+    function localeWeekdays(m, format) {
+        var weekdays = isArray(this._weekdays)
+            ? this._weekdays
+            : this._weekdays[
+                  m && m !== true && this._weekdays.isFormat.test(format)
+                      ? 'format'
+                      : 'standalone'
+              ];
+        return m === true
+            ? shiftWeekdays(weekdays, this._week.dow)
+            : m
+            ? weekdays[m.day()]
+            : weekdays;
+    }
+
+    function localeWeekdaysShort(m) {
+        return m === true
+            ? shiftWeekdays(this._weekdaysShort, this._week.dow)
+            : m
+            ? this._weekdaysShort[m.day()]
+            : this._weekdaysShort;
+    }
+
+    function localeWeekdaysMin(m) {
+        return m === true
+            ? shiftWeekdays(this._weekdaysMin, this._week.dow)
+            : m
+            ? this._weekdaysMin[m.day()]
+            : this._weekdaysMin;
+    }
+
+    function handleStrictParse$1(weekdayName, format, strict) {
+        var i,
+            ii,
+            mom,
+            llc = weekdayName.toLocaleLowerCase();
+        if (!this._weekdaysParse) {
+            this._weekdaysParse = [];
+            this._shortWeekdaysParse = [];
+            this._minWeekdaysParse = [];
+
+            for (i = 0; i < 7; ++i) {
+                mom = createUTC([2000, 1]).day(i);
+                this._minWeekdaysParse[i] = this.weekdaysMin(
+                    mom,
+                    ''
+                ).toLocaleLowerCase();
+                this._shortWeekdaysParse[i] = this.weekdaysShort(
+                    mom,
+                    ''
+                ).toLocaleLowerCase();
+                this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();
+            }
+        }
+
+        if (strict) {
+            if (format === 'dddd') {
+                ii = indexOf.call(this._weekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else if (format === 'ddd') {
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            }
+        } else {
+            if (format === 'dddd') {
+                ii = indexOf.call(this._weekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else if (format === 'ddd') {
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._weekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._weekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            }
+        }
+    }
+
+    function localeWeekdaysParse(weekdayName, format, strict) {
+        var i, mom, regex;
+
+        if (this._weekdaysParseExact) {
+            return handleStrictParse$1.call(this, weekdayName, format, strict);
+        }
+
+        if (!this._weekdaysParse) {
+            this._weekdaysParse = [];
+            this._minWeekdaysParse = [];
+            this._shortWeekdaysParse = [];
+            this._fullWeekdaysParse = [];
+        }
+
+        for (i = 0; i < 7; i++) {
+            // make the regex if we don't have it already
+
+            mom = createUTC([2000, 1]).day(i);
+            if (strict && !this._fullWeekdaysParse[i]) {
+                this._fullWeekdaysParse[i] = new RegExp(
+                    '^' + this.weekdays(mom, '').replace('.', '\\.?') + '$',
+                    'i'
+                );
+                this._shortWeekdaysParse[i] = new RegExp(
+                    '^' + this.weekdaysShort(mom, '').replace('.', '\\.?') + '$',
+                    'i'
+                );
+                this._minWeekdaysParse[i] = new RegExp(
+                    '^' + this.weekdaysMin(mom, '').replace('.', '\\.?') + '$',
+                    'i'
+                );
+            }
+            if (!this._weekdaysParse[i]) {
+                regex =
+                    '^' +
+                    this.weekdays(mom, '') +
+                    '|^' +
+                    this.weekdaysShort(mom, '') +
+                    '|^' +
+                    this.weekdaysMin(mom, '');
+                this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
+            }
+            // test the regex
+            if (
+                strict &&
+                format === 'dddd' &&
+                this._fullWeekdaysParse[i].test(weekdayName)
+            ) {
+                return i;
+            } else if (
+                strict &&
+                format === 'ddd' &&
+                this._shortWeekdaysParse[i].test(weekdayName)
+            ) {
+                return i;
+            } else if (
+                strict &&
+                format === 'dd' &&
+                this._minWeekdaysParse[i].test(weekdayName)
+            ) {
+                return i;
+            } else if (!strict && this._weekdaysParse[i].test(weekdayName)) {
+                return i;
+            }
+        }
+    }
+
+    // MOMENTS
+
+    function getSetDayOfWeek(input) {
+        if (!this.isValid()) {
+            return input != null ? this : NaN;
+        }
+        var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
+        if (input != null) {
+            input = parseWeekday(input, this.localeData());
+            return this.add(input - day, 'd');
+        } else {
+            return day;
+        }
+    }
+
+    function getSetLocaleDayOfWeek(input) {
+        if (!this.isValid()) {
+            return input != null ? this : NaN;
+        }
+        var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
+        return input == null ? weekday : this.add(input - weekday, 'd');
+    }
+
+    function getSetISODayOfWeek(input) {
+        if (!this.isValid()) {
+            return input != null ? this : NaN;
+        }
+
+        // behaves the same as moment#day except
+        // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
+        // as a setter, sunday should belong to the previous week.
+
+        if (input != null) {
+            var weekday = parseIsoWeekday(input, this.localeData());
+            return this.day(this.day() % 7 ? weekday : weekday - 7);
+        } else {
+            return this.day() || 7;
+        }
+    }
+
+    function weekdaysRegex(isStrict) {
+        if (this._weekdaysParseExact) {
+            if (!hasOwnProp(this, '_weekdaysRegex')) {
+                computeWeekdaysParse.call(this);
+            }
+            if (isStrict) {
+                return this._weekdaysStrictRegex;
+            } else {
+                return this._weekdaysRegex;
+            }
+        } else {
+            if (!hasOwnProp(this, '_weekdaysRegex')) {
+                this._weekdaysRegex = defaultWeekdaysRegex;
+            }
+            return this._weekdaysStrictRegex && isStrict
+                ? this._weekdaysStrictRegex
+                : this._weekdaysRegex;
+        }
+    }
+
+    function weekdaysShortRegex(isStrict) {
+        if (this._weekdaysParseExact) {
+            if (!hasOwnProp(this, '_weekdaysRegex')) {
+                computeWeekdaysParse.call(this);
+            }
+            if (isStrict) {
+                return this._weekdaysShortStrictRegex;
+            } else {
+                return this._weekdaysShortRegex;
+            }
+        } else {
+            if (!hasOwnProp(this, '_weekdaysShortRegex')) {
+                this._weekdaysShortRegex = defaultWeekdaysShortRegex;
+            }
+            return this._weekdaysShortStrictRegex && isStrict
+                ? this._weekdaysShortStrictRegex
+                : this._weekdaysShortRegex;
+        }
+    }
+
+    function weekdaysMinRegex(isStrict) {
+        if (this._weekdaysParseExact) {
+            if (!hasOwnProp(this, '_weekdaysRegex')) {
+                computeWeekdaysParse.call(this);
+            }
+            if (isStrict) {
+                return this._weekdaysMinStrictRegex;
+            } else {
+                return this._weekdaysMinRegex;
+            }
+        } else {
+            if (!hasOwnProp(this, '_weekdaysMinRegex')) {
+                this._weekdaysMinRegex = defaultWeekdaysMinRegex;
+            }
+            return this._weekdaysMinStrictRegex && isStrict
+                ? this._weekdaysMinStrictRegex
+                : this._weekdaysMinRegex;
+        }
+    }
+
+    function computeWeekdaysParse() {
+        function cmpLenRev(a, b) {
+            return b.length - a.length;
+        }
+
+        var minPieces = [],
+            shortPieces = [],
+            longPieces = [],
+            mixedPieces = [],
+            i,
+            mom,
+            minp,
+            shortp,
+            longp;
+        for (i = 0; i < 7; i++) {
+            // make the regex if we don't have it already
+            mom = createUTC([2000, 1]).day(i);
+            minp = regexEscape(this.weekdaysMin(mom, ''));
+            shortp = regexEscape(this.weekdaysShort(mom, ''));
+            longp = regexEscape(this.weekdays(mom, ''));
+            minPieces.push(minp);
+            shortPieces.push(shortp);
+            longPieces.push(longp);
+            mixedPieces.push(minp);
+            mixedPieces.push(shortp);
+            mixedPieces.push(longp);
+        }
+        // Sorting makes sure if one weekday (or abbr) is a prefix of another it
+        // will match the longer piece.
+        minPieces.sort(cmpLenRev);
+        shortPieces.sort(cmpLenRev);
+        longPieces.sort(cmpLenRev);
+        mixedPieces.sort(cmpLenRev);
+
+        this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+        this._weekdaysShortRegex = this._weekdaysRegex;
+        this._weekdaysMinRegex = this._weekdaysRegex;
+
+        this._weekdaysStrictRegex = new RegExp(
+            '^(' + longPieces.join('|') + ')',
+            'i'
+        );
+        this._weekdaysShortStrictRegex = new RegExp(
+            '^(' + shortPieces.join('|') + ')',
+            'i'
+        );
+        this._weekdaysMinStrictRegex = new RegExp(
+            '^(' + minPieces.join('|') + ')',
+            'i'
+        );
+    }
+
+    // FORMATTING
+
+    function hFormat() {
+        return this.hours() % 12 || 12;
+    }
+
+    function kFormat() {
+        return this.hours() || 24;
+    }
+
+    addFormatToken('H', ['HH', 2], 0, 'hour');
+    addFormatToken('h', ['hh', 2], 0, hFormat);
+    addFormatToken('k', ['kk', 2], 0, kFormat);
+
+    addFormatToken('hmm', 0, 0, function () {
+        return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);
+    });
+
+    addFormatToken('hmmss', 0, 0, function () {
+        return (
+            '' +
+            hFormat.apply(this) +
+            zeroFill(this.minutes(), 2) +
+            zeroFill(this.seconds(), 2)
+        );
+    });
+
+    addFormatToken('Hmm', 0, 0, function () {
+        return '' + this.hours() + zeroFill(this.minutes(), 2);
+    });
+
+    addFormatToken('Hmmss', 0, 0, function () {
+        return (
+            '' +
+            this.hours() +
+            zeroFill(this.minutes(), 2) +
+            zeroFill(this.seconds(), 2)
+        );
+    });
+
+    function meridiem(token, lowercase) {
+        addFormatToken(token, 0, 0, function () {
+            return this.localeData().meridiem(
+                this.hours(),
+                this.minutes(),
+                lowercase
+            );
+        });
+    }
+
+    meridiem('a', true);
+    meridiem('A', false);
+
+    // ALIASES
+
+    addUnitAlias('hour', 'h');
+
+    // PRIORITY
+    addUnitPriority('hour', 13);
+
+    // PARSING
+
+    function matchMeridiem(isStrict, locale) {
+        return locale._meridiemParse;
+    }
+
+    addRegexToken('a', matchMeridiem);
+    addRegexToken('A', matchMeridiem);
+    addRegexToken('H', match1to2);
+    addRegexToken('h', match1to2);
+    addRegexToken('k', match1to2);
+    addRegexToken('HH', match1to2, match2);
+    addRegexToken('hh', match1to2, match2);
+    addRegexToken('kk', match1to2, match2);
+
+    addRegexToken('hmm', match3to4);
+    addRegexToken('hmmss', match5to6);
+    addRegexToken('Hmm', match3to4);
+    addRegexToken('Hmmss', match5to6);
+
+    addParseToken(['H', 'HH'], HOUR);
+    addParseToken(['k', 'kk'], function (input, array, config) {
+        var kInput = toInt(input);
+        array[HOUR] = kInput === 24 ? 0 : kInput;
+    });
+    addParseToken(['a', 'A'], function (input, array, config) {
+        config._isPm = config._locale.isPM(input);
+        config._meridiem = input;
+    });
+    addParseToken(['h', 'hh'], function (input, array, config) {
+        array[HOUR] = toInt(input);
+        getParsingFlags(config).bigHour = true;
+    });
+    addParseToken('hmm', function (input, array, config) {
+        var pos = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos));
+        array[MINUTE] = toInt(input.substr(pos));
+        getParsingFlags(config).bigHour = true;
+    });
+    addParseToken('hmmss', function (input, array, config) {
+        var pos1 = input.length - 4,
+            pos2 = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos1));
+        array[MINUTE] = toInt(input.substr(pos1, 2));
+        array[SECOND] = toInt(input.substr(pos2));
+        getParsingFlags(config).bigHour = true;
+    });
+    addParseToken('Hmm', function (input, array, config) {
+        var pos = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos));
+        array[MINUTE] = toInt(input.substr(pos));
+    });
+    addParseToken('Hmmss', function (input, array, config) {
+        var pos1 = input.length - 4,
+            pos2 = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos1));
+        array[MINUTE] = toInt(input.substr(pos1, 2));
+        array[SECOND] = toInt(input.substr(pos2));
+    });
+
+    // LOCALES
+
+    function localeIsPM(input) {
+        // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
+        // Using charAt should be more compatible.
+        return (input + '').toLowerCase().charAt(0) === 'p';
+    }
+
+    var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i,
+        // Setting the hour should keep the time, because the user explicitly
+        // specified which hour they want. So trying to maintain the same hour (in
+        // a new timezone) makes sense. Adding/subtracting hours does not follow
+        // this rule.
+        getSetHour = makeGetSet('Hours', true);
+
+    function localeMeridiem(hours, minutes, isLower) {
+        if (hours > 11) {
+            return isLower ? 'pm' : 'PM';
+        } else {
+            return isLower ? 'am' : 'AM';
+        }
+    }
+
+    var baseConfig = {
+        calendar: defaultCalendar,
+        longDateFormat: defaultLongDateFormat,
+        invalidDate: defaultInvalidDate,
+        ordinal: defaultOrdinal,
+        dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse,
+        relativeTime: defaultRelativeTime,
+
+        months: defaultLocaleMonths,
+        monthsShort: defaultLocaleMonthsShort,
+
+        week: defaultLocaleWeek,
+
+        weekdays: defaultLocaleWeekdays,
+        weekdaysMin: defaultLocaleWeekdaysMin,
+        weekdaysShort: defaultLocaleWeekdaysShort,
+
+        meridiemParse: defaultLocaleMeridiemParse,
+    };
+
+    // internal storage for locale config files
+    var locales = {},
+        localeFamilies = {},
+        globalLocale;
+
+    function commonPrefix(arr1, arr2) {
+        var i,
+            minl = Math.min(arr1.length, arr2.length);
+        for (i = 0; i < minl; i += 1) {
+            if (arr1[i] !== arr2[i]) {
+                return i;
+            }
+        }
+        return minl;
+    }
+
+    function normalizeLocale(key) {
+        return key ? key.toLowerCase().replace('_', '-') : key;
+    }
+
+    // pick the locale from the array
+    // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
+    // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
+    function chooseLocale(names) {
+        var i = 0,
+            j,
+            next,
+            locale,
+            split;
+
+        while (i < names.length) {
+            split = normalizeLocale(names[i]).split('-');
+            j = split.length;
+            next = normalizeLocale(names[i + 1]);
+            next = next ? next.split('-') : null;
+            while (j > 0) {
+                locale = loadLocale(split.slice(0, j).join('-'));
+                if (locale) {
+                    return locale;
+                }
+                if (
+                    next &&
+                    next.length >= j &&
+                    commonPrefix(split, next) >= j - 1
+                ) {
+                    //the next array item is better than a shallower substring of this one
+                    break;
+                }
+                j--;
+            }
+            i++;
+        }
+        return globalLocale;
+    }
+
+    function isLocaleNameSane(name) {
+        // Prevent names that look like filesystem paths, i.e contain '/' or '\'
+        return name.match('^[^/\\\\]*$') != null;
+    }
+
+    function loadLocale(name) {
+        var oldLocale = null,
+            aliasedRequire;
+        // TODO: Find a better way to register and load all the locales in Node
+        if (
+            locales[name] === undefined &&
+            typeof module !== 'undefined' &&
+            module &&
+            module.exports &&
+            isLocaleNameSane(name)
+        ) {
+            try {
+                oldLocale = globalLocale._abbr;
+                aliasedRequire = require;
+                aliasedRequire('./locale/' + name);
+                getSetGlobalLocale(oldLocale);
+            } catch (e) {
+                // mark as not found to avoid repeating expensive file require call causing high CPU
+                // when trying to find en-US, en_US, en-us for every format call
+                locales[name] = null; // null means not found
+            }
+        }
+        return locales[name];
+    }
+
+    // This function will load locale and then set the global locale.  If
+    // no arguments are passed in, it will simply return the current global
+    // locale key.
+    function getSetGlobalLocale(key, values) {
+        var data;
+        if (key) {
+            if (isUndefined(values)) {
+                data = getLocale(key);
+            } else {
+                data = defineLocale(key, values);
+            }
+
+            if (data) {
+                // moment.duration._locale = moment._locale = data;
+                globalLocale = data;
+            } else {
+                if (typeof console !== 'undefined' && console.warn) {
+                    //warn user if arguments are passed but the locale could not be set
+                    console.warn(
+                        'Locale ' + key + ' not found. Did you forget to load it?'
+                    );
+                }
+            }
+        }
+
+        return globalLocale._abbr;
+    }
+
+    function defineLocale(name, config) {
+        if (config !== null) {
+            var locale,
+                parentConfig = baseConfig;
+            config.abbr = name;
+            if (locales[name] != null) {
+                deprecateSimple(
+                    'defineLocaleOverride',
+                    'use moment.updateLocale(localeName, config) to change ' +
+                        'an existing locale. moment.defineLocale(localeName, ' +
+                        'config) should only be used for creating a new locale ' +
+                        'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.'
+                );
+                parentConfig = locales[name]._config;
+            } else if (config.parentLocale != null) {
+                if (locales[config.parentLocale] != null) {
+                    parentConfig = locales[config.parentLocale]._config;
+                } else {
+                    locale = loadLocale(config.parentLocale);
+                    if (locale != null) {
+                        parentConfig = locale._config;
+                    } else {
+                        if (!localeFamilies[config.parentLocale]) {
+                            localeFamilies[config.parentLocale] = [];
+                        }
+                        localeFamilies[config.parentLocale].push({
+                            name: name,
+                            config: config,
+                        });
+                        return null;
+                    }
+                }
+            }
+            locales[name] = new Locale(mergeConfigs(parentConfig, config));
+
+            if (localeFamilies[name]) {
+                localeFamilies[name].forEach(function (x) {
+                    defineLocale(x.name, x.config);
+                });
+            }
+
+            // backwards compat for now: also set the locale
+            // make sure we set the locale AFTER all child locales have been
+            // created, so we won't end up with the child locale set.
+            getSetGlobalLocale(name);
+
+            return locales[name];
+        } else {
+            // useful for testing
+            delete locales[name];
+            return null;
+        }
+    }
+
+    function updateLocale(name, config) {
+        if (config != null) {
+            var locale,
+                tmpLocale,
+                parentConfig = baseConfig;
+
+            if (locales[name] != null && locales[name].parentLocale != null) {
+                // Update existing child locale in-place to avoid memory-leaks
+                locales[name].set(mergeConfigs(locales[name]._config, config));
+            } else {
+                // MERGE
+                tmpLocale = loadLocale(name);
+                if (tmpLocale != null) {
+                    parentConfig = tmpLocale._config;
+                }
+                config = mergeConfigs(parentConfig, config);
+                if (tmpLocale == null) {
+                    // updateLocale is called for creating a new locale
+                    // Set abbr so it will have a name (getters return
+                    // undefined otherwise).
+                    config.abbr = name;
+                }
+                locale = new Locale(config);
+                locale.parentLocale = locales[name];
+                locales[name] = locale;
+            }
+
+            // backwards compat for now: also set the locale
+            getSetGlobalLocale(name);
+        } else {
+            // pass null for config to unupdate, useful for tests
+            if (locales[name] != null) {
+                if (locales[name].parentLocale != null) {
+                    locales[name] = locales[name].parentLocale;
+                    if (name === getSetGlobalLocale()) {
+                        getSetGlobalLocale(name);
+                    }
+                } else if (locales[name] != null) {
+                    delete locales[name];
+                }
+            }
+        }
+        return locales[name];
+    }
+
+    // returns locale data
+    function getLocale(key) {
+        var locale;
+
+        if (key && key._locale && key._locale._abbr) {
+            key = key._locale._abbr;
+        }
+
+        if (!key) {
+            return globalLocale;
+        }
+
+        if (!isArray(key)) {
+            //short-circuit everything else
+            locale = loadLocale(key);
+            if (locale) {
+                return locale;
+            }
+            key = [key];
+        }
+
+        return chooseLocale(key);
+    }
+
+    function listLocales() {
+        return keys(locales);
+    }
+
+    function checkOverflow(m) {
+        var overflow,
+            a = m._a;
+
+        if (a && getParsingFlags(m).overflow === -2) {
+            overflow =
+                a[MONTH] < 0 || a[MONTH] > 11
+                    ? MONTH
+                    : a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH])
+                    ? DATE
+                    : a[HOUR] < 0 ||
+                      a[HOUR] > 24 ||
+                      (a[HOUR] === 24 &&
+                          (a[MINUTE] !== 0 ||
+                              a[SECOND] !== 0 ||
+                              a[MILLISECOND] !== 0))
+                    ? HOUR
+                    : a[MINUTE] < 0 || a[MINUTE] > 59
+                    ? MINUTE
+                    : a[SECOND] < 0 || a[SECOND] > 59
+                    ? SECOND
+                    : a[MILLISECOND] < 0 || a[MILLISECOND] > 999
+                    ? MILLISECOND
+                    : -1;
+
+            if (
+                getParsingFlags(m)._overflowDayOfYear &&
+                (overflow < YEAR || overflow > DATE)
+            ) {
+                overflow = DATE;
+            }
+            if (getParsingFlags(m)._overflowWeeks && overflow === -1) {
+                overflow = WEEK;
+            }
+            if (getParsingFlags(m)._overflowWeekday && overflow === -1) {
+                overflow = WEEKDAY;
+            }
+
+            getParsingFlags(m).overflow = overflow;
+        }
+
+        return m;
+    }
+
+    // iso 8601 regex
+    // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
+    var extendedIsoRegex =
+            /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,
+        basicIsoRegex =
+            /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,
+        tzRegex = /Z|[+-]\d\d(?::?\d\d)?/,
+        isoDates = [
+            ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/],
+            ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/],
+            ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/],
+            ['GGGG-[W]WW', /\d{4}-W\d\d/, false],
+            ['YYYY-DDD', /\d{4}-\d{3}/],
+            ['YYYY-MM', /\d{4}-\d\d/, false],
+            ['YYYYYYMMDD', /[+-]\d{10}/],
+            ['YYYYMMDD', /\d{8}/],
+            ['GGGG[W]WWE', /\d{4}W\d{3}/],
+            ['GGGG[W]WW', /\d{4}W\d{2}/, false],
+            ['YYYYDDD', /\d{7}/],
+            ['YYYYMM', /\d{6}/, false],
+            ['YYYY', /\d{4}/, false],
+        ],
+        // iso time formats and regexes
+        isoTimes = [
+            ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/],
+            ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/],
+            ['HH:mm:ss', /\d\d:\d\d:\d\d/],
+            ['HH:mm', /\d\d:\d\d/],
+            ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/],
+            ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/],
+            ['HHmmss', /\d\d\d\d\d\d/],
+            ['HHmm', /\d\d\d\d/],
+            ['HH', /\d\d/],
+        ],
+        aspNetJsonRegex = /^\/?Date\((-?\d+)/i,
+        // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3
+        rfc2822 =
+            /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/,
+        obsOffsets = {
+            UT: 0,
+            GMT: 0,
+            EDT: -4 * 60,
+            EST: -5 * 60,
+            CDT: -5 * 60,
+            CST: -6 * 60,
+            MDT: -6 * 60,
+            MST: -7 * 60,
+            PDT: -7 * 60,
+            PST: -8 * 60,
+        };
+
+    // date from iso format
+    function configFromISO(config) {
+        var i,
+            l,
+            string = config._i,
+            match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),
+            allowTime,
+            dateFormat,
+            timeFormat,
+            tzFormat,
+            isoDatesLen = isoDates.length,
+            isoTimesLen = isoTimes.length;
+
+        if (match) {
+            getParsingFlags(config).iso = true;
+            for (i = 0, l = isoDatesLen; i < l; i++) {
+                if (isoDates[i][1].exec(match[1])) {
+                    dateFormat = isoDates[i][0];
+                    allowTime = isoDates[i][2] !== false;
+                    break;
+                }
+            }
+            if (dateFormat == null) {
+                config._isValid = false;
+                return;
+            }
+            if (match[3]) {
+                for (i = 0, l = isoTimesLen; i < l; i++) {
+                    if (isoTimes[i][1].exec(match[3])) {
+                        // match[2] should be 'T' or space
+                        timeFormat = (match[2] || ' ') + isoTimes[i][0];
+                        break;
+                    }
+                }
+                if (timeFormat == null) {
+                    config._isValid = false;
+                    return;
+                }
+            }
+            if (!allowTime && timeFormat != null) {
+                config._isValid = false;
+                return;
+            }
+            if (match[4]) {
+                if (tzRegex.exec(match[4])) {
+                    tzFormat = 'Z';
+                } else {
+                    config._isValid = false;
+                    return;
+                }
+            }
+            config._f = dateFormat + (timeFormat || '') + (tzFormat || '');
+            configFromStringAndFormat(config);
+        } else {
+            config._isValid = false;
+        }
+    }
+
+    function extractFromRFC2822Strings(
+        yearStr,
+        monthStr,
+        dayStr,
+        hourStr,
+        minuteStr,
+        secondStr
+    ) {
+        var result = [
+            untruncateYear(yearStr),
+            defaultLocaleMonthsShort.indexOf(monthStr),
+            parseInt(dayStr, 10),
+            parseInt(hourStr, 10),
+            parseInt(minuteStr, 10),
+        ];
+
+        if (secondStr) {
+            result.push(parseInt(secondStr, 10));
+        }
+
+        return result;
+    }
+
+    function untruncateYear(yearStr) {
+        var year = parseInt(yearStr, 10);
+        if (year <= 49) {
+            return 2000 + year;
+        } else if (year <= 999) {
+            return 1900 + year;
+        }
+        return year;
+    }
+
+    function preprocessRFC2822(s) {
+        // Remove comments and folding whitespace and replace multiple-spaces with a single space
+        return s
+            .replace(/\([^)]*\)|[\n\t]/g, ' ')
+            .replace(/(\s\s+)/g, ' ')
+            .replace(/^\s\s*/, '')
+            .replace(/\s\s*$/, '');
+    }
+
+    function checkWeekday(weekdayStr, parsedInput, config) {
+        if (weekdayStr) {
+            // TODO: Replace the vanilla JS Date object with an independent day-of-week check.
+            var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr),
+                weekdayActual = new Date(
+                    parsedInput[0],
+                    parsedInput[1],
+                    parsedInput[2]
+                ).getDay();
+            if (weekdayProvided !== weekdayActual) {
+                getParsingFlags(config).weekdayMismatch = true;
+                config._isValid = false;
+                return false;
+            }
+        }
+        return true;
+    }
+
+    function calculateOffset(obsOffset, militaryOffset, numOffset) {
+        if (obsOffset) {
+            return obsOffsets[obsOffset];
+        } else if (militaryOffset) {
+            // the only allowed military tz is Z
+            return 0;
+        } else {
+            var hm = parseInt(numOffset, 10),
+                m = hm % 100,
+                h = (hm - m) / 100;
+            return h * 60 + m;
+        }
+    }
+
+    // date and time from ref 2822 format
+    function configFromRFC2822(config) {
+        var match = rfc2822.exec(preprocessRFC2822(config._i)),
+            parsedArray;
+        if (match) {
+            parsedArray = extractFromRFC2822Strings(
+                match[4],
+                match[3],
+                match[2],
+                match[5],
+                match[6],
+                match[7]
+            );
+            if (!checkWeekday(match[1], parsedArray, config)) {
+                return;
+            }
+
+            config._a = parsedArray;
+            config._tzm = calculateOffset(match[8], match[9], match[10]);
+
+            config._d = createUTCDate.apply(null, config._a);
+            config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
+
+            getParsingFlags(config).rfc2822 = true;
+        } else {
+            config._isValid = false;
+        }
+    }
+
+    // date from 1) ASP.NET, 2) ISO, 3) RFC 2822 formats, or 4) optional fallback if parsing isn't strict
+    function configFromString(config) {
+        var matched = aspNetJsonRegex.exec(config._i);
+        if (matched !== null) {
+            config._d = new Date(+matched[1]);
+            return;
+        }
+
+        configFromISO(config);
+        if (config._isValid === false) {
+            delete config._isValid;
+        } else {
+            return;
+        }
+
+        configFromRFC2822(config);
+        if (config._isValid === false) {
+            delete config._isValid;
+        } else {
+            return;
+        }
+
+        if (config._strict) {
+            config._isValid = false;
+        } else {
+            // Final attempt, use Input Fallback
+            hooks.createFromInputFallback(config);
+        }
+    }
+
+    hooks.createFromInputFallback = deprecate(
+        'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' +
+            'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' +
+            'discouraged. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.',
+        function (config) {
+            config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
+        }
+    );
+
+    // Pick the first defined of two or three arguments.
+    function defaults(a, b, c) {
+        if (a != null) {
+            return a;
+        }
+        if (b != null) {
+            return b;
+        }
+        return c;
+    }
+
+    function currentDateArray(config) {
+        // hooks is actually the exported moment object
+        var nowValue = new Date(hooks.now());
+        if (config._useUTC) {
+            return [
+                nowValue.getUTCFullYear(),
+                nowValue.getUTCMonth(),
+                nowValue.getUTCDate(),
+            ];
+        }
+        return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];
+    }
+
+    // convert an array to a date.
+    // the array should mirror the parameters below
+    // note: all values past the year are optional and will default to the lowest possible value.
+    // [year, month, day , hour, minute, second, millisecond]
+    function configFromArray(config) {
+        var i,
+            date,
+            input = [],
+            currentDate,
+            expectedWeekday,
+            yearToUse;
+
+        if (config._d) {
+            return;
+        }
+
+        currentDate = currentDateArray(config);
+
+        //compute day of the year from weeks and weekdays
+        if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
+            dayOfYearFromWeekInfo(config);
+        }
+
+        //if the day of the year is set, figure out what it is
+        if (config._dayOfYear != null) {
+            yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);
+
+            if (
+                config._dayOfYear > daysInYear(yearToUse) ||
+                config._dayOfYear === 0
+            ) {
+                getParsingFlags(config)._overflowDayOfYear = true;
+            }
+
+            date = createUTCDate(yearToUse, 0, config._dayOfYear);
+            config._a[MONTH] = date.getUTCMonth();
+            config._a[DATE] = date.getUTCDate();
+        }
+
+        // Default to current date.
+        // * if no year, month, day of month are given, default to today
+        // * if day of month is given, default month and year
+        // * if month is given, default only year
+        // * if year is given, don't default anything
+        for (i = 0; i < 3 && config._a[i] == null; ++i) {
+            config._a[i] = input[i] = currentDate[i];
+        }
+
+        // Zero out whatever was not defaulted, including time
+        for (; i < 7; i++) {
+            config._a[i] = input[i] =
+                config._a[i] == null ? (i === 2 ? 1 : 0) : config._a[i];
+        }
+
+        // Check for 24:00:00.000
+        if (
+            config._a[HOUR] === 24 &&
+            config._a[MINUTE] === 0 &&
+            config._a[SECOND] === 0 &&
+            config._a[MILLISECOND] === 0
+        ) {
+            config._nextDay = true;
+            config._a[HOUR] = 0;
+        }
+
+        config._d = (config._useUTC ? createUTCDate : createDate).apply(
+            null,
+            input
+        );
+        expectedWeekday = config._useUTC
+            ? config._d.getUTCDay()
+            : config._d.getDay();
+
+        // Apply timezone offset from input. The actual utcOffset can be changed
+        // with parseZone.
+        if (config._tzm != null) {
+            config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
+        }
+
+        if (config._nextDay) {
+            config._a[HOUR] = 24;
+        }
+
+        // check for mismatching day of week
+        if (
+            config._w &&
+            typeof config._w.d !== 'undefined' &&
+            config._w.d !== expectedWeekday
+        ) {
+            getParsingFlags(config).weekdayMismatch = true;
+        }
+    }
+
+    function dayOfYearFromWeekInfo(config) {
+        var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow, curWeek;
+
+        w = config._w;
+        if (w.GG != null || w.W != null || w.E != null) {
+            dow = 1;
+            doy = 4;
+
+            // TODO: We need to take the current isoWeekYear, but that depends on
+            // how we interpret now (local, utc, fixed offset). So create
+            // a now version of current config (take local/utc/offset flags, and
+            // create now).
+            weekYear = defaults(
+                w.GG,
+                config._a[YEAR],
+                weekOfYear(createLocal(), 1, 4).year
+            );
+            week = defaults(w.W, 1);
+            weekday = defaults(w.E, 1);
+            if (weekday < 1 || weekday > 7) {
+                weekdayOverflow = true;
+            }
+        } else {
+            dow = config._locale._week.dow;
+            doy = config._locale._week.doy;
+
+            curWeek = weekOfYear(createLocal(), dow, doy);
+
+            weekYear = defaults(w.gg, config._a[YEAR], curWeek.year);
+
+            // Default to current week.
+            week = defaults(w.w, curWeek.week);
+
+            if (w.d != null) {
+                // weekday -- low day numbers are considered next week
+                weekday = w.d;
+                if (weekday < 0 || weekday > 6) {
+                    weekdayOverflow = true;
+                }
+            } else if (w.e != null) {
+                // local weekday -- counting starts from beginning of week
+                weekday = w.e + dow;
+                if (w.e < 0 || w.e > 6) {
+                    weekdayOverflow = true;
+                }
+            } else {
+                // default to beginning of week
+                weekday = dow;
+            }
+        }
+        if (week < 1 || week > weeksInYear(weekYear, dow, doy)) {
+            getParsingFlags(config)._overflowWeeks = true;
+        } else if (weekdayOverflow != null) {
+            getParsingFlags(config)._overflowWeekday = true;
+        } else {
+            temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy);
+            config._a[YEAR] = temp.year;
+            config._dayOfYear = temp.dayOfYear;
+        }
+    }
+
+    // constant that refers to the ISO standard
+    hooks.ISO_8601 = function () {};
+
+    // constant that refers to the RFC 2822 form
+    hooks.RFC_2822 = function () {};
+
+    // date from string and format string
+    function configFromStringAndFormat(config) {
+        // TODO: Move this to another part of the creation flow to prevent circular deps
+        if (config._f === hooks.ISO_8601) {
+            configFromISO(config);
+            return;
+        }
+        if (config._f === hooks.RFC_2822) {
+            configFromRFC2822(config);
+            return;
+        }
+        config._a = [];
+        getParsingFlags(config).empty = true;
+
+        // This array is used to make a Date, either with `new Date` or `Date.UTC`
+        var string = '' + config._i,
+            i,
+            parsedInput,
+            tokens,
+            token,
+            skipped,
+            stringLength = string.length,
+            totalParsedInputLength = 0,
+            era,
+            tokenLen;
+
+        tokens =
+            expandFormat(config._f, config._locale).match(formattingTokens) || [];
+        tokenLen = tokens.length;
+        for (i = 0; i < tokenLen; i++) {
+            token = tokens[i];
+            parsedInput = (string.match(getParseRegexForToken(token, config)) ||
+                [])[0];
+            if (parsedInput) {
+                skipped = string.substr(0, string.indexOf(parsedInput));
+                if (skipped.length > 0) {
+                    getParsingFlags(config).unusedInput.push(skipped);
+                }
+                string = string.slice(
+                    string.indexOf(parsedInput) + parsedInput.length
+                );
+                totalParsedInputLength += parsedInput.length;
+            }
+            // don't parse if it's not a known token
+            if (formatTokenFunctions[token]) {
+                if (parsedInput) {
+                    getParsingFlags(config).empty = false;
+                } else {
+                    getParsingFlags(config).unusedTokens.push(token);
+                }
+                addTimeToArrayFromToken(token, parsedInput, config);
+            } else if (config._strict && !parsedInput) {
+                getParsingFlags(config).unusedTokens.push(token);
+            }
+        }
+
+        // add remaining unparsed input length to the string
+        getParsingFlags(config).charsLeftOver =
+            stringLength - totalParsedInputLength;
+        if (string.length > 0) {
+            getParsingFlags(config).unusedInput.push(string);
+        }
+
+        // clear _12h flag if hour is <= 12
+        if (
+            config._a[HOUR] <= 12 &&
+            getParsingFlags(config).bigHour === true &&
+            config._a[HOUR] > 0
+        ) {
+            getParsingFlags(config).bigHour = undefined;
+        }
+
+        getParsingFlags(config).parsedDateParts = config._a.slice(0);
+        getParsingFlags(config).meridiem = config._meridiem;
+        // handle meridiem
+        config._a[HOUR] = meridiemFixWrap(
+            config._locale,
+            config._a[HOUR],
+            config._meridiem
+        );
+
+        // handle era
+        era = getParsingFlags(config).era;
+        if (era !== null) {
+            config._a[YEAR] = config._locale.erasConvertYear(era, config._a[YEAR]);
+        }
+
+        configFromArray(config);
+        checkOverflow(config);
+    }
+
+    function meridiemFixWrap(locale, hour, meridiem) {
+        var isPm;
+
+        if (meridiem == null) {
+            // nothing to do
+            return hour;
+        }
+        if (locale.meridiemHour != null) {
+            return locale.meridiemHour(hour, meridiem);
+        } else if (locale.isPM != null) {
+            // Fallback
+            isPm = locale.isPM(meridiem);
+            if (isPm && hour < 12) {
+                hour += 12;
+            }
+            if (!isPm && hour === 12) {
+                hour = 0;
+            }
+            return hour;
+        } else {
+            // this is not supposed to happen
+            return hour;
+        }
+    }
+
+    // date from string and array of format strings
+    function configFromStringAndArray(config) {
+        var tempConfig,
+            bestMoment,
+            scoreToBeat,
+            i,
+            currentScore,
+            validFormatFound,
+            bestFormatIsValid = false,
+            configfLen = config._f.length;
+
+        if (configfLen === 0) {
+            getParsingFlags(config).invalidFormat = true;
+            config._d = new Date(NaN);
+            return;
+        }
+
+        for (i = 0; i < configfLen; i++) {
+            currentScore = 0;
+            validFormatFound = false;
+            tempConfig = copyConfig({}, config);
+            if (config._useUTC != null) {
+                tempConfig._useUTC = config._useUTC;
+            }
+            tempConfig._f = config._f[i];
+            configFromStringAndFormat(tempConfig);
+
+            if (isValid(tempConfig)) {
+                validFormatFound = true;
+            }
+
+            // if there is any input that was not parsed add a penalty for that format
+            currentScore += getParsingFlags(tempConfig).charsLeftOver;
+
+            //or tokens
+            currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;
+
+            getParsingFlags(tempConfig).score = currentScore;
+
+            if (!bestFormatIsValid) {
+                if (
+                    scoreToBeat == null ||
+                    currentScore < scoreToBeat ||
+                    validFormatFound
+                ) {
+                    scoreToBeat = currentScore;
+                    bestMoment = tempConfig;
+                    if (validFormatFound) {
+                        bestFormatIsValid = true;
+                    }
+                }
+            } else {
+                if (currentScore < scoreToBeat) {
+                    scoreToBeat = currentScore;
+                    bestMoment = tempConfig;
+                }
+            }
+        }
+
+        extend(config, bestMoment || tempConfig);
+    }
+
+    function configFromObject(config) {
+        if (config._d) {
+            return;
+        }
+
+        var i = normalizeObjectUnits(config._i),
+            dayOrDate = i.day === undefined ? i.date : i.day;
+        config._a = map(
+            [i.year, i.month, dayOrDate, i.hour, i.minute, i.second, i.millisecond],
+            function (obj) {
+                return obj && parseInt(obj, 10);
+            }
+        );
+
+        configFromArray(config);
+    }
+
+    function createFromConfig(config) {
+        var res = new Moment(checkOverflow(prepareConfig(config)));
+        if (res._nextDay) {
+            // Adding is smart enough around DST
+            res.add(1, 'd');
+            res._nextDay = undefined;
+        }
+
+        return res;
+    }
+
+    function prepareConfig(config) {
+        var input = config._i,
+            format = config._f;
+
+        config._locale = config._locale || getLocale(config._l);
+
+        if (input === null || (format === undefined && input === '')) {
+            return createInvalid({ nullInput: true });
+        }
+
+        if (typeof input === 'string') {
+            config._i = input = config._locale.preparse(input);
+        }
+
+        if (isMoment(input)) {
+            return new Moment(checkOverflow(input));
+        } else if (isDate(input)) {
+            config._d = input;
+        } else if (isArray(format)) {
+            configFromStringAndArray(config);
+        } else if (format) {
+            configFromStringAndFormat(config);
+        } else {
+            configFromInput(config);
+        }
+
+        if (!isValid(config)) {
+            config._d = null;
+        }
+
+        return config;
+    }
+
+    function configFromInput(config) {
+        var input = config._i;
+        if (isUndefined(input)) {
+            config._d = new Date(hooks.now());
+        } else if (isDate(input)) {
+            config._d = new Date(input.valueOf());
+        } else if (typeof input === 'string') {
+            configFromString(config);
+        } else if (isArray(input)) {
+            config._a = map(input.slice(0), function (obj) {
+                return parseInt(obj, 10);
+            });
+            configFromArray(config);
+        } else if (isObject(input)) {
+            configFromObject(config);
+        } else if (isNumber(input)) {
+            // from milliseconds
+            config._d = new Date(input);
+        } else {
+            hooks.createFromInputFallback(config);
+        }
+    }
+
+    function createLocalOrUTC(input, format, locale, strict, isUTC) {
+        var c = {};
+
+        if (format === true || format === false) {
+            strict = format;
+            format = undefined;
+        }
+
+        if (locale === true || locale === false) {
+            strict = locale;
+            locale = undefined;
+        }
+
+        if (
+            (isObject(input) && isObjectEmpty(input)) ||
+            (isArray(input) && input.length === 0)
+        ) {
+            input = undefined;
+        }
+        // object construction must be done this way.
+        // https://github.com/moment/moment/issues/1423
+        c._isAMomentObject = true;
+        c._useUTC = c._isUTC = isUTC;
+        c._l = locale;
+        c._i = input;
+        c._f = format;
+        c._strict = strict;
+
+        return createFromConfig(c);
+    }
+
+    function createLocal(input, format, locale, strict) {
+        return createLocalOrUTC(input, format, locale, strict, false);
+    }
+
+    var prototypeMin = deprecate(
+            'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/',
+            function () {
+                var other = createLocal.apply(null, arguments);
+                if (this.isValid() && other.isValid()) {
+                    return other < this ? this : other;
+                } else {
+                    return createInvalid();
+                }
+            }
+        ),
+        prototypeMax = deprecate(
+            'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/',
+            function () {
+                var other = createLocal.apply(null, arguments);
+                if (this.isValid() && other.isValid()) {
+                    return other > this ? this : other;
+                } else {
+                    return createInvalid();
+                }
+            }
+        );
+
+    // Pick a moment m from moments so that m[fn](other) is true for all
+    // other. This relies on the function fn to be transitive.
+    //
+    // moments should either be an array of moment objects or an array, whose
+    // first element is an array of moment objects.
+    function pickBy(fn, moments) {
+        var res, i;
+        if (moments.length === 1 && isArray(moments[0])) {
+            moments = moments[0];
+        }
+        if (!moments.length) {
+            return createLocal();
+        }
+        res = moments[0];
+        for (i = 1; i < moments.length; ++i) {
+            if (!moments[i].isValid() || moments[i][fn](res)) {
+                res = moments[i];
+            }
+        }
+        return res;
+    }
+
+    // TODO: Use [].sort instead?
+    function min() {
+        var args = [].slice.call(arguments, 0);
+
+        return pickBy('isBefore', args);
+    }
+
+    function max() {
+        var args = [].slice.call(arguments, 0);
+
+        return pickBy('isAfter', args);
+    }
+
+    var now = function () {
+        return Date.now ? Date.now() : +new Date();
+    };
+
+    var ordering = [
+        'year',
+        'quarter',
+        'month',
+        'week',
+        'day',
+        'hour',
+        'minute',
+        'second',
+        'millisecond',
+    ];
+
+    function isDurationValid(m) {
+        var key,
+            unitHasDecimal = false,
+            i,
+            orderLen = ordering.length;
+        for (key in m) {
+            if (
+                hasOwnProp(m, key) &&
+                !(
+                    indexOf.call(ordering, key) !== -1 &&
+                    (m[key] == null || !isNaN(m[key]))
+                )
+            ) {
+                return false;
+            }
+        }
+
+        for (i = 0; i < orderLen; ++i) {
+            if (m[ordering[i]]) {
+                if (unitHasDecimal) {
+                    return false; // only allow non-integers for smallest unit
+                }
+                if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) {
+                    unitHasDecimal = true;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    function isValid$1() {
+        return this._isValid;
+    }
+
+    function createInvalid$1() {
+        return createDuration(NaN);
+    }
+
+    function Duration(duration) {
+        var normalizedInput = normalizeObjectUnits(duration),
+            years = normalizedInput.year || 0,
+            quarters = normalizedInput.quarter || 0,
+            months = normalizedInput.month || 0,
+            weeks = normalizedInput.week || normalizedInput.isoWeek || 0,
+            days = normalizedInput.day || 0,
+            hours = normalizedInput.hour || 0,
+            minutes = normalizedInput.minute || 0,
+            seconds = normalizedInput.second || 0,
+            milliseconds = normalizedInput.millisecond || 0;
+
+        this._isValid = isDurationValid(normalizedInput);
+
+        // representation for dateAddRemove
+        this._milliseconds =
+            +milliseconds +
+            seconds * 1e3 + // 1000
+            minutes * 6e4 + // 1000 * 60
+            hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978
+        // Because of dateAddRemove treats 24 hours as different from a
+        // day when working around DST, we need to store them separately
+        this._days = +days + weeks * 7;
+        // It is impossible to translate months into days without knowing
+        // which months you are are talking about, so we have to store
+        // it separately.
+        this._months = +months + quarters * 3 + years * 12;
+
+        this._data = {};
+
+        this._locale = getLocale();
+
+        this._bubble();
+    }
+
+    function isDuration(obj) {
+        return obj instanceof Duration;
+    }
+
+    function absRound(number) {
+        if (number < 0) {
+            return Math.round(-1 * number) * -1;
+        } else {
+            return Math.round(number);
+        }
+    }
+
+    // compare two arrays, return the number of differences
+    function compareArrays(array1, array2, dontConvert) {
+        var len = Math.min(array1.length, array2.length),
+            lengthDiff = Math.abs(array1.length - array2.length),
+            diffs = 0,
+            i;
+        for (i = 0; i < len; i++) {
+            if (
+                (dontConvert && array1[i] !== array2[i]) ||
+                (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))
+            ) {
+                diffs++;
+            }
+        }
+        return diffs + lengthDiff;
+    }
+
+    // FORMATTING
+
+    function offset(token, separator) {
+        addFormatToken(token, 0, 0, function () {
+            var offset = this.utcOffset(),
+                sign = '+';
+            if (offset < 0) {
+                offset = -offset;
+                sign = '-';
+            }
+            return (
+                sign +
+                zeroFill(~~(offset / 60), 2) +
+                separator +
+                zeroFill(~~offset % 60, 2)
+            );
+        });
+    }
+
+    offset('Z', ':');
+    offset('ZZ', '');
+
+    // PARSING
+
+    addRegexToken('Z', matchShortOffset);
+    addRegexToken('ZZ', matchShortOffset);
+    addParseToken(['Z', 'ZZ'], function (input, array, config) {
+        config._useUTC = true;
+        config._tzm = offsetFromString(matchShortOffset, input);
+    });
+
+    // HELPERS
+
+    // timezone chunker
+    // '+10:00' > ['10',  '00']
+    // '-1530'  > ['-15', '30']
+    var chunkOffset = /([\+\-]|\d\d)/gi;
+
+    function offsetFromString(matcher, string) {
+        var matches = (string || '').match(matcher),
+            chunk,
+            parts,
+            minutes;
+
+        if (matches === null) {
+            return null;
+        }
+
+        chunk = matches[matches.length - 1] || [];
+        parts = (chunk + '').match(chunkOffset) || ['-', 0, 0];
+        minutes = +(parts[1] * 60) + toInt(parts[2]);
+
+        return minutes === 0 ? 0 : parts[0] === '+' ? minutes : -minutes;
+    }
+
+    // Return a moment from input, that is local/utc/zone equivalent to model.
+    function cloneWithOffset(input, model) {
+        var res, diff;
+        if (model._isUTC) {
+            res = model.clone();
+            diff =
+                (isMoment(input) || isDate(input)
+                    ? input.valueOf()
+                    : createLocal(input).valueOf()) - res.valueOf();
+            // Use low-level api, because this fn is low-level api.
+            res._d.setTime(res._d.valueOf() + diff);
+            hooks.updateOffset(res, false);
+            return res;
+        } else {
+            return createLocal(input).local();
+        }
+    }
+
+    function getDateOffset(m) {
+        // On Firefox.24 Date#getTimezoneOffset returns a floating point.
+        // https://github.com/moment/moment/pull/1871
+        return -Math.round(m._d.getTimezoneOffset());
+    }
+
+    // HOOKS
+
+    // This function will be called whenever a moment is mutated.
+    // It is intended to keep the offset in sync with the timezone.
+    hooks.updateOffset = function () {};
+
+    // MOMENTS
+
+    // keepLocalTime = true means only change the timezone, without
+    // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
+    // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
+    // +0200, so we adjust the time as needed, to be valid.
+    //
+    // Keeping the time actually adds/subtracts (one hour)
+    // from the actual represented time. That is why we call updateOffset
+    // a second time. In case it wants us to change the offset again
+    // _changeInProgress == true case, then we have to adjust, because
+    // there is no such time in the given timezone.
+    function getSetOffset(input, keepLocalTime, keepMinutes) {
+        var offset = this._offset || 0,
+            localAdjust;
+        if (!this.isValid()) {
+            return input != null ? this : NaN;
+        }
+        if (input != null) {
+            if (typeof input === 'string') {
+                input = offsetFromString(matchShortOffset, input);
+                if (input === null) {
+                    return this;
+                }
+            } else if (Math.abs(input) < 16 && !keepMinutes) {
+                input = input * 60;
+            }
+            if (!this._isUTC && keepLocalTime) {
+                localAdjust = getDateOffset(this);
+            }
+            this._offset = input;
+            this._isUTC = true;
+            if (localAdjust != null) {
+                this.add(localAdjust, 'm');
+            }
+            if (offset !== input) {
+                if (!keepLocalTime || this._changeInProgress) {
+                    addSubtract(
+                        this,
+                        createDuration(input - offset, 'm'),
+                        1,
+                        false
+                    );
+                } else if (!this._changeInProgress) {
+                    this._changeInProgress = true;
+                    hooks.updateOffset(this, true);
+                    this._changeInProgress = null;
+                }
+            }
+            return this;
+        } else {
+            return this._isUTC ? offset : getDateOffset(this);
+        }
+    }
+
+    function getSetZone(input, keepLocalTime) {
+        if (input != null) {
+            if (typeof input !== 'string') {
+                input = -input;
+            }
+
+            this.utcOffset(input, keepLocalTime);
+
+            return this;
+        } else {
+            return -this.utcOffset();
+        }
+    }
+
+    function setOffsetToUTC(keepLocalTime) {
+        return this.utcOffset(0, keepLocalTime);
+    }
+
+    function setOffsetToLocal(keepLocalTime) {
+        if (this._isUTC) {
+            this.utcOffset(0, keepLocalTime);
+            this._isUTC = false;
+
+            if (keepLocalTime) {
+                this.subtract(getDateOffset(this), 'm');
+            }
+        }
+        return this;
+    }
+
+    function setOffsetToParsedOffset() {
+        if (this._tzm != null) {
+            this.utcOffset(this._tzm, false, true);
+        } else if (typeof this._i === 'string') {
+            var tZone = offsetFromString(matchOffset, this._i);
+            if (tZone != null) {
+                this.utcOffset(tZone);
+            } else {
+                this.utcOffset(0, true);
+            }
+        }
+        return this;
+    }
+
+    function hasAlignedHourOffset(input) {
+        if (!this.isValid()) {
+            return false;
+        }
+        input = input ? createLocal(input).utcOffset() : 0;
+
+        return (this.utcOffset() - input) % 60 === 0;
+    }
+
+    function isDaylightSavingTime() {
+        return (
+            this.utcOffset() > this.clone().month(0).utcOffset() ||
+            this.utcOffset() > this.clone().month(5).utcOffset()
+        );
+    }
+
+    function isDaylightSavingTimeShifted() {
+        if (!isUndefined(this._isDSTShifted)) {
+            return this._isDSTShifted;
+        }
+
+        var c = {},
+            other;
+
+        copyConfig(c, this);
+        c = prepareConfig(c);
+
+        if (c._a) {
+            other = c._isUTC ? createUTC(c._a) : createLocal(c._a);
+            this._isDSTShifted =
+                this.isValid() && compareArrays(c._a, other.toArray()) > 0;
+        } else {
+            this._isDSTShifted = false;
+        }
+
+        return this._isDSTShifted;
+    }
+
+    function isLocal() {
+        return this.isValid() ? !this._isUTC : false;
+    }
+
+    function isUtcOffset() {
+        return this.isValid() ? this._isUTC : false;
+    }
+
+    function isUtc() {
+        return this.isValid() ? this._isUTC && this._offset === 0 : false;
+    }
+
+    // ASP.NET json date format regex
+    var aspNetRegex = /^(-|\+)?(?:(\d*)[. ])?(\d+):(\d+)(?::(\d+)(\.\d*)?)?$/,
+        // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
+        // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
+        // and further modified to allow for strings containing both week and day
+        isoRegex =
+            /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;
+
+    function createDuration(input, key) {
+        var duration = input,
+            // matching against regexp is expensive, do it on demand
+            match = null,
+            sign,
+            ret,
+            diffRes;
+
+        if (isDuration(input)) {
+            duration = {
+                ms: input._milliseconds,
+                d: input._days,
+                M: input._months,
+            };
+        } else if (isNumber(input) || !isNaN(+input)) {
+            duration = {};
+            if (key) {
+                duration[key] = +input;
+            } else {
+                duration.milliseconds = +input;
+            }
+        } else if ((match = aspNetRegex.exec(input))) {
+            sign = match[1] === '-' ? -1 : 1;
+            duration = {
+                y: 0,
+                d: toInt(match[DATE]) * sign,
+                h: toInt(match[HOUR]) * sign,
+                m: toInt(match[MINUTE]) * sign,
+                s: toInt(match[SECOND]) * sign,
+                ms: toInt(absRound(match[MILLISECOND] * 1000)) * sign, // the millisecond decimal point is included in the match
+            };
+        } else if ((match = isoRegex.exec(input))) {
+            sign = match[1] === '-' ? -1 : 1;
+            duration = {
+                y: parseIso(match[2], sign),
+                M: parseIso(match[3], sign),
+                w: parseIso(match[4], sign),
+                d: parseIso(match[5], sign),
+                h: parseIso(match[6], sign),
+                m: parseIso(match[7], sign),
+                s: parseIso(match[8], sign),
+            };
+        } else if (duration == null) {
+            // checks for null or undefined
+            duration = {};
+        } else if (
+            typeof duration === 'object' &&
+            ('from' in duration || 'to' in duration)
+        ) {
+            diffRes = momentsDifference(
+                createLocal(duration.from),
+                createLocal(duration.to)
+            );
+
+            duration = {};
+            duration.ms = diffRes.milliseconds;
+            duration.M = diffRes.months;
+        }
+
+        ret = new Duration(duration);
+
+        if (isDuration(input) && hasOwnProp(input, '_locale')) {
+            ret._locale = input._locale;
+        }
+
+        if (isDuration(input) && hasOwnProp(input, '_isValid')) {
+            ret._isValid = input._isValid;
+        }
+
+        return ret;
+    }
+
+    createDuration.fn = Duration.prototype;
+    createDuration.invalid = createInvalid$1;
+
+    function parseIso(inp, sign) {
+        // We'd normally use ~~inp for this, but unfortunately it also
+        // converts floats to ints.
+        // inp may be undefined, so careful calling replace on it.
+        var res = inp && parseFloat(inp.replace(',', '.'));
+        // apply sign while we're at it
+        return (isNaN(res) ? 0 : res) * sign;
+    }
+
+    function positiveMomentsDifference(base, other) {
+        var res = {};
+
+        res.months =
+            other.month() - base.month() + (other.year() - base.year()) * 12;
+        if (base.clone().add(res.months, 'M').isAfter(other)) {
+            --res.months;
+        }
+
+        res.milliseconds = +other - +base.clone().add(res.months, 'M');
+
+        return res;
+    }
+
+    function momentsDifference(base, other) {
+        var res;
+        if (!(base.isValid() && other.isValid())) {
+            return { milliseconds: 0, months: 0 };
+        }
+
+        other = cloneWithOffset(other, base);
+        if (base.isBefore(other)) {
+            res = positiveMomentsDifference(base, other);
+        } else {
+            res = positiveMomentsDifference(other, base);
+            res.milliseconds = -res.milliseconds;
+            res.months = -res.months;
+        }
+
+        return res;
+    }
+
+    // TODO: remove 'name' arg after deprecation is removed
+    function createAdder(direction, name) {
+        return function (val, period) {
+            var dur, tmp;
+            //invert the arguments, but complain about it
+            if (period !== null && !isNaN(+period)) {
+                deprecateSimple(
+                    name,
+                    'moment().' +
+                        name +
+                        '(period, number) is deprecated. Please use moment().' +
+                        name +
+                        '(number, period). ' +
+                        'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.'
+                );
+                tmp = val;
+                val = period;
+                period = tmp;
+            }
+
+            dur = createDuration(val, period);
+            addSubtract(this, dur, direction);
+            return this;
+        };
     }
 
-    // date from string and format string
-    function makeDateFromStringAndFormat(config) {
-        if (config._f === moment.ISO_8601) {
-            parseISO(config);
+    function addSubtract(mom, duration, isAdding, updateOffset) {
+        var milliseconds = duration._milliseconds,
+            days = absRound(duration._days),
+            months = absRound(duration._months);
+
+        if (!mom.isValid()) {
+            // No op
             return;
         }
 
-        config._a = [];
-        config._pf.empty = true;
-
-        // This array is used to make a Date, either with `new Date` or `Date.UTC`
-        var string = '' + config._i,
-            i, parsedInput, tokens, token, skipped,
-            stringLength = string.length,
-            totalParsedInputLength = 0;
-
-        tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
+        updateOffset = updateOffset == null ? true : updateOffset;
 
-        for (i = 0; i < tokens.length; i++) {
-            token = tokens[i];
-            parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
-            if (parsedInput) {
-                skipped = string.substr(0, string.indexOf(parsedInput));
-                if (skipped.length > 0) {
-                    config._pf.unusedInput.push(skipped);
-                }
-                string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
-                totalParsedInputLength += parsedInput.length;
-            }
-            // don't parse if it's not a known token
-            if (formatTokenFunctions[token]) {
-                if (parsedInput) {
-                    config._pf.empty = false;
-                }
-                else {
-                    config._pf.unusedTokens.push(token);
-                }
-                addTimeToArrayFromToken(token, parsedInput, config);
-            }
-            else if (config._strict && !parsedInput) {
-                config._pf.unusedTokens.push(token);
-            }
+        if (months) {
+            setMonth(mom, get(mom, 'Month') + months * isAdding);
         }
-
-        // add remaining unparsed input length to the string
-        config._pf.charsLeftOver = stringLength - totalParsedInputLength;
-        if (string.length > 0) {
-            config._pf.unusedInput.push(string);
+        if (days) {
+            set$1(mom, 'Date', get(mom, 'Date') + days * isAdding);
         }
-
-        // clear _12h flag if hour is <= 12
-        if (config._pf.bigHour === true && config._a[HOUR] <= 12) {
-            config._pf.bigHour = undefined;
+        if (milliseconds) {
+            mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);
+        }
+        if (updateOffset) {
+            hooks.updateOffset(mom, days || months);
         }
-        // handle meridiem
-        config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR],
-                config._meridiem);
-        dateFromConfig(config);
-        checkOverflow(config);
     }
 
-    function unescapeFormat(s) {
-        return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
-            return p1 || p2 || p3 || p4;
-        });
-    }
+    var add = createAdder(1, 'add'),
+        subtract = createAdder(-1, 'subtract');
 
-    // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
-    function regexpEscape(s) {
-        return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+    function isString(input) {
+        return typeof input === 'string' || input instanceof String;
     }
 
-    // date from string and array of format strings
-    function makeDateFromStringAndArray(config) {
-        var tempConfig,
-            bestMoment,
+    // type MomentInput = Moment | Date | string | number | (number | string)[] | MomentInputObject | void; // null | undefined
+    function isMomentInput(input) {
+        return (
+            isMoment(input) ||
+            isDate(input) ||
+            isString(input) ||
+            isNumber(input) ||
+            isNumberOrStringArray(input) ||
+            isMomentInputObject(input) ||
+            input === null ||
+            input === undefined
+        );
+    }
 
-            scoreToBeat,
+    function isMomentInputObject(input) {
+        var objectTest = isObject(input) && !isObjectEmpty(input),
+            propertyTest = false,
+            properties = [
+                'years',
+                'year',
+                'y',
+                'months',
+                'month',
+                'M',
+                'days',
+                'day',
+                'd',
+                'dates',
+                'date',
+                'D',
+                'hours',
+                'hour',
+                'h',
+                'minutes',
+                'minute',
+                'm',
+                'seconds',
+                'second',
+                's',
+                'milliseconds',
+                'millisecond',
+                'ms',
+            ],
             i,
-            currentScore;
+            property,
+            propertyLen = properties.length;
 
-        if (config._f.length === 0) {
-            config._pf.invalidFormat = true;
-            config._d = new Date(NaN);
-            return;
+        for (i = 0; i < propertyLen; i += 1) {
+            property = properties[i];
+            propertyTest = propertyTest || hasOwnProp(input, property);
         }
 
-        for (i = 0; i < config._f.length; i++) {
-            currentScore = 0;
-            tempConfig = copyConfig({}, config);
-            if (config._useUTC != null) {
-                tempConfig._useUTC = config._useUTC;
-            }
-            tempConfig._pf = defaultParsingFlags();
-            tempConfig._f = config._f[i];
-            makeDateFromStringAndFormat(tempConfig);
+        return objectTest && propertyTest;
+    }
 
-            if (!isValid(tempConfig)) {
-                continue;
-            }
+    function isNumberOrStringArray(input) {
+        var arrayTest = isArray(input),
+            dataTypeTest = false;
+        if (arrayTest) {
+            dataTypeTest =
+                input.filter(function (item) {
+                    return !isNumber(item) && isString(input);
+                }).length === 0;
+        }
+        return arrayTest && dataTypeTest;
+    }
 
-            // if there is any input that was not parsed add a penalty for that format
-            currentScore += tempConfig._pf.charsLeftOver;
+    function isCalendarSpec(input) {
+        var objectTest = isObject(input) && !isObjectEmpty(input),
+            propertyTest = false,
+            properties = [
+                'sameDay',
+                'nextDay',
+                'lastDay',
+                'nextWeek',
+                'lastWeek',
+                'sameElse',
+            ],
+            i,
+            property;
 
-            //or tokens
-            currentScore += tempConfig._pf.unusedTokens.length * 10;
+        for (i = 0; i < properties.length; i += 1) {
+            property = properties[i];
+            propertyTest = propertyTest || hasOwnProp(input, property);
+        }
 
-            tempConfig._pf.score = currentScore;
+        return objectTest && propertyTest;
+    }
+
+    function getCalendarFormat(myMoment, now) {
+        var diff = myMoment.diff(now, 'days', true);
+        return diff < -6
+            ? 'sameElse'
+            : diff < -1
+            ? 'lastWeek'
+            : diff < 0
+            ? 'lastDay'
+            : diff < 1
+            ? 'sameDay'
+            : diff < 2
+            ? 'nextDay'
+            : diff < 7
+            ? 'nextWeek'
+            : 'sameElse';
+    }
 
-            if (scoreToBeat == null || currentScore < scoreToBeat) {
-                scoreToBeat = currentScore;
-                bestMoment = tempConfig;
+    function calendar$1(time, formats) {
+        // Support for single parameter, formats only overload to the calendar function
+        if (arguments.length === 1) {
+            if (!arguments[0]) {
+                time = undefined;
+                formats = undefined;
+            } else if (isMomentInput(arguments[0])) {
+                time = arguments[0];
+                formats = undefined;
+            } else if (isCalendarSpec(arguments[0])) {
+                formats = arguments[0];
+                time = undefined;
             }
         }
-
-        extend(config, bestMoment || tempConfig);
+        // We want to compare the start of today, vs this.
+        // Getting start-of-today depends on whether we're local/utc/offset or not.
+        var now = time || createLocal(),
+            sod = cloneWithOffset(now, this).startOf('day'),
+            format = hooks.calendarFormat(this, sod) || 'sameElse',
+            output =
+                formats &&
+                (isFunction(formats[format])
+                    ? formats[format].call(this, now)
+                    : formats[format]);
+
+        return this.format(
+            output || this.localeData().calendar(format, this, createLocal(now))
+        );
     }
 
-    // date from iso format
-    function parseISO(config) {
-        var i, l,
-            string = config._i,
-            match = isoRegex.exec(string);
+    function clone() {
+        return new Moment(this);
+    }
 
-        if (match) {
-            config._pf.iso = true;
-            for (i = 0, l = isoDates.length; i < l; i++) {
-                if (isoDates[i][1].exec(string)) {
-                    // match[5] should be 'T' or undefined
-                    config._f = isoDates[i][0] + (match[6] || ' ');
-                    break;
-                }
-            }
-            for (i = 0, l = isoTimes.length; i < l; i++) {
-                if (isoTimes[i][1].exec(string)) {
-                    config._f += isoTimes[i][0];
-                    break;
-                }
-            }
-            if (string.match(parseTokenTimezone)) {
-                config._f += 'Z';
-            }
-            makeDateFromStringAndFormat(config);
+    function isAfter(input, units) {
+        var localInput = isMoment(input) ? input : createLocal(input);
+        if (!(this.isValid() && localInput.isValid())) {
+            return false;
+        }
+        units = normalizeUnits(units) || 'millisecond';
+        if (units === 'millisecond') {
+            return this.valueOf() > localInput.valueOf();
         } else {
-            config._isValid = false;
+            return localInput.valueOf() < this.clone().startOf(units).valueOf();
         }
     }
 
-    // date from iso format or fallback
-    function makeDateFromString(config) {
-        parseISO(config);
-        if (config._isValid === false) {
-            delete config._isValid;
-            moment.createFromInputFallback(config);
+    function isBefore(input, units) {
+        var localInput = isMoment(input) ? input : createLocal(input);
+        if (!(this.isValid() && localInput.isValid())) {
+            return false;
+        }
+        units = normalizeUnits(units) || 'millisecond';
+        if (units === 'millisecond') {
+            return this.valueOf() < localInput.valueOf();
+        } else {
+            return this.clone().endOf(units).valueOf() < localInput.valueOf();
         }
     }
 
-    function map(arr, fn) {
-        var res = [], i;
-        for (i = 0; i < arr.length; ++i) {
-            res.push(fn(arr[i], i));
+    function isBetween(from, to, units, inclusivity) {
+        var localFrom = isMoment(from) ? from : createLocal(from),
+            localTo = isMoment(to) ? to : createLocal(to);
+        if (!(this.isValid() && localFrom.isValid() && localTo.isValid())) {
+            return false;
         }
-        return res;
+        inclusivity = inclusivity || '()';
+        return (
+            (inclusivity[0] === '('
+                ? this.isAfter(localFrom, units)
+                : !this.isBefore(localFrom, units)) &&
+            (inclusivity[1] === ')'
+                ? this.isBefore(localTo, units)
+                : !this.isAfter(localTo, units))
+        );
     }
 
-    function makeDateFromInput(config) {
-        var input = config._i, matched;
-        if (input === undefined) {
-            config._d = new Date();
-        } else if (isDate(input)) {
-            config._d = new Date(+input);
-        } else if ((matched = aspNetJsonRegex.exec(input)) !== null) {
-            config._d = new Date(+matched[1]);
-        } else if (typeof input === 'string') {
-            makeDateFromString(config);
-        } else if (isArray(input)) {
-            config._a = map(input.slice(0), function (obj) {
-                return parseInt(obj, 10);
-            });
-            dateFromConfig(config);
-        } else if (typeof(input) === 'object') {
-            dateFromObject(config);
-        } else if (typeof(input) === 'number') {
-            // from milliseconds
-            config._d = new Date(input);
+    function isSame(input, units) {
+        var localInput = isMoment(input) ? input : createLocal(input),
+            inputMs;
+        if (!(this.isValid() && localInput.isValid())) {
+            return false;
+        }
+        units = normalizeUnits(units) || 'millisecond';
+        if (units === 'millisecond') {
+            return this.valueOf() === localInput.valueOf();
         } else {
-            moment.createFromInputFallback(config);
+            inputMs = localInput.valueOf();
+            return (
+                this.clone().startOf(units).valueOf() <= inputMs &&
+                inputMs <= this.clone().endOf(units).valueOf()
+            );
         }
     }
 
-    function makeDate(y, m, d, h, M, s, ms) {
-        //can't just apply() to create a date:
-        //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply
-        var date = new Date(y, m, d, h, M, s, ms);
-
-        //the date constructor doesn't accept years < 1970
-        if (y < 1970) {
-            date.setFullYear(y);
-        }
-        return date;
+    function isSameOrAfter(input, units) {
+        return this.isSame(input, units) || this.isAfter(input, units);
     }
 
-    function makeUTCDate(y) {
-        var date = new Date(Date.UTC.apply(null, arguments));
-        if (y < 1970) {
-            date.setUTCFullYear(y);
-        }
-        return date;
+    function isSameOrBefore(input, units) {
+        return this.isSame(input, units) || this.isBefore(input, units);
     }
 
-    function parseWeekday(input, locale) {
-        if (typeof input === 'string') {
-            if (!isNaN(input)) {
-                input = parseInt(input, 10);
-            }
-            else {
-                input = locale.weekdaysParse(input);
-                if (typeof input !== 'number') {
-                    return null;
-                }
-            }
+    function diff(input, units, asFloat) {
+        var that, zoneDelta, output;
+
+        if (!this.isValid()) {
+            return NaN;
         }
-        return input;
-    }
 
-    /************************************
-        Relative Time
-    ************************************/
+        that = cloneWithOffset(input, this);
 
+        if (!that.isValid()) {
+            return NaN;
+        }
 
-    // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
-    function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
-        return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
-    }
+        zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;
 
-    function relativeTime(posNegDuration, withoutSuffix, locale) {
-        var duration = moment.duration(posNegDuration).abs(),
-            seconds = round(duration.as('s')),
-            minutes = round(duration.as('m')),
-            hours = round(duration.as('h')),
-            days = round(duration.as('d')),
-            months = round(duration.as('M')),
-            years = round(duration.as('y')),
+        units = normalizeUnits(units);
 
-            args = seconds < relativeTimeThresholds.s && ['s', seconds] ||
-                minutes === 1 && ['m'] ||
-                minutes < relativeTimeThresholds.m && ['mm', minutes] ||
-                hours === 1 && ['h'] ||
-                hours < relativeTimeThresholds.h && ['hh', hours] ||
-                days === 1 && ['d'] ||
-                days < relativeTimeThresholds.d && ['dd', days] ||
-                months === 1 && ['M'] ||
-                months < relativeTimeThresholds.M && ['MM', months] ||
-                years === 1 && ['y'] || ['yy', years];
+        switch (units) {
+            case 'year':
+                output = monthDiff(this, that) / 12;
+                break;
+            case 'month':
+                output = monthDiff(this, that);
+                break;
+            case 'quarter':
+                output = monthDiff(this, that) / 3;
+                break;
+            case 'second':
+                output = (this - that) / 1e3;
+                break; // 1000
+            case 'minute':
+                output = (this - that) / 6e4;
+                break; // 1000 * 60
+            case 'hour':
+                output = (this - that) / 36e5;
+                break; // 1000 * 60 * 60
+            case 'day':
+                output = (this - that - zoneDelta) / 864e5;
+                break; // 1000 * 60 * 60 * 24, negate dst
+            case 'week':
+                output = (this - that - zoneDelta) / 6048e5;
+                break; // 1000 * 60 * 60 * 24 * 7, negate dst
+            default:
+                output = this - that;
+        }
 
-        args[2] = withoutSuffix;
-        args[3] = +posNegDuration > 0;
-        args[4] = locale;
-        return substituteTimeAgo.apply({}, args);
+        return asFloat ? output : absFloor(output);
     }
 
+    function monthDiff(a, b) {
+        if (a.date() < b.date()) {
+            // end-of-month calculations work correct when the start month has more
+            // days than the end month.
+            return -monthDiff(b, a);
+        }
+        // difference in months
+        var wholeMonthDiff = (b.year() - a.year()) * 12 + (b.month() - a.month()),
+            // b is in (anchor - 1 month, anchor + 1 month)
+            anchor = a.clone().add(wholeMonthDiff, 'months'),
+            anchor2,
+            adjust;
 
-    /************************************
-        Week of Year
-    ************************************/
+        if (b - anchor < 0) {
+            anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
+            // linear across the month
+            adjust = (b - anchor) / (anchor - anchor2);
+        } else {
+            anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
+            // linear across the month
+            adjust = (b - anchor) / (anchor2 - anchor);
+        }
 
+        //check for negative zero, return zero if negative zero
+        return -(wholeMonthDiff + adjust) || 0;
+    }
 
-    // firstDayOfWeek       0 = sun, 6 = sat
-    //                      the day of the week that starts the week
-    //                      (usually sunday or monday)
-    // firstDayOfWeekOfYear 0 = sun, 6 = sat
-    //                      the first week is the week that contains the first
-    //                      of this day of the week
-    //                      (eg. ISO weeks use thursday (4))
-    function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) {
-        var end = firstDayOfWeekOfYear - firstDayOfWeek,
-            daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(),
-            adjustedMoment;
+    hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
+    hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';
 
+    function toString() {
+        return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
+    }
 
-        if (daysToDayOfWeek > end) {
-            daysToDayOfWeek -= 7;
+    function toISOString(keepOffset) {
+        if (!this.isValid()) {
+            return null;
+        }
+        var utc = keepOffset !== true,
+            m = utc ? this.clone().utc() : this;
+        if (m.year() < 0 || m.year() > 9999) {
+            return formatMoment(
+                m,
+                utc
+                    ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'
+                    : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ'
+            );
+        }
+        if (isFunction(Date.prototype.toISOString)) {
+            // native implementation is ~50x faster, use it when we can
+            if (utc) {
+                return this.toDate().toISOString();
+            } else {
+                return new Date(this.valueOf() + this.utcOffset() * 60 * 1000)
+                    .toISOString()
+                    .replace('Z', formatMoment(m, 'Z'));
+            }
         }
+        return formatMoment(
+            m,
+            utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ'
+        );
+    }
 
-        if (daysToDayOfWeek < end - 7) {
-            daysToDayOfWeek += 7;
+    /**
+     * Return a human readable representation of a moment that can
+     * also be evaluated to get a new moment which is the same
+     *
+     * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects
+     */
+    function inspect() {
+        if (!this.isValid()) {
+            return 'moment.invalid(/* ' + this._i + ' */)';
         }
+        var func = 'moment',
+            zone = '',
+            prefix,
+            year,
+            datetime,
+            suffix;
+        if (!this.isLocal()) {
+            func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone';
+            zone = 'Z';
+        }
+        prefix = '[' + func + '("]';
+        year = 0 <= this.year() && this.year() <= 9999 ? 'YYYY' : 'YYYYYY';
+        datetime = '-MM-DD[T]HH:mm:ss.SSS';
+        suffix = zone + '[")]';
 
-        adjustedMoment = moment(mom).add(daysToDayOfWeek, 'd');
-        return {
-            week: Math.ceil(adjustedMoment.dayOfYear() / 7),
-            year: adjustedMoment.year()
-        };
+        return this.format(prefix + year + datetime + suffix);
     }
 
-    //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
-    function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) {
-        var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear;
-
-        d = d === 0 ? 7 : d;
-        weekday = weekday != null ? weekday : firstDayOfWeek;
-        daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0);
-        dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1;
+    function format(inputString) {
+        if (!inputString) {
+            inputString = this.isUtc()
+                ? hooks.defaultFormatUtc
+                : hooks.defaultFormat;
+        }
+        var output = formatMoment(this, inputString);
+        return this.localeData().postformat(output);
+    }
 
-        return {
-            year: dayOfYear > 0 ? year : year - 1,
-            dayOfYear: dayOfYear > 0 ?  dayOfYear : daysInYear(year - 1) + dayOfYear
-        };
+    function from(time, withoutSuffix) {
+        if (
+            this.isValid() &&
+            ((isMoment(time) && time.isValid()) || createLocal(time).isValid())
+        ) {
+            return createDuration({ to: this, from: time })
+                .locale(this.locale())
+                .humanize(!withoutSuffix);
+        } else {
+            return this.localeData().invalidDate();
+        }
     }
 
-    /************************************
-        Top Level Functions
-    ************************************/
+    function fromNow(withoutSuffix) {
+        return this.from(createLocal(), withoutSuffix);
+    }
 
-    function makeMoment(config) {
-        var input = config._i,
-            format = config._f,
-            res;
+    function to(time, withoutSuffix) {
+        if (
+            this.isValid() &&
+            ((isMoment(time) && time.isValid()) || createLocal(time).isValid())
+        ) {
+            return createDuration({ from: this, to: time })
+                .locale(this.locale())
+                .humanize(!withoutSuffix);
+        } else {
+            return this.localeData().invalidDate();
+        }
+    }
 
-        config._locale = config._locale || moment.localeData(config._l);
+    function toNow(withoutSuffix) {
+        return this.to(createLocal(), withoutSuffix);
+    }
 
-        if (input === null || (format === undefined && input === '')) {
-            return moment.invalid({nullInput: true});
-        }
+    // If passed a locale key, it will set the locale for this
+    // instance.  Otherwise, it will return the locale configuration
+    // variables for this instance.
+    function locale(key) {
+        var newLocaleData;
 
-        if (typeof input === 'string') {
-            config._i = input = config._locale.preparse(input);
+        if (key === undefined) {
+            return this._locale._abbr;
+        } else {
+            newLocaleData = getLocale(key);
+            if (newLocaleData != null) {
+                this._locale = newLocaleData;
+            }
+            return this;
         }
+    }
 
-        if (moment.isMoment(input)) {
-            return new Moment(input, true);
-        } else if (format) {
-            if (isArray(format)) {
-                makeDateFromStringAndArray(config);
+    var lang = deprecate(
+        'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
+        function (key) {
+            if (key === undefined) {
+                return this.localeData();
             } else {
-                makeDateFromStringAndFormat(config);
+                return this.locale(key);
             }
-        } else {
-            makeDateFromInput(config);
         }
+    );
 
-        res = new Moment(config);
-        if (res._nextDay) {
-            // Adding is smart enough around DST
-            res.add(1, 'd');
-            res._nextDay = undefined;
-        }
+    function localeData() {
+        return this._locale;
+    }
 
-        return res;
+    var MS_PER_SECOND = 1000,
+        MS_PER_MINUTE = 60 * MS_PER_SECOND,
+        MS_PER_HOUR = 60 * MS_PER_MINUTE,
+        MS_PER_400_YEARS = (365 * 400 + 97) * 24 * MS_PER_HOUR;
+
+    // actual modulo - handles negative numbers (for dates before 1970):
+    function mod$1(dividend, divisor) {
+        return ((dividend % divisor) + divisor) % divisor;
     }
 
-    moment = function (input, format, locale, strict) {
-        var c;
+    function localStartOfDate(y, m, d) {
+        // the date constructor remaps years 0-99 to 1900-1999
+        if (y < 100 && y >= 0) {
+            // preserve leap years using a full 400 year cycle, then reset
+            return new Date(y + 400, m, d) - MS_PER_400_YEARS;
+        } else {
+            return new Date(y, m, d).valueOf();
+        }
+    }
 
-        if (typeof(locale) === 'boolean') {
-            strict = locale;
-            locale = undefined;
+    function utcStartOfDate(y, m, d) {
+        // Date.UTC remaps years 0-99 to 1900-1999
+        if (y < 100 && y >= 0) {
+            // preserve leap years using a full 400 year cycle, then reset
+            return Date.UTC(y + 400, m, d) - MS_PER_400_YEARS;
+        } else {
+            return Date.UTC(y, m, d);
         }
-        // object construction must be done this way.
-        // https://github.com/moment/moment/issues/1423
-        c = {};
-        c._isAMomentObject = true;
-        c._i = input;
-        c._f = format;
-        c._l = locale;
-        c._strict = strict;
-        c._isUTC = false;
-        c._pf = defaultParsingFlags();
+    }
 
-        return makeMoment(c);
-    };
+    function startOf(units) {
+        var time, startOfDate;
+        units = normalizeUnits(units);
+        if (units === undefined || units === 'millisecond' || !this.isValid()) {
+            return this;
+        }
 
-    moment.suppressDeprecationWarnings = false;
+        startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;
 
-    moment.createFromInputFallback = deprecate(
-        'moment construction falls back to js Date. This is ' +
-        'discouraged and will be removed in upcoming major ' +
-        'release. Please refer to ' +
-        'https://github.com/moment/moment/issues/1407 for more info.',
-        function (config) {
-            config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
+        switch (units) {
+            case 'year':
+                time = startOfDate(this.year(), 0, 1);
+                break;
+            case 'quarter':
+                time = startOfDate(
+                    this.year(),
+                    this.month() - (this.month() % 3),
+                    1
+                );
+                break;
+            case 'month':
+                time = startOfDate(this.year(), this.month(), 1);
+                break;
+            case 'week':
+                time = startOfDate(
+                    this.year(),
+                    this.month(),
+                    this.date() - this.weekday()
+                );
+                break;
+            case 'isoWeek':
+                time = startOfDate(
+                    this.year(),
+                    this.month(),
+                    this.date() - (this.isoWeekday() - 1)
+                );
+                break;
+            case 'day':
+            case 'date':
+                time = startOfDate(this.year(), this.month(), this.date());
+                break;
+            case 'hour':
+                time = this._d.valueOf();
+                time -= mod$1(
+                    time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE),
+                    MS_PER_HOUR
+                );
+                break;
+            case 'minute':
+                time = this._d.valueOf();
+                time -= mod$1(time, MS_PER_MINUTE);
+                break;
+            case 'second':
+                time = this._d.valueOf();
+                time -= mod$1(time, MS_PER_SECOND);
+                break;
         }
-    );
 
-    // Pick a moment m from moments so that m[fn](other) is true for all
-    // other. This relies on the function fn to be transitive.
-    //
-    // moments should either be an array of moment objects or an array, whose
-    // first element is an array of moment objects.
-    function pickBy(fn, moments) {
-        var res, i;
-        if (moments.length === 1 && isArray(moments[0])) {
-            moments = moments[0];
-        }
-        if (!moments.length) {
-            return moment();
+        this._d.setTime(time);
+        hooks.updateOffset(this, true);
+        return this;
+    }
+
+    function endOf(units) {
+        var time, startOfDate;
+        units = normalizeUnits(units);
+        if (units === undefined || units === 'millisecond' || !this.isValid()) {
+            return this;
         }
-        res = moments[0];
-        for (i = 1; i < moments.length; ++i) {
-            if (moments[i][fn](res)) {
-                res = moments[i];
-            }
+
+        startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;
+
+        switch (units) {
+            case 'year':
+                time = startOfDate(this.year() + 1, 0, 1) - 1;
+                break;
+            case 'quarter':
+                time =
+                    startOfDate(
+                        this.year(),
+                        this.month() - (this.month() % 3) + 3,
+                        1
+                    ) - 1;
+                break;
+            case 'month':
+                time = startOfDate(this.year(), this.month() + 1, 1) - 1;
+                break;
+            case 'week':
+                time =
+                    startOfDate(
+                        this.year(),
+                        this.month(),
+                        this.date() - this.weekday() + 7
+                    ) - 1;
+                break;
+            case 'isoWeek':
+                time =
+                    startOfDate(
+                        this.year(),
+                        this.month(),
+                        this.date() - (this.isoWeekday() - 1) + 7
+                    ) - 1;
+                break;
+            case 'day':
+            case 'date':
+                time = startOfDate(this.year(), this.month(), this.date() + 1) - 1;
+                break;
+            case 'hour':
+                time = this._d.valueOf();
+                time +=
+                    MS_PER_HOUR -
+                    mod$1(
+                        time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE),
+                        MS_PER_HOUR
+                    ) -
+                    1;
+                break;
+            case 'minute':
+                time = this._d.valueOf();
+                time += MS_PER_MINUTE - mod$1(time, MS_PER_MINUTE) - 1;
+                break;
+            case 'second':
+                time = this._d.valueOf();
+                time += MS_PER_SECOND - mod$1(time, MS_PER_SECOND) - 1;
+                break;
         }
-        return res;
+
+        this._d.setTime(time);
+        hooks.updateOffset(this, true);
+        return this;
+    }
+
+    function valueOf() {
+        return this._d.valueOf() - (this._offset || 0) * 60000;
     }
 
-    moment.min = function () {
-        var args = [].slice.call(arguments, 0);
+    function unix() {
+        return Math.floor(this.valueOf() / 1000);
+    }
 
-        return pickBy('isBefore', args);
-    };
+    function toDate() {
+        return new Date(this.valueOf());
+    }
 
-    moment.max = function () {
-        var args = [].slice.call(arguments, 0);
+    function toArray() {
+        var m = this;
+        return [
+            m.year(),
+            m.month(),
+            m.date(),
+            m.hour(),
+            m.minute(),
+            m.second(),
+            m.millisecond(),
+        ];
+    }
 
-        return pickBy('isAfter', args);
-    };
+    function toObject() {
+        var m = this;
+        return {
+            years: m.year(),
+            months: m.month(),
+            date: m.date(),
+            hours: m.hours(),
+            minutes: m.minutes(),
+            seconds: m.seconds(),
+            milliseconds: m.milliseconds(),
+        };
+    }
 
-    // creating with utc
-    moment.utc = function (input, format, locale, strict) {
-        var c;
+    function toJSON() {
+        // new Date(NaN).toJSON() === null
+        return this.isValid() ? this.toISOString() : null;
+    }
 
-        if (typeof(locale) === 'boolean') {
-            strict = locale;
-            locale = undefined;
-        }
-        // object construction must be done this way.
-        // https://github.com/moment/moment/issues/1423
-        c = {};
-        c._isAMomentObject = true;
-        c._useUTC = true;
-        c._isUTC = true;
-        c._l = locale;
-        c._i = input;
-        c._f = format;
-        c._strict = strict;
-        c._pf = defaultParsingFlags();
+    function isValid$2() {
+        return isValid(this);
+    }
 
-        return makeMoment(c).utc();
-    };
+    function parsingFlags() {
+        return extend({}, getParsingFlags(this));
+    }
 
-    // creating with unix timestamp (in seconds)
-    moment.unix = function (input) {
-        return moment(input * 1000);
-    };
+    function invalidAt() {
+        return getParsingFlags(this).overflow;
+    }
 
-    // duration
-    moment.duration = function (input, key) {
-        var duration = input,
-            // matching against regexp is expensive, do it on demand
-            match = null,
-            sign,
-            ret,
-            parseIso,
-            diffRes;
+    function creationData() {
+        return {
+            input: this._i,
+            format: this._f,
+            locale: this._locale,
+            isUTC: this._isUTC,
+            strict: this._strict,
+        };
+    }
 
-        if (moment.isDuration(input)) {
-            duration = {
-                ms: input._milliseconds,
-                d: input._days,
-                M: input._months
-            };
-        } else if (typeof input === 'number') {
-            duration = {};
-            if (key) {
-                duration[key] = input;
+    addFormatToken('N', 0, 0, 'eraAbbr');
+    addFormatToken('NN', 0, 0, 'eraAbbr');
+    addFormatToken('NNN', 0, 0, 'eraAbbr');
+    addFormatToken('NNNN', 0, 0, 'eraName');
+    addFormatToken('NNNNN', 0, 0, 'eraNarrow');
+
+    addFormatToken('y', ['y', 1], 'yo', 'eraYear');
+    addFormatToken('y', ['yy', 2], 0, 'eraYear');
+    addFormatToken('y', ['yyy', 3], 0, 'eraYear');
+    addFormatToken('y', ['yyyy', 4], 0, 'eraYear');
+
+    addRegexToken('N', matchEraAbbr);
+    addRegexToken('NN', matchEraAbbr);
+    addRegexToken('NNN', matchEraAbbr);
+    addRegexToken('NNNN', matchEraName);
+    addRegexToken('NNNNN', matchEraNarrow);
+
+    addParseToken(
+        ['N', 'NN', 'NNN', 'NNNN', 'NNNNN'],
+        function (input, array, config, token) {
+            var era = config._locale.erasParse(input, token, config._strict);
+            if (era) {
+                getParsingFlags(config).era = era;
             } else {
-                duration.milliseconds = input;
+                getParsingFlags(config).invalidEra = input;
             }
-        } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) {
-            sign = (match[1] === '-') ? -1 : 1;
-            duration = {
-                y: 0,
-                d: toInt(match[DATE]) * sign,
-                h: toInt(match[HOUR]) * sign,
-                m: toInt(match[MINUTE]) * sign,
-                s: toInt(match[SECOND]) * sign,
-                ms: toInt(match[MILLISECOND]) * sign
-            };
-        } else if (!!(match = isoDurationRegex.exec(input))) {
-            sign = (match[1] === '-') ? -1 : 1;
-            parseIso = function (inp) {
-                // We'd normally use ~~inp for this, but unfortunately it also
-                // converts floats to ints.
-                // inp may be undefined, so careful calling replace on it.
-                var res = inp && parseFloat(inp.replace(',', '.'));
-                // apply sign while we're at it
-                return (isNaN(res) ? 0 : res) * sign;
-            };
-            duration = {
-                y: parseIso(match[2]),
-                M: parseIso(match[3]),
-                d: parseIso(match[4]),
-                h: parseIso(match[5]),
-                m: parseIso(match[6]),
-                s: parseIso(match[7]),
-                w: parseIso(match[8])
-            };
-        } else if (duration == null) {// checks for null or undefined
-            duration = {};
-        } else if (typeof duration === 'object' &&
-                ('from' in duration || 'to' in duration)) {
-            diffRes = momentsDifference(moment(duration.from), moment(duration.to));
-
-            duration = {};
-            duration.ms = diffRes.milliseconds;
-            duration.M = diffRes.months;
         }
+    );
 
-        ret = new Duration(duration);
+    addRegexToken('y', matchUnsigned);
+    addRegexToken('yy', matchUnsigned);
+    addRegexToken('yyy', matchUnsigned);
+    addRegexToken('yyyy', matchUnsigned);
+    addRegexToken('yo', matchEraYearOrdinal);
+
+    addParseToken(['y', 'yy', 'yyy', 'yyyy'], YEAR);
+    addParseToken(['yo'], function (input, array, config, token) {
+        var match;
+        if (config._locale._eraYearOrdinalRegex) {
+            match = input.match(config._locale._eraYearOrdinalRegex);
+        }
 
-        if (moment.isDuration(input) && hasOwnProp(input, '_locale')) {
-            ret._locale = input._locale;
+        if (config._locale.eraYearOrdinalParse) {
+            array[YEAR] = config._locale.eraYearOrdinalParse(input, match);
+        } else {
+            array[YEAR] = parseInt(input, 10);
         }
+    });
 
-        return ret;
-    };
+    function localeEras(m, format) {
+        var i,
+            l,
+            date,
+            eras = this._eras || getLocale('en')._eras;
+        for (i = 0, l = eras.length; i < l; ++i) {
+            switch (typeof eras[i].since) {
+                case 'string':
+                    // truncate time
+                    date = hooks(eras[i].since).startOf('day');
+                    eras[i].since = date.valueOf();
+                    break;
+            }
 
-    // version number
-    moment.version = VERSION;
+            switch (typeof eras[i].until) {
+                case 'undefined':
+                    eras[i].until = +Infinity;
+                    break;
+                case 'string':
+                    // truncate time
+                    date = hooks(eras[i].until).startOf('day').valueOf();
+                    eras[i].until = date.valueOf();
+                    break;
+            }
+        }
+        return eras;
+    }
 
-    // default format
-    moment.defaultFormat = isoFormat;
+    function localeErasParse(eraName, format, strict) {
+        var i,
+            l,
+            eras = this.eras(),
+            name,
+            abbr,
+            narrow;
+        eraName = eraName.toUpperCase();
 
-    // constant that refers to the ISO standard
-    moment.ISO_8601 = function () {};
+        for (i = 0, l = eras.length; i < l; ++i) {
+            name = eras[i].name.toUpperCase();
+            abbr = eras[i].abbr.toUpperCase();
+            narrow = eras[i].narrow.toUpperCase();
 
-    // Plugins that add properties should also add the key here (null value),
-    // so we can properly clone ourselves.
-    moment.momentProperties = momentProperties;
+            if (strict) {
+                switch (format) {
+                    case 'N':
+                    case 'NN':
+                    case 'NNN':
+                        if (abbr === eraName) {
+                            return eras[i];
+                        }
+                        break;
 
-    // This function will be called whenever a moment is mutated.
-    // It is intended to keep the offset in sync with the timezone.
-    moment.updateOffset = function () {};
+                    case 'NNNN':
+                        if (name === eraName) {
+                            return eras[i];
+                        }
+                        break;
 
-    // This function allows you to set a threshold for relative time strings
-    moment.relativeTimeThreshold = function (threshold, limit) {
-        if (relativeTimeThresholds[threshold] === undefined) {
-            return false;
-        }
-        if (limit === undefined) {
-            return relativeTimeThresholds[threshold];
+                    case 'NNNNN':
+                        if (narrow === eraName) {
+                            return eras[i];
+                        }
+                        break;
+                }
+            } else if ([name, abbr, narrow].indexOf(eraName) >= 0) {
+                return eras[i];
+            }
         }
-        relativeTimeThresholds[threshold] = limit;
-        return true;
-    };
+    }
 
-    moment.lang = deprecate(
-        'moment.lang is deprecated. Use moment.locale instead.',
-        function (key, value) {
-            return moment.locale(key, value);
+    function localeErasConvertYear(era, year) {
+        var dir = era.since <= era.until ? +1 : -1;
+        if (year === undefined) {
+            return hooks(era.since).year();
+        } else {
+            return hooks(era.since).year() + (year - era.offset) * dir;
         }
-    );
+    }
 
-    // This function will load locale and then set the global locale.  If
-    // no arguments are passed in, it will simply return the current global
-    // locale key.
-    moment.locale = function (key, values) {
-        var data;
-        if (key) {
-            if (typeof(values) !== 'undefined') {
-                data = moment.defineLocale(key, values);
+    function getEraName() {
+        var i,
+            l,
+            val,
+            eras = this.localeData().eras();
+        for (i = 0, l = eras.length; i < l; ++i) {
+            // truncate time
+            val = this.clone().startOf('day').valueOf();
+
+            if (eras[i].since <= val && val <= eras[i].until) {
+                return eras[i].name;
             }
-            else {
-                data = moment.localeData(key);
-            }
-
-            if (data) {
-                moment.duration._locale = moment._locale = data;
+            if (eras[i].until <= val && val <= eras[i].since) {
+                return eras[i].name;
             }
         }
 
-        return moment._locale._abbr;
-    };
+        return '';
+    }
 
-    moment.defineLocale = function (name, values) {
-        if (values !== null) {
-            values.abbr = name;
-            if (!locales[name]) {
-                locales[name] = new Locale();
+    function getEraNarrow() {
+        var i,
+            l,
+            val,
+            eras = this.localeData().eras();
+        for (i = 0, l = eras.length; i < l; ++i) {
+            // truncate time
+            val = this.clone().startOf('day').valueOf();
+
+            if (eras[i].since <= val && val <= eras[i].until) {
+                return eras[i].narrow;
+            }
+            if (eras[i].until <= val && val <= eras[i].since) {
+                return eras[i].narrow;
             }
-            locales[name].set(values);
+        }
 
-            // backwards compat for now: also set the locale
-            moment.locale(name);
+        return '';
+    }
 
-            return locales[name];
-        } else {
-            // useful for testing
-            delete locales[name];
-            return null;
+    function getEraAbbr() {
+        var i,
+            l,
+            val,
+            eras = this.localeData().eras();
+        for (i = 0, l = eras.length; i < l; ++i) {
+            // truncate time
+            val = this.clone().startOf('day').valueOf();
+
+            if (eras[i].since <= val && val <= eras[i].until) {
+                return eras[i].abbr;
+            }
+            if (eras[i].until <= val && val <= eras[i].since) {
+                return eras[i].abbr;
+            }
         }
-    };
 
-    moment.langData = deprecate(
-        'moment.langData is deprecated. Use moment.localeData instead.',
-        function (key) {
-            return moment.localeData(key);
+        return '';
+    }
+
+    function getEraYear() {
+        var i,
+            l,
+            dir,
+            val,
+            eras = this.localeData().eras();
+        for (i = 0, l = eras.length; i < l; ++i) {
+            dir = eras[i].since <= eras[i].until ? +1 : -1;
+
+            // truncate time
+            val = this.clone().startOf('day').valueOf();
+
+            if (
+                (eras[i].since <= val && val <= eras[i].until) ||
+                (eras[i].until <= val && val <= eras[i].since)
+            ) {
+                return (
+                    (this.year() - hooks(eras[i].since).year()) * dir +
+                    eras[i].offset
+                );
+            }
         }
-    );
 
-    // returns locale data
-    moment.localeData = function (key) {
-        var locale;
+        return this.year();
+    }
 
-        if (key && key._locale && key._locale._abbr) {
-            key = key._locale._abbr;
+    function erasNameRegex(isStrict) {
+        if (!hasOwnProp(this, '_erasNameRegex')) {
+            computeErasParse.call(this);
         }
+        return isStrict ? this._erasNameRegex : this._erasRegex;
+    }
 
-        if (!key) {
-            return moment._locale;
+    function erasAbbrRegex(isStrict) {
+        if (!hasOwnProp(this, '_erasAbbrRegex')) {
+            computeErasParse.call(this);
         }
+        return isStrict ? this._erasAbbrRegex : this._erasRegex;
+    }
 
-        if (!isArray(key)) {
-            //short-circuit everything else
-            locale = loadLocale(key);
-            if (locale) {
-                return locale;
-            }
-            key = [key];
+    function erasNarrowRegex(isStrict) {
+        if (!hasOwnProp(this, '_erasNarrowRegex')) {
+            computeErasParse.call(this);
         }
+        return isStrict ? this._erasNarrowRegex : this._erasRegex;
+    }
 
-        return chooseLocale(key);
-    };
+    function matchEraAbbr(isStrict, locale) {
+        return locale.erasAbbrRegex(isStrict);
+    }
 
-    // compare moment object
-    moment.isMoment = function (obj) {
-        return obj instanceof Moment ||
-            (obj != null && hasOwnProp(obj, '_isAMomentObject'));
-    };
+    function matchEraName(isStrict, locale) {
+        return locale.erasNameRegex(isStrict);
+    }
 
-    // for typechecking Duration objects
-    moment.isDuration = function (obj) {
-        return obj instanceof Duration;
-    };
+    function matchEraNarrow(isStrict, locale) {
+        return locale.erasNarrowRegex(isStrict);
+    }
 
-    for (i = lists.length - 1; i >= 0; --i) {
-        makeList(lists[i]);
+    function matchEraYearOrdinal(isStrict, locale) {
+        return locale._eraYearOrdinalRegex || matchUnsigned;
     }
 
-    moment.normalizeUnits = function (units) {
-        return normalizeUnits(units);
-    };
+    function computeErasParse() {
+        var abbrPieces = [],
+            namePieces = [],
+            narrowPieces = [],
+            mixedPieces = [],
+            i,
+            l,
+            eras = this.eras();
 
-    moment.invalid = function (flags) {
-        var m = moment.utc(NaN);
-        if (flags != null) {
-            extend(m._pf, flags);
-        }
-        else {
-            m._pf.userInvalidated = true;
+        for (i = 0, l = eras.length; i < l; ++i) {
+            namePieces.push(regexEscape(eras[i].name));
+            abbrPieces.push(regexEscape(eras[i].abbr));
+            narrowPieces.push(regexEscape(eras[i].narrow));
+
+            mixedPieces.push(regexEscape(eras[i].name));
+            mixedPieces.push(regexEscape(eras[i].abbr));
+            mixedPieces.push(regexEscape(eras[i].narrow));
         }
 
-        return m;
-    };
+        this._erasRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+        this._erasNameRegex = new RegExp('^(' + namePieces.join('|') + ')', 'i');
+        this._erasAbbrRegex = new RegExp('^(' + abbrPieces.join('|') + ')', 'i');
+        this._erasNarrowRegex = new RegExp(
+            '^(' + narrowPieces.join('|') + ')',
+            'i'
+        );
+    }
 
-    moment.parseZone = function () {
-        return moment.apply(null, arguments).parseZone();
-    };
+    // FORMATTING
 
-    moment.parseTwoDigitYear = function (input) {
-        return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
-    };
+    addFormatToken(0, ['gg', 2], 0, function () {
+        return this.weekYear() % 100;
+    });
 
-    moment.isDate = isDate;
+    addFormatToken(0, ['GG', 2], 0, function () {
+        return this.isoWeekYear() % 100;
+    });
 
-    /************************************
-        Moment Prototype
-    ************************************/
+    function addWeekYearFormatToken(token, getter) {
+        addFormatToken(0, [token, token.length], 0, getter);
+    }
 
+    addWeekYearFormatToken('gggg', 'weekYear');
+    addWeekYearFormatToken('ggggg', 'weekYear');
+    addWeekYearFormatToken('GGGG', 'isoWeekYear');
+    addWeekYearFormatToken('GGGGG', 'isoWeekYear');
 
-    extend(moment.fn = Moment.prototype, {
+    // ALIASES
 
-        clone : function () {
-            return moment(this);
-        },
+    addUnitAlias('weekYear', 'gg');
+    addUnitAlias('isoWeekYear', 'GG');
 
-        valueOf : function () {
-            return +this._d - ((this._offset || 0) * 60000);
-        },
+    // PRIORITY
 
-        unix : function () {
-            return Math.floor(+this / 1000);
-        },
+    addUnitPriority('weekYear', 1);
+    addUnitPriority('isoWeekYear', 1);
 
-        toString : function () {
-            return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
-        },
+    // PARSING
 
-        toDate : function () {
-            return this._offset ? new Date(+this) : this._d;
-        },
+    addRegexToken('G', matchSigned);
+    addRegexToken('g', matchSigned);
+    addRegexToken('GG', match1to2, match2);
+    addRegexToken('gg', match1to2, match2);
+    addRegexToken('GGGG', match1to4, match4);
+    addRegexToken('gggg', match1to4, match4);
+    addRegexToken('GGGGG', match1to6, match6);
+    addRegexToken('ggggg', match1to6, match6);
 
-        toISOString : function () {
-            var m = moment(this).utc();
-            if (0 < m.year() && m.year() <= 9999) {
-                if ('function' === typeof Date.prototype.toISOString) {
-                    // native implementation is ~50x faster, use it when we can
-                    return this.toDate().toISOString();
-                } else {
-                    return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
-                }
-            } else {
-                return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
-            }
-        },
+    addWeekParseToken(
+        ['gggg', 'ggggg', 'GGGG', 'GGGGG'],
+        function (input, week, config, token) {
+            week[token.substr(0, 2)] = toInt(input);
+        }
+    );
 
-        toArray : function () {
-            var m = this;
-            return [
-                m.year(),
-                m.month(),
-                m.date(),
-                m.hours(),
-                m.minutes(),
-                m.seconds(),
-                m.milliseconds()
-            ];
-        },
+    addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
+        week[token] = hooks.parseTwoDigitYear(input);
+    });
 
-        isValid : function () {
-            return isValid(this);
-        },
+    // MOMENTS
+
+    function getSetWeekYear(input) {
+        return getSetWeekYearHelper.call(
+            this,
+            input,
+            this.week(),
+            this.weekday(),
+            this.localeData()._week.dow,
+            this.localeData()._week.doy
+        );
+    }
+
+    function getSetISOWeekYear(input) {
+        return getSetWeekYearHelper.call(
+            this,
+            input,
+            this.isoWeek(),
+            this.isoWeekday(),
+            1,
+            4
+        );
+    }
+
+    function getISOWeeksInYear() {
+        return weeksInYear(this.year(), 1, 4);
+    }
+
+    function getISOWeeksInISOWeekYear() {
+        return weeksInYear(this.isoWeekYear(), 1, 4);
+    }
+
+    function getWeeksInYear() {
+        var weekInfo = this.localeData()._week;
+        return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
+    }
+
+    function getWeeksInWeekYear() {
+        var weekInfo = this.localeData()._week;
+        return weeksInYear(this.weekYear(), weekInfo.dow, weekInfo.doy);
+    }
 
-        isDSTShifted : function () {
-            if (this._a) {
-                return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0;
+    function getSetWeekYearHelper(input, week, weekday, dow, doy) {
+        var weeksTarget;
+        if (input == null) {
+            return weekOfYear(this, dow, doy).year;
+        } else {
+            weeksTarget = weeksInYear(input, dow, doy);
+            if (week > weeksTarget) {
+                week = weeksTarget;
             }
+            return setWeekAll.call(this, input, week, weekday, dow, doy);
+        }
+    }
 
-            return false;
-        },
+    function setWeekAll(weekYear, week, weekday, dow, doy) {
+        var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),
+            date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);
 
-        parsingFlags : function () {
-            return extend({}, this._pf);
-        },
+        this.year(date.getUTCFullYear());
+        this.month(date.getUTCMonth());
+        this.date(date.getUTCDate());
+        return this;
+    }
 
-        invalidAt: function () {
-            return this._pf.overflow;
-        },
+    // FORMATTING
 
-        utc : function (keepLocalTime) {
-            return this.utcOffset(0, keepLocalTime);
-        },
+    addFormatToken('Q', 0, 'Qo', 'quarter');
 
-        local : function (keepLocalTime) {
-            if (this._isUTC) {
-                this.utcOffset(0, keepLocalTime);
-                this._isUTC = false;
+    // ALIASES
 
-                if (keepLocalTime) {
-                    this.subtract(this._dateUtcOffset(), 'm');
-                }
-            }
-            return this;
-        },
+    addUnitAlias('quarter', 'Q');
 
-        format : function (inputString) {
-            var output = formatMoment(this, inputString || moment.defaultFormat);
-            return this.localeData().postformat(output);
-        },
+    // PRIORITY
 
-        add : createAdder(1, 'add'),
+    addUnitPriority('quarter', 7);
 
-        subtract : createAdder(-1, 'subtract'),
+    // PARSING
 
-        diff : function (input, units, asFloat) {
-            var that = makeAs(input, this),
-                zoneDiff = (that.utcOffset() - this.utcOffset()) * 6e4,
-                anchor, diff, output, daysAdjust;
+    addRegexToken('Q', match1);
+    addParseToken('Q', function (input, array) {
+        array[MONTH] = (toInt(input) - 1) * 3;
+    });
 
-            units = normalizeUnits(units);
+    // MOMENTS
 
-            if (units === 'year' || units === 'month' || units === 'quarter') {
-                output = monthDiff(this, that);
-                if (units === 'quarter') {
-                    output = output / 3;
-                } else if (units === 'year') {
-                    output = output / 12;
-                }
-            } else {
-                diff = this - that;
-                output = units === 'second' ? diff / 1e3 : // 1000
-                    units === 'minute' ? diff / 6e4 : // 1000 * 60
-                    units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60
-                    units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst
-                    units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst
-                    diff;
-            }
-            return asFloat ? output : absRound(output);
-        },
+    function getSetQuarter(input) {
+        return input == null
+            ? Math.ceil((this.month() + 1) / 3)
+            : this.month((input - 1) * 3 + (this.month() % 3));
+    }
 
-        from : function (time, withoutSuffix) {
-            return moment.duration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
-        },
+    // FORMATTING
 
-        fromNow : function (withoutSuffix) {
-            return this.from(moment(), withoutSuffix);
-        },
+    addFormatToken('D', ['DD', 2], 'Do', 'date');
 
-        calendar : function (time) {
-            // We want to compare the start of today, vs this.
-            // Getting start-of-today depends on whether we're locat/utc/offset
-            // or not.
-            var now = time || moment(),
-                sod = makeAs(now, this).startOf('day'),
-                diff = this.diff(sod, 'days', true),
-                format = diff < -6 ? 'sameElse' :
-                    diff < -1 ? 'lastWeek' :
-                    diff < 0 ? 'lastDay' :
-                    diff < 1 ? 'sameDay' :
-                    diff < 2 ? 'nextDay' :
-                    diff < 7 ? 'nextWeek' : 'sameElse';
-            return this.format(this.localeData().calendar(format, this, moment(now)));
-        },
+    // ALIASES
 
-        isLeapYear : function () {
-            return isLeapYear(this.year());
-        },
+    addUnitAlias('date', 'D');
 
-        isDST : function () {
-            return (this.utcOffset() > this.clone().month(0).utcOffset() ||
-                this.utcOffset() > this.clone().month(5).utcOffset());
-        },
+    // PRIORITY
+    addUnitPriority('date', 9);
 
-        day : function (input) {
-            var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
-            if (input != null) {
-                input = parseWeekday(input, this.localeData());
-                return this.add(input - day, 'd');
-            } else {
-                return day;
-            }
-        },
+    // PARSING
 
-        month : makeAccessor('Month', true),
+    addRegexToken('D', match1to2);
+    addRegexToken('DD', match1to2, match2);
+    addRegexToken('Do', function (isStrict, locale) {
+        // TODO: Remove "ordinalParse" fallback in next major release.
+        return isStrict
+            ? locale._dayOfMonthOrdinalParse || locale._ordinalParse
+            : locale._dayOfMonthOrdinalParseLenient;
+    });
 
-        startOf : function (units) {
-            units = normalizeUnits(units);
-            // the following switch intentionally omits break keywords
-            // to utilize falling through the cases.
-            switch (units) {
-            case 'year':
-                this.month(0);
-                /* falls through */
-            case 'quarter':
-            case 'month':
-                this.date(1);
-                /* falls through */
-            case 'week':
-            case 'isoWeek':
-            case 'day':
-                this.hours(0);
-                /* falls through */
-            case 'hour':
-                this.minutes(0);
-                /* falls through */
-            case 'minute':
-                this.seconds(0);
-                /* falls through */
-            case 'second':
-                this.milliseconds(0);
-                /* falls through */
-            }
+    addParseToken(['D', 'DD'], DATE);
+    addParseToken('Do', function (input, array) {
+        array[DATE] = toInt(input.match(match1to2)[0]);
+    });
 
-            // weeks are a special case
-            if (units === 'week') {
-                this.weekday(0);
-            } else if (units === 'isoWeek') {
-                this.isoWeekday(1);
-            }
+    // MOMENTS
 
-            // quarters are also special
-            if (units === 'quarter') {
-                this.month(Math.floor(this.month() / 3) * 3);
-            }
+    var getSetDayOfMonth = makeGetSet('Date', true);
 
-            return this;
-        },
+    // FORMATTING
 
-        endOf: function (units) {
-            units = normalizeUnits(units);
-            if (units === undefined || units === 'millisecond') {
-                return this;
-            }
-            return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms');
-        },
+    addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');
 
-        isAfter: function (input, units) {
-            var inputMs;
-            units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond');
-            if (units === 'millisecond') {
-                input = moment.isMoment(input) ? input : moment(input);
-                return +this > +input;
-            } else {
-                inputMs = moment.isMoment(input) ? +input : +moment(input);
-                return inputMs < +this.clone().startOf(units);
-            }
-        },
+    // ALIASES
+
+    addUnitAlias('dayOfYear', 'DDD');
+
+    // PRIORITY
+    addUnitPriority('dayOfYear', 4);
+
+    // PARSING
+
+    addRegexToken('DDD', match1to3);
+    addRegexToken('DDDD', match3);
+    addParseToken(['DDD', 'DDDD'], function (input, array, config) {
+        config._dayOfYear = toInt(input);
+    });
+
+    // HELPERS
+
+    // MOMENTS
+
+    function getSetDayOfYear(input) {
+        var dayOfYear =
+            Math.round(
+                (this.clone().startOf('day') - this.clone().startOf('year')) / 864e5
+            ) + 1;
+        return input == null ? dayOfYear : this.add(input - dayOfYear, 'd');
+    }
+
+    // FORMATTING
+
+    addFormatToken('m', ['mm', 2], 0, 'minute');
 
-        isBefore: function (input, units) {
-            var inputMs;
-            units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond');
-            if (units === 'millisecond') {
-                input = moment.isMoment(input) ? input : moment(input);
-                return +this < +input;
-            } else {
-                inputMs = moment.isMoment(input) ? +input : +moment(input);
-                return +this.clone().endOf(units) < inputMs;
-            }
-        },
+    // ALIASES
 
-        isBetween: function (from, to, units) {
-            return this.isAfter(from, units) && this.isBefore(to, units);
-        },
+    addUnitAlias('minute', 'm');
 
-        isSame: function (input, units) {
-            var inputMs;
-            units = normalizeUnits(units || 'millisecond');
-            if (units === 'millisecond') {
-                input = moment.isMoment(input) ? input : moment(input);
-                return +this === +input;
-            } else {
-                inputMs = +moment(input);
-                return +(this.clone().startOf(units)) <= inputMs && inputMs <= +(this.clone().endOf(units));
-            }
-        },
+    // PRIORITY
 
-        min: deprecate(
-                 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548',
-                 function (other) {
-                     other = moment.apply(null, arguments);
-                     return other < this ? this : other;
-                 }
-         ),
-
-        max: deprecate(
-                'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548',
-                function (other) {
-                    other = moment.apply(null, arguments);
-                    return other > this ? this : other;
-                }
-        ),
+    addUnitPriority('minute', 14);
 
-        zone : deprecate(
-                'moment().zone is deprecated, use moment().utcOffset instead. ' +
-                'https://github.com/moment/moment/issues/1779',
-                function (input, keepLocalTime) {
-                    if (input != null) {
-                        if (typeof input !== 'string') {
-                            input = -input;
-                        }
+    // PARSING
 
-                        this.utcOffset(input, keepLocalTime);
+    addRegexToken('m', match1to2);
+    addRegexToken('mm', match1to2, match2);
+    addParseToken(['m', 'mm'], MINUTE);
 
-                        return this;
-                    } else {
-                        return -this.utcOffset();
-                    }
-                }
-        ),
+    // MOMENTS
 
-        // keepLocalTime = true means only change the timezone, without
-        // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
-        // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
-        // +0200, so we adjust the time as needed, to be valid.
-        //
-        // Keeping the time actually adds/subtracts (one hour)
-        // from the actual represented time. That is why we call updateOffset
-        // a second time. In case it wants us to change the offset again
-        // _changeInProgress == true case, then we have to adjust, because
-        // there is no such time in the given timezone.
-        utcOffset : function (input, keepLocalTime) {
-            var offset = this._offset || 0,
-                localAdjust;
-            if (input != null) {
-                if (typeof input === 'string') {
-                    input = utcOffsetFromString(input);
-                }
-                if (Math.abs(input) < 16) {
-                    input = input * 60;
-                }
-                if (!this._isUTC && keepLocalTime) {
-                    localAdjust = this._dateUtcOffset();
-                }
-                this._offset = input;
-                this._isUTC = true;
-                if (localAdjust != null) {
-                    this.add(localAdjust, 'm');
-                }
-                if (offset !== input) {
-                    if (!keepLocalTime || this._changeInProgress) {
-                        addOrSubtractDurationFromMoment(this,
-                                moment.duration(input - offset, 'm'), 1, false);
-                    } else if (!this._changeInProgress) {
-                        this._changeInProgress = true;
-                        moment.updateOffset(this, true);
-                        this._changeInProgress = null;
-                    }
-                }
+    var getSetMinute = makeGetSet('Minutes', false);
 
-                return this;
-            } else {
-                return this._isUTC ? offset : this._dateUtcOffset();
-            }
-        },
+    // FORMATTING
 
-        isLocal : function () {
-            return !this._isUTC;
-        },
+    addFormatToken('s', ['ss', 2], 0, 'second');
 
-        isUtcOffset : function () {
-            return this._isUTC;
-        },
+    // ALIASES
 
-        isUtc : function () {
-            return this._isUTC && this._offset === 0;
-        },
+    addUnitAlias('second', 's');
 
-        zoneAbbr : function () {
-            return this._isUTC ? 'UTC' : '';
-        },
+    // PRIORITY
 
-        zoneName : function () {
-            return this._isUTC ? 'Coordinated Universal Time' : '';
-        },
+    addUnitPriority('second', 15);
 
-        parseZone : function () {
-            if (this._tzm) {
-                this.utcOffset(this._tzm);
-            } else if (typeof this._i === 'string') {
-                this.utcOffset(utcOffsetFromString(this._i));
-            }
-            return this;
-        },
+    // PARSING
 
-        hasAlignedHourOffset : function (input) {
-            if (!input) {
-                input = 0;
-            }
-            else {
-                input = moment(input).utcOffset();
-            }
+    addRegexToken('s', match1to2);
+    addRegexToken('ss', match1to2, match2);
+    addParseToken(['s', 'ss'], SECOND);
 
-            return (this.utcOffset() - input) % 60 === 0;
-        },
+    // MOMENTS
 
-        daysInMonth : function () {
-            return daysInMonth(this.year(), this.month());
-        },
+    var getSetSecond = makeGetSet('Seconds', false);
 
-        dayOfYear : function (input) {
-            var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1;
-            return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
-        },
+    // FORMATTING
 
-        quarter : function (input) {
-            return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
-        },
+    addFormatToken('S', 0, 0, function () {
+        return ~~(this.millisecond() / 100);
+    });
 
-        weekYear : function (input) {
-            var year = weekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year;
-            return input == null ? year : this.add((input - year), 'y');
-        },
+    addFormatToken(0, ['SS', 2], 0, function () {
+        return ~~(this.millisecond() / 10);
+    });
 
-        isoWeekYear : function (input) {
-            var year = weekOfYear(this, 1, 4).year;
-            return input == null ? year : this.add((input - year), 'y');
-        },
+    addFormatToken(0, ['SSS', 3], 0, 'millisecond');
+    addFormatToken(0, ['SSSS', 4], 0, function () {
+        return this.millisecond() * 10;
+    });
+    addFormatToken(0, ['SSSSS', 5], 0, function () {
+        return this.millisecond() * 100;
+    });
+    addFormatToken(0, ['SSSSSS', 6], 0, function () {
+        return this.millisecond() * 1000;
+    });
+    addFormatToken(0, ['SSSSSSS', 7], 0, function () {
+        return this.millisecond() * 10000;
+    });
+    addFormatToken(0, ['SSSSSSSS', 8], 0, function () {
+        return this.millisecond() * 100000;
+    });
+    addFormatToken(0, ['SSSSSSSSS', 9], 0, function () {
+        return this.millisecond() * 1000000;
+    });
 
-        week : function (input) {
-            var week = this.localeData().week(this);
-            return input == null ? week : this.add((input - week) * 7, 'd');
-        },
+    // ALIASES
 
-        isoWeek : function (input) {
-            var week = weekOfYear(this, 1, 4).week;
-            return input == null ? week : this.add((input - week) * 7, 'd');
-        },
+    addUnitAlias('millisecond', 'ms');
 
-        weekday : function (input) {
-            var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
-            return input == null ? weekday : this.add(input - weekday, 'd');
-        },
+    // PRIORITY
 
-        isoWeekday : function (input) {
-            // behaves the same as moment#day except
-            // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
-            // as a setter, sunday should belong to the previous week.
-            return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7);
-        },
+    addUnitPriority('millisecond', 16);
 
-        isoWeeksInYear : function () {
-            return weeksInYear(this.year(), 1, 4);
-        },
+    // PARSING
 
-        weeksInYear : function () {
-            var weekInfo = this.localeData()._week;
-            return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
-        },
+    addRegexToken('S', match1to3, match1);
+    addRegexToken('SS', match1to3, match2);
+    addRegexToken('SSS', match1to3, match3);
 
-        get : function (units) {
-            units = normalizeUnits(units);
-            return this[units]();
-        },
+    var token, getSetMillisecond;
+    for (token = 'SSSS'; token.length <= 9; token += 'S') {
+        addRegexToken(token, matchUnsigned);
+    }
 
-        set : function (units, value) {
-            var unit;
-            if (typeof units === 'object') {
-                for (unit in units) {
-                    this.set(unit, units[unit]);
-                }
-            }
-            else {
-                units = normalizeUnits(units);
-                if (typeof this[units] === 'function') {
-                    this[units](value);
-                }
-            }
-            return this;
-        },
+    function parseMs(input, array) {
+        array[MILLISECOND] = toInt(('0.' + input) * 1000);
+    }
 
-        // If passed a locale key, it will set the locale for this
-        // instance.  Otherwise, it will return the locale configuration
-        // variables for this instance.
-        locale : function (key) {
-            var newLocaleData;
+    for (token = 'S'; token.length <= 9; token += 'S') {
+        addParseToken(token, parseMs);
+    }
 
-            if (key === undefined) {
-                return this._locale._abbr;
-            } else {
-                newLocaleData = moment.localeData(key);
-                if (newLocaleData != null) {
-                    this._locale = newLocaleData;
-                }
-                return this;
-            }
-        },
+    getSetMillisecond = makeGetSet('Milliseconds', false);
 
-        lang : deprecate(
-            'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
-            function (key) {
-                if (key === undefined) {
-                    return this.localeData();
-                } else {
-                    return this.locale(key);
-                }
-            }
-        ),
+    // FORMATTING
 
-        localeData : function () {
-            return this._locale;
-        },
+    addFormatToken('z', 0, 0, 'zoneAbbr');
+    addFormatToken('zz', 0, 0, 'zoneName');
 
-        _dateUtcOffset : function () {
-            // On Firefox.24 Date#getTimezoneOffset returns a floating point.
-            // https://github.com/moment/moment/pull/1871
-            return -Math.round(this._d.getTimezoneOffset() / 15) * 15;
-        }
+    // MOMENTS
 
-    });
+    function getZoneAbbr() {
+        return this._isUTC ? 'UTC' : '';
+    }
 
-    function rawMonthSetter(mom, value) {
-        var dayOfMonth;
+    function getZoneName() {
+        return this._isUTC ? 'Coordinated Universal Time' : '';
+    }
 
-        // TODO: Move this out of here!
-        if (typeof value === 'string') {
-            value = mom.localeData().monthsParse(value);
-            // TODO: Another silent failure?
-            if (typeof value !== 'number') {
-                return mom;
-            }
-        }
+    var proto = Moment.prototype;
+
+    proto.add = add;
+    proto.calendar = calendar$1;
+    proto.clone = clone;
+    proto.diff = diff;
+    proto.endOf = endOf;
+    proto.format = format;
+    proto.from = from;
+    proto.fromNow = fromNow;
+    proto.to = to;
+    proto.toNow = toNow;
+    proto.get = stringGet;
+    proto.invalidAt = invalidAt;
+    proto.isAfter = isAfter;
+    proto.isBefore = isBefore;
+    proto.isBetween = isBetween;
+    proto.isSame = isSame;
+    proto.isSameOrAfter = isSameOrAfter;
+    proto.isSameOrBefore = isSameOrBefore;
+    proto.isValid = isValid$2;
+    proto.lang = lang;
+    proto.locale = locale;
+    proto.localeData = localeData;
+    proto.max = prototypeMax;
+    proto.min = prototypeMin;
+    proto.parsingFlags = parsingFlags;
+    proto.set = stringSet;
+    proto.startOf = startOf;
+    proto.subtract = subtract;
+    proto.toArray = toArray;
+    proto.toObject = toObject;
+    proto.toDate = toDate;
+    proto.toISOString = toISOString;
+    proto.inspect = inspect;
+    if (typeof Symbol !== 'undefined' && Symbol.for != null) {
+        proto[Symbol.for('nodejs.util.inspect.custom')] = function () {
+            return 'Moment<' + this.format() + '>';
+        };
+    }
+    proto.toJSON = toJSON;
+    proto.toString = toString;
+    proto.unix = unix;
+    proto.valueOf = valueOf;
+    proto.creationData = creationData;
+    proto.eraName = getEraName;
+    proto.eraNarrow = getEraNarrow;
+    proto.eraAbbr = getEraAbbr;
+    proto.eraYear = getEraYear;
+    proto.year = getSetYear;
+    proto.isLeapYear = getIsLeapYear;
+    proto.weekYear = getSetWeekYear;
+    proto.isoWeekYear = getSetISOWeekYear;
+    proto.quarter = proto.quarters = getSetQuarter;
+    proto.month = getSetMonth;
+    proto.daysInMonth = getDaysInMonth;
+    proto.week = proto.weeks = getSetWeek;
+    proto.isoWeek = proto.isoWeeks = getSetISOWeek;
+    proto.weeksInYear = getWeeksInYear;
+    proto.weeksInWeekYear = getWeeksInWeekYear;
+    proto.isoWeeksInYear = getISOWeeksInYear;
+    proto.isoWeeksInISOWeekYear = getISOWeeksInISOWeekYear;
+    proto.date = getSetDayOfMonth;
+    proto.day = proto.days = getSetDayOfWeek;
+    proto.weekday = getSetLocaleDayOfWeek;
+    proto.isoWeekday = getSetISODayOfWeek;
+    proto.dayOfYear = getSetDayOfYear;
+    proto.hour = proto.hours = getSetHour;
+    proto.minute = proto.minutes = getSetMinute;
+    proto.second = proto.seconds = getSetSecond;
+    proto.millisecond = proto.milliseconds = getSetMillisecond;
+    proto.utcOffset = getSetOffset;
+    proto.utc = setOffsetToUTC;
+    proto.local = setOffsetToLocal;
+    proto.parseZone = setOffsetToParsedOffset;
+    proto.hasAlignedHourOffset = hasAlignedHourOffset;
+    proto.isDST = isDaylightSavingTime;
+    proto.isLocal = isLocal;
+    proto.isUtcOffset = isUtcOffset;
+    proto.isUtc = isUtc;
+    proto.isUTC = isUtc;
+    proto.zoneAbbr = getZoneAbbr;
+    proto.zoneName = getZoneName;
+    proto.dates = deprecate(
+        'dates accessor is deprecated. Use date instead.',
+        getSetDayOfMonth
+    );
+    proto.months = deprecate(
+        'months accessor is deprecated. Use month instead',
+        getSetMonth
+    );
+    proto.years = deprecate(
+        'years accessor is deprecated. Use year instead',
+        getSetYear
+    );
+    proto.zone = deprecate(
+        'moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/',
+        getSetZone
+    );
+    proto.isDSTShifted = deprecate(
+        'isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information',
+        isDaylightSavingTimeShifted
+    );
 
-        dayOfMonth = Math.min(mom.date(),
-                daysInMonth(mom.year(), value));
-        mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
-        return mom;
+    function createUnix(input) {
+        return createLocal(input * 1000);
     }
 
-    function rawGetter(mom, unit) {
-        return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]();
+    function createInZone() {
+        return createLocal.apply(null, arguments).parseZone();
     }
 
-    function rawSetter(mom, unit, value) {
-        if (unit === 'Month') {
-            return rawMonthSetter(mom, value);
-        } else {
-            return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
+    function preParsePostFormat(string) {
+        return string;
+    }
+
+    var proto$1 = Locale.prototype;
+
+    proto$1.calendar = calendar;
+    proto$1.longDateFormat = longDateFormat;
+    proto$1.invalidDate = invalidDate;
+    proto$1.ordinal = ordinal;
+    proto$1.preparse = preParsePostFormat;
+    proto$1.postformat = preParsePostFormat;
+    proto$1.relativeTime = relativeTime;
+    proto$1.pastFuture = pastFuture;
+    proto$1.set = set;
+    proto$1.eras = localeEras;
+    proto$1.erasParse = localeErasParse;
+    proto$1.erasConvertYear = localeErasConvertYear;
+    proto$1.erasAbbrRegex = erasAbbrRegex;
+    proto$1.erasNameRegex = erasNameRegex;
+    proto$1.erasNarrowRegex = erasNarrowRegex;
+
+    proto$1.months = localeMonths;
+    proto$1.monthsShort = localeMonthsShort;
+    proto$1.monthsParse = localeMonthsParse;
+    proto$1.monthsRegex = monthsRegex;
+    proto$1.monthsShortRegex = monthsShortRegex;
+    proto$1.week = localeWeek;
+    proto$1.firstDayOfYear = localeFirstDayOfYear;
+    proto$1.firstDayOfWeek = localeFirstDayOfWeek;
+
+    proto$1.weekdays = localeWeekdays;
+    proto$1.weekdaysMin = localeWeekdaysMin;
+    proto$1.weekdaysShort = localeWeekdaysShort;
+    proto$1.weekdaysParse = localeWeekdaysParse;
+
+    proto$1.weekdaysRegex = weekdaysRegex;
+    proto$1.weekdaysShortRegex = weekdaysShortRegex;
+    proto$1.weekdaysMinRegex = weekdaysMinRegex;
+
+    proto$1.isPM = localeIsPM;
+    proto$1.meridiem = localeMeridiem;
+
+    function get$1(format, index, field, setter) {
+        var locale = getLocale(),
+            utc = createUTC().set(setter, index);
+        return locale[field](utc, format);
+    }
+
+    function listMonthsImpl(format, index, field) {
+        if (isNumber(format)) {
+            index = format;
+            format = undefined;
+        }
+
+        format = format || '';
+
+        if (index != null) {
+            return get$1(format, index, field, 'month');
+        }
+
+        var i,
+            out = [];
+        for (i = 0; i < 12; i++) {
+            out[i] = get$1(format, i, field, 'month');
         }
+        return out;
     }
 
-    function makeAccessor(unit, keepTime) {
-        return function (value) {
-            if (value != null) {
-                rawSetter(this, unit, value);
-                moment.updateOffset(this, keepTime);
-                return this;
-            } else {
-                return rawGetter(this, unit);
+    // ()
+    // (5)
+    // (fmt, 5)
+    // (fmt)
+    // (true)
+    // (true, 5)
+    // (true, fmt, 5)
+    // (true, fmt)
+    function listWeekdaysImpl(localeSorted, format, index, field) {
+        if (typeof localeSorted === 'boolean') {
+            if (isNumber(format)) {
+                index = format;
+                format = undefined;
+            }
+
+            format = format || '';
+        } else {
+            format = localeSorted;
+            index = format;
+            localeSorted = false;
+
+            if (isNumber(format)) {
+                index = format;
+                format = undefined;
             }
-        };
-    }
 
-    moment.fn.millisecond = moment.fn.milliseconds = makeAccessor('Milliseconds', false);
-    moment.fn.second = moment.fn.seconds = makeAccessor('Seconds', false);
-    moment.fn.minute = moment.fn.minutes = makeAccessor('Minutes', false);
-    // Setting the hour should keep the time, because the user explicitly
-    // specified which hour he wants. So trying to maintain the same hour (in
-    // a new timezone) makes sense. Adding/subtracting hours does not follow
-    // this rule.
-    moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true);
-    // moment.fn.month is defined separately
-    moment.fn.date = makeAccessor('Date', true);
-    moment.fn.dates = deprecate('dates accessor is deprecated. Use date instead.', makeAccessor('Date', true));
-    moment.fn.year = makeAccessor('FullYear', true);
-    moment.fn.years = deprecate('years accessor is deprecated. Use year instead.', makeAccessor('FullYear', true));
+            format = format || '';
+        }
 
-    // add plural methods
-    moment.fn.days = moment.fn.day;
-    moment.fn.months = moment.fn.month;
-    moment.fn.weeks = moment.fn.week;
-    moment.fn.isoWeeks = moment.fn.isoWeek;
-    moment.fn.quarters = moment.fn.quarter;
+        var locale = getLocale(),
+            shift = localeSorted ? locale._week.dow : 0,
+            i,
+            out = [];
 
-    // add aliased format methods
-    moment.fn.toJSON = moment.fn.toISOString;
+        if (index != null) {
+            return get$1(format, (index + shift) % 7, field, 'day');
+        }
 
-    // alias isUtc for dev-friendliness
-    moment.fn.isUTC = moment.fn.isUtc;
+        for (i = 0; i < 7; i++) {
+            out[i] = get$1(format, (i + shift) % 7, field, 'day');
+        }
+        return out;
+    }
 
-    /************************************
-        Duration Prototype
-    ************************************/
+    function listMonths(format, index) {
+        return listMonthsImpl(format, index, 'months');
+    }
 
+    function listMonthsShort(format, index) {
+        return listMonthsImpl(format, index, 'monthsShort');
+    }
 
-    function daysToYears (days) {
-        // 400 years have 146097 days (taking into account leap year rules)
-        return days * 400 / 146097;
+    function listWeekdays(localeSorted, format, index) {
+        return listWeekdaysImpl(localeSorted, format, index, 'weekdays');
     }
 
-    function yearsToDays (years) {
-        // years * 365 + absRound(years / 4) -
-        //     absRound(years / 100) + absRound(years / 400);
-        return years * 146097 / 400;
+    function listWeekdaysShort(localeSorted, format, index) {
+        return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');
     }
 
-    extend(moment.duration.fn = Duration.prototype, {
+    function listWeekdaysMin(localeSorted, format, index) {
+        return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');
+    }
 
-        _bubble : function () {
-            var milliseconds = this._milliseconds,
-                days = this._days,
-                months = this._months,
-                data = this._data,
-                seconds, minutes, hours, years = 0;
+    getSetGlobalLocale('en', {
+        eras: [
+            {
+                since: '0001-01-01',
+                until: +Infinity,
+                offset: 1,
+                name: 'Anno Domini',
+                narrow: 'AD',
+                abbr: 'AD',
+            },
+            {
+                since: '0000-12-31',
+                until: -Infinity,
+                offset: 1,
+                name: 'Before Christ',
+                narrow: 'BC',
+                abbr: 'BC',
+            },
+        ],
+        dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/,
+        ordinal: function (number) {
+            var b = number % 10,
+                output =
+                    toInt((number % 100) / 10) === 1
+                        ? 'th'
+                        : b === 1
+                        ? 'st'
+                        : b === 2
+                        ? 'nd'
+                        : b === 3
+                        ? 'rd'
+                        : 'th';
+            return number + output;
+        },
+    });
 
-            // The following code bubbles up values, see the tests for
-            // examples of what that means.
-            data.milliseconds = milliseconds % 1000;
+    // Side effect imports
 
-            seconds = absRound(milliseconds / 1000);
-            data.seconds = seconds % 60;
+    hooks.lang = deprecate(
+        'moment.lang is deprecated. Use moment.locale instead.',
+        getSetGlobalLocale
+    );
+    hooks.langData = deprecate(
+        'moment.langData is deprecated. Use moment.localeData instead.',
+        getLocale
+    );
 
-            minutes = absRound(seconds / 60);
-            data.minutes = minutes % 60;
+    var mathAbs = Math.abs;
 
-            hours = absRound(minutes / 60);
-            data.hours = hours % 24;
+    function abs() {
+        var data = this._data;
 
-            days += absRound(hours / 24);
+        this._milliseconds = mathAbs(this._milliseconds);
+        this._days = mathAbs(this._days);
+        this._months = mathAbs(this._months);
 
-            // Accurately convert days to years, assume start from year 0.
-            years = absRound(daysToYears(days));
-            days -= absRound(yearsToDays(years));
+        data.milliseconds = mathAbs(data.milliseconds);
+        data.seconds = mathAbs(data.seconds);
+        data.minutes = mathAbs(data.minutes);
+        data.hours = mathAbs(data.hours);
+        data.months = mathAbs(data.months);
+        data.years = mathAbs(data.years);
 
-            // 30 days to a month
-            // TODO (iskren): Use anchor date (like 1st Jan) to compute this.
-            months += absRound(days / 30);
-            days %= 30;
+        return this;
+    }
 
-            // 12 months -> 1 year
-            years += absRound(months / 12);
-            months %= 12;
+    function addSubtract$1(duration, input, value, direction) {
+        var other = createDuration(input, value);
 
-            data.days = days;
-            data.months = months;
-            data.years = years;
-        },
+        duration._milliseconds += direction * other._milliseconds;
+        duration._days += direction * other._days;
+        duration._months += direction * other._months;
 
-        abs : function () {
-            this._milliseconds = Math.abs(this._milliseconds);
-            this._days = Math.abs(this._days);
-            this._months = Math.abs(this._months);
+        return duration._bubble();
+    }
 
-            this._data.milliseconds = Math.abs(this._data.milliseconds);
-            this._data.seconds = Math.abs(this._data.seconds);
-            this._data.minutes = Math.abs(this._data.minutes);
-            this._data.hours = Math.abs(this._data.hours);
-            this._data.months = Math.abs(this._data.months);
-            this._data.years = Math.abs(this._data.years);
+    // supports only 2.0-style add(1, 's') or add(duration)
+    function add$1(input, value) {
+        return addSubtract$1(this, input, value, 1);
+    }
 
-            return this;
-        },
+    // supports only 2.0-style subtract(1, 's') or subtract(duration)
+    function subtract$1(input, value) {
+        return addSubtract$1(this, input, value, -1);
+    }
 
-        weeks : function () {
-            return absRound(this.days() / 7);
-        },
+    function absCeil(number) {
+        if (number < 0) {
+            return Math.floor(number);
+        } else {
+            return Math.ceil(number);
+        }
+    }
 
-        valueOf : function () {
-            return this._milliseconds +
-              this._days * 864e5 +
-              (this._months % 12) * 2592e6 +
-              toInt(this._months / 12) * 31536e6;
-        },
+    function bubble() {
+        var milliseconds = this._milliseconds,
+            days = this._days,
+            months = this._months,
+            data = this._data,
+            seconds,
+            minutes,
+            hours,
+            years,
+            monthsFromDays;
+
+        // if we have a mix of positive and negative values, bubble down first
+        // check: https://github.com/moment/moment/issues/2166
+        if (
+            !(
+                (milliseconds >= 0 && days >= 0 && months >= 0) ||
+                (milliseconds <= 0 && days <= 0 && months <= 0)
+            )
+        ) {
+            milliseconds += absCeil(monthsToDays(months) + days) * 864e5;
+            days = 0;
+            months = 0;
+        }
 
-        humanize : function (withSuffix) {
-            var output = relativeTime(this, !withSuffix, this.localeData());
+        // The following code bubbles up values, see the tests for
+        // examples of what that means.
+        data.milliseconds = milliseconds % 1000;
 
-            if (withSuffix) {
-                output = this.localeData().pastFuture(+this, output);
-            }
+        seconds = absFloor(milliseconds / 1000);
+        data.seconds = seconds % 60;
 
-            return this.localeData().postformat(output);
-        },
+        minutes = absFloor(seconds / 60);
+        data.minutes = minutes % 60;
 
-        add : function (input, val) {
-            // supports only 2.0-style add(1, 's') or add(moment)
-            var dur = moment.duration(input, val);
+        hours = absFloor(minutes / 60);
+        data.hours = hours % 24;
 
-            this._milliseconds += dur._milliseconds;
-            this._days += dur._days;
-            this._months += dur._months;
+        days += absFloor(hours / 24);
 
-            this._bubble();
+        // convert days to months
+        monthsFromDays = absFloor(daysToMonths(days));
+        months += monthsFromDays;
+        days -= absCeil(monthsToDays(monthsFromDays));
 
-            return this;
-        },
+        // 12 months -> 1 year
+        years = absFloor(months / 12);
+        months %= 12;
 
-        subtract : function (input, val) {
-            var dur = moment.duration(input, val);
+        data.days = days;
+        data.months = months;
+        data.years = years;
 
-            this._milliseconds -= dur._milliseconds;
-            this._days -= dur._days;
-            this._months -= dur._months;
+        return this;
+    }
 
-            this._bubble();
+    function daysToMonths(days) {
+        // 400 years have 146097 days (taking into account leap year rules)
+        // 400 years have 12 months === 4800
+        return (days * 4800) / 146097;
+    }
 
-            return this;
-        },
+    function monthsToDays(months) {
+        // the reverse of daysToMonths
+        return (months * 146097) / 4800;
+    }
 
-        get : function (units) {
-            units = normalizeUnits(units);
-            return this[units.toLowerCase() + 's']();
-        },
+    function as(units) {
+        if (!this.isValid()) {
+            return NaN;
+        }
+        var days,
+            months,
+            milliseconds = this._milliseconds;
 
-        as : function (units) {
-            var days, months;
-            units = normalizeUnits(units);
+        units = normalizeUnits(units);
 
-            if (units === 'month' || units === 'year') {
-                days = this._days + this._milliseconds / 864e5;
-                months = this._months + daysToYears(days) * 12;
-                return units === 'month' ? months : months / 12;
-            } else {
-                // handle milliseconds separately because of floating point math errors (issue #1867)
-                days = this._days + Math.round(yearsToDays(this._months / 12));
-                switch (units) {
-                    case 'week': return days / 7 + this._milliseconds / 6048e5;
-                    case 'day': return days + this._milliseconds / 864e5;
-                    case 'hour': return days * 24 + this._milliseconds / 36e5;
-                    case 'minute': return days * 24 * 60 + this._milliseconds / 6e4;
-                    case 'second': return days * 24 * 60 * 60 + this._milliseconds / 1000;
-                    // Math.floor prevents floating point math errors here
-                    case 'millisecond': return Math.floor(days * 24 * 60 * 60 * 1000) + this._milliseconds;
-                    default: throw new Error('Unknown unit ' + units);
-                }
+        if (units === 'month' || units === 'quarter' || units === 'year') {
+            days = this._days + milliseconds / 864e5;
+            months = this._months + daysToMonths(days);
+            switch (units) {
+                case 'month':
+                    return months;
+                case 'quarter':
+                    return months / 3;
+                case 'year':
+                    return months / 12;
             }
-        },
+        } else {
+            // handle milliseconds separately because of floating point math errors (issue #1867)
+            days = this._days + Math.round(monthsToDays(this._months));
+            switch (units) {
+                case 'week':
+                    return days / 7 + milliseconds / 6048e5;
+                case 'day':
+                    return days + milliseconds / 864e5;
+                case 'hour':
+                    return days * 24 + milliseconds / 36e5;
+                case 'minute':
+                    return days * 1440 + milliseconds / 6e4;
+                case 'second':
+                    return days * 86400 + milliseconds / 1000;
+                // Math.floor prevents floating point math errors here
+                case 'millisecond':
+                    return Math.floor(days * 864e5) + milliseconds;
+                default:
+                    throw new Error('Unknown unit ' + units);
+            }
+        }
+    }
 
-        lang : moment.fn.lang,
-        locale : moment.fn.locale,
+    // TODO: Use this.as('ms')?
+    function valueOf$1() {
+        if (!this.isValid()) {
+            return NaN;
+        }
+        return (
+            this._milliseconds +
+            this._days * 864e5 +
+            (this._months % 12) * 2592e6 +
+            toInt(this._months / 12) * 31536e6
+        );
+    }
 
-        toIsoString : deprecate(
-            'toIsoString() is deprecated. Please use toISOString() instead ' +
-            '(notice the capitals)',
-            function () {
-                return this.toISOString();
-            }
-        ),
+    function makeAs(alias) {
+        return function () {
+            return this.as(alias);
+        };
+    }
 
-        toISOString : function () {
-            // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
-            var years = Math.abs(this.years()),
-                months = Math.abs(this.months()),
-                days = Math.abs(this.days()),
-                hours = Math.abs(this.hours()),
-                minutes = Math.abs(this.minutes()),
-                seconds = Math.abs(this.seconds() + this.milliseconds() / 1000);
-
-            if (!this.asSeconds()) {
-                // this is the same as C#'s (Noda) and python (isodate)...
-                // but not other JS (goog.date)
-                return 'P0D';
-            }
-
-            return (this.asSeconds() < 0 ? '-' : '') +
-                'P' +
-                (years ? years + 'Y' : '') +
-                (months ? months + 'M' : '') +
-                (days ? days + 'D' : '') +
-                ((hours || minutes || seconds) ? 'T' : '') +
-                (hours ? hours + 'H' : '') +
-                (minutes ? minutes + 'M' : '') +
-                (seconds ? seconds + 'S' : '');
-        },
+    var asMilliseconds = makeAs('ms'),
+        asSeconds = makeAs('s'),
+        asMinutes = makeAs('m'),
+        asHours = makeAs('h'),
+        asDays = makeAs('d'),
+        asWeeks = makeAs('w'),
+        asMonths = makeAs('M'),
+        asQuarters = makeAs('Q'),
+        asYears = makeAs('y');
+
+    function clone$1() {
+        return createDuration(this);
+    }
 
-        localeData : function () {
-            return this._locale;
-        },
+    function get$2(units) {
+        units = normalizeUnits(units);
+        return this.isValid() ? this[units + 's']() : NaN;
+    }
 
-        toJSON : function () {
-            return this.toISOString();
-        }
-    });
+    function makeGetter(name) {
+        return function () {
+            return this.isValid() ? this._data[name] : NaN;
+        };
+    }
+
+    var milliseconds = makeGetter('milliseconds'),
+        seconds = makeGetter('seconds'),
+        minutes = makeGetter('minutes'),
+        hours = makeGetter('hours'),
+        days = makeGetter('days'),
+        months = makeGetter('months'),
+        years = makeGetter('years');
 
-    moment.duration.fn.toString = moment.duration.fn.toISOString;
+    function weeks() {
+        return absFloor(this.days() / 7);
+    }
 
-    function makeDurationGetter(name) {
-        moment.duration.fn[name] = function () {
-            return this._data[name];
+    var round = Math.round,
+        thresholds = {
+            ss: 44, // a few seconds to seconds
+            s: 45, // seconds to minute
+            m: 45, // minutes to hour
+            h: 22, // hours to day
+            d: 26, // days to month/week
+            w: null, // weeks to month
+            M: 11, // months to year
         };
+
+    // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
+    function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
+        return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
     }
 
-    for (i in unitMillisecondFactors) {
-        if (hasOwnProp(unitMillisecondFactors, i)) {
-            makeDurationGetter(i.toLowerCase());
+    function relativeTime$1(posNegDuration, withoutSuffix, thresholds, locale) {
+        var duration = createDuration(posNegDuration).abs(),
+            seconds = round(duration.as('s')),
+            minutes = round(duration.as('m')),
+            hours = round(duration.as('h')),
+            days = round(duration.as('d')),
+            months = round(duration.as('M')),
+            weeks = round(duration.as('w')),
+            years = round(duration.as('y')),
+            a =
+                (seconds <= thresholds.ss && ['s', seconds]) ||
+                (seconds < thresholds.s && ['ss', seconds]) ||
+                (minutes <= 1 && ['m']) ||
+                (minutes < thresholds.m && ['mm', minutes]) ||
+                (hours <= 1 && ['h']) ||
+                (hours < thresholds.h && ['hh', hours]) ||
+                (days <= 1 && ['d']) ||
+                (days < thresholds.d && ['dd', days]);
+
+        if (thresholds.w != null) {
+            a =
+                a ||
+                (weeks <= 1 && ['w']) ||
+                (weeks < thresholds.w && ['ww', weeks]);
         }
+        a = a ||
+            (months <= 1 && ['M']) ||
+            (months < thresholds.M && ['MM', months]) ||
+            (years <= 1 && ['y']) || ['yy', years];
+
+        a[2] = withoutSuffix;
+        a[3] = +posNegDuration > 0;
+        a[4] = locale;
+        return substituteTimeAgo.apply(null, a);
     }
 
-    moment.duration.fn.asMilliseconds = function () {
-        return this.as('ms');
-    };
-    moment.duration.fn.asSeconds = function () {
-        return this.as('s');
-    };
-    moment.duration.fn.asMinutes = function () {
-        return this.as('m');
-    };
-    moment.duration.fn.asHours = function () {
-        return this.as('h');
-    };
-    moment.duration.fn.asDays = function () {
-        return this.as('d');
-    };
-    moment.duration.fn.asWeeks = function () {
-        return this.as('weeks');
-    };
-    moment.duration.fn.asMonths = function () {
-        return this.as('M');
-    };
-    moment.duration.fn.asYears = function () {
-        return this.as('y');
-    };
+    // This function allows you to set the rounding function for relative time strings
+    function getSetRelativeTimeRounding(roundingFunction) {
+        if (roundingFunction === undefined) {
+            return round;
+        }
+        if (typeof roundingFunction === 'function') {
+            round = roundingFunction;
+            return true;
+        }
+        return false;
+    }
+
+    // This function allows you to set a threshold for relative time strings
+    function getSetRelativeTimeThreshold(threshold, limit) {
+        if (thresholds[threshold] === undefined) {
+            return false;
+        }
+        if (limit === undefined) {
+            return thresholds[threshold];
+        }
+        thresholds[threshold] = limit;
+        if (threshold === 's') {
+            thresholds.ss = limit - 1;
+        }
+        return true;
+    }
 
-    /************************************
-        Default Locale
-    ************************************/
+    function humanize(argWithSuffix, argThresholds) {
+        if (!this.isValid()) {
+            return this.localeData().invalidDate();
+        }
 
+        var withSuffix = false,
+            th = thresholds,
+            locale,
+            output;
 
-    // Set default locale, other locale will inherit from English.
-    moment.locale('en', {
-        ordinalParse: /\d{1,2}(th|st|nd|rd)/,
-        ordinal : function (number) {
-            var b = number % 10,
-                output = (toInt(number % 100 / 10) === 1) ? 'th' :
-                (b === 1) ? 'st' :
-                (b === 2) ? 'nd' :
-                (b === 3) ? 'rd' : 'th';
-            return number + output;
+        if (typeof argWithSuffix === 'object') {
+            argThresholds = argWithSuffix;
+            argWithSuffix = false;
+        }
+        if (typeof argWithSuffix === 'boolean') {
+            withSuffix = argWithSuffix;
+        }
+        if (typeof argThresholds === 'object') {
+            th = Object.assign({}, thresholds, argThresholds);
+            if (argThresholds.s != null && argThresholds.ss == null) {
+                th.ss = argThresholds.s - 1;
+            }
         }
-    });
 
-    /* EMBED_LOCALES */
+        locale = this.localeData();
+        output = relativeTime$1(this, !withSuffix, th, locale);
 
-    /************************************
-        Exposing Moment
-    ************************************/
+        if (withSuffix) {
+            output = locale.pastFuture(+this, output);
+        }
 
-    function makeGlobal(shouldDeprecate) {
-        /*global ender:false */
-        if (typeof ender !== 'undefined') {
-            return;
+        return locale.postformat(output);
+    }
+
+    var abs$1 = Math.abs;
+
+    function sign(x) {
+        return (x > 0) - (x < 0) || +x;
+    }
+
+    function toISOString$1() {
+        // for ISO strings we do not use the normal bubbling rules:
+        //  * milliseconds bubble up until they become hours
+        //  * days do not bubble at all
+        //  * months bubble up until they become years
+        // This is because there is no context-free conversion between hours and days
+        // (think of clock changes)
+        // and also not between days and months (28-31 days per month)
+        if (!this.isValid()) {
+            return this.localeData().invalidDate();
         }
-        oldGlobalMoment = globalScope.moment;
-        if (shouldDeprecate) {
-            globalScope.moment = deprecate(
-                    'Accessing Moment through the global scope is ' +
-                    'deprecated, and will be removed in an upcoming ' +
-                    'release.',
-                    moment);
-        } else {
-            globalScope.moment = moment;
+
+        var seconds = abs$1(this._milliseconds) / 1000,
+            days = abs$1(this._days),
+            months = abs$1(this._months),
+            minutes,
+            hours,
+            years,
+            s,
+            total = this.asSeconds(),
+            totalSign,
+            ymSign,
+            daysSign,
+            hmsSign;
+
+        if (!total) {
+            // this is the same as C#'s (Noda) and python (isodate)...
+            // but not other JS (goog.date)
+            return 'P0D';
         }
+
+        // 3600 seconds -> 60 minutes -> 1 hour
+        minutes = absFloor(seconds / 60);
+        hours = absFloor(minutes / 60);
+        seconds %= 60;
+        minutes %= 60;
+
+        // 12 months -> 1 year
+        years = absFloor(months / 12);
+        months %= 12;
+
+        // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
+        s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : '';
+
+        totalSign = total < 0 ? '-' : '';
+        ymSign = sign(this._months) !== sign(total) ? '-' : '';
+        daysSign = sign(this._days) !== sign(total) ? '-' : '';
+        hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : '';
+
+        return (
+            totalSign +
+            'P' +
+            (years ? ymSign + years + 'Y' : '') +
+            (months ? ymSign + months + 'M' : '') +
+            (days ? daysSign + days + 'D' : '') +
+            (hours || minutes || seconds ? 'T' : '') +
+            (hours ? hmsSign + hours + 'H' : '') +
+            (minutes ? hmsSign + minutes + 'M' : '') +
+            (seconds ? hmsSign + s + 'S' : '')
+        );
     }
 
-    // CommonJS module is defined
-    if (hasModule) {
-        module.exports = moment;
-    } else if (typeof define === 'function' && define.amd) {
-        define(function (require, exports, module) {
-            if (module.config && module.config() && module.config().noGlobal === true) {
-                // release the global variable
-                globalScope.moment = oldGlobalMoment;
-            }
+    var proto$2 = Duration.prototype;
+
+    proto$2.isValid = isValid$1;
+    proto$2.abs = abs;
+    proto$2.add = add$1;
+    proto$2.subtract = subtract$1;
+    proto$2.as = as;
+    proto$2.asMilliseconds = asMilliseconds;
+    proto$2.asSeconds = asSeconds;
+    proto$2.asMinutes = asMinutes;
+    proto$2.asHours = asHours;
+    proto$2.asDays = asDays;
+    proto$2.asWeeks = asWeeks;
+    proto$2.asMonths = asMonths;
+    proto$2.asQuarters = asQuarters;
+    proto$2.asYears = asYears;
+    proto$2.valueOf = valueOf$1;
+    proto$2._bubble = bubble;
+    proto$2.clone = clone$1;
+    proto$2.get = get$2;
+    proto$2.milliseconds = milliseconds;
+    proto$2.seconds = seconds;
+    proto$2.minutes = minutes;
+    proto$2.hours = hours;
+    proto$2.days = days;
+    proto$2.weeks = weeks;
+    proto$2.months = months;
+    proto$2.years = years;
+    proto$2.humanize = humanize;
+    proto$2.toISOString = toISOString$1;
+    proto$2.toString = toISOString$1;
+    proto$2.toJSON = toISOString$1;
+    proto$2.locale = locale;
+    proto$2.localeData = localeData;
+
+    proto$2.toIsoString = deprecate(
+        'toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)',
+        toISOString$1
+    );
+    proto$2.lang = lang;
 
-            return moment;
-        });
-        makeGlobal(true);
-    } else {
-        makeGlobal();
-    }
-}).call(this);
+    // FORMATTING
+
+    addFormatToken('X', 0, 0, 'unix');
+    addFormatToken('x', 0, 0, 'valueOf');
+
+    // PARSING
+
+    addRegexToken('x', matchSigned);
+    addRegexToken('X', matchTimestamp);
+    addParseToken('X', function (input, array, config) {
+        config._d = new Date(parseFloat(input) * 1000);
+    });
+    addParseToken('x', function (input, array, config) {
+        config._d = new Date(toInt(input));
+    });
+
+    //! moment.js
+
+    hooks.version = '2.29.2';
+
+    setHookCallback(createLocal);
+
+    hooks.fn = proto;
+    hooks.min = min;
+    hooks.max = max;
+    hooks.now = now;
+    hooks.utc = createUTC;
+    hooks.unix = createUnix;
+    hooks.months = listMonths;
+    hooks.isDate = isDate;
+    hooks.locale = getSetGlobalLocale;
+    hooks.invalid = createInvalid;
+    hooks.duration = createDuration;
+    hooks.isMoment = isMoment;
+    hooks.weekdays = listWeekdays;
+    hooks.parseZone = createInZone;
+    hooks.localeData = getLocale;
+    hooks.isDuration = isDuration;
+    hooks.monthsShort = listMonthsShort;
+    hooks.weekdaysMin = listWeekdaysMin;
+    hooks.defineLocale = defineLocale;
+    hooks.updateLocale = updateLocale;
+    hooks.locales = listLocales;
+    hooks.weekdaysShort = listWeekdaysShort;
+    hooks.normalizeUnits = normalizeUnits;
+    hooks.relativeTimeRounding = getSetRelativeTimeRounding;
+    hooks.relativeTimeThreshold = getSetRelativeTimeThreshold;
+    hooks.calendarFormat = getCalendarFormat;
+    hooks.prototype = proto;
+
+    // currently HTML5 input type only supports 24-hour formats
+    hooks.HTML5_FMT = {
+        DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // <input type="datetime-local" />
+        DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // <input type="datetime-local" step="1" />
+        DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // <input type="datetime-local" step="0.001" />
+        DATE: 'YYYY-MM-DD', // <input type="date" />
+        TIME: 'HH:mm', // <input type="time" />
+        TIME_SECONDS: 'HH:mm:ss', // <input type="time" step="1" />
+        TIME_MS: 'HH:mm:ss.SSS', // <input type="time" step="0.001" />
+        WEEK: 'GGGG-[W]WW', // <input type="week" />
+        MONTH: 'YYYY-MM', // <input type="month" />
+    };
+
+    return hooks;
+
+})));
index 024d488fbc8bda74f0115e1d5116528b3c1128c6..c04a26c950aec0c5e48c312b2ac3b13e01dec7eb 100644 (file)
@@ -1,7 +1,2 @@
-//! moment.js
-//! version : 2.9.0
-//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
-//! license : MIT
-//! momentjs.com
-(function(a){function b(a,b,c){switch(arguments.length){case 2:return null!=a?a:b;case 3:return null!=a?a:null!=b?b:c;default:throw new Error("Implement me")}}function c(a,b){return Bb.call(a,b)}function d(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function e(a){vb.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+a)}function f(a,b){var c=!0;return o(function(){return c&&(e(a),c=!1),b.apply(this,arguments)},b)}function g(a,b){sc[a]||(e(b),sc[a]=!0)}function h(a,b){return function(c){return r(a.call(this,c),b)}}function i(a,b){return function(c){return this.localeData().ordinal(a.call(this,c),b)}}function j(a,b){var c,d,e=12*(b.year()-a.year())+(b.month()-a.month()),f=a.clone().add(e,"months");return 0>b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)}function k(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function l(){}function m(a,b){b!==!1&&H(a),p(this,a),this._d=new Date(+a._d),uc===!1&&(uc=!0,vb.updateOffset(this),uc=!1)}function n(a){var b=A(a),c=b.year||0,d=b.quarter||0,e=b.month||0,f=b.week||0,g=b.day||0,h=b.hour||0,i=b.minute||0,j=b.second||0,k=b.millisecond||0;this._milliseconds=+k+1e3*j+6e4*i+36e5*h,this._days=+g+7*f,this._months=+e+3*d+12*c,this._data={},this._locale=vb.localeData(),this._bubble()}function o(a,b){for(var d in b)c(b,d)&&(a[d]=b[d]);return c(b,"toString")&&(a.toString=b.toString),c(b,"valueOf")&&(a.valueOf=b.valueOf),a}function p(a,b){var c,d,e;if("undefined"!=typeof b._isAMomentObject&&(a._isAMomentObject=b._isAMomentObject),"undefined"!=typeof b._i&&(a._i=b._i),"undefined"!=typeof b._f&&(a._f=b._f),"undefined"!=typeof b._l&&(a._l=b._l),"undefined"!=typeof b._strict&&(a._strict=b._strict),"undefined"!=typeof b._tzm&&(a._tzm=b._tzm),"undefined"!=typeof b._isUTC&&(a._isUTC=b._isUTC),"undefined"!=typeof b._offset&&(a._offset=b._offset),"undefined"!=typeof b._pf&&(a._pf=b._pf),"undefined"!=typeof b._locale&&(a._locale=b._locale),Kb.length>0)for(c in Kb)d=Kb[c],e=b[d],"undefined"!=typeof e&&(a[d]=e);return a}function q(a){return 0>a?Math.ceil(a):Math.floor(a)}function r(a,b,c){for(var d=""+Math.abs(a),e=a>=0;d.length<b;)d="0"+d;return(e?c?"+":"":"-")+d}function s(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function t(a,b){var c;return b=M(b,a),a.isBefore(b)?c=s(a,b):(c=s(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c}function u(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||(g(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period)."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=vb.duration(c,d),v(this,e,a),this}}function v(a,b,c,d){var e=b._milliseconds,f=b._days,g=b._months;d=null==d?!0:d,e&&a._d.setTime(+a._d+e*c),f&&pb(a,"Date",ob(a,"Date")+f*c),g&&nb(a,ob(a,"Month")+g*c),d&&vb.updateOffset(a,f||g)}function w(a){return"[object Array]"===Object.prototype.toString.call(a)}function x(a){return"[object Date]"===Object.prototype.toString.call(a)||a instanceof Date}function y(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&C(a[d])!==C(b[d]))&&g++;return g+f}function z(a){if(a){var b=a.toLowerCase().replace(/(.)s$/,"$1");a=lc[a]||mc[b]||b}return a}function A(a){var b,d,e={};for(d in a)c(a,d)&&(b=z(d),b&&(e[b]=a[d]));return e}function B(b){var c,d;if(0===b.indexOf("week"))c=7,d="day";else{if(0!==b.indexOf("month"))return;c=12,d="month"}vb[b]=function(e,f){var g,h,i=vb._locale[b],j=[];if("number"==typeof e&&(f=e,e=a),h=function(a){var b=vb().utc().set(d,a);return i.call(vb._locale,b,e||"")},null!=f)return h(f);for(g=0;c>g;g++)j.push(h(g));return j}}function C(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function D(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function E(a,b,c){return jb(vb([a,11,31+b-c]),b,c).week}function F(a){return G(a)?366:365}function G(a){return a%4===0&&a%100!==0||a%400===0}function H(a){var b;a._a&&-2===a._pf.overflow&&(b=a._a[Db]<0||a._a[Db]>11?Db:a._a[Eb]<1||a._a[Eb]>D(a._a[Cb],a._a[Db])?Eb:a._a[Fb]<0||a._a[Fb]>24||24===a._a[Fb]&&(0!==a._a[Gb]||0!==a._a[Hb]||0!==a._a[Ib])?Fb:a._a[Gb]<0||a._a[Gb]>59?Gb:a._a[Hb]<0||a._a[Hb]>59?Hb:a._a[Ib]<0||a._a[Ib]>999?Ib:-1,a._pf._overflowDayOfYear&&(Cb>b||b>Eb)&&(b=Eb),a._pf.overflow=b)}function I(b){return null==b._isValid&&(b._isValid=!isNaN(b._d.getTime())&&b._pf.overflow<0&&!b._pf.empty&&!b._pf.invalidMonth&&!b._pf.nullInput&&!b._pf.invalidFormat&&!b._pf.userInvalidated,b._strict&&(b._isValid=b._isValid&&0===b._pf.charsLeftOver&&0===b._pf.unusedTokens.length&&b._pf.bigHour===a)),b._isValid}function J(a){return a?a.toLowerCase().replace("_","-"):a}function K(a){for(var b,c,d,e,f=0;f<a.length;){for(e=J(a[f]).split("-"),b=e.length,c=J(a[f+1]),c=c?c.split("-"):null;b>0;){if(d=L(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&y(e,c,!0)>=b-1)break;b--}f++}return null}function L(a){var b=null;if(!Jb[a]&&Lb)try{b=vb.locale(),require("./locale/"+a),vb.locale(b)}catch(c){}return Jb[a]}function M(a,b){var c,d;return b._isUTC?(c=b.clone(),d=(vb.isMoment(a)||x(a)?+a:+vb(a))-+c,c._d.setTime(+c._d+d),vb.updateOffset(c,!1),c):vb(a).local()}function N(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function O(a){var b,c,d=a.match(Pb);for(b=0,c=d.length;c>b;b++)d[b]=rc[d[b]]?rc[d[b]]:N(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function P(a,b){return a.isValid()?(b=Q(b,a.localeData()),nc[b]||(nc[b]=O(b)),nc[b](a)):a.localeData().invalidDate()}function Q(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(Qb.lastIndex=0;d>=0&&Qb.test(a);)a=a.replace(Qb,c),Qb.lastIndex=0,d-=1;return a}function R(a,b){var c,d=b._strict;switch(a){case"Q":return _b;case"DDDD":return bc;case"YYYY":case"GGGG":case"gggg":return d?cc:Tb;case"Y":case"G":case"g":return ec;case"YYYYYY":case"YYYYY":case"GGGGG":case"ggggg":return d?dc:Ub;case"S":if(d)return _b;case"SS":if(d)return ac;case"SSS":if(d)return bc;case"DDD":return Sb;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return Wb;case"a":case"A":return b._locale._meridiemParse;case"x":return Zb;case"X":return $b;case"Z":case"ZZ":return Xb;case"T":return Yb;case"SSSS":return Vb;case"MM":case"DD":case"YY":case"GG":case"gg":case"HH":case"hh":case"mm":case"ss":case"ww":case"WW":return d?ac:Rb;case"M":case"D":case"d":case"H":case"h":case"m":case"s":case"w":case"W":case"e":case"E":return Rb;case"Do":return d?b._locale._ordinalParse:b._locale._ordinalParseLenient;default:return c=new RegExp($(Z(a.replace("\\","")),"i"))}}function S(a){a=a||"";var b=a.match(Xb)||[],c=b[b.length-1]||[],d=(c+"").match(jc)||["-",0,0],e=+(60*d[1])+C(d[2]);return"+"===d[0]?e:-e}function T(a,b,c){var d,e=c._a;switch(a){case"Q":null!=b&&(e[Db]=3*(C(b)-1));break;case"M":case"MM":null!=b&&(e[Db]=C(b)-1);break;case"MMM":case"MMMM":d=c._locale.monthsParse(b,a,c._strict),null!=d?e[Db]=d:c._pf.invalidMonth=b;break;case"D":case"DD":null!=b&&(e[Eb]=C(b));break;case"Do":null!=b&&(e[Eb]=C(parseInt(b.match(/\d{1,2}/)[0],10)));break;case"DDD":case"DDDD":null!=b&&(c._dayOfYear=C(b));break;case"YY":e[Cb]=vb.parseTwoDigitYear(b);break;case"YYYY":case"YYYYY":case"YYYYYY":e[Cb]=C(b);break;case"a":case"A":c._meridiem=b;break;case"h":case"hh":c._pf.bigHour=!0;case"H":case"HH":e[Fb]=C(b);break;case"m":case"mm":e[Gb]=C(b);break;case"s":case"ss":e[Hb]=C(b);break;case"S":case"SS":case"SSS":case"SSSS":e[Ib]=C(1e3*("0."+b));break;case"x":c._d=new Date(C(b));break;case"X":c._d=new Date(1e3*parseFloat(b));break;case"Z":case"ZZ":c._useUTC=!0,c._tzm=S(b);break;case"dd":case"ddd":case"dddd":d=c._locale.weekdaysParse(b),null!=d?(c._w=c._w||{},c._w.d=d):c._pf.invalidWeekday=b;break;case"w":case"ww":case"W":case"WW":case"d":case"e":case"E":a=a.substr(0,1);case"gggg":case"GGGG":case"GGGGG":a=a.substr(0,2),b&&(c._w=c._w||{},c._w[a]=C(b));break;case"gg":case"GG":c._w=c._w||{},c._w[a]=vb.parseTwoDigitYear(b)}}function U(a){var c,d,e,f,g,h,i;c=a._w,null!=c.GG||null!=c.W||null!=c.E?(g=1,h=4,d=b(c.GG,a._a[Cb],jb(vb(),1,4).year),e=b(c.W,1),f=b(c.E,1)):(g=a._locale._week.dow,h=a._locale._week.doy,d=b(c.gg,a._a[Cb],jb(vb(),g,h).year),e=b(c.w,1),null!=c.d?(f=c.d,g>f&&++e):f=null!=c.e?c.e+g:g),i=kb(d,e,f,h,g),a._a[Cb]=i.year,a._dayOfYear=i.dayOfYear}function V(a){var c,d,e,f,g=[];if(!a._d){for(e=X(a),a._w&&null==a._a[Eb]&&null==a._a[Db]&&U(a),a._dayOfYear&&(f=b(a._a[Cb],e[Cb]),a._dayOfYear>F(f)&&(a._pf._overflowDayOfYear=!0),d=fb(f,0,a._dayOfYear),a._a[Db]=d.getUTCMonth(),a._a[Eb]=d.getUTCDate()),c=0;3>c&&null==a._a[c];++c)a._a[c]=g[c]=e[c];for(;7>c;c++)a._a[c]=g[c]=null==a._a[c]?2===c?1:0:a._a[c];24===a._a[Fb]&&0===a._a[Gb]&&0===a._a[Hb]&&0===a._a[Ib]&&(a._nextDay=!0,a._a[Fb]=0),a._d=(a._useUTC?fb:eb).apply(null,g),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[Fb]=24)}}function W(a){var b;a._d||(b=A(a._i),a._a=[b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],V(a))}function X(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function Y(b){if(b._f===vb.ISO_8601)return void ab(b);b._a=[],b._pf.empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,j=0;for(e=Q(b._f,b._locale).match(Pb)||[],c=0;c<e.length;c++)f=e[c],d=(h.match(R(f,b))||[])[0],d&&(g=h.substr(0,h.indexOf(d)),g.length>0&&b._pf.unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),j+=d.length),rc[f]?(d?b._pf.empty=!1:b._pf.unusedTokens.push(f),T(f,d,b)):b._strict&&!d&&b._pf.unusedTokens.push(f);b._pf.charsLeftOver=i-j,h.length>0&&b._pf.unusedInput.push(h),b._pf.bigHour===!0&&b._a[Fb]<=12&&(b._pf.bigHour=a),b._a[Fb]=k(b._locale,b._a[Fb],b._meridiem),V(b),H(b)}function Z(a){return a.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e})}function $(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function _(a){var b,c,e,f,g;if(0===a._f.length)return a._pf.invalidFormat=!0,void(a._d=new Date(0/0));for(f=0;f<a._f.length;f++)g=0,b=p({},a),null!=a._useUTC&&(b._useUTC=a._useUTC),b._pf=d(),b._f=a._f[f],Y(b),I(b)&&(g+=b._pf.charsLeftOver,g+=10*b._pf.unusedTokens.length,b._pf.score=g,(null==e||e>g)&&(e=g,c=b));o(a,c||b)}function ab(a){var b,c,d=a._i,e=fc.exec(d);if(e){for(a._pf.iso=!0,b=0,c=hc.length;c>b;b++)if(hc[b][1].exec(d)){a._f=hc[b][0]+(e[6]||" ");break}for(b=0,c=ic.length;c>b;b++)if(ic[b][1].exec(d)){a._f+=ic[b][0];break}d.match(Xb)&&(a._f+="Z"),Y(a)}else a._isValid=!1}function bb(a){ab(a),a._isValid===!1&&(delete a._isValid,vb.createFromInputFallback(a))}function cb(a,b){var c,d=[];for(c=0;c<a.length;++c)d.push(b(a[c],c));return d}function db(b){var c,d=b._i;d===a?b._d=new Date:x(d)?b._d=new Date(+d):null!==(c=Mb.exec(d))?b._d=new Date(+c[1]):"string"==typeof d?bb(b):w(d)?(b._a=cb(d.slice(0),function(a){return parseInt(a,10)}),V(b)):"object"==typeof d?W(b):"number"==typeof d?b._d=new Date(d):vb.createFromInputFallback(b)}function eb(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function fb(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function gb(a,b){if("string"==typeof a)if(isNaN(a)){if(a=b.weekdaysParse(a),"number"!=typeof a)return null}else a=parseInt(a,10);return a}function hb(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function ib(a,b,c){var d=vb.duration(a).abs(),e=Ab(d.as("s")),f=Ab(d.as("m")),g=Ab(d.as("h")),h=Ab(d.as("d")),i=Ab(d.as("M")),j=Ab(d.as("y")),k=e<oc.s&&["s",e]||1===f&&["m"]||f<oc.m&&["mm",f]||1===g&&["h"]||g<oc.h&&["hh",g]||1===h&&["d"]||h<oc.d&&["dd",h]||1===i&&["M"]||i<oc.M&&["MM",i]||1===j&&["y"]||["yy",j];return k[2]=b,k[3]=+a>0,k[4]=c,hb.apply({},k)}function jb(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=vb(a).add(f,"d"),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function kb(a,b,c,d,e){var f,g,h=fb(a,0,1).getUTCDay();return h=0===h?7:h,c=null!=c?c:e,f=e-h+(h>d?7:0)-(e>h?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:F(a-1)+g}}function lb(b){var c,d=b._i,e=b._f;return b._locale=b._locale||vb.localeData(b._l),null===d||e===a&&""===d?vb.invalid({nullInput:!0}):("string"==typeof d&&(b._i=d=b._locale.preparse(d)),vb.isMoment(d)?new m(d,!0):(e?w(e)?_(b):Y(b):db(b),c=new m(b),c._nextDay&&(c.add(1,"d"),c._nextDay=a),c))}function mb(a,b){var c,d;if(1===b.length&&w(b[0])&&(b=b[0]),!b.length)return vb();for(c=b[0],d=1;d<b.length;++d)b[d][a](c)&&(c=b[d]);return c}function nb(a,b){var c;return"string"==typeof b&&(b=a.localeData().monthsParse(b),"number"!=typeof b)?a:(c=Math.min(a.date(),D(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a)}function ob(a,b){return a._d["get"+(a._isUTC?"UTC":"")+b]()}function pb(a,b,c){return"Month"===b?nb(a,c):a._d["set"+(a._isUTC?"UTC":"")+b](c)}function qb(a,b){return function(c){return null!=c?(pb(this,a,c),vb.updateOffset(this,b),this):ob(this,a)}}function rb(a){return 400*a/146097}function sb(a){return 146097*a/400}function tb(a){vb.duration.fn[a]=function(){return this._data[a]}}function ub(a){"undefined"==typeof ender&&(wb=zb.moment,zb.moment=a?f("Accessing Moment through the global scope is deprecated, and will be removed in an upcoming release.",vb):vb)}for(var vb,wb,xb,yb="2.9.0",zb="undefined"==typeof global||"undefined"!=typeof window&&window!==global.window?this:global,Ab=Math.round,Bb=Object.prototype.hasOwnProperty,Cb=0,Db=1,Eb=2,Fb=3,Gb=4,Hb=5,Ib=6,Jb={},Kb=[],Lb="undefined"!=typeof module&&module&&module.exports,Mb=/^\/?Date\((\-?\d+)/i,Nb=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,Ob=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,Pb=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|x|X|zz?|ZZ?|.)/g,Qb=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Rb=/\d\d?/,Sb=/\d{1,3}/,Tb=/\d{1,4}/,Ub=/[+\-]?\d{1,6}/,Vb=/\d+/,Wb=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Xb=/Z|[\+\-]\d\d:?\d\d/gi,Yb=/T/i,Zb=/[\+\-]?\d+/,$b=/[\+\-]?\d+(\.\d{1,3})?/,_b=/\d/,ac=/\d\d/,bc=/\d{3}/,cc=/\d{4}/,dc=/[+-]?\d{6}/,ec=/[+-]?\d+/,fc=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,gc="YYYY-MM-DDTHH:mm:ssZ",hc=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],ic=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],jc=/([\+\-]|\d\d)/gi,kc=("Date|Hours|Minutes|Seconds|Milliseconds".split("|"),{Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6}),lc={ms:"millisecond",s:"second",m:"minute",h:"hour",d:"day",D:"date",w:"week",W:"isoWeek",M:"month",Q:"quarter",y:"year",DDD:"dayOfYear",e:"weekday",E:"isoWeekday",gg:"weekYear",GG:"isoWeekYear"},mc={dayofyear:"dayOfYear",isoweekday:"isoWeekday",isoweek:"isoWeek",weekyear:"weekYear",isoweekyear:"isoWeekYear"},nc={},oc={s:45,m:45,h:22,d:26,M:11},pc="DDD w W M D d".split(" "),qc="M D H h m s w W".split(" "),rc={M:function(){return this.month()+1},MMM:function(a){return this.localeData().monthsShort(this,a)},MMMM:function(a){return this.localeData().months(this,a)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(a){return this.localeData().weekdaysMin(this,a)},ddd:function(a){return this.localeData().weekdaysShort(this,a)},dddd:function(a){return this.localeData().weekdays(this,a)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return r(this.year()%100,2)},YYYY:function(){return r(this.year(),4)},YYYYY:function(){return r(this.year(),5)},YYYYYY:function(){var a=this.year(),b=a>=0?"+":"-";return b+r(Math.abs(a),6)},gg:function(){return r(this.weekYear()%100,2)},gggg:function(){return r(this.weekYear(),4)},ggggg:function(){return r(this.weekYear(),5)},GG:function(){return r(this.isoWeekYear()%100,2)},GGGG:function(){return r(this.isoWeekYear(),4)},GGGGG:function(){return r(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.localeData().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.localeData().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return C(this.milliseconds()/100)},SS:function(){return r(C(this.milliseconds()/10),2)},SSS:function(){return r(this.milliseconds(),3)},SSSS:function(){return r(this.milliseconds(),3)},Z:function(){var a=this.utcOffset(),b="+";return 0>a&&(a=-a,b="-"),b+r(C(a/60),2)+":"+r(C(a)%60,2)},ZZ:function(){var a=this.utcOffset(),b="+";return 0>a&&(a=-a,b="-"),b+r(C(a/60),2)+r(C(a)%60,2)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},x:function(){return this.valueOf()},X:function(){return this.unix()},Q:function(){return this.quarter()}},sc={},tc=["months","monthsShort","weekdays","weekdaysShort","weekdaysMin"],uc=!1;pc.length;)xb=pc.pop(),rc[xb+"o"]=i(rc[xb],xb);for(;qc.length;)xb=qc.pop(),rc[xb+xb]=h(rc[xb],2);rc.DDDD=h(rc.DDD,3),o(l.prototype,{set:function(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(a){return this._months[a.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(a){return this._monthsShort[a.month()]},monthsParse:function(a,b,c){var d,e,f;for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),d=0;12>d;d++){if(e=vb.utc([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(a){return this._weekdays[a.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(a){return this._weekdaysShort[a.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(a){return this._weekdaysMin[a.day()]},weekdaysParse:function(a){var b,c,d;for(this._weekdaysParse||(this._weekdaysParse=[]),b=0;7>b;b++)if(this._weekdaysParse[b]||(c=vb([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b},_longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY LT",LLLL:"dddd, MMMM D, YYYY LT"},longDateFormat:function(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b},isPM:function(a){return"p"===(a+"").toLowerCase().charAt(0)},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(a,b,c){var d=this._calendar[a];return"function"==typeof d?d.apply(b,[c]):d},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)},pastFuture:function(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)},ordinal:function(a){return this._ordinal.replace("%d",a)},_ordinal:"%d",_ordinalParse:/\d{1,2}/,preparse:function(a){return a},postformat:function(a){return a},week:function(a){return jb(a,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6},firstDayOfWeek:function(){return this._week.dow},firstDayOfYear:function(){return this._week.doy},_invalidDate:"Invalid date",invalidDate:function(){return this._invalidDate}}),vb=function(b,c,e,f){var g;return"boolean"==typeof e&&(f=e,e=a),g={},g._isAMomentObject=!0,g._i=b,g._f=c,g._l=e,g._strict=f,g._isUTC=!1,g._pf=d(),lb(g)},vb.suppressDeprecationWarnings=!1,vb.createFromInputFallback=f("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),vb.min=function(){var a=[].slice.call(arguments,0);return mb("isBefore",a)},vb.max=function(){var a=[].slice.call(arguments,0);return mb("isAfter",a)},vb.utc=function(b,c,e,f){var g;return"boolean"==typeof e&&(f=e,e=a),g={},g._isAMomentObject=!0,g._useUTC=!0,g._isUTC=!0,g._l=e,g._i=b,g._f=c,g._strict=f,g._pf=d(),lb(g).utc()},vb.unix=function(a){return vb(1e3*a)},vb.duration=function(a,b){var d,e,f,g,h=a,i=null;return vb.isDuration(a)?h={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(h={},b?h[b]=a:h.milliseconds=a):(i=Nb.exec(a))?(d="-"===i[1]?-1:1,h={y:0,d:C(i[Eb])*d,h:C(i[Fb])*d,m:C(i[Gb])*d,s:C(i[Hb])*d,ms:C(i[Ib])*d}):(i=Ob.exec(a))?(d="-"===i[1]?-1:1,f=function(a){var b=a&&parseFloat(a.replace(",","."));return(isNaN(b)?0:b)*d},h={y:f(i[2]),M:f(i[3]),d:f(i[4]),h:f(i[5]),m:f(i[6]),s:f(i[7]),w:f(i[8])}):null==h?h={}:"object"==typeof h&&("from"in h||"to"in h)&&(g=t(vb(h.from),vb(h.to)),h={},h.ms=g.milliseconds,h.M=g.months),e=new n(h),vb.isDuration(a)&&c(a,"_locale")&&(e._locale=a._locale),e},vb.version=yb,vb.defaultFormat=gc,vb.ISO_8601=function(){},vb.momentProperties=Kb,vb.updateOffset=function(){},vb.relativeTimeThreshold=function(b,c){return oc[b]===a?!1:c===a?oc[b]:(oc[b]=c,!0)},vb.lang=f("moment.lang is deprecated. Use moment.locale instead.",function(a,b){return vb.locale(a,b)}),vb.locale=function(a,b){var c;return a&&(c="undefined"!=typeof b?vb.defineLocale(a,b):vb.localeData(a),c&&(vb.duration._locale=vb._locale=c)),vb._locale._abbr},vb.defineLocale=function(a,b){return null!==b?(b.abbr=a,Jb[a]||(Jb[a]=new l),Jb[a].set(b),vb.locale(a),Jb[a]):(delete Jb[a],null)},vb.langData=f("moment.langData is deprecated. Use moment.localeData instead.",function(a){return vb.localeData(a)}),vb.localeData=function(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return vb._locale;if(!w(a)){if(b=L(a))return b;a=[a]}return K(a)},vb.isMoment=function(a){return a instanceof m||null!=a&&c(a,"_isAMomentObject")},vb.isDuration=function(a){return a instanceof n};for(xb=tc.length-1;xb>=0;--xb)B(tc[xb]);vb.normalizeUnits=function(a){return z(a)},vb.invalid=function(a){var b=vb.utc(0/0);return null!=a?o(b._pf,a):b._pf.userInvalidated=!0,b},vb.parseZone=function(){return vb.apply(null,arguments).parseZone()},vb.parseTwoDigitYear=function(a){return C(a)+(C(a)>68?1900:2e3)},vb.isDate=x,o(vb.fn=m.prototype,{clone:function(){return vb(this)},valueOf:function(){return+this._d-6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){var a=vb(this).utc();return 0<a.year()&&a.year()<=9999?"function"==typeof Date.prototype.toISOString?this.toDate().toISOString():P(a,"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]"):P(a,"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]")},toArray:function(){var a=this;return[a.year(),a.month(),a.date(),a.hours(),a.minutes(),a.seconds(),a.milliseconds()]},isValid:function(){return I(this)},isDSTShifted:function(){return this._a?this.isValid()&&y(this._a,(this._isUTC?vb.utc(this._a):vb(this._a)).toArray())>0:!1},parsingFlags:function(){return o({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(a){return this.utcOffset(0,a)},local:function(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(this._dateUtcOffset(),"m")),this},format:function(a){var b=P(this,a||vb.defaultFormat);return this.localeData().postformat(b)},add:u(1,"add"),subtract:u(-1,"subtract"),diff:function(a,b,c){var d,e,f=M(a,this),g=6e4*(f.utcOffset()-this.utcOffset());return b=z(b),"year"===b||"month"===b||"quarter"===b?(e=j(this,f),"quarter"===b?e/=3:"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:q(e)},from:function(a,b){return vb.duration({to:this,from:a}).locale(this.locale()).humanize(!b)},fromNow:function(a){return this.from(vb(),a)},calendar:function(a){var b=a||vb(),c=M(b,this).startOf("day"),d=this.diff(c,"days",!0),e=-6>d?"sameElse":-1>d?"lastWeek":0>d?"lastDay":1>d?"sameDay":2>d?"nextDay":7>d?"nextWeek":"sameElse";return this.format(this.localeData().calendar(e,this,vb(b)))},isLeapYear:function(){return G(this.year())},isDST:function(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},day:function(a){var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=gb(a,this.localeData()),this.add(a-b,"d")):b},month:qb("Month",!0),startOf:function(a){switch(a=z(a)){case"year":this.month(0);case"quarter":case"month":this.date(1);case"week":case"isoWeek":case"day":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return"week"===a?this.weekday(0):"isoWeek"===a&&this.isoWeekday(1),"quarter"===a&&this.month(3*Math.floor(this.month()/3)),this},endOf:function(b){return b=z(b),b===a||"millisecond"===b?this:this.startOf(b).add(1,"isoWeek"===b?"week":b).subtract(1,"ms")},isAfter:function(a,b){var c;return b=z("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=vb.isMoment(a)?a:vb(a),+this>+a):(c=vb.isMoment(a)?+a:+vb(a),c<+this.clone().startOf(b))},isBefore:function(a,b){var c;return b=z("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=vb.isMoment(a)?a:vb(a),+a>+this):(c=vb.isMoment(a)?+a:+vb(a),+this.clone().endOf(b)<c)},isBetween:function(a,b,c){return this.isAfter(a,c)&&this.isBefore(b,c)},isSame:function(a,b){var c;return b=z(b||"millisecond"),"millisecond"===b?(a=vb.isMoment(a)?a:vb(a),+this===+a):(c=+vb(a),+this.clone().startOf(b)<=c&&c<=+this.clone().endOf(b))},min:f("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(a){return a=vb.apply(null,arguments),this>a?this:a}),max:f("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(a){return a=vb.apply(null,arguments),a>this?this:a}),zone:f("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",function(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}),utcOffset:function(a,b){var c,d=this._offset||0;return null!=a?("string"==typeof a&&(a=S(a)),Math.abs(a)<16&&(a=60*a),!this._isUTC&&b&&(c=this._dateUtcOffset()),this._offset=a,this._isUTC=!0,null!=c&&this.add(c,"m"),d!==a&&(!b||this._changeInProgress?v(this,vb.duration(a-d,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,vb.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?d:this._dateUtcOffset()},isLocal:function(){return!this._isUTC},isUtcOffset:function(){return this._isUTC},isUtc:function(){return this._isUTC&&0===this._offset},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(S(this._i)),this},hasAlignedHourOffset:function(a){return a=a?vb(a).utcOffset():0,(this.utcOffset()-a)%60===0},daysInMonth:function(){return D(this.year(),this.month())},dayOfYear:function(a){var b=Ab((vb(this).startOf("day")-vb(this).startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")},quarter:function(a){return null==a?Math.ceil((this.month()+1)/3):this.month(3*(a-1)+this.month()%3)},weekYear:function(a){var b=jb(this,this.localeData()._week.dow,this.localeData()._week.doy).year;return null==a?b:this.add(a-b,"y")},isoWeekYear:function(a){var b=jb(this,1,4).year;return null==a?b:this.add(a-b,"y")},week:function(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")},isoWeek:function(a){var b=jb(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")},weekday:function(a){var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")},isoWeekday:function(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)},isoWeeksInYear:function(){return E(this.year(),1,4)},weeksInYear:function(){var a=this.localeData()._week;return E(this.year(),a.dow,a.doy)},get:function(a){return a=z(a),this[a]()},set:function(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else a=z(a),"function"==typeof this[a]&&this[a](b);return this},locale:function(b){var c;return b===a?this._locale._abbr:(c=vb.localeData(b),null!=c&&(this._locale=c),this)},lang:f("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(b){return b===a?this.localeData():this.locale(b)}),localeData:function(){return this._locale},_dateUtcOffset:function(){return 15*-Math.round(this._d.getTimezoneOffset()/15)}}),vb.fn.millisecond=vb.fn.milliseconds=qb("Milliseconds",!1),vb.fn.second=vb.fn.seconds=qb("Seconds",!1),vb.fn.minute=vb.fn.minutes=qb("Minutes",!1),vb.fn.hour=vb.fn.hours=qb("Hours",!0),vb.fn.date=qb("Date",!0),vb.fn.dates=f("dates accessor is deprecated. Use date instead.",qb("Date",!0)),vb.fn.year=qb("FullYear",!0),vb.fn.years=f("years accessor is deprecated. Use year instead.",qb("FullYear",!0)),vb.fn.days=vb.fn.day,vb.fn.months=vb.fn.month,vb.fn.weeks=vb.fn.week,vb.fn.isoWeeks=vb.fn.isoWeek,vb.fn.quarters=vb.fn.quarter,vb.fn.toJSON=vb.fn.toISOString,vb.fn.isUTC=vb.fn.isUtc,o(vb.duration.fn=n.prototype,{_bubble:function(){var a,b,c,d=this._milliseconds,e=this._days,f=this._months,g=this._data,h=0;g.milliseconds=d%1e3,a=q(d/1e3),g.seconds=a%60,b=q(a/60),g.minutes=b%60,c=q(b/60),g.hours=c%24,e+=q(c/24),h=q(rb(e)),e-=q(sb(h)),f+=q(e/30),e%=30,h+=q(f/12),f%=12,g.days=e,g.months=f,g.years=h},abs:function(){return this._milliseconds=Math.abs(this._milliseconds),this._days=Math.abs(this._days),this._months=Math.abs(this._months),this._data.milliseconds=Math.abs(this._data.milliseconds),this._data.seconds=Math.abs(this._data.seconds),this._data.minutes=Math.abs(this._data.minutes),this._data.hours=Math.abs(this._data.hours),this._data.months=Math.abs(this._data.months),this._data.years=Math.abs(this._data.years),this},weeks:function(){return q(this.days()/7)},valueOf:function(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*C(this._months/12)
-},humanize:function(a){var b=ib(this,!a,this.localeData());return a&&(b=this.localeData().pastFuture(+this,b)),this.localeData().postformat(b)},add:function(a,b){var c=vb.duration(a,b);return this._milliseconds+=c._milliseconds,this._days+=c._days,this._months+=c._months,this._bubble(),this},subtract:function(a,b){var c=vb.duration(a,b);return this._milliseconds-=c._milliseconds,this._days-=c._days,this._months-=c._months,this._bubble(),this},get:function(a){return a=z(a),this[a.toLowerCase()+"s"]()},as:function(a){var b,c;if(a=z(a),"month"===a||"year"===a)return b=this._days+this._milliseconds/864e5,c=this._months+12*rb(b),"month"===a?c:c/12;switch(b=this._days+Math.round(sb(this._months/12)),a){case"week":return b/7+this._milliseconds/6048e5;case"day":return b+this._milliseconds/864e5;case"hour":return 24*b+this._milliseconds/36e5;case"minute":return 24*b*60+this._milliseconds/6e4;case"second":return 24*b*60*60+this._milliseconds/1e3;case"millisecond":return Math.floor(24*b*60*60*1e3)+this._milliseconds;default:throw new Error("Unknown unit "+a)}},lang:vb.fn.lang,locale:vb.fn.locale,toIsoString:f("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",function(){return this.toISOString()}),toISOString:function(){var a=Math.abs(this.years()),b=Math.abs(this.months()),c=Math.abs(this.days()),d=Math.abs(this.hours()),e=Math.abs(this.minutes()),f=Math.abs(this.seconds()+this.milliseconds()/1e3);return this.asSeconds()?(this.asSeconds()<0?"-":"")+"P"+(a?a+"Y":"")+(b?b+"M":"")+(c?c+"D":"")+(d||e||f?"T":"")+(d?d+"H":"")+(e?e+"M":"")+(f?f+"S":""):"P0D"},localeData:function(){return this._locale},toJSON:function(){return this.toISOString()}}),vb.duration.fn.toString=vb.duration.fn.toISOString;for(xb in kc)c(kc,xb)&&tb(xb.toLowerCase());vb.duration.fn.asMilliseconds=function(){return this.as("ms")},vb.duration.fn.asSeconds=function(){return this.as("s")},vb.duration.fn.asMinutes=function(){return this.as("m")},vb.duration.fn.asHours=function(){return this.as("h")},vb.duration.fn.asDays=function(){return this.as("d")},vb.duration.fn.asWeeks=function(){return this.as("weeks")},vb.duration.fn.asMonths=function(){return this.as("M")},vb.duration.fn.asYears=function(){return this.as("y")},vb.locale("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===C(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),Lb?module.exports=vb:"function"==typeof define&&define.amd?(define(function(a,b,c){return c.config&&c.config()&&c.config().noGlobal===!0&&(zb.moment=wb),vb}),ub(!0)):ub()}).call(this);
\ No newline at end of file
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.moment=t()}(this,function(){"use strict";var H;function f(){return H.apply(null,arguments)}function a(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function F(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function c(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function L(e){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(e).length;for(var t in e)if(c(e,t))return;return 1}function o(e){return void 0===e}function u(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function V(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function G(e,t){for(var n=[],s=e.length,i=0;i<s;++i)n.push(t(e[i],i));return n}function E(e,t){for(var n in t)c(t,n)&&(e[n]=t[n]);return c(t,"toString")&&(e.toString=t.toString),c(t,"valueOf")&&(e.valueOf=t.valueOf),e}function l(e,t,n,s){return Pt(e,t,n,s,!0).utc()}function m(e){return null==e._pf&&(e._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidEra:null,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1,parsedDateParts:[],era:null,meridiem:null,rfc2822:!1,weekdayMismatch:!1}),e._pf}function A(e){if(null==e._isValid){var t=m(e),n=j.call(t.parsedDateParts,function(e){return null!=e}),n=!isNaN(e._d.getTime())&&t.overflow<0&&!t.empty&&!t.invalidEra&&!t.invalidMonth&&!t.invalidWeekday&&!t.weekdayMismatch&&!t.nullInput&&!t.invalidFormat&&!t.userInvalidated&&(!t.meridiem||t.meridiem&&n);if(e._strict&&(n=n&&0===t.charsLeftOver&&0===t.unusedTokens.length&&void 0===t.bigHour),null!=Object.isFrozen&&Object.isFrozen(e))return n;e._isValid=n}return e._isValid}function I(e){var t=l(NaN);return null!=e?E(m(t),e):m(t).userInvalidated=!0,t}var j=Array.prototype.some||function(e){for(var t=Object(this),n=t.length>>>0,s=0;s<n;s++)if(s in t&&e.call(this,t[s],s,t))return!0;return!1},Z=f.momentProperties=[],z=!1;function $(e,t){var n,s,i,r=Z.length;if(o(t._isAMomentObject)||(e._isAMomentObject=t._isAMomentObject),o(t._i)||(e._i=t._i),o(t._f)||(e._f=t._f),o(t._l)||(e._l=t._l),o(t._strict)||(e._strict=t._strict),o(t._tzm)||(e._tzm=t._tzm),o(t._isUTC)||(e._isUTC=t._isUTC),o(t._offset)||(e._offset=t._offset),o(t._pf)||(e._pf=m(t)),o(t._locale)||(e._locale=t._locale),0<r)for(n=0;n<r;n++)o(i=t[s=Z[n]])||(e[s]=i);return e}function q(e){$(this,e),this._d=new Date(null!=e._d?e._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),!1===z&&(z=!0,f.updateOffset(this),z=!1)}function h(e){return e instanceof q||null!=e&&null!=e._isAMomentObject}function B(e){!1===f.suppressDeprecationWarnings&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+e)}function e(r,a){var o=!0;return E(function(){if(null!=f.deprecationHandler&&f.deprecationHandler(null,r),o){for(var e,t,n=[],s=arguments.length,i=0;i<s;i++){if(e="","object"==typeof arguments[i]){for(t in e+="\n["+i+"] ",arguments[0])c(arguments[0],t)&&(e+=t+": "+arguments[0][t]+", ");e=e.slice(0,-2)}else e=arguments[i];n.push(e)}B(r+"\nArguments: "+Array.prototype.slice.call(n).join("")+"\n"+(new Error).stack),o=!1}return a.apply(this,arguments)},a)}var J={};function Q(e,t){null!=f.deprecationHandler&&f.deprecationHandler(e,t),J[e]||(B(t),J[e]=!0)}function d(e){return"undefined"!=typeof Function&&e instanceof Function||"[object Function]"===Object.prototype.toString.call(e)}function X(e,t){var n,s=E({},e);for(n in t)c(t,n)&&(F(e[n])&&F(t[n])?(s[n]={},E(s[n],e[n]),E(s[n],t[n])):null!=t[n]?s[n]=t[n]:delete s[n]);for(n in e)c(e,n)&&!c(t,n)&&F(e[n])&&(s[n]=E({},s[n]));return s}function K(e){null!=e&&this.set(e)}f.suppressDeprecationWarnings=!1,f.deprecationHandler=null;var ee=Object.keys||function(e){var t,n=[];for(t in e)c(e,t)&&n.push(t);return n};function r(e,t,n){var s=""+Math.abs(e);return(0<=e?n?"+":"":"-")+Math.pow(10,Math.max(0,t-s.length)).toString().substr(1)+s}var te=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,ne=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,se={},ie={};function s(e,t,n,s){var i="string"==typeof s?function(){return this[s]()}:s;e&&(ie[e]=i),t&&(ie[t[0]]=function(){return r(i.apply(this,arguments),t[1],t[2])}),n&&(ie[n]=function(){return this.localeData().ordinal(i.apply(this,arguments),e)})}function re(e,t){return e.isValid()?(t=ae(t,e.localeData()),se[t]=se[t]||function(s){for(var e,i=s.match(te),t=0,r=i.length;t<r;t++)ie[i[t]]?i[t]=ie[i[t]]:i[t]=(e=i[t]).match(/\[[\s\S]/)?e.replace(/^\[|\]$/g,""):e.replace(/\\/g,"");return function(e){for(var t="",n=0;n<r;n++)t+=d(i[n])?i[n].call(e,s):i[n];return t}}(t),se[t](e)):e.localeData().invalidDate()}function ae(e,t){var n=5;function s(e){return t.longDateFormat(e)||e}for(ne.lastIndex=0;0<=n&&ne.test(e);)e=e.replace(ne,s),ne.lastIndex=0,--n;return e}var oe={};function t(e,t){var n=e.toLowerCase();oe[n]=oe[n+"s"]=oe[t]=e}function _(e){return"string"==typeof e?oe[e]||oe[e.toLowerCase()]:void 0}function ue(e){var t,n,s={};for(n in e)c(e,n)&&(t=_(n))&&(s[t]=e[n]);return s}var le={};function n(e,t){le[e]=t}function he(e){return e%4==0&&e%100!=0||e%400==0}function y(e){return e<0?Math.ceil(e)||0:Math.floor(e)}function g(e){var e=+e,t=0;return t=0!=e&&isFinite(e)?y(e):t}function de(t,n){return function(e){return null!=e?(fe(this,t,e),f.updateOffset(this,n),this):ce(this,t)}}function ce(e,t){return e.isValid()?e._d["get"+(e._isUTC?"UTC":"")+t]():NaN}function fe(e,t,n){e.isValid()&&!isNaN(n)&&("FullYear"===t&&he(e.year())&&1===e.month()&&29===e.date()?(n=g(n),e._d["set"+(e._isUTC?"UTC":"")+t](n,e.month(),We(n,e.month()))):e._d["set"+(e._isUTC?"UTC":"")+t](n))}var i=/\d/,w=/\d\d/,me=/\d{3}/,_e=/\d{4}/,ye=/[+-]?\d{6}/,p=/\d\d?/,ge=/\d\d\d\d?/,we=/\d\d\d\d\d\d?/,pe=/\d{1,3}/,ke=/\d{1,4}/,ve=/[+-]?\d{1,6}/,Me=/\d+/,De=/[+-]?\d+/,Se=/Z|[+-]\d\d:?\d\d/gi,Ye=/Z|[+-]\d\d(?::?\d\d)?/gi,k=/[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i;function v(e,n,s){be[e]=d(n)?n:function(e,t){return e&&s?s:n}}function Oe(e,t){return c(be,e)?be[e](t._strict,t._locale):new RegExp(M(e.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(e,t,n,s,i){return t||n||s||i})))}function M(e){return e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}var be={},xe={};function D(e,n){var t,s,i=n;for("string"==typeof e&&(e=[e]),u(n)&&(i=function(e,t){t[n]=g(e)}),s=e.length,t=0;t<s;t++)xe[e[t]]=i}function Te(e,i){D(e,function(e,t,n,s){n._w=n._w||{},i(e,n._w,n,s)})}var S,Y=0,O=1,b=2,x=3,T=4,N=5,Ne=6,Pe=7,Re=8;function We(e,t){if(isNaN(e)||isNaN(t))return NaN;var n=(t%(n=12)+n)%n;return e+=(t-n)/12,1==n?he(e)?29:28:31-n%7%2}S=Array.prototype.indexOf||function(e){for(var t=0;t<this.length;++t)if(this[t]===e)return t;return-1},s("M",["MM",2],"Mo",function(){return this.month()+1}),s("MMM",0,0,function(e){return this.localeData().monthsShort(this,e)}),s("MMMM",0,0,function(e){return this.localeData().months(this,e)}),t("month","M"),n("month",8),v("M",p),v("MM",p,w),v("MMM",function(e,t){return t.monthsShortRegex(e)}),v("MMMM",function(e,t){return t.monthsRegex(e)}),D(["M","MM"],function(e,t){t[O]=g(e)-1}),D(["MMM","MMMM"],function(e,t,n,s){s=n._locale.monthsParse(e,s,n._strict);null!=s?t[O]=s:m(n).invalidMonth=e});var Ce="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),Ue="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),He=/D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/,Fe=k,Le=k;function Ve(e,t){var n;if(e.isValid()){if("string"==typeof t)if(/^\d+$/.test(t))t=g(t);else if(!u(t=e.localeData().monthsParse(t)))return;n=Math.min(e.date(),We(e.year(),t)),e._d["set"+(e._isUTC?"UTC":"")+"Month"](t,n)}}function Ge(e){return null!=e?(Ve(this,e),f.updateOffset(this,!0),this):ce(this,"Month")}function Ee(){function e(e,t){return t.length-e.length}for(var t,n=[],s=[],i=[],r=0;r<12;r++)t=l([2e3,r]),n.push(this.monthsShort(t,"")),s.push(this.months(t,"")),i.push(this.months(t,"")),i.push(this.monthsShort(t,""));for(n.sort(e),s.sort(e),i.sort(e),r=0;r<12;r++)n[r]=M(n[r]),s[r]=M(s[r]);for(r=0;r<24;r++)i[r]=M(i[r]);this._monthsRegex=new RegExp("^("+i.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+s.join("|")+")","i"),this._monthsShortStrictRegex=new RegExp("^("+n.join("|")+")","i")}function Ae(e){return he(e)?366:365}s("Y",0,0,function(){var e=this.year();return e<=9999?r(e,4):"+"+e}),s(0,["YY",2],0,function(){return this.year()%100}),s(0,["YYYY",4],0,"year"),s(0,["YYYYY",5],0,"year"),s(0,["YYYYYY",6,!0],0,"year"),t("year","y"),n("year",1),v("Y",De),v("YY",p,w),v("YYYY",ke,_e),v("YYYYY",ve,ye),v("YYYYYY",ve,ye),D(["YYYYY","YYYYYY"],Y),D("YYYY",function(e,t){t[Y]=2===e.length?f.parseTwoDigitYear(e):g(e)}),D("YY",function(e,t){t[Y]=f.parseTwoDigitYear(e)}),D("Y",function(e,t){t[Y]=parseInt(e,10)}),f.parseTwoDigitYear=function(e){return g(e)+(68<g(e)?1900:2e3)};var Ie=de("FullYear",!0);function je(e,t,n,s,i,r,a){var o;return e<100&&0<=e?(o=new Date(e+400,t,n,s,i,r,a),isFinite(o.getFullYear())&&o.setFullYear(e)):o=new Date(e,t,n,s,i,r,a),o}function Ze(e){var t;return e<100&&0<=e?((t=Array.prototype.slice.call(arguments))[0]=e+400,t=new Date(Date.UTC.apply(null,t)),isFinite(t.getUTCFullYear())&&t.setUTCFullYear(e)):t=new Date(Date.UTC.apply(null,arguments)),t}function ze(e,t,n){n=7+t-n;return n-(7+Ze(e,0,n).getUTCDay()-t)%7-1}function $e(e,t,n,s,i){var r,t=1+7*(t-1)+(7+n-s)%7+ze(e,s,i),n=t<=0?Ae(r=e-1)+t:t>Ae(e)?(r=e+1,t-Ae(e)):(r=e,t);return{year:r,dayOfYear:n}}function qe(e,t,n){var s,i,r=ze(e.year(),t,n),r=Math.floor((e.dayOfYear()-r-1)/7)+1;return r<1?s=r+P(i=e.year()-1,t,n):r>P(e.year(),t,n)?(s=r-P(e.year(),t,n),i=e.year()+1):(i=e.year(),s=r),{week:s,year:i}}function P(e,t,n){var s=ze(e,t,n),t=ze(e+1,t,n);return(Ae(e)-s+t)/7}s("w",["ww",2],"wo","week"),s("W",["WW",2],"Wo","isoWeek"),t("week","w"),t("isoWeek","W"),n("week",5),n("isoWeek",5),v("w",p),v("ww",p,w),v("W",p),v("WW",p,w),Te(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=g(e)});function Be(e,t){return e.slice(t,7).concat(e.slice(0,t))}s("d",0,"do","day"),s("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),s("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),s("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),s("e",0,0,"weekday"),s("E",0,0,"isoWeekday"),t("day","d"),t("weekday","e"),t("isoWeekday","E"),n("day",11),n("weekday",11),n("isoWeekday",11),v("d",p),v("e",p),v("E",p),v("dd",function(e,t){return t.weekdaysMinRegex(e)}),v("ddd",function(e,t){return t.weekdaysShortRegex(e)}),v("dddd",function(e,t){return t.weekdaysRegex(e)}),Te(["dd","ddd","dddd"],function(e,t,n,s){s=n._locale.weekdaysParse(e,s,n._strict);null!=s?t.d=s:m(n).invalidWeekday=e}),Te(["d","e","E"],function(e,t,n,s){t[s]=g(e)});var Je="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Qe="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Xe="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),Ke=k,et=k,tt=k;function nt(){function e(e,t){return t.length-e.length}for(var t,n,s,i=[],r=[],a=[],o=[],u=0;u<7;u++)s=l([2e3,1]).day(u),t=M(this.weekdaysMin(s,"")),n=M(this.weekdaysShort(s,"")),s=M(this.weekdays(s,"")),i.push(t),r.push(n),a.push(s),o.push(t),o.push(n),o.push(s);i.sort(e),r.sort(e),a.sort(e),o.sort(e),this._weekdaysRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+r.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+i.join("|")+")","i")}function st(){return this.hours()%12||12}function it(e,t){s(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function rt(e,t){return t._meridiemParse}s("H",["HH",2],0,"hour"),s("h",["hh",2],0,st),s("k",["kk",2],0,function(){return this.hours()||24}),s("hmm",0,0,function(){return""+st.apply(this)+r(this.minutes(),2)}),s("hmmss",0,0,function(){return""+st.apply(this)+r(this.minutes(),2)+r(this.seconds(),2)}),s("Hmm",0,0,function(){return""+this.hours()+r(this.minutes(),2)}),s("Hmmss",0,0,function(){return""+this.hours()+r(this.minutes(),2)+r(this.seconds(),2)}),it("a",!0),it("A",!1),t("hour","h"),n("hour",13),v("a",rt),v("A",rt),v("H",p),v("h",p),v("k",p),v("HH",p,w),v("hh",p,w),v("kk",p,w),v("hmm",ge),v("hmmss",we),v("Hmm",ge),v("Hmmss",we),D(["H","HH"],x),D(["k","kk"],function(e,t,n){e=g(e);t[x]=24===e?0:e}),D(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),D(["h","hh"],function(e,t,n){t[x]=g(e),m(n).bigHour=!0}),D("hmm",function(e,t,n){var s=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s)),m(n).bigHour=!0}),D("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s,2)),t[N]=g(e.substr(i)),m(n).bigHour=!0}),D("Hmm",function(e,t,n){var s=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s))}),D("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s,2)),t[N]=g(e.substr(i))});k=de("Hours",!0);var at,ot={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Ce,monthsShort:Ue,week:{dow:0,doy:6},weekdays:Je,weekdaysMin:Xe,weekdaysShort:Qe,meridiemParse:/[ap]\.?m?\.?/i},R={},ut={};function lt(e){return e&&e.toLowerCase().replace("_","-")}function ht(e){for(var t,n,s,i,r=0;r<e.length;){for(t=(i=lt(e[r]).split("-")).length,n=(n=lt(e[r+1]))?n.split("-"):null;0<t;){if(s=dt(i.slice(0,t).join("-")))return s;if(n&&n.length>=t&&function(e,t){for(var n=Math.min(e.length,t.length),s=0;s<n;s+=1)if(e[s]!==t[s])return s;return n}(i,n)>=t-1)break;t--}r++}return at}function dt(t){var e;if(void 0===R[t]&&"undefined"!=typeof module&&module&&module.exports&&null!=t.match("^[^/\\\\]*$"))try{e=at._abbr,require("./locale/"+t),ct(e)}catch(e){R[t]=null}return R[t]}function ct(e,t){return e&&((t=o(t)?mt(e):ft(e,t))?at=t:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),at._abbr}function ft(e,t){if(null===t)return delete R[e],null;var n,s=ot;if(t.abbr=e,null!=R[e])Q("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=R[e]._config;else if(null!=t.parentLocale)if(null!=R[t.parentLocale])s=R[t.parentLocale]._config;else{if(null==(n=dt(t.parentLocale)))return ut[t.parentLocale]||(ut[t.parentLocale]=[]),ut[t.parentLocale].push({name:e,config:t}),null;s=n._config}return R[e]=new K(X(s,t)),ut[e]&&ut[e].forEach(function(e){ft(e.name,e.config)}),ct(e),R[e]}function mt(e){var t;if(!(e=e&&e._locale&&e._locale._abbr?e._locale._abbr:e))return at;if(!a(e)){if(t=dt(e))return t;e=[e]}return ht(e)}function _t(e){var t=e._a;return t&&-2===m(e).overflow&&(t=t[O]<0||11<t[O]?O:t[b]<1||t[b]>We(t[Y],t[O])?b:t[x]<0||24<t[x]||24===t[x]&&(0!==t[T]||0!==t[N]||0!==t[Ne])?x:t[T]<0||59<t[T]?T:t[N]<0||59<t[N]?N:t[Ne]<0||999<t[Ne]?Ne:-1,m(e)._overflowDayOfYear&&(t<Y||b<t)&&(t=b),m(e)._overflowWeeks&&-1===t&&(t=Pe),m(e)._overflowWeekday&&-1===t&&(t=Re),m(e).overflow=t),e}var yt=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,gt=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,wt=/Z|[+-]\d\d(?::?\d\d)?/,pt=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/],["YYYYMM",/\d{6}/,!1],["YYYY",/\d{4}/,!1]],kt=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],vt=/^\/?Date\((-?\d+)/i,Mt=/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/,Dt={UT:0,GMT:0,EDT:-240,EST:-300,CDT:-300,CST:-360,MDT:-360,MST:-420,PDT:-420,PST:-480};function St(e){var t,n,s,i,r,a,o=e._i,u=yt.exec(o)||gt.exec(o),o=pt.length,l=kt.length;if(u){for(m(e).iso=!0,t=0,n=o;t<n;t++)if(pt[t][1].exec(u[1])){i=pt[t][0],s=!1!==pt[t][2];break}if(null==i)e._isValid=!1;else{if(u[3]){for(t=0,n=l;t<n;t++)if(kt[t][1].exec(u[3])){r=(u[2]||" ")+kt[t][0];break}if(null==r)return void(e._isValid=!1)}if(s||null==r){if(u[4]){if(!wt.exec(u[4]))return void(e._isValid=!1);a="Z"}e._f=i+(r||"")+(a||""),Tt(e)}else e._isValid=!1}}else e._isValid=!1}function Yt(e,t,n,s,i,r){e=[function(e){e=parseInt(e,10);{if(e<=49)return 2e3+e;if(e<=999)return 1900+e}return e}(e),Ue.indexOf(t),parseInt(n,10),parseInt(s,10),parseInt(i,10)];return r&&e.push(parseInt(r,10)),e}function Ot(e){var t,n,s,i,r=Mt.exec(e._i.replace(/\([^)]*\)|[\n\t]/g," ").replace(/(\s\s+)/g," ").replace(/^\s\s*/,"").replace(/\s\s*$/,""));r?(t=Yt(r[4],r[3],r[2],r[5],r[6],r[7]),n=r[1],s=t,i=e,n&&Qe.indexOf(n)!==new Date(s[0],s[1],s[2]).getDay()?(m(i).weekdayMismatch=!0,i._isValid=!1):(e._a=t,e._tzm=(n=r[8],s=r[9],i=r[10],n?Dt[n]:s?0:60*(((n=parseInt(i,10))-(s=n%100))/100)+s),e._d=Ze.apply(null,e._a),e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),m(e).rfc2822=!0)):e._isValid=!1}function bt(e,t,n){return null!=e?e:null!=t?t:n}function xt(e){var t,n,s,i,r,a,o,u,l,h,d,c=[];if(!e._d){for(s=e,i=new Date(f.now()),n=s._useUTC?[i.getUTCFullYear(),i.getUTCMonth(),i.getUTCDate()]:[i.getFullYear(),i.getMonth(),i.getDate()],e._w&&null==e._a[b]&&null==e._a[O]&&(null!=(i=(s=e)._w).GG||null!=i.W||null!=i.E?(u=1,l=4,r=bt(i.GG,s._a[Y],qe(W(),1,4).year),a=bt(i.W,1),((o=bt(i.E,1))<1||7<o)&&(h=!0)):(u=s._locale._week.dow,l=s._locale._week.doy,d=qe(W(),u,l),r=bt(i.gg,s._a[Y],d.year),a=bt(i.w,d.week),null!=i.d?((o=i.d)<0||6<o)&&(h=!0):null!=i.e?(o=i.e+u,(i.e<0||6<i.e)&&(h=!0)):o=u),a<1||a>P(r,u,l)?m(s)._overflowWeeks=!0:null!=h?m(s)._overflowWeekday=!0:(d=$e(r,a,o,u,l),s._a[Y]=d.year,s._dayOfYear=d.dayOfYear)),null!=e._dayOfYear&&(i=bt(e._a[Y],n[Y]),(e._dayOfYear>Ae(i)||0===e._dayOfYear)&&(m(e)._overflowDayOfYear=!0),h=Ze(i,0,e._dayOfYear),e._a[O]=h.getUTCMonth(),e._a[b]=h.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=c[t]=n[t];for(;t<7;t++)e._a[t]=c[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[x]&&0===e._a[T]&&0===e._a[N]&&0===e._a[Ne]&&(e._nextDay=!0,e._a[x]=0),e._d=(e._useUTC?Ze:je).apply(null,c),r=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[x]=24),e._w&&void 0!==e._w.d&&e._w.d!==r&&(m(e).weekdayMismatch=!0)}}function Tt(e){if(e._f===f.ISO_8601)St(e);else if(e._f===f.RFC_2822)Ot(e);else{e._a=[],m(e).empty=!0;for(var t,n,s,i,r,a=""+e._i,o=a.length,u=0,l=ae(e._f,e._locale).match(te)||[],h=l.length,d=0;d<h;d++)n=l[d],(t=(a.match(Oe(n,e))||[])[0])&&(0<(s=a.substr(0,a.indexOf(t))).length&&m(e).unusedInput.push(s),a=a.slice(a.indexOf(t)+t.length),u+=t.length),ie[n]?(t?m(e).empty=!1:m(e).unusedTokens.push(n),s=n,r=e,null!=(i=t)&&c(xe,s)&&xe[s](i,r._a,r,s)):e._strict&&!t&&m(e).unusedTokens.push(n);m(e).charsLeftOver=o-u,0<a.length&&m(e).unusedInput.push(a),e._a[x]<=12&&!0===m(e).bigHour&&0<e._a[x]&&(m(e).bigHour=void 0),m(e).parsedDateParts=e._a.slice(0),m(e).meridiem=e._meridiem,e._a[x]=function(e,t,n){if(null==n)return t;return null!=e.meridiemHour?e.meridiemHour(t,n):null!=e.isPM?((e=e.isPM(n))&&t<12&&(t+=12),t=e||12!==t?t:0):t}(e._locale,e._a[x],e._meridiem),null!==(o=m(e).era)&&(e._a[Y]=e._locale.erasConvertYear(o,e._a[Y])),xt(e),_t(e)}}function Nt(e){var t,n,s,i=e._i,r=e._f;if(e._locale=e._locale||mt(e._l),null===i||void 0===r&&""===i)return I({nullInput:!0});if("string"==typeof i&&(e._i=i=e._locale.preparse(i)),h(i))return new q(_t(i));if(V(i))e._d=i;else if(a(r))!function(e){var t,n,s,i,r,a,o=!1,u=e._f.length;if(0===u)return m(e).invalidFormat=!0,e._d=new Date(NaN);for(i=0;i<u;i++)r=0,a=!1,t=$({},e),null!=e._useUTC&&(t._useUTC=e._useUTC),t._f=e._f[i],Tt(t),A(t)&&(a=!0),r=(r+=m(t).charsLeftOver)+10*m(t).unusedTokens.length,m(t).score=r,o?r<s&&(s=r,n=t):(null==s||r<s||a)&&(s=r,n=t,a&&(o=!0));E(e,n||t)}(e);else if(r)Tt(e);else if(o(r=(i=e)._i))i._d=new Date(f.now());else V(r)?i._d=new Date(r.valueOf()):"string"==typeof r?(n=i,null!==(t=vt.exec(n._i))?n._d=new Date(+t[1]):(St(n),!1===n._isValid&&(delete n._isValid,Ot(n),!1===n._isValid&&(delete n._isValid,n._strict?n._isValid=!1:f.createFromInputFallback(n))))):a(r)?(i._a=G(r.slice(0),function(e){return parseInt(e,10)}),xt(i)):F(r)?(t=i)._d||(s=void 0===(n=ue(t._i)).day?n.date:n.day,t._a=G([n.year,n.month,s,n.hour,n.minute,n.second,n.millisecond],function(e){return e&&parseInt(e,10)}),xt(t)):u(r)?i._d=new Date(r):f.createFromInputFallback(i);return A(e)||(e._d=null),e}function Pt(e,t,n,s,i){var r={};return!0!==t&&!1!==t||(s=t,t=void 0),!0!==n&&!1!==n||(s=n,n=void 0),(F(e)&&L(e)||a(e)&&0===e.length)&&(e=void 0),r._isAMomentObject=!0,r._useUTC=r._isUTC=i,r._l=n,r._i=e,r._f=t,r._strict=s,(i=new q(_t(Nt(i=r))))._nextDay&&(i.add(1,"d"),i._nextDay=void 0),i}function W(e,t,n,s){return Pt(e,t,n,s,!1)}f.createFromInputFallback=e("value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are discouraged. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.",function(e){e._d=new Date(e._i+(e._useUTC?" UTC":""))}),f.ISO_8601=function(){},f.RFC_2822=function(){};ge=e("moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var e=W.apply(null,arguments);return this.isValid()&&e.isValid()?e<this?this:e:I()}),we=e("moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var e=W.apply(null,arguments);return this.isValid()&&e.isValid()?this<e?this:e:I()});function Rt(e,t){var n,s;if(!(t=1===t.length&&a(t[0])?t[0]:t).length)return W();for(n=t[0],s=1;s<t.length;++s)t[s].isValid()&&!t[s][e](n)||(n=t[s]);return n}var Wt=["year","quarter","month","week","day","hour","minute","second","millisecond"];function Ct(e){var e=ue(e),t=e.year||0,n=e.quarter||0,s=e.month||0,i=e.week||e.isoWeek||0,r=e.day||0,a=e.hour||0,o=e.minute||0,u=e.second||0,l=e.millisecond||0;this._isValid=function(e){var t,n,s=!1,i=Wt.length;for(t in e)if(c(e,t)&&(-1===S.call(Wt,t)||null!=e[t]&&isNaN(e[t])))return!1;for(n=0;n<i;++n)if(e[Wt[n]]){if(s)return!1;parseFloat(e[Wt[n]])!==g(e[Wt[n]])&&(s=!0)}return!0}(e),this._milliseconds=+l+1e3*u+6e4*o+1e3*a*60*60,this._days=+r+7*i,this._months=+s+3*n+12*t,this._data={},this._locale=mt(),this._bubble()}function Ut(e){return e instanceof Ct}function Ht(e){return e<0?-1*Math.round(-1*e):Math.round(e)}function Ft(e,n){s(e,0,0,function(){var e=this.utcOffset(),t="+";return e<0&&(e=-e,t="-"),t+r(~~(e/60),2)+n+r(~~e%60,2)})}Ft("Z",":"),Ft("ZZ",""),v("Z",Ye),v("ZZ",Ye),D(["Z","ZZ"],function(e,t,n){n._useUTC=!0,n._tzm=Vt(Ye,e)});var Lt=/([\+\-]|\d\d)/gi;function Vt(e,t){var t=(t||"").match(e);return null===t?null:0===(t=60*(e=((t[t.length-1]||[])+"").match(Lt)||["-",0,0])[1]+g(e[2]))?0:"+"===e[0]?t:-t}function Gt(e,t){var n;return t._isUTC?(t=t.clone(),n=(h(e)||V(e)?e:W(e)).valueOf()-t.valueOf(),t._d.setTime(t._d.valueOf()+n),f.updateOffset(t,!1),t):W(e).local()}function Et(e){return-Math.round(e._d.getTimezoneOffset())}function At(){return!!this.isValid()&&(this._isUTC&&0===this._offset)}f.updateOffset=function(){};var It=/^(-|\+)?(?:(\d*)[. ])?(\d+):(\d+)(?::(\d+)(\.\d*)?)?$/,jt=/^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;function C(e,t){var n,s=e,i=null;return Ut(e)?s={ms:e._milliseconds,d:e._days,M:e._months}:u(e)||!isNaN(+e)?(s={},t?s[t]=+e:s.milliseconds=+e):(i=It.exec(e))?(n="-"===i[1]?-1:1,s={y:0,d:g(i[b])*n,h:g(i[x])*n,m:g(i[T])*n,s:g(i[N])*n,ms:g(Ht(1e3*i[Ne]))*n}):(i=jt.exec(e))?(n="-"===i[1]?-1:1,s={y:Zt(i[2],n),M:Zt(i[3],n),w:Zt(i[4],n),d:Zt(i[5],n),h:Zt(i[6],n),m:Zt(i[7],n),s:Zt(i[8],n)}):null==s?s={}:"object"==typeof s&&("from"in s||"to"in s)&&(t=function(e,t){var n;if(!e.isValid()||!t.isValid())return{milliseconds:0,months:0};t=Gt(t,e),e.isBefore(t)?n=zt(e,t):((n=zt(t,e)).milliseconds=-n.milliseconds,n.months=-n.months);return n}(W(s.from),W(s.to)),(s={}).ms=t.milliseconds,s.M=t.months),i=new Ct(s),Ut(e)&&c(e,"_locale")&&(i._locale=e._locale),Ut(e)&&c(e,"_isValid")&&(i._isValid=e._isValid),i}function Zt(e,t){e=e&&parseFloat(e.replace(",","."));return(isNaN(e)?0:e)*t}function zt(e,t){var n={};return n.months=t.month()-e.month()+12*(t.year()-e.year()),e.clone().add(n.months,"M").isAfter(t)&&--n.months,n.milliseconds=+t-+e.clone().add(n.months,"M"),n}function $t(s,i){return function(e,t){var n;return null===t||isNaN(+t)||(Q(i,"moment()."+i+"(period, number) is deprecated. Please use moment()."+i+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),n=e,e=t,t=n),qt(this,C(e,t),s),this}}function qt(e,t,n,s){var i=t._milliseconds,r=Ht(t._days),t=Ht(t._months);e.isValid()&&(s=null==s||s,t&&Ve(e,ce(e,"Month")+t*n),r&&fe(e,"Date",ce(e,"Date")+r*n),i&&e._d.setTime(e._d.valueOf()+i*n),s&&f.updateOffset(e,r||t))}C.fn=Ct.prototype,C.invalid=function(){return C(NaN)};Ce=$t(1,"add"),Je=$t(-1,"subtract");function Bt(e){return"string"==typeof e||e instanceof String}function Jt(e){return h(e)||V(e)||Bt(e)||u(e)||function(t){var e=a(t),n=!1;e&&(n=0===t.filter(function(e){return!u(e)&&Bt(t)}).length);return e&&n}(e)||function(e){var t,n,s=F(e)&&!L(e),i=!1,r=["years","year","y","months","month","M","days","day","d","dates","date","D","hours","hour","h","minutes","minute","m","seconds","second","s","milliseconds","millisecond","ms"],a=r.length;for(t=0;t<a;t+=1)n=r[t],i=i||c(e,n);return s&&i}(e)||null==e}function Qt(e,t){if(e.date()<t.date())return-Qt(t,e);var n=12*(t.year()-e.year())+(t.month()-e.month()),s=e.clone().add(n,"months"),t=t-s<0?(t-s)/(s-e.clone().add(n-1,"months")):(t-s)/(e.clone().add(1+n,"months")-s);return-(n+t)||0}function Xt(e){return void 0===e?this._locale._abbr:(null!=(e=mt(e))&&(this._locale=e),this)}f.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",f.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";Xe=e("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(e){return void 0===e?this.localeData():this.locale(e)});function Kt(){return this._locale}var en=126227808e5;function tn(e,t){return(e%t+t)%t}function nn(e,t,n){return e<100&&0<=e?new Date(e+400,t,n)-en:new Date(e,t,n).valueOf()}function sn(e,t,n){return e<100&&0<=e?Date.UTC(e+400,t,n)-en:Date.UTC(e,t,n)}function rn(e,t){return t.erasAbbrRegex(e)}function an(){for(var e=[],t=[],n=[],s=[],i=this.eras(),r=0,a=i.length;r<a;++r)t.push(M(i[r].name)),e.push(M(i[r].abbr)),n.push(M(i[r].narrow)),s.push(M(i[r].name)),s.push(M(i[r].abbr)),s.push(M(i[r].narrow));this._erasRegex=new RegExp("^("+s.join("|")+")","i"),this._erasNameRegex=new RegExp("^("+t.join("|")+")","i"),this._erasAbbrRegex=new RegExp("^("+e.join("|")+")","i"),this._erasNarrowRegex=new RegExp("^("+n.join("|")+")","i")}function on(e,t){s(0,[e,e.length],0,t)}function un(e,t,n,s,i){var r;return null==e?qe(this,s,i).year:(r=P(e,s,i),function(e,t,n,s,i){e=$e(e,t,n,s,i),t=Ze(e.year,0,e.dayOfYear);return this.year(t.getUTCFullYear()),this.month(t.getUTCMonth()),this.date(t.getUTCDate()),this}.call(this,e,t=r<t?r:t,n,s,i))}s("N",0,0,"eraAbbr"),s("NN",0,0,"eraAbbr"),s("NNN",0,0,"eraAbbr"),s("NNNN",0,0,"eraName"),s("NNNNN",0,0,"eraNarrow"),s("y",["y",1],"yo","eraYear"),s("y",["yy",2],0,"eraYear"),s("y",["yyy",3],0,"eraYear"),s("y",["yyyy",4],0,"eraYear"),v("N",rn),v("NN",rn),v("NNN",rn),v("NNNN",function(e,t){return t.erasNameRegex(e)}),v("NNNNN",function(e,t){return t.erasNarrowRegex(e)}),D(["N","NN","NNN","NNNN","NNNNN"],function(e,t,n,s){s=n._locale.erasParse(e,s,n._strict);s?m(n).era=s:m(n).invalidEra=e}),v("y",Me),v("yy",Me),v("yyy",Me),v("yyyy",Me),v("yo",function(e,t){return t._eraYearOrdinalRegex||Me}),D(["y","yy","yyy","yyyy"],Y),D(["yo"],function(e,t,n,s){var i;n._locale._eraYearOrdinalRegex&&(i=e.match(n._locale._eraYearOrdinalRegex)),n._locale.eraYearOrdinalParse?t[Y]=n._locale.eraYearOrdinalParse(e,i):t[Y]=parseInt(e,10)}),s(0,["gg",2],0,function(){return this.weekYear()%100}),s(0,["GG",2],0,function(){return this.isoWeekYear()%100}),on("gggg","weekYear"),on("ggggg","weekYear"),on("GGGG","isoWeekYear"),on("GGGGG","isoWeekYear"),t("weekYear","gg"),t("isoWeekYear","GG"),n("weekYear",1),n("isoWeekYear",1),v("G",De),v("g",De),v("GG",p,w),v("gg",p,w),v("GGGG",ke,_e),v("gggg",ke,_e),v("GGGGG",ve,ye),v("ggggg",ve,ye),Te(["gggg","ggggg","GGGG","GGGGG"],function(e,t,n,s){t[s.substr(0,2)]=g(e)}),Te(["gg","GG"],function(e,t,n,s){t[s]=f.parseTwoDigitYear(e)}),s("Q",0,"Qo","quarter"),t("quarter","Q"),n("quarter",7),v("Q",i),D("Q",function(e,t){t[O]=3*(g(e)-1)}),s("D",["DD",2],"Do","date"),t("date","D"),n("date",9),v("D",p),v("DD",p,w),v("Do",function(e,t){return e?t._dayOfMonthOrdinalParse||t._ordinalParse:t._dayOfMonthOrdinalParseLenient}),D(["D","DD"],b),D("Do",function(e,t){t[b]=g(e.match(p)[0])});ke=de("Date",!0);s("DDD",["DDDD",3],"DDDo","dayOfYear"),t("dayOfYear","DDD"),n("dayOfYear",4),v("DDD",pe),v("DDDD",me),D(["DDD","DDDD"],function(e,t,n){n._dayOfYear=g(e)}),s("m",["mm",2],0,"minute"),t("minute","m"),n("minute",14),v("m",p),v("mm",p,w),D(["m","mm"],T);var ln,_e=de("Minutes",!1),ve=(s("s",["ss",2],0,"second"),t("second","s"),n("second",15),v("s",p),v("ss",p,w),D(["s","ss"],N),de("Seconds",!1));for(s("S",0,0,function(){return~~(this.millisecond()/100)}),s(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),s(0,["SSS",3],0,"millisecond"),s(0,["SSSS",4],0,function(){return 10*this.millisecond()}),s(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),s(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),s(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),s(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),s(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),t("millisecond","ms"),n("millisecond",16),v("S",pe,i),v("SS",pe,w),v("SSS",pe,me),ln="SSSS";ln.length<=9;ln+="S")v(ln,Me);function hn(e,t){t[Ne]=g(1e3*("0."+e))}for(ln="S";ln.length<=9;ln+="S")D(ln,hn);ye=de("Milliseconds",!1),s("z",0,0,"zoneAbbr"),s("zz",0,0,"zoneName");i=q.prototype;function dn(e){return e}i.add=Ce,i.calendar=function(e,t){1===arguments.length&&(arguments[0]?Jt(arguments[0])?(e=arguments[0],t=void 0):function(e){for(var t=F(e)&&!L(e),n=!1,s=["sameDay","nextDay","lastDay","nextWeek","lastWeek","sameElse"],i=0;i<s.length;i+=1)n=n||c(e,s[i]);return t&&n}(arguments[0])&&(t=arguments[0],e=void 0):t=e=void 0);var e=e||W(),n=Gt(e,this).startOf("day"),n=f.calendarFormat(this,n)||"sameElse",t=t&&(d(t[n])?t[n].call(this,e):t[n]);return this.format(t||this.localeData().calendar(n,this,W(e)))},i.clone=function(){return new q(this)},i.diff=function(e,t,n){var s,i,r;if(!this.isValid())return NaN;if(!(s=Gt(e,this)).isValid())return NaN;switch(i=6e4*(s.utcOffset()-this.utcOffset()),t=_(t)){case"year":r=Qt(this,s)/12;break;case"month":r=Qt(this,s);break;case"quarter":r=Qt(this,s)/3;break;case"second":r=(this-s)/1e3;break;case"minute":r=(this-s)/6e4;break;case"hour":r=(this-s)/36e5;break;case"day":r=(this-s-i)/864e5;break;case"week":r=(this-s-i)/6048e5;break;default:r=this-s}return n?r:y(r)},i.endOf=function(e){var t,n;if(void 0===(e=_(e))||"millisecond"===e||!this.isValid())return this;switch(n=this._isUTC?sn:nn,e){case"year":t=n(this.year()+1,0,1)-1;break;case"quarter":t=n(this.year(),this.month()-this.month()%3+3,1)-1;break;case"month":t=n(this.year(),this.month()+1,1)-1;break;case"week":t=n(this.year(),this.month(),this.date()-this.weekday()+7)-1;break;case"isoWeek":t=n(this.year(),this.month(),this.date()-(this.isoWeekday()-1)+7)-1;break;case"day":case"date":t=n(this.year(),this.month(),this.date()+1)-1;break;case"hour":t=this._d.valueOf(),t+=36e5-tn(t+(this._isUTC?0:6e4*this.utcOffset()),36e5)-1;break;case"minute":t=this._d.valueOf(),t+=6e4-tn(t,6e4)-1;break;case"second":t=this._d.valueOf(),t+=1e3-tn(t,1e3)-1;break}return this._d.setTime(t),f.updateOffset(this,!0),this},i.format=function(e){return e=e||(this.isUtc()?f.defaultFormatUtc:f.defaultFormat),e=re(this,e),this.localeData().postformat(e)},i.from=function(e,t){return this.isValid()&&(h(e)&&e.isValid()||W(e).isValid())?C({to:this,from:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()},i.fromNow=function(e){return this.from(W(),e)},i.to=function(e,t){return this.isValid()&&(h(e)&&e.isValid()||W(e).isValid())?C({from:this,to:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()},i.toNow=function(e){return this.to(W(),e)},i.get=function(e){return d(this[e=_(e)])?this[e]():this},i.invalidAt=function(){return m(this).overflow},i.isAfter=function(e,t){return e=h(e)?e:W(e),!(!this.isValid()||!e.isValid())&&("millisecond"===(t=_(t)||"millisecond")?this.valueOf()>e.valueOf():e.valueOf()<this.clone().startOf(t).valueOf())},i.isBefore=function(e,t){return e=h(e)?e:W(e),!(!this.isValid()||!e.isValid())&&("millisecond"===(t=_(t)||"millisecond")?this.valueOf()<e.valueOf():this.clone().endOf(t).valueOf()<e.valueOf())},i.isBetween=function(e,t,n,s){return e=h(e)?e:W(e),t=h(t)?t:W(t),!!(this.isValid()&&e.isValid()&&t.isValid())&&(("("===(s=s||"()")[0]?this.isAfter(e,n):!this.isBefore(e,n))&&(")"===s[1]?this.isBefore(t,n):!this.isAfter(t,n)))},i.isSame=function(e,t){var e=h(e)?e:W(e);return!(!this.isValid()||!e.isValid())&&("millisecond"===(t=_(t)||"millisecond")?this.valueOf()===e.valueOf():(e=e.valueOf(),this.clone().startOf(t).valueOf()<=e&&e<=this.clone().endOf(t).valueOf()))},i.isSameOrAfter=function(e,t){return this.isSame(e,t)||this.isAfter(e,t)},i.isSameOrBefore=function(e,t){return this.isSame(e,t)||this.isBefore(e,t)},i.isValid=function(){return A(this)},i.lang=Xe,i.locale=Xt,i.localeData=Kt,i.max=we,i.min=ge,i.parsingFlags=function(){return E({},m(this))},i.set=function(e,t){if("object"==typeof e)for(var n=function(e){var t,n=[];for(t in e)c(e,t)&&n.push({unit:t,priority:le[t]});return n.sort(function(e,t){return e.priority-t.priority}),n}(e=ue(e)),s=n.length,i=0;i<s;i++)this[n[i].unit](e[n[i].unit]);else if(d(this[e=_(e)]))return this[e](t);return this},i.startOf=function(e){var t,n;if(void 0===(e=_(e))||"millisecond"===e||!this.isValid())return this;switch(n=this._isUTC?sn:nn,e){case"year":t=n(this.year(),0,1);break;case"quarter":t=n(this.year(),this.month()-this.month()%3,1);break;case"month":t=n(this.year(),this.month(),1);break;case"week":t=n(this.year(),this.month(),this.date()-this.weekday());break;case"isoWeek":t=n(this.year(),this.month(),this.date()-(this.isoWeekday()-1));break;case"day":case"date":t=n(this.year(),this.month(),this.date());break;case"hour":t=this._d.valueOf(),t-=tn(t+(this._isUTC?0:6e4*this.utcOffset()),36e5);break;case"minute":t=this._d.valueOf(),t-=tn(t,6e4);break;case"second":t=this._d.valueOf(),t-=tn(t,1e3);break}return this._d.setTime(t),f.updateOffset(this,!0),this},i.subtract=Je,i.toArray=function(){var e=this;return[e.year(),e.month(),e.date(),e.hour(),e.minute(),e.second(),e.millisecond()]},i.toObject=function(){var e=this;return{years:e.year(),months:e.month(),date:e.date(),hours:e.hours(),minutes:e.minutes(),seconds:e.seconds(),milliseconds:e.milliseconds()}},i.toDate=function(){return new Date(this.valueOf())},i.toISOString=function(e){if(!this.isValid())return null;var t=(e=!0!==e)?this.clone().utc():this;return t.year()<0||9999<t.year()?re(t,e?"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYYYY-MM-DD[T]HH:mm:ss.SSSZ"):d(Date.prototype.toISOString)?e?this.toDate().toISOString():new Date(this.valueOf()+60*this.utcOffset()*1e3).toISOString().replace("Z",re(t,"Z")):re(t,e?"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYY-MM-DD[T]HH:mm:ss.SSSZ")},i.inspect=function(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var e,t="moment",n="";return this.isLocal()||(t=0===this.utcOffset()?"moment.utc":"moment.parseZone",n="Z"),t="["+t+'("]',e=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY",this.format(t+e+"-MM-DD[T]HH:mm:ss.SSS"+(n+'[")]'))},"undefined"!=typeof Symbol&&null!=Symbol.for&&(i[Symbol.for("nodejs.util.inspect.custom")]=function(){return"Moment<"+this.format()+">"}),i.toJSON=function(){return this.isValid()?this.toISOString():null},i.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},i.unix=function(){return Math.floor(this.valueOf()/1e3)},i.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},i.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},i.eraName=function(){for(var e,t=this.localeData().eras(),n=0,s=t.length;n<s;++n){if(e=this.clone().startOf("day").valueOf(),t[n].since<=e&&e<=t[n].until)return t[n].name;if(t[n].until<=e&&e<=t[n].since)return t[n].name}return""},i.eraNarrow=function(){for(var e,t=this.localeData().eras(),n=0,s=t.length;n<s;++n){if(e=this.clone().startOf("day").valueOf(),t[n].since<=e&&e<=t[n].until)return t[n].narrow;if(t[n].until<=e&&e<=t[n].since)return t[n].narrow}return""},i.eraAbbr=function(){for(var e,t=this.localeData().eras(),n=0,s=t.length;n<s;++n){if(e=this.clone().startOf("day").valueOf(),t[n].since<=e&&e<=t[n].until)return t[n].abbr;if(t[n].until<=e&&e<=t[n].since)return t[n].abbr}return""},i.eraYear=function(){for(var e,t,n=this.localeData().eras(),s=0,i=n.length;s<i;++s)if(e=n[s].since<=n[s].until?1:-1,t=this.clone().startOf("day").valueOf(),n[s].since<=t&&t<=n[s].until||n[s].until<=t&&t<=n[s].since)return(this.year()-f(n[s].since).year())*e+n[s].offset;return this.year()},i.year=Ie,i.isLeapYear=function(){return he(this.year())},i.weekYear=function(e){return un.call(this,e,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)},i.isoWeekYear=function(e){return un.call(this,e,this.isoWeek(),this.isoWeekday(),1,4)},i.quarter=i.quarters=function(e){return null==e?Math.ceil((this.month()+1)/3):this.month(3*(e-1)+this.month()%3)},i.month=Ge,i.daysInMonth=function(){return We(this.year(),this.month())},i.week=i.weeks=function(e){var t=this.localeData().week(this);return null==e?t:this.add(7*(e-t),"d")},i.isoWeek=i.isoWeeks=function(e){var t=qe(this,1,4).week;return null==e?t:this.add(7*(e-t),"d")},i.weeksInYear=function(){var e=this.localeData()._week;return P(this.year(),e.dow,e.doy)},i.weeksInWeekYear=function(){var e=this.localeData()._week;return P(this.weekYear(),e.dow,e.doy)},i.isoWeeksInYear=function(){return P(this.year(),1,4)},i.isoWeeksInISOWeekYear=function(){return P(this.isoWeekYear(),1,4)},i.date=ke,i.day=i.days=function(e){if(!this.isValid())return null!=e?this:NaN;var t,n,s=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=e?(t=e,n=this.localeData(),e="string"!=typeof t?t:isNaN(t)?"number"==typeof(t=n.weekdaysParse(t))?t:null:parseInt(t,10),this.add(e-s,"d")):s},i.weekday=function(e){if(!this.isValid())return null!=e?this:NaN;var t=(this.day()+7-this.localeData()._week.dow)%7;return null==e?t:this.add(e-t,"d")},i.isoWeekday=function(e){return this.isValid()?null!=e?(t=e,n=this.localeData(),n="string"==typeof t?n.weekdaysParse(t)%7||7:isNaN(t)?null:t,this.day(this.day()%7?n:n-7)):this.day()||7:null!=e?this:NaN;var t,n},i.dayOfYear=function(e){var t=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==e?t:this.add(e-t,"d")},i.hour=i.hours=k,i.minute=i.minutes=_e,i.second=i.seconds=ve,i.millisecond=i.milliseconds=ye,i.utcOffset=function(e,t,n){var s,i=this._offset||0;if(!this.isValid())return null!=e?this:NaN;if(null==e)return this._isUTC?i:Et(this);if("string"==typeof e){if(null===(e=Vt(Ye,e)))return this}else Math.abs(e)<16&&!n&&(e*=60);return!this._isUTC&&t&&(s=Et(this)),this._offset=e,this._isUTC=!0,null!=s&&this.add(s,"m"),i!==e&&(!t||this._changeInProgress?qt(this,C(e-i,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,f.updateOffset(this,!0),this._changeInProgress=null)),this},i.utc=function(e){return this.utcOffset(0,e)},i.local=function(e){return this._isUTC&&(this.utcOffset(0,e),this._isUTC=!1,e&&this.subtract(Et(this),"m")),this},i.parseZone=function(){var e;return null!=this._tzm?this.utcOffset(this._tzm,!1,!0):"string"==typeof this._i&&(null!=(e=Vt(Se,this._i))?this.utcOffset(e):this.utcOffset(0,!0)),this},i.hasAlignedHourOffset=function(e){return!!this.isValid()&&(e=e?W(e).utcOffset():0,(this.utcOffset()-e)%60==0)},i.isDST=function(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},i.isLocal=function(){return!!this.isValid()&&!this._isUTC},i.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},i.isUtc=At,i.isUTC=At,i.zoneAbbr=function(){return this._isUTC?"UTC":""},i.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},i.dates=e("dates accessor is deprecated. Use date instead.",ke),i.months=e("months accessor is deprecated. Use month instead",Ge),i.years=e("years accessor is deprecated. Use year instead",Ie),i.zone=e("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?(this.utcOffset(e="string"!=typeof e?-e:e,t),this):-this.utcOffset()}),i.isDSTShifted=e("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!o(this._isDSTShifted))return this._isDSTShifted;var e,t={};return $(t,this),(t=Nt(t))._a?(e=(t._isUTC?l:W)(t._a),this._isDSTShifted=this.isValid()&&0<function(e,t,n){for(var s=Math.min(e.length,t.length),i=Math.abs(e.length-t.length),r=0,a=0;a<s;a++)(n&&e[a]!==t[a]||!n&&g(e[a])!==g(t[a]))&&r++;return r+i}(t._a,e.toArray())):this._isDSTShifted=!1,this._isDSTShifted});w=K.prototype;function cn(e,t,n,s){var i=mt(),s=l().set(s,t);return i[n](s,e)}function fn(e,t,n){if(u(e)&&(t=e,e=void 0),e=e||"",null!=t)return cn(e,t,n,"month");for(var s=[],i=0;i<12;i++)s[i]=cn(e,i,n,"month");return s}function mn(e,t,n,s){t=("boolean"==typeof e?u(t)&&(n=t,t=void 0):(t=e,e=!1,u(n=t)&&(n=t,t=void 0)),t||"");var i,r=mt(),a=e?r._week.dow:0,o=[];if(null!=n)return cn(t,(n+a)%7,s,"day");for(i=0;i<7;i++)o[i]=cn(t,(i+a)%7,s,"day");return o}w.calendar=function(e,t,n){return d(e=this._calendar[e]||this._calendar.sameElse)?e.call(t,n):e},w.longDateFormat=function(e){var t=this._longDateFormat[e],n=this._longDateFormat[e.toUpperCase()];return t||!n?t:(this._longDateFormat[e]=n.match(te).map(function(e){return"MMMM"===e||"MM"===e||"DD"===e||"dddd"===e?e.slice(1):e}).join(""),this._longDateFormat[e])},w.invalidDate=function(){return this._invalidDate},w.ordinal=function(e){return this._ordinal.replace("%d",e)},w.preparse=dn,w.postformat=dn,w.relativeTime=function(e,t,n,s){var i=this._relativeTime[n];return d(i)?i(e,t,n,s):i.replace(/%d/i,e)},w.pastFuture=function(e,t){return d(e=this._relativeTime[0<e?"future":"past"])?e(t):e.replace(/%s/i,t)},w.set=function(e){var t,n;for(n in e)c(e,n)&&(d(t=e[n])?this[n]=t:this["_"+n]=t);this._config=e,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+"|"+/\d{1,2}/.source)},w.eras=function(e,t){for(var n,s=this._eras||mt("en")._eras,i=0,r=s.length;i<r;++i){switch(typeof s[i].since){case"string":n=f(s[i].since).startOf("day"),s[i].since=n.valueOf();break}switch(typeof s[i].until){case"undefined":s[i].until=1/0;break;case"string":n=f(s[i].until).startOf("day").valueOf(),s[i].until=n.valueOf();break}}return s},w.erasParse=function(e,t,n){var s,i,r,a,o,u=this.eras();for(e=e.toUpperCase(),s=0,i=u.length;s<i;++s)if(r=u[s].name.toUpperCase(),a=u[s].abbr.toUpperCase(),o=u[s].narrow.toUpperCase(),n)switch(t){case"N":case"NN":case"NNN":if(a===e)return u[s];break;case"NNNN":if(r===e)return u[s];break;case"NNNNN":if(o===e)return u[s];break}else if(0<=[r,a,o].indexOf(e))return u[s]},w.erasConvertYear=function(e,t){var n=e.since<=e.until?1:-1;return void 0===t?f(e.since).year():f(e.since).year()+(t-e.offset)*n},w.erasAbbrRegex=function(e){return c(this,"_erasAbbrRegex")||an.call(this),e?this._erasAbbrRegex:this._erasRegex},w.erasNameRegex=function(e){return c(this,"_erasNameRegex")||an.call(this),e?this._erasNameRegex:this._erasRegex},w.erasNarrowRegex=function(e){return c(this,"_erasNarrowRegex")||an.call(this),e?this._erasNarrowRegex:this._erasRegex},w.months=function(e,t){return e?(a(this._months)?this._months:this._months[(this._months.isFormat||He).test(t)?"format":"standalone"])[e.month()]:a(this._months)?this._months:this._months.standalone},w.monthsShort=function(e,t){return e?(a(this._monthsShort)?this._monthsShort:this._monthsShort[He.test(t)?"format":"standalone"])[e.month()]:a(this._monthsShort)?this._monthsShort:this._monthsShort.standalone},w.monthsParse=function(e,t,n){var s,i;if(this._monthsParseExact)return function(e,t,n){var s,i,r,e=e.toLocaleLowerCase();if(!this._monthsParse)for(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[],s=0;s<12;++s)r=l([2e3,s]),this._shortMonthsParse[s]=this.monthsShort(r,"").toLocaleLowerCase(),this._longMonthsParse[s]=this.months(r,"").toLocaleLowerCase();return n?"MMM"===t?-1!==(i=S.call(this._shortMonthsParse,e))?i:null:-1!==(i=S.call(this._longMonthsParse,e))?i:null:"MMM"===t?-1!==(i=S.call(this._shortMonthsParse,e))||-1!==(i=S.call(this._longMonthsParse,e))?i:null:-1!==(i=S.call(this._longMonthsParse,e))||-1!==(i=S.call(this._shortMonthsParse,e))?i:null}.call(this,e,t,n);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),s=0;s<12;s++){if(i=l([2e3,s]),n&&!this._longMonthsParse[s]&&(this._longMonthsParse[s]=new RegExp("^"+this.months(i,"").replace(".","")+"$","i"),this._shortMonthsParse[s]=new RegExp("^"+this.monthsShort(i,"").replace(".","")+"$","i")),n||this._monthsParse[s]||(i="^"+this.months(i,"")+"|^"+this.monthsShort(i,""),this._monthsParse[s]=new RegExp(i.replace(".",""),"i")),n&&"MMMM"===t&&this._longMonthsParse[s].test(e))return s;if(n&&"MMM"===t&&this._shortMonthsParse[s].test(e))return s;if(!n&&this._monthsParse[s].test(e))return s}},w.monthsRegex=function(e){return this._monthsParseExact?(c(this,"_monthsRegex")||Ee.call(this),e?this._monthsStrictRegex:this._monthsRegex):(c(this,"_monthsRegex")||(this._monthsRegex=Le),this._monthsStrictRegex&&e?this._monthsStrictRegex:this._monthsRegex)},w.monthsShortRegex=function(e){return this._monthsParseExact?(c(this,"_monthsRegex")||Ee.call(this),e?this._monthsShortStrictRegex:this._monthsShortRegex):(c(this,"_monthsShortRegex")||(this._monthsShortRegex=Fe),this._monthsShortStrictRegex&&e?this._monthsShortStrictRegex:this._monthsShortRegex)},w.week=function(e){return qe(e,this._week.dow,this._week.doy).week},w.firstDayOfYear=function(){return this._week.doy},w.firstDayOfWeek=function(){return this._week.dow},w.weekdays=function(e,t){return t=a(this._weekdays)?this._weekdays:this._weekdays[e&&!0!==e&&this._weekdays.isFormat.test(t)?"format":"standalone"],!0===e?Be(t,this._week.dow):e?t[e.day()]:t},w.weekdaysMin=function(e){return!0===e?Be(this._weekdaysMin,this._week.dow):e?this._weekdaysMin[e.day()]:this._weekdaysMin},w.weekdaysShort=function(e){return!0===e?Be(this._weekdaysShort,this._week.dow):e?this._weekdaysShort[e.day()]:this._weekdaysShort},w.weekdaysParse=function(e,t,n){var s,i;if(this._weekdaysParseExact)return function(e,t,n){var s,i,r,e=e.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],s=0;s<7;++s)r=l([2e3,1]).day(s),this._minWeekdaysParse[s]=this.weekdaysMin(r,"").toLocaleLowerCase(),this._shortWeekdaysParse[s]=this.weekdaysShort(r,"").toLocaleLowerCase(),this._weekdaysParse[s]=this.weekdays(r,"").toLocaleLowerCase();return n?"dddd"===t?-1!==(i=S.call(this._weekdaysParse,e))?i:null:"ddd"===t?-1!==(i=S.call(this._shortWeekdaysParse,e))?i:null:-1!==(i=S.call(this._minWeekdaysParse,e))?i:null:"dddd"===t?-1!==(i=S.call(this._weekdaysParse,e))||-1!==(i=S.call(this._shortWeekdaysParse,e))||-1!==(i=S.call(this._minWeekdaysParse,e))?i:null:"ddd"===t?-1!==(i=S.call(this._shortWeekdaysParse,e))||-1!==(i=S.call(this._weekdaysParse,e))||-1!==(i=S.call(this._minWeekdaysParse,e))?i:null:-1!==(i=S.call(this._minWeekdaysParse,e))||-1!==(i=S.call(this._weekdaysParse,e))||-1!==(i=S.call(this._shortWeekdaysParse,e))?i:null}.call(this,e,t,n);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),s=0;s<7;s++){if(i=l([2e3,1]).day(s),n&&!this._fullWeekdaysParse[s]&&(this._fullWeekdaysParse[s]=new RegExp("^"+this.weekdays(i,"").replace(".","\\.?")+"$","i"),this._shortWeekdaysParse[s]=new RegExp("^"+this.weekdaysShort(i,"").replace(".","\\.?")+"$","i"),this._minWeekdaysParse[s]=new RegExp("^"+this.weekdaysMin(i,"").replace(".","\\.?")+"$","i")),this._weekdaysParse[s]||(i="^"+this.weekdays(i,"")+"|^"+this.weekdaysShort(i,"")+"|^"+this.weekdaysMin(i,""),this._weekdaysParse[s]=new RegExp(i.replace(".",""),"i")),n&&"dddd"===t&&this._fullWeekdaysParse[s].test(e))return s;if(n&&"ddd"===t&&this._shortWeekdaysParse[s].test(e))return s;if(n&&"dd"===t&&this._minWeekdaysParse[s].test(e))return s;if(!n&&this._weekdaysParse[s].test(e))return s}},w.weekdaysRegex=function(e){return this._weekdaysParseExact?(c(this,"_weekdaysRegex")||nt.call(this),e?this._weekdaysStrictRegex:this._weekdaysRegex):(c(this,"_weekdaysRegex")||(this._weekdaysRegex=Ke),this._weekdaysStrictRegex&&e?this._weekdaysStrictRegex:this._weekdaysRegex)},w.weekdaysShortRegex=function(e){return this._weekdaysParseExact?(c(this,"_weekdaysRegex")||nt.call(this),e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(c(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=et),this._weekdaysShortStrictRegex&&e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)},w.weekdaysMinRegex=function(e){return this._weekdaysParseExact?(c(this,"_weekdaysRegex")||nt.call(this),e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(c(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=tt),this._weekdaysMinStrictRegex&&e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)},w.isPM=function(e){return"p"===(e+"").toLowerCase().charAt(0)},w.meridiem=function(e,t,n){return 11<e?n?"pm":"PM":n?"am":"AM"},ct("en",{eras:[{since:"0001-01-01",until:1/0,offset:1,name:"Anno Domini",narrow:"AD",abbr:"AD"},{since:"0000-12-31",until:-1/0,offset:1,name:"Before Christ",narrow:"BC",abbr:"BC"}],dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var t=e%10;return e+(1===g(e%100/10)?"th":1==t?"st":2==t?"nd":3==t?"rd":"th")}}),f.lang=e("moment.lang is deprecated. Use moment.locale instead.",ct),f.langData=e("moment.langData is deprecated. Use moment.localeData instead.",mt);var _n=Math.abs;function yn(e,t,n,s){t=C(t,n);return e._milliseconds+=s*t._milliseconds,e._days+=s*t._days,e._months+=s*t._months,e._bubble()}function gn(e){return e<0?Math.floor(e):Math.ceil(e)}function wn(e){return 4800*e/146097}function pn(e){return 146097*e/4800}function kn(e){return function(){return this.as(e)}}pe=kn("ms"),me=kn("s"),Ce=kn("m"),we=kn("h"),ge=kn("d"),Je=kn("w"),k=kn("M"),_e=kn("Q"),ve=kn("y");function vn(e){return function(){return this.isValid()?this._data[e]:NaN}}var ye=vn("milliseconds"),ke=vn("seconds"),Ie=vn("minutes"),w=vn("hours"),Mn=vn("days"),Dn=vn("months"),Sn=vn("years");var Yn=Math.round,On={ss:44,s:45,m:45,h:22,d:26,w:null,M:11};function bn(e,t,n,s){var i=C(e).abs(),r=Yn(i.as("s")),a=Yn(i.as("m")),o=Yn(i.as("h")),u=Yn(i.as("d")),l=Yn(i.as("M")),h=Yn(i.as("w")),i=Yn(i.as("y")),r=(r<=n.ss?["s",r]:r<n.s&&["ss",r])||a<=1&&["m"]||a<n.m&&["mm",a]||o<=1&&["h"]||o<n.h&&["hh",o]||u<=1&&["d"]||u<n.d&&["dd",u];return(r=(r=null!=n.w?r||h<=1&&["w"]||h<n.w&&["ww",h]:r)||l<=1&&["M"]||l<n.M&&["MM",l]||i<=1&&["y"]||["yy",i])[2]=t,r[3]=0<+e,r[4]=s,function(e,t,n,s,i){return i.relativeTime(t||1,!!n,e,s)}.apply(null,r)}var xn=Math.abs;function Tn(e){return(0<e)-(e<0)||+e}function Nn(){if(!this.isValid())return this.localeData().invalidDate();var e,t,n,s,i,r,a,o=xn(this._milliseconds)/1e3,u=xn(this._days),l=xn(this._months),h=this.asSeconds();return h?(e=y(o/60),t=y(e/60),o%=60,e%=60,n=y(l/12),l%=12,s=o?o.toFixed(3).replace(/\.?0+$/,""):"",i=Tn(this._months)!==Tn(h)?"-":"",r=Tn(this._days)!==Tn(h)?"-":"",a=Tn(this._milliseconds)!==Tn(h)?"-":"",(h<0?"-":"")+"P"+(n?i+n+"Y":"")+(l?i+l+"M":"")+(u?r+u+"D":"")+(t||e||o?"T":"")+(t?a+t+"H":"")+(e?a+e+"M":"")+(o?a+s+"S":"")):"P0D"}var U=Ct.prototype;return U.isValid=function(){return this._isValid},U.abs=function(){var e=this._data;return this._milliseconds=_n(this._milliseconds),this._days=_n(this._days),this._months=_n(this._months),e.milliseconds=_n(e.milliseconds),e.seconds=_n(e.seconds),e.minutes=_n(e.minutes),e.hours=_n(e.hours),e.months=_n(e.months),e.years=_n(e.years),this},U.add=function(e,t){return yn(this,e,t,1)},U.subtract=function(e,t){return yn(this,e,t,-1)},U.as=function(e){if(!this.isValid())return NaN;var t,n,s=this._milliseconds;if("month"===(e=_(e))||"quarter"===e||"year"===e)switch(t=this._days+s/864e5,n=this._months+wn(t),e){case"month":return n;case"quarter":return n/3;case"year":return n/12}else switch(t=this._days+Math.round(pn(this._months)),e){case"week":return t/7+s/6048e5;case"day":return t+s/864e5;case"hour":return 24*t+s/36e5;case"minute":return 1440*t+s/6e4;case"second":return 86400*t+s/1e3;case"millisecond":return Math.floor(864e5*t)+s;default:throw new Error("Unknown unit "+e)}},U.asMilliseconds=pe,U.asSeconds=me,U.asMinutes=Ce,U.asHours=we,U.asDays=ge,U.asWeeks=Je,U.asMonths=k,U.asQuarters=_e,U.asYears=ve,U.valueOf=function(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*g(this._months/12):NaN},U._bubble=function(){var e=this._milliseconds,t=this._days,n=this._months,s=this._data;return 0<=e&&0<=t&&0<=n||e<=0&&t<=0&&n<=0||(e+=864e5*gn(pn(n)+t),n=t=0),s.milliseconds=e%1e3,e=y(e/1e3),s.seconds=e%60,e=y(e/60),s.minutes=e%60,e=y(e/60),s.hours=e%24,t+=y(e/24),n+=e=y(wn(t)),t-=gn(pn(e)),e=y(n/12),n%=12,s.days=t,s.months=n,s.years=e,this},U.clone=function(){return C(this)},U.get=function(e){return e=_(e),this.isValid()?this[e+"s"]():NaN},U.milliseconds=ye,U.seconds=ke,U.minutes=Ie,U.hours=w,U.days=Mn,U.weeks=function(){return y(this.days()/7)},U.months=Dn,U.years=Sn,U.humanize=function(e,t){if(!this.isValid())return this.localeData().invalidDate();var n=!1,s=On;return"object"==typeof e&&(t=e,e=!1),"boolean"==typeof e&&(n=e),"object"==typeof t&&(s=Object.assign({},On,t),null!=t.s&&null==t.ss&&(s.ss=t.s-1)),e=this.localeData(),t=bn(this,!n,s,e),n&&(t=e.pastFuture(+this,t)),e.postformat(t)},U.toISOString=Nn,U.toString=Nn,U.toJSON=Nn,U.locale=Xt,U.localeData=Kt,U.toIsoString=e("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Nn),U.lang=Xe,s("X",0,0,"unix"),s("x",0,0,"valueOf"),v("x",De),v("X",/[+-]?\d+(\.\d{1,3})?/),D("X",function(e,t,n){n._d=new Date(1e3*parseFloat(e))}),D("x",function(e,t,n){n._d=new Date(g(e))}),f.version="2.29.2",H=W,f.fn=i,f.min=function(){return Rt("isBefore",[].slice.call(arguments,0))},f.max=function(){return Rt("isAfter",[].slice.call(arguments,0))},f.now=function(){return Date.now?Date.now():+new Date},f.utc=l,f.unix=function(e){return W(1e3*e)},f.months=function(e,t){return fn(e,t,"months")},f.isDate=V,f.locale=ct,f.invalid=I,f.duration=C,f.isMoment=h,f.weekdays=function(e,t,n){return mn(e,t,n,"weekdays")},f.parseZone=function(){return W.apply(null,arguments).parseZone()},f.localeData=mt,f.isDuration=Ut,f.monthsShort=function(e,t){return fn(e,t,"monthsShort")},f.weekdaysMin=function(e,t,n){return mn(e,t,n,"weekdaysMin")},f.defineLocale=ft,f.updateLocale=function(e,t){var n,s;return null!=t?(s=ot,null!=R[e]&&null!=R[e].parentLocale?R[e].set(X(R[e]._config,t)):(t=X(s=null!=(n=dt(e))?n._config:s,t),null==n&&(t.abbr=e),(s=new K(t)).parentLocale=R[e],R[e]=s),ct(e)):null!=R[e]&&(null!=R[e].parentLocale?(R[e]=R[e].parentLocale,e===ct()&&ct(e)):null!=R[e]&&delete R[e]),R[e]},f.locales=function(){return ee(R)},f.weekdaysShort=function(e,t,n){return mn(e,t,n,"weekdaysShort")},f.normalizeUnits=_,f.relativeTimeRounding=function(e){return void 0===e?Yn:"function"==typeof e&&(Yn=e,!0)},f.relativeTimeThreshold=function(e,t){return void 0!==On[e]&&(void 0===t?On[e]:(On[e]=t,"s"===e&&(On.ss=t-1),!0))},f.calendarFormat=function(e,t){return(e=e.diff(t,"days",!0))<-6?"sameElse":e<-1?"lastWeek":e<0?"lastDay":e<1?"sameDay":e<2?"nextDay":e<7?"nextWeek":"sameElse"},f.prototype=i,f.HTML5_FMT={DATETIME_LOCAL:"YYYY-MM-DDTHH:mm",DATETIME_LOCAL_SECONDS:"YYYY-MM-DDTHH:mm:ss",DATETIME_LOCAL_MS:"YYYY-MM-DDTHH:mm:ss.SSS",DATE:"YYYY-MM-DD",TIME:"HH:mm",TIME_SECONDS:"HH:mm:ss",TIME_MS:"HH:mm:ss.SSS",WEEK:"GGGG-[W]WW",MONTH:"YYYY-MM"},f});
+//# sourceMappingURL=moment.min.js.map
\ No newline at end of file
diff --git a/pdns/recursordist/html/js/underscore-min.js b/pdns/recursordist/html/js/underscore-min.js
deleted file mode 100644 (file)
index 7ed6e52..0000000
+++ /dev/null
@@ -1 +0,0 @@
-(function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,v=e.reduce,h=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,j=i.bind,w=function(n){return n instanceof w?n:this instanceof w?(this._wrapped=n,void 0):new w(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=w),exports._=w):n._=w,w.VERSION="1.4.3";var A=w.each=w.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(w.has(n,a)&&t.call(e,n[a],a,n)===r)return};w.map=w.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e[e.length]=t.call(r,n,u,i)}),e)};var O="Reduce of empty array with no initial value";w.reduce=w.foldl=w.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduce===v)return e&&(t=w.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},w.reduceRight=w.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduceRight===h)return e&&(t=w.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=w.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},w.find=w.detect=function(n,t,r){var e;return E(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},w.filter=w.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&(e[e.length]=n)}),e)},w.reject=function(n,t,r){return w.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},w.every=w.all=function(n,t,e){t||(t=w.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var E=w.some=w.any=function(n,t,e){t||(t=w.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};w.contains=w.include=function(n,t){return null==n?!1:y&&n.indexOf===y?-1!=n.indexOf(t):E(n,function(n){return n===t})},w.invoke=function(n,t){var r=o.call(arguments,2);return w.map(n,function(n){return(w.isFunction(t)?t:n[t]).apply(n,r)})},w.pluck=function(n,t){return w.map(n,function(n){return n[t]})},w.where=function(n,t){return w.isEmpty(t)?[]:w.filter(n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},w.max=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.max.apply(Math,n);if(!t&&w.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>=e.computed&&(e={value:n,computed:a})}),e.value},w.min=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.min.apply(Math,n);if(!t&&w.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;e.computed>a&&(e={value:n,computed:a})}),e.value},w.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=w.random(r++),e[r-1]=e[t],e[t]=n}),e};var F=function(n){return w.isFunction(n)?n:function(t){return t[n]}};w.sortBy=function(n,t,r){var e=F(t);return w.pluck(w.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||void 0===r)return 1;if(e>r||void 0===e)return-1}return n.index<t.index?-1:1}),"value")};var k=function(n,t,r,e){var u={},i=F(t||w.identity);return A(n,function(t,a){var o=i.call(r,t,a,n);e(u,o,t)}),u};w.groupBy=function(n,t,r){return k(n,t,r,function(n,t,r){(w.has(n,t)?n[t]:n[t]=[]).push(r)})},w.countBy=function(n,t,r){return k(n,t,r,function(n,t){w.has(n,t)||(n[t]=0),n[t]++})},w.sortedIndex=function(n,t,r,e){r=null==r?w.identity:F(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;u>r.call(e,n[o])?i=o+1:a=o}return i},w.toArray=function(n){return n?w.isArray(n)?o.call(n):n.length===+n.length?w.map(n,w.identity):w.values(n):[]},w.size=function(n){return null==n?0:n.length===+n.length?n.length:w.keys(n).length},w.first=w.head=w.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},w.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},w.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},w.rest=w.tail=w.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},w.compact=function(n){return w.filter(n,w.identity)};var R=function(n,t,r){return A(n,function(n){w.isArray(n)?t?a.apply(r,n):R(n,t,r):r.push(n)}),r};w.flatten=function(n,t){return R(n,t,[])},w.without=function(n){return w.difference(n,o.call(arguments,1))},w.uniq=w.unique=function(n,t,r,e){w.isFunction(t)&&(e=r,r=t,t=!1);var u=r?w.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:w.contains(a,r))||(a.push(r),i.push(n[e]))}),i},w.union=function(){return w.uniq(c.apply(e,arguments))},w.intersection=function(n){var t=o.call(arguments,1);return w.filter(w.uniq(n),function(n){return w.every(t,function(t){return w.indexOf(t,n)>=0})})},w.difference=function(n){var t=c.apply(e,o.call(arguments,1));return w.filter(n,function(n){return!w.contains(t,n)})},w.zip=function(){for(var n=o.call(arguments),t=w.max(w.pluck(n,"length")),r=Array(t),e=0;t>e;e++)r[e]=w.pluck(n,""+e);return r},w.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},w.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=w.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},w.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},w.range=function(n,t,r){1>=arguments.length&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=Array(e);e>u;)i[u++]=n,n+=r;return i};var I=function(){};w.bind=function(n,t){var r,e;if(n.bind===j&&j)return j.apply(n,o.call(arguments,1));if(!w.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));I.prototype=n.prototype;var u=new I;I.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},w.bindAll=function(n){var t=o.call(arguments,1);return 0==t.length&&(t=w.functions(n)),A(t,function(t){n[t]=w.bind(n[t],n)}),n},w.memoize=function(n,t){var r={};return t||(t=w.identity),function(){var e=t.apply(this,arguments);return w.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},w.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},w.defer=function(n){return w.delay.apply(w,[n,1].concat(o.call(arguments,1)))},w.throttle=function(n,t){var r,e,u,i,a=0,o=function(){a=new Date,u=null,i=n.apply(r,e)};return function(){var c=new Date,l=t-(c-a);return r=this,e=arguments,0>=l?(clearTimeout(u),u=null,a=c,i=n.apply(r,e)):u||(u=setTimeout(o,l)),i}},w.debounce=function(n,t,r){var e,u;return function(){var i=this,a=arguments,o=function(){e=null,r||(u=n.apply(i,a))},c=r&&!e;return clearTimeout(e),e=setTimeout(o,t),c&&(u=n.apply(i,a)),u}},w.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},w.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},w.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},w.after=function(n,t){return 0>=n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},w.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)w.has(n,r)&&(t[t.length]=r);return t},w.values=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push(n[r]);return t},w.pairs=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push([r,n[r]]);return t},w.invert=function(n){var t={};for(var r in n)w.has(n,r)&&(t[n[r]]=r);return t},w.functions=w.methods=function(n){var t=[];for(var r in n)w.isFunction(n[r])&&t.push(r);return t.sort()},w.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},w.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},w.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)w.contains(r,u)||(t[u]=n[u]);return t},w.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)null==n[r]&&(n[r]=t[r])}),n},w.clone=function(n){return w.isObject(n)?w.isArray(n)?n.slice():w.extend({},n):n},w.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof w&&(n=n._wrapped),t instanceof w&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==t+"";case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;r.push(n),e.push(t);var a=0,o=!0;if("[object Array]"==u){if(a=n.length,o=a==t.length)for(;a--&&(o=S(n[a],t[a],r,e)););}else{var c=n.constructor,f=t.constructor;if(c!==f&&!(w.isFunction(c)&&c instanceof c&&w.isFunction(f)&&f instanceof f))return!1;for(var s in n)if(w.has(n,s)&&(a++,!(o=w.has(t,s)&&S(n[s],t[s],r,e))))break;if(o){for(s in t)if(w.has(t,s)&&!a--)break;o=!a}}return r.pop(),e.pop(),o};w.isEqual=function(n,t){return S(n,t,[],[])},w.isEmpty=function(n){if(null==n)return!0;if(w.isArray(n)||w.isString(n))return 0===n.length;for(var t in n)if(w.has(n,t))return!1;return!0},w.isElement=function(n){return!(!n||1!==n.nodeType)},w.isArray=x||function(n){return"[object Array]"==l.call(n)},w.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){w["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),w.isArguments(arguments)||(w.isArguments=function(n){return!(!n||!w.has(n,"callee"))}),w.isFunction=function(n){return"function"==typeof n},w.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},w.isNaN=function(n){return w.isNumber(n)&&n!=+n},w.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},w.isNull=function(n){return null===n},w.isUndefined=function(n){return void 0===n},w.has=function(n,t){return f.call(n,t)},w.noConflict=function(){return n._=t,this},w.identity=function(n){return n},w.times=function(n,t,r){for(var e=Array(n),u=0;n>u;u++)e[u]=t.call(r,u);return e},w.random=function(n,t){return null==t&&(t=n,n=0),n+(0|Math.random()*(t-n+1))};var T={escape:{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","/":"&#x2F;"}};T.unescape=w.invert(T.escape);var M={escape:RegExp("["+w.keys(T.escape).join("")+"]","g"),unescape:RegExp("("+w.keys(T.unescape).join("|")+")","g")};w.each(["escape","unescape"],function(n){w[n]=function(t){return null==t?"":(""+t).replace(M[n],function(t){return T[n][t]})}}),w.result=function(n,t){if(null==n)return null;var r=n[t];return w.isFunction(r)?r.call(n):r},w.mixin=function(n){A(w.functions(n),function(t){var r=w[t]=n[t];w.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(w,n))}})};var N=0;w.uniqueId=function(n){var t=""+ ++N;return n?n+t:t},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n","    ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;w.template=function(n,t,r){r=w.defaults({},r,w.templateSettings);var e=RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,a,o){return i+=n.slice(u,o).replace(D,function(n){return"\\"+B[n]}),r&&(i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(i+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),a&&(i+="';\n"+a+"\n__p+='"),u=o+t.length,t}),i+="';\n",r.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var a=Function(r.variable||"obj","_",i)}catch(o){throw o.source=i,o}if(t)return a(t,w);var c=function(n){return a.call(this,n,w)};return c.source="function("+(r.variable||"obj")+"){\n"+i+"}",c},w.chain=function(n){return w(n).chain()};var z=function(n){return this._chain?w(n).chain():n};w.mixin(w),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];w.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];w.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),w.extend(w.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this);
\ No newline at end of file
diff --git a/pdns/recursordist/html/js/underscore.js b/pdns/recursordist/html/js/underscore.js
deleted file mode 100644 (file)
index b29332f..0000000
+++ /dev/null
@@ -1,1548 +0,0 @@
-//     Underscore.js 1.8.3
-//     http://underscorejs.org
-//     (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
-//     Underscore may be freely distributed under the MIT license.
-
-(function() {
-
-  // Baseline setup
-  // --------------
-
-  // Establish the root object, `window` in the browser, or `exports` on the server.
-  var root = this;
-
-  // Save the previous value of the `_` variable.
-  var previousUnderscore = root._;
-
-  // Save bytes in the minified (but not gzipped) version:
-  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
-
-  // Create quick reference variables for speed access to core prototypes.
-  var
-    push             = ArrayProto.push,
-    slice            = ArrayProto.slice,
-    toString         = ObjProto.toString,
-    hasOwnProperty   = ObjProto.hasOwnProperty;
-
-  // All **ECMAScript 5** native function implementations that we hope to use
-  // are declared here.
-  var
-    nativeIsArray      = Array.isArray,
-    nativeKeys         = Object.keys,
-    nativeBind         = FuncProto.bind,
-    nativeCreate       = Object.create;
-
-  // Naked function reference for surrogate-prototype-swapping.
-  var Ctor = function(){};
-
-  // Create a safe reference to the Underscore object for use below.
-  var _ = function(obj) {
-    if (obj instanceof _) return obj;
-    if (!(this instanceof _)) return new _(obj);
-    this._wrapped = obj;
-  };
-
-  // Export the Underscore object for **Node.js**, with
-  // backwards-compatibility for the old `require()` API. If we're in
-  // the browser, add `_` as a global object.
-  if (typeof exports !== 'undefined') {
-    if (typeof module !== 'undefined' && module.exports) {
-      exports = module.exports = _;
-    }
-    exports._ = _;
-  } else {
-    root._ = _;
-  }
-
-  // Current version.
-  _.VERSION = '1.8.3';
-
-  // Internal function that returns an efficient (for current engines) version
-  // of the passed-in callback, to be repeatedly applied in other Underscore
-  // functions.
-  var optimizeCb = function(func, context, argCount) {
-    if (context === void 0) return func;
-    switch (argCount == null ? 3 : argCount) {
-      case 1: return function(value) {
-        return func.call(context, value);
-      };
-      case 2: return function(value, other) {
-        return func.call(context, value, other);
-      };
-      case 3: return function(value, index, collection) {
-        return func.call(context, value, index, collection);
-      };
-      case 4: return function(accumulator, value, index, collection) {
-        return func.call(context, accumulator, value, index, collection);
-      };
-    }
-    return function() {
-      return func.apply(context, arguments);
-    };
-  };
-
-  // A mostly-internal function to generate callbacks that can be applied
-  // to each element in a collection, returning the desired result — either
-  // identity, an arbitrary callback, a property matcher, or a property accessor.
-  var cb = function(value, context, argCount) {
-    if (value == null) return _.identity;
-    if (_.isFunction(value)) return optimizeCb(value, context, argCount);
-    if (_.isObject(value)) return _.matcher(value);
-    return _.property(value);
-  };
-  _.iteratee = function(value, context) {
-    return cb(value, context, Infinity);
-  };
-
-  // An internal function for creating assigner functions.
-  var createAssigner = function(keysFunc, undefinedOnly) {
-    return function(obj) {
-      var length = arguments.length;
-      if (length < 2 || obj == null) return obj;
-      for (var index = 1; index < length; index++) {
-        var source = arguments[index],
-            keys = keysFunc(source),
-            l = keys.length;
-        for (var i = 0; i < l; i++) {
-          var key = keys[i];
-          if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
-        }
-      }
-      return obj;
-    };
-  };
-
-  // An internal function for creating a new object that inherits from another.
-  var baseCreate = function(prototype) {
-    if (!_.isObject(prototype)) return {};
-    if (nativeCreate) return nativeCreate(prototype);
-    Ctor.prototype = prototype;
-    var result = new Ctor;
-    Ctor.prototype = null;
-    return result;
-  };
-
-  var property = function(key) {
-    return function(obj) {
-      return obj == null ? void 0 : obj[key];
-    };
-  };
-
-  // Helper for collection methods to determine whether a collection
-  // should be iterated as an array or as an object
-  // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
-  // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
-  var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
-  var getLength = property('length');
-  var isArrayLike = function(collection) {
-    var length = getLength(collection);
-    return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
-  };
-
-  // Collection Functions
-  // --------------------
-
-  // The cornerstone, an `each` implementation, aka `forEach`.
-  // Handles raw objects in addition to array-likes. Treats all
-  // sparse array-likes as if they were dense.
-  _.each = _.forEach = function(obj, iteratee, context) {
-    iteratee = optimizeCb(iteratee, context);
-    var i, length;
-    if (isArrayLike(obj)) {
-      for (i = 0, length = obj.length; i < length; i++) {
-        iteratee(obj[i], i, obj);
-      }
-    } else {
-      var keys = _.keys(obj);
-      for (i = 0, length = keys.length; i < length; i++) {
-        iteratee(obj[keys[i]], keys[i], obj);
-      }
-    }
-    return obj;
-  };
-
-  // Return the results of applying the iteratee to each element.
-  _.map = _.collect = function(obj, iteratee, context) {
-    iteratee = cb(iteratee, context);
-    var keys = !isArrayLike(obj) && _.keys(obj),
-        length = (keys || obj).length,
-        results = Array(length);
-    for (var index = 0; index < length; index++) {
-      var currentKey = keys ? keys[index] : index;
-      results[index] = iteratee(obj[currentKey], currentKey, obj);
-    }
-    return results;
-  };
-
-  // Create a reducing function iterating left or right.
-  function createReduce(dir) {
-    // Optimized iterator function as using arguments.length
-    // in the main function will deoptimize the, see #1991.
-    function iterator(obj, iteratee, memo, keys, index, length) {
-      for (; index >= 0 && index < length; index += dir) {
-        var currentKey = keys ? keys[index] : index;
-        memo = iteratee(memo, obj[currentKey], currentKey, obj);
-      }
-      return memo;
-    }
-
-    return function(obj, iteratee, memo, context) {
-      iteratee = optimizeCb(iteratee, context, 4);
-      var keys = !isArrayLike(obj) && _.keys(obj),
-          length = (keys || obj).length,
-          index = dir > 0 ? 0 : length - 1;
-      // Determine the initial value if none is provided.
-      if (arguments.length < 3) {
-        memo = obj[keys ? keys[index] : index];
-        index += dir;
-      }
-      return iterator(obj, iteratee, memo, keys, index, length);
-    };
-  }
-
-  // **Reduce** builds up a single result from a list of values, aka `inject`,
-  // or `foldl`.
-  _.reduce = _.foldl = _.inject = createReduce(1);
-
-  // The right-associative version of reduce, also known as `foldr`.
-  _.reduceRight = _.foldr = createReduce(-1);
-
-  // Return the first value which passes a truth test. Aliased as `detect`.
-  _.find = _.detect = function(obj, predicate, context) {
-    var key;
-    if (isArrayLike(obj)) {
-      key = _.findIndex(obj, predicate, context);
-    } else {
-      key = _.findKey(obj, predicate, context);
-    }
-    if (key !== void 0 && key !== -1) return obj[key];
-  };
-
-  // Return all the elements that pass a truth test.
-  // Aliased as `select`.
-  _.filter = _.select = function(obj, predicate, context) {
-    var results = [];
-    predicate = cb(predicate, context);
-    _.each(obj, function(value, index, list) {
-      if (predicate(value, index, list)) results.push(value);
-    });
-    return results;
-  };
-
-  // Return all the elements for which a truth test fails.
-  _.reject = function(obj, predicate, context) {
-    return _.filter(obj, _.negate(cb(predicate)), context);
-  };
-
-  // Determine whether all of the elements match a truth test.
-  // Aliased as `all`.
-  _.every = _.all = function(obj, predicate, context) {
-    predicate = cb(predicate, context);
-    var keys = !isArrayLike(obj) && _.keys(obj),
-        length = (keys || obj).length;
-    for (var index = 0; index < length; index++) {
-      var currentKey = keys ? keys[index] : index;
-      if (!predicate(obj[currentKey], currentKey, obj)) return false;
-    }
-    return true;
-  };
-
-  // Determine if at least one element in the object matches a truth test.
-  // Aliased as `any`.
-  _.some = _.any = function(obj, predicate, context) {
-    predicate = cb(predicate, context);
-    var keys = !isArrayLike(obj) && _.keys(obj),
-        length = (keys || obj).length;
-    for (var index = 0; index < length; index++) {
-      var currentKey = keys ? keys[index] : index;
-      if (predicate(obj[currentKey], currentKey, obj)) return true;
-    }
-    return false;
-  };
-
-  // Determine if the array or object contains a given item (using `===`).
-  // Aliased as `includes` and `include`.
-  _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {
-    if (!isArrayLike(obj)) obj = _.values(obj);
-    if (typeof fromIndex != 'number' || guard) fromIndex = 0;
-    return _.indexOf(obj, item, fromIndex) >= 0;
-  };
-
-  // Invoke a method (with arguments) on every item in a collection.
-  _.invoke = function(obj, method) {
-    var args = slice.call(arguments, 2);
-    var isFunc = _.isFunction(method);
-    return _.map(obj, function(value) {
-      var func = isFunc ? method : value[method];
-      return func == null ? func : func.apply(value, args);
-    });
-  };
-
-  // Convenience version of a common use case of `map`: fetching a property.
-  _.pluck = function(obj, key) {
-    return _.map(obj, _.property(key));
-  };
-
-  // Convenience version of a common use case of `filter`: selecting only objects
-  // containing specific `key:value` pairs.
-  _.where = function(obj, attrs) {
-    return _.filter(obj, _.matcher(attrs));
-  };
-
-  // Convenience version of a common use case of `find`: getting the first object
-  // containing specific `key:value` pairs.
-  _.findWhere = function(obj, attrs) {
-    return _.find(obj, _.matcher(attrs));
-  };
-
-  // Return the maximum element (or element-based computation).
-  _.max = function(obj, iteratee, context) {
-    var result = -Infinity, lastComputed = -Infinity,
-        value, computed;
-    if (iteratee == null && obj != null) {
-      obj = isArrayLike(obj) ? obj : _.values(obj);
-      for (var i = 0, length = obj.length; i < length; i++) {
-        value = obj[i];
-        if (value > result) {
-          result = value;
-        }
-      }
-    } else {
-      iteratee = cb(iteratee, context);
-      _.each(obj, function(value, index, list) {
-        computed = iteratee(value, index, list);
-        if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
-          result = value;
-          lastComputed = computed;
-        }
-      });
-    }
-    return result;
-  };
-
-  // Return the minimum element (or element-based computation).
-  _.min = function(obj, iteratee, context) {
-    var result = Infinity, lastComputed = Infinity,
-        value, computed;
-    if (iteratee == null && obj != null) {
-      obj = isArrayLike(obj) ? obj : _.values(obj);
-      for (var i = 0, length = obj.length; i < length; i++) {
-        value = obj[i];
-        if (value < result) {
-          result = value;
-        }
-      }
-    } else {
-      iteratee = cb(iteratee, context);
-      _.each(obj, function(value, index, list) {
-        computed = iteratee(value, index, list);
-        if (computed < lastComputed || computed === Infinity && result === Infinity) {
-          result = value;
-          lastComputed = computed;
-        }
-      });
-    }
-    return result;
-  };
-
-  // Shuffle a collection, using the modern version of the
-  // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
-  _.shuffle = function(obj) {
-    var set = isArrayLike(obj) ? obj : _.values(obj);
-    var length = set.length;
-    var shuffled = Array(length);
-    for (var index = 0, rand; index < length; index++) {
-      rand = _.random(0, index);
-      if (rand !== index) shuffled[index] = shuffled[rand];
-      shuffled[rand] = set[index];
-    }
-    return shuffled;
-  };
-
-  // Sample **n** random values from a collection.
-  // If **n** is not specified, returns a single random element.
-  // The internal `guard` argument allows it to work with `map`.
-  _.sample = function(obj, n, guard) {
-    if (n == null || guard) {
-      if (!isArrayLike(obj)) obj = _.values(obj);
-      return obj[_.random(obj.length - 1)];
-    }
-    return _.shuffle(obj).slice(0, Math.max(0, n));
-  };
-
-  // Sort the object's values by a criterion produced by an iteratee.
-  _.sortBy = function(obj, iteratee, context) {
-    iteratee = cb(iteratee, context);
-    return _.pluck(_.map(obj, function(value, index, list) {
-      return {
-        value: value,
-        index: index,
-        criteria: iteratee(value, index, list)
-      };
-    }).sort(function(left, right) {
-      var a = left.criteria;
-      var b = right.criteria;
-      if (a !== b) {
-        if (a > b || a === void 0) return 1;
-        if (a < b || b === void 0) return -1;
-      }
-      return left.index - right.index;
-    }), 'value');
-  };
-
-  // An internal function used for aggregate "group by" operations.
-  var group = function(behavior) {
-    return function(obj, iteratee, context) {
-      var result = {};
-      iteratee = cb(iteratee, context);
-      _.each(obj, function(value, index) {
-        var key = iteratee(value, index, obj);
-        behavior(result, value, key);
-      });
-      return result;
-    };
-  };
-
-  // Groups the object's values by a criterion. Pass either a string attribute
-  // to group by, or a function that returns the criterion.
-  _.groupBy = group(function(result, value, key) {
-    if (_.has(result, key)) result[key].push(value); else result[key] = [value];
-  });
-
-  // Indexes the object's values by a criterion, similar to `groupBy`, but for
-  // when you know that your index values will be unique.
-  _.indexBy = group(function(result, value, key) {
-    result[key] = value;
-  });
-
-  // Counts instances of an object that group by a certain criterion. Pass
-  // either a string attribute to count by, or a function that returns the
-  // criterion.
-  _.countBy = group(function(result, value, key) {
-    if (_.has(result, key)) result[key]++; else result[key] = 1;
-  });
-
-  // Safely create a real, live array from anything iterable.
-  _.toArray = function(obj) {
-    if (!obj) return [];
-    if (_.isArray(obj)) return slice.call(obj);
-    if (isArrayLike(obj)) return _.map(obj, _.identity);
-    return _.values(obj);
-  };
-
-  // Return the number of elements in an object.
-  _.size = function(obj) {
-    if (obj == null) return 0;
-    return isArrayLike(obj) ? obj.length : _.keys(obj).length;
-  };
-
-  // Split a collection into two arrays: one whose elements all satisfy the given
-  // predicate, and one whose elements all do not satisfy the predicate.
-  _.partition = function(obj, predicate, context) {
-    predicate = cb(predicate, context);
-    var pass = [], fail = [];
-    _.each(obj, function(value, key, obj) {
-      (predicate(value, key, obj) ? pass : fail).push(value);
-    });
-    return [pass, fail];
-  };
-
-  // Array Functions
-  // ---------------
-
-  // Get the first element of an array. Passing **n** will return the first N
-  // values in the array. Aliased as `head` and `take`. The **guard** check
-  // allows it to work with `_.map`.
-  _.first = _.head = _.take = function(array, n, guard) {
-    if (array == null) return void 0;
-    if (n == null || guard) return array[0];
-    return _.initial(array, array.length - n);
-  };
-
-  // Returns everything but the last entry of the array. Especially useful on
-  // the arguments object. Passing **n** will return all the values in
-  // the array, excluding the last N.
-  _.initial = function(array, n, guard) {
-    return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
-  };
-
-  // Get the last element of an array. Passing **n** will return the last N
-  // values in the array.
-  _.last = function(array, n, guard) {
-    if (array == null) return void 0;
-    if (n == null || guard) return array[array.length - 1];
-    return _.rest(array, Math.max(0, array.length - n));
-  };
-
-  // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
-  // Especially useful on the arguments object. Passing an **n** will return
-  // the rest N values in the array.
-  _.rest = _.tail = _.drop = function(array, n, guard) {
-    return slice.call(array, n == null || guard ? 1 : n);
-  };
-
-  // Trim out all falsy values from an array.
-  _.compact = function(array) {
-    return _.filter(array, _.identity);
-  };
-
-  // Internal implementation of a recursive `flatten` function.
-  var flatten = function(input, shallow, strict, startIndex) {
-    var output = [], idx = 0;
-    for (var i = startIndex || 0, length = getLength(input); i < length; i++) {
-      var value = input[i];
-      if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
-        //flatten current level of array or arguments object
-        if (!shallow) value = flatten(value, shallow, strict);
-        var j = 0, len = value.length;
-        output.length += len;
-        while (j < len) {
-          output[idx++] = value[j++];
-        }
-      } else if (!strict) {
-        output[idx++] = value;
-      }
-    }
-    return output;
-  };
-
-  // Flatten out an array, either recursively (by default), or just one level.
-  _.flatten = function(array, shallow) {
-    return flatten(array, shallow, false);
-  };
-
-  // Return a version of the array that does not contain the specified value(s).
-  _.without = function(array) {
-    return _.difference(array, slice.call(arguments, 1));
-  };
-
-  // Produce a duplicate-free version of the array. If the array has already
-  // been sorted, you have the option of using a faster algorithm.
-  // Aliased as `unique`.
-  _.uniq = _.unique = function(array, isSorted, iteratee, context) {
-    if (!_.isBoolean(isSorted)) {
-      context = iteratee;
-      iteratee = isSorted;
-      isSorted = false;
-    }
-    if (iteratee != null) iteratee = cb(iteratee, context);
-    var result = [];
-    var seen = [];
-    for (var i = 0, length = getLength(array); i < length; i++) {
-      var value = array[i],
-          computed = iteratee ? iteratee(value, i, array) : value;
-      if (isSorted) {
-        if (!i || seen !== computed) result.push(value);
-        seen = computed;
-      } else if (iteratee) {
-        if (!_.contains(seen, computed)) {
-          seen.push(computed);
-          result.push(value);
-        }
-      } else if (!_.contains(result, value)) {
-        result.push(value);
-      }
-    }
-    return result;
-  };
-
-  // Produce an array that contains the union: each distinct element from all of
-  // the passed-in arrays.
-  _.union = function() {
-    return _.uniq(flatten(arguments, true, true));
-  };
-
-  // Produce an array that contains every item shared between all the
-  // passed-in arrays.
-  _.intersection = function(array) {
-    var result = [];
-    var argsLength = arguments.length;
-    for (var i = 0, length = getLength(array); i < length; i++) {
-      var item = array[i];
-      if (_.contains(result, item)) continue;
-      for (var j = 1; j < argsLength; j++) {
-        if (!_.contains(arguments[j], item)) break;
-      }
-      if (j === argsLength) result.push(item);
-    }
-    return result;
-  };
-
-  // Take the difference between one array and a number of other arrays.
-  // Only the elements present in just the first array will remain.
-  _.difference = function(array) {
-    var rest = flatten(arguments, true, true, 1);
-    return _.filter(array, function(value){
-      return !_.contains(rest, value);
-    });
-  };
-
-  // Zip together multiple lists into a single array -- elements that share
-  // an index go together.
-  _.zip = function() {
-    return _.unzip(arguments);
-  };
-
-  // Complement of _.zip. Unzip accepts an array of arrays and groups
-  // each array's elements on shared indices
-  _.unzip = function(array) {
-    var length = array && _.max(array, getLength).length || 0;
-    var result = Array(length);
-
-    for (var index = 0; index < length; index++) {
-      result[index] = _.pluck(array, index);
-    }
-    return result;
-  };
-
-  // Converts lists into objects. Pass either a single array of `[key, value]`
-  // pairs, or two parallel arrays of the same length -- one of keys, and one of
-  // the corresponding values.
-  _.object = function(list, values) {
-    var result = {};
-    for (var i = 0, length = getLength(list); i < length; i++) {
-      if (values) {
-        result[list[i]] = values[i];
-      } else {
-        result[list[i][0]] = list[i][1];
-      }
-    }
-    return result;
-  };
-
-  // Generator function to create the findIndex and findLastIndex functions
-  function createPredicateIndexFinder(dir) {
-    return function(array, predicate, context) {
-      predicate = cb(predicate, context);
-      var length = getLength(array);
-      var index = dir > 0 ? 0 : length - 1;
-      for (; index >= 0 && index < length; index += dir) {
-        if (predicate(array[index], index, array)) return index;
-      }
-      return -1;
-    };
-  }
-
-  // Returns the first index on an array-like that passes a predicate test
-  _.findIndex = createPredicateIndexFinder(1);
-  _.findLastIndex = createPredicateIndexFinder(-1);
-
-  // Use a comparator function to figure out the smallest index at which
-  // an object should be inserted so as to maintain order. Uses binary search.
-  _.sortedIndex = function(array, obj, iteratee, context) {
-    iteratee = cb(iteratee, context, 1);
-    var value = iteratee(obj);
-    var low = 0, high = getLength(array);
-    while (low < high) {
-      var mid = Math.floor((low + high) / 2);
-      if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
-    }
-    return low;
-  };
-
-  // Generator function to create the indexOf and lastIndexOf functions
-  function createIndexFinder(dir, predicateFind, sortedIndex) {
-    return function(array, item, idx) {
-      var i = 0, length = getLength(array);
-      if (typeof idx == 'number') {
-        if (dir > 0) {
-            i = idx >= 0 ? idx : Math.max(idx + length, i);
-        } else {
-            length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
-        }
-      } else if (sortedIndex && idx && length) {
-        idx = sortedIndex(array, item);
-        return array[idx] === item ? idx : -1;
-      }
-      if (item !== item) {
-        idx = predicateFind(slice.call(array, i, length), _.isNaN);
-        return idx >= 0 ? idx + i : -1;
-      }
-      for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
-        if (array[idx] === item) return idx;
-      }
-      return -1;
-    };
-  }
-
-  // Return the position of the first occurrence of an item in an array,
-  // or -1 if the item is not included in the array.
-  // If the array is large and already in sort order, pass `true`
-  // for **isSorted** to use binary search.
-  _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
-  _.lastIndexOf = createIndexFinder(-1, _.findLastIndex);
-
-  // Generate an integer Array containing an arithmetic progression. A port of
-  // the native Python `range()` function. See
-  // [the Python documentation](http://docs.python.org/library/functions.html#range).
-  _.range = function(start, stop, step) {
-    if (stop == null) {
-      stop = start || 0;
-      start = 0;
-    }
-    step = step || 1;
-
-    var length = Math.max(Math.ceil((stop - start) / step), 0);
-    var range = Array(length);
-
-    for (var idx = 0; idx < length; idx++, start += step) {
-      range[idx] = start;
-    }
-
-    return range;
-  };
-
-  // Function (ahem) Functions
-  // ------------------
-
-  // Determines whether to execute a function as a constructor
-  // or a normal function with the provided arguments
-  var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
-    if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
-    var self = baseCreate(sourceFunc.prototype);
-    var result = sourceFunc.apply(self, args);
-    if (_.isObject(result)) return result;
-    return self;
-  };
-
-  // Create a function bound to a given object (assigning `this`, and arguments,
-  // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
-  // available.
-  _.bind = function(func, context) {
-    if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
-    if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
-    var args = slice.call(arguments, 2);
-    var bound = function() {
-      return executeBound(func, bound, context, this, args.concat(slice.call(arguments)));
-    };
-    return bound;
-  };
-
-  // Partially apply a function by creating a version that has had some of its
-  // arguments pre-filled, without changing its dynamic `this` context. _ acts
-  // as a placeholder, allowing any combination of arguments to be pre-filled.
-  _.partial = function(func) {
-    var boundArgs = slice.call(arguments, 1);
-    var bound = function() {
-      var position = 0, length = boundArgs.length;
-      var args = Array(length);
-      for (var i = 0; i < length; i++) {
-        args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i];
-      }
-      while (position < arguments.length) args.push(arguments[position++]);
-      return executeBound(func, bound, this, this, args);
-    };
-    return bound;
-  };
-
-  // Bind a number of an object's methods to that object. Remaining arguments
-  // are the method names to be bound. Useful for ensuring that all callbacks
-  // defined on an object belong to it.
-  _.bindAll = function(obj) {
-    var i, length = arguments.length, key;
-    if (length <= 1) throw new Error('bindAll must be passed function names');
-    for (i = 1; i < length; i++) {
-      key = arguments[i];
-      obj[key] = _.bind(obj[key], obj);
-    }
-    return obj;
-  };
-
-  // Memoize an expensive function by storing its results.
-  _.memoize = function(func, hasher) {
-    var memoize = function(key) {
-      var cache = memoize.cache;
-      var address = '' + (hasher ? hasher.apply(this, arguments) : key);
-      if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
-      return cache[address];
-    };
-    memoize.cache = {};
-    return memoize;
-  };
-
-  // Delays a function for the given number of milliseconds, and then calls
-  // it with the arguments supplied.
-  _.delay = function(func, wait) {
-    var args = slice.call(arguments, 2);
-    return setTimeout(function(){
-      return func.apply(null, args);
-    }, wait);
-  };
-
-  // Defers a function, scheduling it to run after the current call stack has
-  // cleared.
-  _.defer = _.partial(_.delay, _, 1);
-
-  // Returns a function, that, when invoked, will only be triggered at most once
-  // during a given window of time. Normally, the throttled function will run
-  // as much as it can, without ever going more than once per `wait` duration;
-  // but if you'd like to disable the execution on the leading edge, pass
-  // `{leading: false}`. To disable execution on the trailing edge, ditto.
-  _.throttle = function(func, wait, options) {
-    var context, args, result;
-    var timeout = null;
-    var previous = 0;
-    if (!options) options = {};
-    var later = function() {
-      previous = options.leading === false ? 0 : _.now();
-      timeout = null;
-      result = func.apply(context, args);
-      if (!timeout) context = args = null;
-    };
-    return function() {
-      var now = _.now();
-      if (!previous && options.leading === false) previous = now;
-      var remaining = wait - (now - previous);
-      context = this;
-      args = arguments;
-      if (remaining <= 0 || remaining > wait) {
-        if (timeout) {
-          clearTimeout(timeout);
-          timeout = null;
-        }
-        previous = now;
-        result = func.apply(context, args);
-        if (!timeout) context = args = null;
-      } else if (!timeout && options.trailing !== false) {
-        timeout = setTimeout(later, remaining);
-      }
-      return result;
-    };
-  };
-
-  // Returns a function, that, as long as it continues to be invoked, will not
-  // be triggered. The function will be called after it stops being called for
-  // N milliseconds. If `immediate` is passed, trigger the function on the
-  // leading edge, instead of the trailing.
-  _.debounce = function(func, wait, immediate) {
-    var timeout, args, context, timestamp, result;
-
-    var later = function() {
-      var last = _.now() - timestamp;
-
-      if (last < wait && last >= 0) {
-        timeout = setTimeout(later, wait - last);
-      } else {
-        timeout = null;
-        if (!immediate) {
-          result = func.apply(context, args);
-          if (!timeout) context = args = null;
-        }
-      }
-    };
-
-    return function() {
-      context = this;
-      args = arguments;
-      timestamp = _.now();
-      var callNow = immediate && !timeout;
-      if (!timeout) timeout = setTimeout(later, wait);
-      if (callNow) {
-        result = func.apply(context, args);
-        context = args = null;
-      }
-
-      return result;
-    };
-  };
-
-  // Returns the first function passed as an argument to the second,
-  // allowing you to adjust arguments, run code before and after, and
-  // conditionally execute the original function.
-  _.wrap = function(func, wrapper) {
-    return _.partial(wrapper, func);
-  };
-
-  // Returns a negated version of the passed-in predicate.
-  _.negate = function(predicate) {
-    return function() {
-      return !predicate.apply(this, arguments);
-    };
-  };
-
-  // Returns a function that is the composition of a list of functions, each
-  // consuming the return value of the function that follows.
-  _.compose = function() {
-    var args = arguments;
-    var start = args.length - 1;
-    return function() {
-      var i = start;
-      var result = args[start].apply(this, arguments);
-      while (i--) result = args[i].call(this, result);
-      return result;
-    };
-  };
-
-  // Returns a function that will only be executed on and after the Nth call.
-  _.after = function(times, func) {
-    return function() {
-      if (--times < 1) {
-        return func.apply(this, arguments);
-      }
-    };
-  };
-
-  // Returns a function that will only be executed up to (but not including) the Nth call.
-  _.before = function(times, func) {
-    var memo;
-    return function() {
-      if (--times > 0) {
-        memo = func.apply(this, arguments);
-      }
-      if (times <= 1) func = null;
-      return memo;
-    };
-  };
-
-  // Returns a function that will be executed at most one time, no matter how
-  // often you call it. Useful for lazy initialization.
-  _.once = _.partial(_.before, 2);
-
-  // Object Functions
-  // ----------------
-
-  // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
-  var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
-  var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
-                      'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];
-
-  function collectNonEnumProps(obj, keys) {
-    var nonEnumIdx = nonEnumerableProps.length;
-    var constructor = obj.constructor;
-    var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto;
-
-    // Constructor is a special case.
-    var prop = 'constructor';
-    if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);
-
-    while (nonEnumIdx--) {
-      prop = nonEnumerableProps[nonEnumIdx];
-      if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
-        keys.push(prop);
-      }
-    }
-  }
-
-  // Retrieve the names of an object's own properties.
-  // Delegates to **ECMAScript 5**'s native `Object.keys`
-  _.keys = function(obj) {
-    if (!_.isObject(obj)) return [];
-    if (nativeKeys) return nativeKeys(obj);
-    var keys = [];
-    for (var key in obj) if (_.has(obj, key)) keys.push(key);
-    // Ahem, IE < 9.
-    if (hasEnumBug) collectNonEnumProps(obj, keys);
-    return keys;
-  };
-
-  // Retrieve all the property names of an object.
-  _.allKeys = function(obj) {
-    if (!_.isObject(obj)) return [];
-    var keys = [];
-    for (var key in obj) keys.push(key);
-    // Ahem, IE < 9.
-    if (hasEnumBug) collectNonEnumProps(obj, keys);
-    return keys;
-  };
-
-  // Retrieve the values of an object's properties.
-  _.values = function(obj) {
-    var keys = _.keys(obj);
-    var length = keys.length;
-    var values = Array(length);
-    for (var i = 0; i < length; i++) {
-      values[i] = obj[keys[i]];
-    }
-    return values;
-  };
-
-  // Returns the results of applying the iteratee to each element of the object
-  // In contrast to _.map it returns an object
-  _.mapObject = function(obj, iteratee, context) {
-    iteratee = cb(iteratee, context);
-    var keys =  _.keys(obj),
-          length = keys.length,
-          results = {},
-          currentKey;
-      for (var index = 0; index < length; index++) {
-        currentKey = keys[index];
-        results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
-      }
-      return results;
-  };
-
-  // Convert an object into a list of `[key, value]` pairs.
-  _.pairs = function(obj) {
-    var keys = _.keys(obj);
-    var length = keys.length;
-    var pairs = Array(length);
-    for (var i = 0; i < length; i++) {
-      pairs[i] = [keys[i], obj[keys[i]]];
-    }
-    return pairs;
-  };
-
-  // Invert the keys and values of an object. The values must be serializable.
-  _.invert = function(obj) {
-    var result = {};
-    var keys = _.keys(obj);
-    for (var i = 0, length = keys.length; i < length; i++) {
-      result[obj[keys[i]]] = keys[i];
-    }
-    return result;
-  };
-
-  // Return a sorted list of the function names available on the object.
-  // Aliased as `methods`
-  _.functions = _.methods = function(obj) {
-    var names = [];
-    for (var key in obj) {
-      if (_.isFunction(obj[key])) names.push(key);
-    }
-    return names.sort();
-  };
-
-  // Extend a given object with all the properties in passed-in object(s).
-  _.extend = createAssigner(_.allKeys);
-
-  // Assigns a given object with all the own properties in the passed-in object(s)
-  // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
-  _.extendOwn = _.assign = createAssigner(_.keys);
-
-  // Returns the first key on an object that passes a predicate test
-  _.findKey = function(obj, predicate, context) {
-    predicate = cb(predicate, context);
-    var keys = _.keys(obj), key;
-    for (var i = 0, length = keys.length; i < length; i++) {
-      key = keys[i];
-      if (predicate(obj[key], key, obj)) return key;
-    }
-  };
-
-  // Return a copy of the object only containing the whitelisted properties.
-  _.pick = function(object, oiteratee, context) {
-    var result = {}, obj = object, iteratee, keys;
-    if (obj == null) return result;
-    if (_.isFunction(oiteratee)) {
-      keys = _.allKeys(obj);
-      iteratee = optimizeCb(oiteratee, context);
-    } else {
-      keys = flatten(arguments, false, false, 1);
-      iteratee = function(value, key, obj) { return key in obj; };
-      obj = Object(obj);
-    }
-    for (var i = 0, length = keys.length; i < length; i++) {
-      var key = keys[i];
-      var value = obj[key];
-      if (iteratee(value, key, obj)) result[key] = value;
-    }
-    return result;
-  };
-
-   // Return a copy of the object without the blacklisted properties.
-  _.omit = function(obj, iteratee, context) {
-    if (_.isFunction(iteratee)) {
-      iteratee = _.negate(iteratee);
-    } else {
-      var keys = _.map(flatten(arguments, false, false, 1), String);
-      iteratee = function(value, key) {
-        return !_.contains(keys, key);
-      };
-    }
-    return _.pick(obj, iteratee, context);
-  };
-
-  // Fill in a given object with default properties.
-  _.defaults = createAssigner(_.allKeys, true);
-
-  // Creates an object that inherits from the given prototype object.
-  // If additional properties are provided then they will be added to the
-  // created object.
-  _.create = function(prototype, props) {
-    var result = baseCreate(prototype);
-    if (props) _.extendOwn(result, props);
-    return result;
-  };
-
-  // Create a (shallow-cloned) duplicate of an object.
-  _.clone = function(obj) {
-    if (!_.isObject(obj)) return obj;
-    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
-  };
-
-  // Invokes interceptor with the obj, and then returns obj.
-  // The primary purpose of this method is to "tap into" a method chain, in
-  // order to perform operations on intermediate results within the chain.
-  _.tap = function(obj, interceptor) {
-    interceptor(obj);
-    return obj;
-  };
-
-  // Returns whether an object has a given set of `key:value` pairs.
-  _.isMatch = function(object, attrs) {
-    var keys = _.keys(attrs), length = keys.length;
-    if (object == null) return !length;
-    var obj = Object(object);
-    for (var i = 0; i < length; i++) {
-      var key = keys[i];
-      if (attrs[key] !== obj[key] || !(key in obj)) return false;
-    }
-    return true;
-  };
-
-
-  // Internal recursive comparison function for `isEqual`.
-  var eq = function(a, b, aStack, bStack) {
-    // Identical objects are equal. `0 === -0`, but they aren't identical.
-    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
-    if (a === b) return a !== 0 || 1 / a === 1 / b;
-    // A strict comparison is necessary because `null == undefined`.
-    if (a == null || b == null) return a === b;
-    // Unwrap any wrapped objects.
-    if (a instanceof _) a = a._wrapped;
-    if (b instanceof _) b = b._wrapped;
-    // Compare `[[Class]]` names.
-    var className = toString.call(a);
-    if (className !== toString.call(b)) return false;
-    switch (className) {
-      // Strings, numbers, regular expressions, dates, and booleans are compared by value.
-      case '[object RegExp]':
-      // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
-      case '[object String]':
-        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
-        // equivalent to `new String("5")`.
-        return '' + a === '' + b;
-      case '[object Number]':
-        // `NaN`s are equivalent, but non-reflexive.
-        // Object(NaN) is equivalent to NaN
-        if (+a !== +a) return +b !== +b;
-        // An `egal` comparison is performed for other numeric values.
-        return +a === 0 ? 1 / +a === 1 / b : +a === +b;
-      case '[object Date]':
-      case '[object Boolean]':
-        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
-        // millisecond representations. Note that invalid dates with millisecond representations
-        // of `NaN` are not equivalent.
-        return +a === +b;
-    }
-
-    var areArrays = className === '[object Array]';
-    if (!areArrays) {
-      if (typeof a != 'object' || typeof b != 'object') return false;
-
-      // Objects with different constructors are not equivalent, but `Object`s or `Array`s
-      // from different frames are.
-      var aCtor = a.constructor, bCtor = b.constructor;
-      if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
-                               _.isFunction(bCtor) && bCtor instanceof bCtor)
-                          && ('constructor' in a && 'constructor' in b)) {
-        return false;
-      }
-    }
-    // Assume equality for cyclic structures. The algorithm for detecting cyclic
-    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
-
-    // Initializing stack of traversed objects.
-    // It's done here since we only need them for objects and arrays comparison.
-    aStack = aStack || [];
-    bStack = bStack || [];
-    var length = aStack.length;
-    while (length--) {
-      // Linear search. Performance is inversely proportional to the number of
-      // unique nested structures.
-      if (aStack[length] === a) return bStack[length] === b;
-    }
-
-    // Add the first object to the stack of traversed objects.
-    aStack.push(a);
-    bStack.push(b);
-
-    // Recursively compare objects and arrays.
-    if (areArrays) {
-      // Compare array lengths to determine if a deep comparison is necessary.
-      length = a.length;
-      if (length !== b.length) return false;
-      // Deep compare the contents, ignoring non-numeric properties.
-      while (length--) {
-        if (!eq(a[length], b[length], aStack, bStack)) return false;
-      }
-    } else {
-      // Deep compare objects.
-      var keys = _.keys(a), key;
-      length = keys.length;
-      // Ensure that both objects contain the same number of properties before comparing deep equality.
-      if (_.keys(b).length !== length) return false;
-      while (length--) {
-        // Deep compare each member
-        key = keys[length];
-        if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
-      }
-    }
-    // Remove the first object from the stack of traversed objects.
-    aStack.pop();
-    bStack.pop();
-    return true;
-  };
-
-  // Perform a deep comparison to check if two objects are equal.
-  _.isEqual = function(a, b) {
-    return eq(a, b);
-  };
-
-  // Is a given array, string, or object empty?
-  // An "empty" object has no enumerable own-properties.
-  _.isEmpty = function(obj) {
-    if (obj == null) return true;
-    if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
-    return _.keys(obj).length === 0;
-  };
-
-  // Is a given value a DOM element?
-  _.isElement = function(obj) {
-    return !!(obj && obj.nodeType === 1);
-  };
-
-  // Is a given value an array?
-  // Delegates to ECMA5's native Array.isArray
-  _.isArray = nativeIsArray || function(obj) {
-    return toString.call(obj) === '[object Array]';
-  };
-
-  // Is a given variable an object?
-  _.isObject = function(obj) {
-    var type = typeof obj;
-    return type === 'function' || type === 'object' && !!obj;
-  };
-
-  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError.
-  _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) {
-    _['is' + name] = function(obj) {
-      return toString.call(obj) === '[object ' + name + ']';
-    };
-  });
-
-  // Define a fallback version of the method in browsers (ahem, IE < 9), where
-  // there isn't any inspectable "Arguments" type.
-  if (!_.isArguments(arguments)) {
-    _.isArguments = function(obj) {
-      return _.has(obj, 'callee');
-    };
-  }
-
-  // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8,
-  // IE 11 (#1621), and in Safari 8 (#1929).
-  if (typeof /./ != 'function' && typeof Int8Array != 'object') {
-    _.isFunction = function(obj) {
-      return typeof obj == 'function' || false;
-    };
-  }
-
-  // Is a given object a finite number?
-  _.isFinite = function(obj) {
-    return isFinite(obj) && !isNaN(parseFloat(obj));
-  };
-
-  // Is the given value `NaN`? (NaN is the only number which does not equal itself).
-  _.isNaN = function(obj) {
-    return _.isNumber(obj) && obj !== +obj;
-  };
-
-  // Is a given value a boolean?
-  _.isBoolean = function(obj) {
-    return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
-  };
-
-  // Is a given value equal to null?
-  _.isNull = function(obj) {
-    return obj === null;
-  };
-
-  // Is a given variable undefined?
-  _.isUndefined = function(obj) {
-    return obj === void 0;
-  };
-
-  // Shortcut function for checking if an object has a given property directly
-  // on itself (in other words, not on a prototype).
-  _.has = function(obj, key) {
-    return obj != null && hasOwnProperty.call(obj, key);
-  };
-
-  // Utility Functions
-  // -----------------
-
-  // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
-  // previous owner. Returns a reference to the Underscore object.
-  _.noConflict = function() {
-    root._ = previousUnderscore;
-    return this;
-  };
-
-  // Keep the identity function around for default iteratees.
-  _.identity = function(value) {
-    return value;
-  };
-
-  // Predicate-generating functions. Often useful outside of Underscore.
-  _.constant = function(value) {
-    return function() {
-      return value;
-    };
-  };
-
-  _.noop = function(){};
-
-  _.property = property;
-
-  // Generates a function for a given object that returns a given property.
-  _.propertyOf = function(obj) {
-    return obj == null ? function(){} : function(key) {
-      return obj[key];
-    };
-  };
-
-  // Returns a predicate for checking whether an object has a given set of
-  // `key:value` pairs.
-  _.matcher = _.matches = function(attrs) {
-    attrs = _.extendOwn({}, attrs);
-    return function(obj) {
-      return _.isMatch(obj, attrs);
-    };
-  };
-
-  // Run a function **n** times.
-  _.times = function(n, iteratee, context) {
-    var accum = Array(Math.max(0, n));
-    iteratee = optimizeCb(iteratee, context, 1);
-    for (var i = 0; i < n; i++) accum[i] = iteratee(i);
-    return accum;
-  };
-
-  // Return a random integer between min and max (inclusive).
-  _.random = function(min, max) {
-    if (max == null) {
-      max = min;
-      min = 0;
-    }
-    return min + Math.floor(Math.random() * (max - min + 1));
-  };
-
-  // A (possibly faster) way to get the current timestamp as an integer.
-  _.now = Date.now || function() {
-    return new Date().getTime();
-  };
-
-   // List of HTML entities for escaping.
-  var escapeMap = {
-    '&': '&amp;',
-    '<': '&lt;',
-    '>': '&gt;',
-    '"': '&quot;',
-    "'": '&#x27;',
-    '`': '&#x60;'
-  };
-  var unescapeMap = _.invert(escapeMap);
-
-  // Functions for escaping and unescaping strings to/from HTML interpolation.
-  var createEscaper = function(map) {
-    var escaper = function(match) {
-      return map[match];
-    };
-    // Regexes for identifying a key that needs to be escaped
-    var source = '(?:' + _.keys(map).join('|') + ')';
-    var testRegexp = RegExp(source);
-    var replaceRegexp = RegExp(source, 'g');
-    return function(string) {
-      string = string == null ? '' : '' + string;
-      return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
-    };
-  };
-  _.escape = createEscaper(escapeMap);
-  _.unescape = createEscaper(unescapeMap);
-
-  // If the value of the named `property` is a function then invoke it with the
-  // `object` as context; otherwise, return it.
-  _.result = function(object, property, fallback) {
-    var value = object == null ? void 0 : object[property];
-    if (value === void 0) {
-      value = fallback;
-    }
-    return _.isFunction(value) ? value.call(object) : value;
-  };
-
-  // Generate a unique integer id (unique within the entire client session).
-  // Useful for temporary DOM ids.
-  var idCounter = 0;
-  _.uniqueId = function(prefix) {
-    var id = ++idCounter + '';
-    return prefix ? prefix + id : id;
-  };
-
-  // By default, Underscore uses ERB-style template delimiters, change the
-  // following template settings to use alternative delimiters.
-  _.templateSettings = {
-    evaluate    : /<%([\s\S]+?)%>/g,
-    interpolate : /<%=([\s\S]+?)%>/g,
-    escape      : /<%-([\s\S]+?)%>/g
-  };
-
-  // When customizing `templateSettings`, if you don't want to define an
-  // interpolation, evaluation or escaping regex, we need one that is
-  // guaranteed not to match.
-  var noMatch = /(.)^/;
-
-  // Certain characters need to be escaped so that they can be put into a
-  // string literal.
-  var escapes = {
-    "'":      "'",
-    '\\':     '\\',
-    '\r':     'r',
-    '\n':     'n',
-    '\u2028': 'u2028',
-    '\u2029': 'u2029'
-  };
-
-  var escaper = /\\|'|\r|\n|\u2028|\u2029/g;
-
-  var escapeChar = function(match) {
-    return '\\' + escapes[match];
-  };
-
-  // JavaScript micro-templating, similar to John Resig's implementation.
-  // Underscore templating handles arbitrary delimiters, preserves whitespace,
-  // and correctly escapes quotes within interpolated code.
-  // NB: `oldSettings` only exists for backwards compatibility.
-  _.template = function(text, settings, oldSettings) {
-    if (!settings && oldSettings) settings = oldSettings;
-    settings = _.defaults({}, settings, _.templateSettings);
-
-    // Combine delimiters into one regular expression via alternation.
-    var matcher = RegExp([
-      (settings.escape || noMatch).source,
-      (settings.interpolate || noMatch).source,
-      (settings.evaluate || noMatch).source
-    ].join('|') + '|$', 'g');
-
-    // Compile the template source, escaping string literals appropriately.
-    var index = 0;
-    var source = "__p+='";
-    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
-      source += text.slice(index, offset).replace(escaper, escapeChar);
-      index = offset + match.length;
-
-      if (escape) {
-        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
-      } else if (interpolate) {
-        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
-      } else if (evaluate) {
-        source += "';\n" + evaluate + "\n__p+='";
-      }
-
-      // Adobe VMs need the match returned to produce the correct offest.
-      return match;
-    });
-    source += "';\n";
-
-    // If a variable is not specified, place data values in local scope.
-    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
-
-    source = "var __t,__p='',__j=Array.prototype.join," +
-      "print=function(){__p+=__j.call(arguments,'');};\n" +
-      source + 'return __p;\n';
-
-    try {
-      var render = new Function(settings.variable || 'obj', '_', source);
-    } catch (e) {
-      e.source = source;
-      throw e;
-    }
-
-    var template = function(data) {
-      return render.call(this, data, _);
-    };
-
-    // Provide the compiled source as a convenience for precompilation.
-    var argument = settings.variable || 'obj';
-    template.source = 'function(' + argument + '){\n' + source + '}';
-
-    return template;
-  };
-
-  // Add a "chain" function. Start chaining a wrapped Underscore object.
-  _.chain = function(obj) {
-    var instance = _(obj);
-    instance._chain = true;
-    return instance;
-  };
-
-  // OOP
-  // ---------------
-  // If Underscore is called as a function, it returns a wrapped object that
-  // can be used OO-style. This wrapper holds altered versions of all the
-  // underscore functions. Wrapped objects may be chained.
-
-  // Helper function to continue chaining intermediate results.
-  var result = function(instance, obj) {
-    return instance._chain ? _(obj).chain() : obj;
-  };
-
-  // Add your own custom functions to the Underscore object.
-  _.mixin = function(obj) {
-    _.each(_.functions(obj), function(name) {
-      var func = _[name] = obj[name];
-      _.prototype[name] = function() {
-        var args = [this._wrapped];
-        push.apply(args, arguments);
-        return result(this, func.apply(_, args));
-      };
-    });
-  };
-
-  // Add all of the Underscore functions to the wrapper object.
-  _.mixin(_);
-
-  // Add all mutator Array functions to the wrapper.
-  _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
-    var method = ArrayProto[name];
-    _.prototype[name] = function() {
-      var obj = this._wrapped;
-      method.apply(obj, arguments);
-      if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
-      return result(this, obj);
-    };
-  });
-
-  // Add all accessor Array functions to the wrapper.
-  _.each(['concat', 'join', 'slice'], function(name) {
-    var method = ArrayProto[name];
-    _.prototype[name] = function() {
-      return result(this, method.apply(this._wrapped, arguments));
-    };
-  });
-
-  // Extracts the result from a wrapped and chained object.
-  _.prototype.value = function() {
-    return this._wrapped;
-  };
-
-  // Provide unwrapping proxy for some methods used in engine operations
-  // such as arithmetic and JSON stringification.
-  _.prototype.valueOf = _.prototype.toJSON = _.prototype.value;
-
-  _.prototype.toString = function() {
-    return '' + this._wrapped;
-  };
-
-  // AMD registration happens at the end for compatibility with AMD loaders
-  // that may not enforce next-turn semantics on modules. Even though general
-  // practice for AMD registration is to be anonymous, underscore registers
-  // as a named module because, like jQuery, it is a base library that is
-  // popular enough to be bundled in a third party lib, but not be part of
-  // an AMD load request. Those cases could generate an error when an
-  // anonymous define() is called outside of a loader request.
-  if (typeof define === 'function' && define.amd) {
-    define('underscore', [], function() {
-      return _;
-    });
-  }
-}.call(this));
similarity index 64%
rename from pdns/recursordist/html/local.js
rename to pdns/recursordist/html/local-2022.js
index 549102c0e2c139c61315e6de0b8072df5ab59dd1..122a8ac5b9cf85c5ce7c65b351d3aef7ceabe56b 100644 (file)
@@ -1,13 +1,49 @@
 "use strict";
 
-// var moment= require('moment');
+const fetchConfig = {
+    baseURL: window.location,
+    mode: 'same-origin',
+    headers: {'Accept': 'application/json'},
+};
+/*
+// Useful for development of the embedded webserver files.
+const fetchConfig = {
+    baseURL: 'http://127.0.0.1:8083/',
+    mode: 'cors',
+    headers: {'Accept': 'application/json', 'X-API-Key': 'changeme'},
+};
+*/
+
 var gdata = {};
 
-$(document).ready(function () {
-    $.ajaxSetup({cache: false});
+function get_json(url, params) {
+    const realURL = new URL(url, fetchConfig.baseURL);
+    if (params) {
+        for (const [k, v] of Object.entries(params)) {
+            realURL.searchParams.append(k, v);
+        }
+    }
+    return new Promise((resolve, reject) => {
+        fetch(realURL, {
+            method: 'GET',
+            mode: fetchConfig.mode,
+            cache: 'no-cache',
+            headers: fetchConfig.headers,
+        }).then((response) => {
+            if (response.ok) {
+                response.json().then((json) => resolve(json));
+            } else {
+                reject(`HTTP Status ${response.status} ${response.statusText}`);
+            }
+        }).catch((error) => {
+            reject(error.message);
+        })
+    });
+}
 
+function startup() {
     var getTemplate = function (name) {
-        var template = $('#' + name + '-template').html();
+        const template = document.querySelector(`#${name}-template`).innerHTML;
         return Handlebars.compile(template);
     };
     var cachedTemplates = {};
@@ -17,8 +53,8 @@ $(document).ready(function () {
             t = getTemplate(name);
             cachedTemplates[name] = t;
         }
-        var h = t(ctx);
-        $('#' + name).html(h);
+        const html = t(ctx);
+        document.querySelector('#' + name).innerHTML = html;
     };
 
     var qpsgraph = new Rickshaw.Graph({
@@ -77,7 +113,7 @@ $(document).ready(function () {
         var num = 0;
         var total = 0, rest = 0;
         var rows = [];
-        $.each(data["entries"], function (a, b) {
+        data["entries"].forEach((b) => {
             total += b[0];
             if (num++ > 10) {
                 rest += b[0];
@@ -95,72 +131,69 @@ $(document).ready(function () {
     };
 
     function updateRingBuffers() {
-        $.getJSON('jsonstat', jsonstatParams('get-query-ring', 'queries', $("#filter1").is(':checked')),
+        const filterChecked = document.querySelector("#filter1").checked;
+        get_json('jsonstat', jsonstatParams('get-query-ring', 'queries', filterChecked)).then(
             function (data) {
                 var rows = makeRingRows(data);
                 render('queryring', {rows: rows});
             });
 
-        $.getJSON('jsonstat', jsonstatParams('get-query-ring', 'servfail-queries', $("#filter1").is(':checked')),
+        get_json('jsonstat', jsonstatParams('get-query-ring', 'servfail-queries', filterChecked)).then(
             function (data) {
                 var rows = makeRingRows(data);
                 render('servfailqueryring', {rows: rows});
             });
 
-        $.getJSON('jsonstat', jsonstatParams('get-query-ring', 'bogus-queries', $("#filter1").is(':checked')),
+        get_json('jsonstat', jsonstatParams('get-query-ring', 'bogus-queries', filterChecked)).then(
             function (data) {
                 var rows = makeRingRows(data);
                 render('bogusqueryring', {rows: rows});
             });
 
-        $.getJSON('jsonstat', jsonstatParams('get-remote-ring', 'remotes', false),
+        get_json('jsonstat', jsonstatParams('get-remote-ring', 'remotes', false)).then(
             function (data) {
                 var rows = makeRingRows(data);
                 render('remotering', {rows: rows});
             });
 
-        $.getJSON('jsonstat', jsonstatParams('get-remote-ring', 'servfail-remotes', false),
+        get_json('jsonstat', jsonstatParams('get-remote-ring', 'servfail-remotes', false)).then(
             function (data) {
                 var rows = makeRingRows(data);
                 render('servfailremotering', {rows: rows});
             });
 
-        $.getJSON('jsonstat', jsonstatParams('get-remote-ring', 'bogus-remotes', false),
+        get_json('jsonstat', jsonstatParams('get-remote-ring', 'bogus-remotes', false)).then(
             function (data) {
                 var rows = makeRingRows(data);
                 render('bogusremotering', {rows: rows});
             });
-        $.getJSON('jsonstat', jsonstatParams('get-remote-ring', 'timeouts', false),
+        get_json('jsonstat', jsonstatParams('get-remote-ring', 'timeouts', false)).then(
             function (data) {
                 var rows = makeRingRows(data);
                 render('timeouts', {rows: rows});
             });
     }
 
-    var connectionOK = function (ok, o) {
+    var connectionOK = function (ok, reason) {
         if (ok) {
-            $("#connection-status").hide();
-            $("#connection-error").html("");
-            $("#content-hidden-on-load").show();
+            document.querySelector("#connection-status").style.display = "none";
+            document.querySelector("#connection-error").innerHTML = "";
+            document.querySelector("#content-hidden-on-load").style.display = "inherit";
         } else {
-            $("#connection-status").show();
-            $("#connection-error").html(o.status + " " + o.statusText);
+            document.querySelector("#connection-status").style.display = "inherit";
+            document.querySelector("#connection-error").innerHTML = reason;
         }
     };
 
     var version = null;
 
     function update() {
-        $.ajax({
-            url: 'api/v1/servers/localhost/statistics',
-            type: 'GET',
-            dataType: 'json',
-            success: function (adata, x, y) {
+        get_json('api/v1/servers/localhost/statistics').then((adata) => {
                 connectionOK(true);
 
                 var data = {};
-                $.each(adata, function (key, val) {
-                    data[val.name] = val.value;
+                adata.forEach((statItem) => {
+                    data[statItem.name] = statItem.value;
                 });
 
                 if (!gdata["sys-msec"])
@@ -197,22 +230,13 @@ $(document).ready(function () {
                 cpugraph.render();
 
                 gdata = data;
-            },
-            error: function (o) {
-                connectionOK(false, o);
-            },
-            beforeSend: function (xhr) {
-                xhr.setRequestHeader('X-API-Key', 'changeme');
-                return true;
-            }
+        }).catch((reason) => {
+            connectionOK(false, reason);
         });
 
         if (!version) {
-            $.ajax({
-                url: 'api/v1/servers/localhost', type: 'GET', dataType: 'json',
-                success: function (data) {
-                    version = "PowerDNS " + data["daemon_type"] + " " + data["version"];
-                }
+            get_json('api/v1/servers/localhost').then((data) => {
+                version = "PowerDNS " + data["daemon_type"] + " " + data["version"]
             });
         }
 
@@ -221,9 +245,11 @@ $(document).ready(function () {
         updateRingBuffers();
     }
 
-    $("#filter1").click(updateRingBuffers);
-    $("#filter2").click(updateRingBuffers);
+    document.querySelector("#filter1").addEventListener('click', updateRingBuffers);
 
     update();
     setInterval(update, 1000);
-});
+}
+
+// rely on "defer" on <script> tag for document to be ready before running.
+startup();
index a65909a53e814c6431836edb9ebef5b9350284a9..b0e67ec0f410c6469c1599a625516426a294e600 100755 (executable)
@@ -8,7 +8,10 @@ then
        DIR=$1/
 fi
 
-for a in $(find ${DIR}html -type f | grep -v \~ | sort)
+files=$(find ${DIR}html -type f \( -name '*.css' -or -name '*.html' -or -name '*.png' -or -name 'local-2022.js' -or -name '*min.js' \) | sort)
+
+echo // $files
+for a in $files
 do
        c=$(echo $a | sed s:${DIR}html/:: | tr "/.-" "___")
         echo "static const unsigned char g${c}Data[] = {"
@@ -17,7 +20,7 @@ do
 done
 
 echo "static const map<string,string> g_urlmap={"
-for a in $(find ${DIR}html -type f | grep -v \~ | sort)
+for a in $files
 do
        b=$(echo $a | sed s:${DIR}html/::g)
        c=$(echo $b | tr "/.-" "___")
deleted file mode 120000 (symlink)
index 03eb18b5e463d1b1a7545c60c6bff9b015acf342..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../lazy_allocator.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..05ab56b1fa0e1bd89172008e4f288d363b1e895d
--- /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 <cstddef>
+#include <utility>
+#include <type_traits>
+#include <new>
+#include <sys/mman.h>
+#include <unistd.h>
+
+// 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
+#endif
+
+template <typename T>
+struct lazy_allocator
+{
+  using value_type = T;
+  using pointer = T*;
+  using size_type = std::size_t;
+  static_assert(std::is_trivial<T>::value,
+                "lazy_allocator must only be used with trivial types");
+
+#ifndef LAZY_ALLOCATOR_USES_NEW
+  /* Pad the requested size to be a multiple of page size, so that we can
+     properly restrict read and write access to our guard pages only */
+  static size_type getAlignmentPadding(size_type requestedSize, size_type pageSize)
+  {
+    size_type remaining = requestedSize % pageSize;
+    if (remaining == 0) {
+      return 0;
+    }
+    return pageSize - remaining;
+  }
+#endif /* LAZY_ALLOCATOR_USES_NEW */
+
+  pointer
+  allocate(size_type const n)
+  {
+#ifdef LAZY_ALLOCATOR_USES_NEW
+    return static_cast<pointer>(::operator new(n * sizeof(value_type)));
+#else /* LAZY_ALLOCATOR_USES_NEW */
+    /* This implements a very basic protection against stack overflow
+       by placing two guard pages around the requested memory: one
+       page right before the new stack and one right after.
+       The guard pages cannot be read or written to, any attempt to
+       do so will trigger an immediate access violation, terminating
+       the program.
+       This is much better than the default behaviour for two reasons:
+       1/ the program is stopped right before corrupting memory, which
+          prevents random corruption
+       2/ it's easy to find the point where the stack overflow occurred
+       The memory overhead is two pages (usually 4k on Linux) per stack,
+       and the runtime CPU overhead is one call to mprotect() for every
+       stack allocation.
+    */
+    static const size_type pageSize = sysconf(_SC_PAGESIZE);
+
+    const size_type requestedSize = n * sizeof(value_type);
+    const auto padding = getAlignmentPadding(requestedSize, pageSize);
+    const size_type allocatedSize = requestedSize + padding + (pageSize * 2);
+
+#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
+    // explicitly.
+    const int protection = PROT_READ | PROT_WRITE;
+#else
+    const int protection = PROT_NONE;
+#endif
+    void* p = mmap(nullptr, allocatedSize, protection, MAP_PRIVATE | MAP_ANON | PDNS_MAP_STACK, -1, 0);
+    if (p == MAP_FAILED) {
+      throw std::bad_alloc();
+    }
+    char* basePointer = static_cast<char*>(p);
+    void* usablePointer = basePointer + pageSize;
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+    int res = mprotect(basePointer, pageSize, PROT_NONE);
+    if (res != 0) {
+      munmap(p, allocatedSize);
+      throw std::bad_alloc();
+    }
+    res = mprotect(basePointer + allocatedSize - pageSize, pageSize, PROT_NONE);
+#else
+    int res = mprotect(usablePointer, allocatedSize - (pageSize * 2), PROT_READ | PROT_WRITE);
+#endif
+    if (res != 0) {
+      munmap(p, allocatedSize);
+      throw std::bad_alloc();
+    }
+    return static_cast<pointer>(usablePointer);
+#endif
+  }
+
+  void
+  deallocate(pointer const ptr, size_type const n) noexcept
+  {
+#ifdef LAZY_ALLOCATOR_USES_NEW
+#if defined(__cpp_sized_deallocation) && (__cpp_sized_deallocation >= 201309)
+    ::operator delete(ptr, n * sizeof(value_type));
+#else
+    (void)n;
+    ::operator delete(ptr);
+#endif
+#else /* LAZY_ALLOCATOR_USES_NEW */
+    static const size_type pageSize = sysconf(_SC_PAGESIZE);
+
+    const size_type requestedSize = n * sizeof(value_type);
+    const auto padding = getAlignmentPadding(requestedSize, pageSize);
+    const size_type allocatedSize = requestedSize + padding + (pageSize * 2);
+
+    void* basePointer = static_cast<char*>(ptr) - pageSize;
+    munmap(basePointer, allocatedSize);
+#endif /* LAZY_ALLOCATOR_PROTECT */
+  }
+
+  void construct(T*) const noexcept {}
+
+  template <typename X, typename... Args>
+  void
+  construct(X* place, Args&&... args) const noexcept
+  {
+    new (static_cast<void*>(place)) X(std::forward<Args>(args)...);
+  }
+};
+
+template <typename T>
+inline bool operator==(lazy_allocator<T> const&, lazy_allocator<T> const&) noexcept
+{
+  return true;
+}
+
+template <typename T>
+inline bool operator!=(lazy_allocator<T> const&, lazy_allocator<T> const&) noexcept
+{
+  return false;
+}
index 6196ba3b8f7a8aa572a0668c00c3bd2b15ea492c..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
@@ -106,27 +106,6 @@ std::shared_ptr<Logr::Logger> Logger::withValues(const std::map<std::string, std
   return res;
 }
 
-template struct Loggable<DNSName>;
-template struct Loggable<ComboAddress>;
-template struct Loggable<std::string>;
-template struct Loggable<size_t>;
-
-template <>
-std::string Loggable<DNSName>::to_string() const
-{
-  return _t.toLogString();
-}
-template <>
-std::string Loggable<ComboAddress>::to_string() const
-{
-  return _t.toLogString();
-}
-template <>
-std::string Loggable<std::string>::to_string() const
-{
-  return _t;
-}
-
 std::shared_ptr<Logr::Logger> Logger::withName(const std::string& name) const
 {
   std::shared_ptr<Logger> res;
@@ -163,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)
 {
 }
 
deleted file mode 100644 (file)
index fb03332d3ad3466687597df4e5bcac7959685add..0000000000000000000000000000000000000000
+++ /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 <map>
-#include <memory>
-#include <string>
-#include <sstream>
-#include <boost/optional.hpp>
-
-#include "logr.hh"
-#include "dnsname.hh"
-#include "iputils.hh"
-
-namespace Logging
-{
-
-struct Entry
-{
-  boost::optional<std::string> name; // name parts joined with '.'
-  std::string message; // message as send to log call
-  boost::optional<std::string> error; // error if .Error() was called
-  struct timeval d_timestamp; // time of entry generation
-  std::map<std::string, std::string> values; // key-value pairs
-  size_t level; // level at which this was logged
-  Logr::Priority d_priority; // (syslog) priority)
-};
-
-template <typename T>
-struct Loggable : public Logr::Loggable
-{
-  const T& _t;
-  Loggable(const T& v) :
-    _t(v)
-  {
-  }
-  std::string to_string() const
-  {
-    std::ostringstream oss;
-    oss << _t;
-    return oss.str();
-  }
-};
-template <>
-std::string Loggable<DNSName>::to_string() const;
-template <>
-std::string Loggable<ComboAddress>::to_string() const;
-template <>
-std::string Loggable<std::string>::to_string() const;
-
-// Loggable<std::string>::Loggable(const std::string& v): _t(v) {}
-
-typedef void (*EntryLogger)(const Entry&);
-
-class Logger : public Logr::Logger, public std::enable_shared_from_this<const Logger>
-{
-public:
-  bool enabled(Logr::Priority) const override;
-
-  void info(const std::string& msg) const override;
-  void info(Logr::Priority, const std::string& msg) const override;
-  void error(int err, const std::string& msg) const override;
-  void error(const std::string& err, const std::string& msg) const override;
-  void error(Logr::Priority, int err, const std::string& msg) const override;
-  void error(Logr::Priority, const std::string& err, const std::string& msg) const override;
-
-  std::shared_ptr<Logr::Logger> v(size_t level) const override;
-  std::shared_ptr<Logr::Logger> withValues(const std::map<std::string, std::string>& values) const override;
-  virtual std::shared_ptr<Logr::Logger> withName(const std::string& name) const override;
-
-  static std::shared_ptr<Logger> create(EntryLogger callback);
-  static std::shared_ptr<Logger> create(EntryLogger callback, const std::string& name);
-
-  Logger(EntryLogger callback);
-  Logger(EntryLogger callback, boost::optional<std::string> name);
-  Logger(std::shared_ptr<const Logger> parent, boost::optional<std::string> name, size_t verbosity, size_t lvl, EntryLogger callback);
-  virtual ~Logger();
-
-  size_t getVerbosity() const;
-  void setVerbosity(size_t verbosity);
-
-private:
-  void logMessage(const std::string& msg, boost::optional<const std::string> err) const;
-  void logMessage(const std::string& msg, Logr::Priority p, boost::optional<const std::string> err) const;
-  std::shared_ptr<const Logger> getptr() const;
-
-  std::shared_ptr<const Logger> _parent{nullptr};
-  EntryLogger _callback;
-  boost::optional<std::string> _name;
-  std::map<std::string, std::string> _values;
-  // current Logger's level. the higher the more verbose.
-  size_t _level{0};
-  // verbosity settings. messages with level higher's than verbosity won't appear
-  size_t _verbosity{0};
-};
-}
-
-extern std::shared_ptr<Logging::Logger> g_slog;
-
-// Prefer structured logging?
-extern bool g_slogStructured;
-
-// A helper macro to switch between old-style logging and new-style (structured logging)
-// A typical use:
-//
-// 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));
-//
-#define SLOG(oldStyle, slogCall) \
-  do {                           \
-    if (g_slogStructured) {      \
-      slogCall;                  \
-    }                            \
-    else {                       \
-      oldStyle;                  \
-    }                            \
-  } while (0);
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..020d82c1ef49c5b2f9ad8408e1d23c9747d38163
--- /dev/null
@@ -0,0 +1 @@
+../logging.hh
\ No newline at end of file
index 22a0a6f37fa6f87041d6d56f6aae3e08afd30ebd..dea00b806f74bb916bbab3a56ad5178433610105 100644 (file)
 
 #pragma once
 
+#include <array>
 #include <string>
 #include <memory>
+#include <map>
 
 // Minimal logging API based on https://github.com/go-logr/logr
 
@@ -63,6 +65,15 @@ public:
   // logs.
   virtual bool enabled(Priority) const = 0;
 
+  static std::string toString(Priority arg)
+  {
+    const std::array<std::string, 8> names = {"Absent", "Alert", "Critical", "Error", "Warning", "Notice", "Info", "Debug"};
+    auto p = static_cast<unsigned int>(arg);
+    if (p >= names.size()) {
+      return "?";
+    }
+    return names.at(p);
+  }
   // Info logs a non-error message with the given key/value pairs as context.
   //
   // The msg argument should be used to add some constant description to
@@ -159,9 +170,9 @@ private:
     map.emplace(key, value.to_string());
     mapArguments(map, args...);
   }
-  void mapArguments(std::map<std::string, std::string>& map) const
-  {
-    return;
-  }
+
+  void mapArguments(std::map<std::string, std::string>& /* map */) const {}
 };
+
+using log_t = const std::shared_ptr<Logger>&;
 }
deleted file mode 120000 (symlink)
index 5e234608cde0b06426a6fd9945b3dc0f0569efd3..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../lua-recursor4-ffi.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..f5785bdf7b5d77cfc3562024acf03bf85257d036
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+extern "C"
+{
+  typedef struct pdns_ffi_param pdns_ffi_param_t;
+
+  typedef struct pdns_ednsoption
+  {
+    uint16_t optionCode;
+    uint16_t len;
+    const void* data;
+  } pdns_ednsoption_t;
+
+  typedef struct pdns_proxyprotocol_value
+  {
+    uint8_t type;
+    uint16_t len;
+    const void* data;
+  } pdns_proxyprotocol_value_t;
+
+  typedef enum
+  {
+    pdns_record_place_answer = 1,
+    pdns_record_place_authority = 2,
+    pdns_record_place_additional = 3
+  } pdns_record_place_t;
+
+  // Must match DNSFilterEngine::PolicyKind
+  typedef enum
+  {
+    pdns_policy_kind_noaction = 0,
+    pdns_policy_kind_drop = 1,
+    pdns_policy_kind_nxdomain = 2,
+    pdns_policy_kind_nodata = 3,
+    pdns_policy_kind_truncate = 4,
+    pdns_policy_kind_custom = 5
+  } pdns_policy_kind_t;
+
+  typedef struct pdns_ffi_record
+  {
+    const char* name;
+    size_t name_len;
+    const char* content;
+    size_t content_len;
+    uint32_t ttl;
+    pdns_record_place_t place;
+    uint16_t type;
+  } pdns_ffi_record_t;
+
+  const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref) __attribute__((visibility("default")));
+  void pdns_ffi_param_get_qname_raw(pdns_ffi_param_t* ref, const char** qname, size_t* qnameSize) __attribute__((visibility("default")));
+  uint16_t pdns_ffi_param_get_qtype(const pdns_ffi_param_t* ref) __attribute__((visibility("default")));
+  const char* pdns_ffi_param_get_remote(pdns_ffi_param_t* ref) __attribute__((visibility("default")));
+  void pdns_ffi_param_get_remote_raw(pdns_ffi_param_t* ref, const void** addr, size_t* addrSize) __attribute__((visibility("default")));
+  uint16_t pdns_ffi_param_get_remote_port(const pdns_ffi_param_t* ref) __attribute__((visibility("default")));
+  const char* pdns_ffi_param_get_local(pdns_ffi_param_t* ref) __attribute__((visibility("default")));
+  void pdns_ffi_param_get_local_raw(pdns_ffi_param_t* ref, const void** addr, size_t* addrSize) __attribute__((visibility("default")));
+  uint16_t pdns_ffi_param_get_local_port(const pdns_ffi_param_t* ref) __attribute__((visibility("default")));
+  const char* pdns_ffi_param_get_edns_cs(pdns_ffi_param_t* ref) __attribute__((visibility("default")));
+  void pdns_ffi_param_get_edns_cs_raw(pdns_ffi_param_t* ref, const void** net, size_t* netSize) __attribute__((visibility("default")));
+  uint8_t pdns_ffi_param_get_edns_cs_source_mask(const pdns_ffi_param_t* ref) __attribute__((visibility("default")));
+
+  // returns the length of the resulting 'out' array. 'out' is not set if the length is 0
+  size_t pdns_ffi_param_get_edns_options(pdns_ffi_param_t* ref, const pdns_ednsoption_t** out) __attribute__((visibility("default")));
+  size_t pdns_ffi_param_get_edns_options_by_code(pdns_ffi_param_t* ref, uint16_t optionCode, const pdns_ednsoption_t** out) __attribute__((visibility("default")));
+
+  // returns the length of the resulting 'out' array. 'out' is not set if the length is 0
+  size_t pdns_ffi_param_get_proxy_protocol_values(pdns_ffi_param_t* ref, const pdns_proxyprotocol_value_t** out) __attribute__((visibility("default")));
+
+  void pdns_ffi_param_set_tag(pdns_ffi_param_t* ref, unsigned int tag) __attribute__((visibility("default")));
+  void pdns_ffi_param_add_policytag(pdns_ffi_param_t* ref, const char* name) __attribute__((visibility("default")));
+  void pdns_ffi_param_set_requestorid(pdns_ffi_param_t* ref, const char* name) __attribute__((visibility("default")));
+  void pdns_ffi_param_set_devicename(pdns_ffi_param_t* ref, const char* name) __attribute__((visibility("default")));
+  void pdns_ffi_param_set_deviceid(pdns_ffi_param_t* ref, size_t len, const void* name) __attribute__((visibility("default")));
+  void pdns_ffi_param_set_routingtag(pdns_ffi_param_t* ref, const char* name) __attribute__((visibility("default")));
+
+  void pdns_ffi_param_set_variable(pdns_ffi_param_t* ref, bool variable) __attribute__((visibility("default")));
+  void pdns_ffi_param_set_ttl_cap(pdns_ffi_param_t* ref, uint32_t ttl) __attribute__((visibility("default")));
+  void pdns_ffi_param_set_log_query(pdns_ffi_param_t* ref, bool logQuery) __attribute__((visibility("default")));
+  void pdns_ffi_param_set_log_response(pdns_ffi_param_t* ref, bool logResponse) __attribute__((visibility("default")));
+  void pdns_ffi_param_set_rcode(pdns_ffi_param_t* ref, int rcode) __attribute__((visibility("default")));
+  void pdns_ffi_param_set_follow_cname_records(pdns_ffi_param_t* ref, bool follow) __attribute__((visibility("default")));
+
+  void pdns_ffi_param_set_extended_error_code(pdns_ffi_param_t* ref, uint16_t code) __attribute__((visibility("default")));
+  void pdns_ffi_param_set_extended_error_extra(pdns_ffi_param_t* ref, size_t len, const char* extra) __attribute__((visibility("default")));
+
+  /* returns true if the record was correctly added, false if something went wrong.
+     Passing a NULL pointer to 'name' will result in the qname being used for the record owner name. */
+  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) __attribute__((visibility("default")));
+
+  void pdns_ffi_param_set_padding_disabled(pdns_ffi_param_t* ref, bool disabled) __attribute__((visibility("default")));
+  void pdns_ffi_param_add_meta_single_string_kv(pdns_ffi_param_t* ref, const char* key, const char* val) __attribute__((visibility("default")));
+  void pdns_ffi_param_add_meta_single_int64_kv(pdns_ffi_param_t* ref, const char* key, int64_t val) __attribute__((visibility("default")));
+
+  typedef struct pdns_postresolve_ffi_handle pdns_postresolve_ffi_handle_t;
+
+  const char* pdns_postresolve_ffi_handle_get_qname(pdns_postresolve_ffi_handle_t* ref) __attribute__((visibility("default")));
+  void pdns_postresolve_ffi_handle_get_qname_raw(pdns_postresolve_ffi_handle_t* ref, const char** qname, size_t* qnameSize) __attribute__((visibility("default")));
+  uint16_t pdns_postresolve_ffi_handle_get_qtype(const pdns_postresolve_ffi_handle_t* ref) __attribute__((visibility("default")));
+  uint16_t pdns_postresolve_ffi_handle_get_rcode(const pdns_postresolve_ffi_handle_t* ref) __attribute__((visibility("default")));
+  void pdns_postresolve_ffi_handle_set_rcode(const pdns_postresolve_ffi_handle_t* ref, uint16_t rcode) __attribute__((visibility("default")));
+  pdns_policy_kind_t pdns_postresolve_ffi_handle_get_appliedpolicy_kind(const pdns_postresolve_ffi_handle_t* ref) __attribute__((visibility("default")));
+  void pdns_postresolve_ffi_handle_set_appliedpolicy_kind(pdns_postresolve_ffi_handle_t* ref, pdns_policy_kind_t kind) __attribute__((visibility("default")));
+  bool pdns_postresolve_ffi_handle_get_record(pdns_postresolve_ffi_handle_t* ref, unsigned int i, pdns_ffi_record_t* record, bool raw) __attribute__((visibility("default")));
+  bool pdns_postresolve_ffi_handle_set_record(pdns_postresolve_ffi_handle_t* ref, unsigned int i, const char* content, size_t contentLen, bool raw) __attribute__((visibility("default")));
+  void pdns_postresolve_ffi_handle_clear_records(pdns_postresolve_ffi_handle_t* ref) __attribute__((visibility("default")));
+  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) __attribute__((visibility("default")));
+  const char* pdns_postresolve_ffi_handle_get_authip(pdns_postresolve_ffi_handle_t* ref) __attribute__((visibility("default")));
+  void pdns_postresolve_ffi_handle_get_authip_raw(pdns_postresolve_ffi_handle_t* ref, const void** addr, size_t* addrSize) __attribute__((visibility("default")));
+}
deleted file mode 120000 (symlink)
index d4abe3f5074d4a582ba96429c8ee260ca8b4cac0..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../lua-recursor4.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..0a381b2b890b406168cba35f5ecbe36d3f0f6567
--- /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 "lua-recursor4.hh"
+#include <fstream>
+#include "logger.hh"
+#include "logging.hh"
+#include "dnsparser.hh"
+#include "syncres.hh"
+#include "namespaces.hh"
+#include "rec_channel.hh"
+#include "ednsoptions.hh"
+#include "ednssubnet.hh"
+#include "filterpo.hh"
+#include "rec-snmp.hh"
+#include <unordered_set>
+#include "rec-main.hh"
+
+RecursorLua4::RecursorLua4() { prepareContext(); }
+
+boost::optional<dnsheader> RecursorLua4::DNSQuestion::getDH() const
+{
+  if (dh)
+    return *dh;
+  return boost::optional<dnsheader>();
+}
+
+vector<string> RecursorLua4::DNSQuestion::getEDNSFlags() const
+{
+  vector<string> ret;
+  if (ednsFlags) {
+    if (*ednsFlags & EDNSOpts::DNSSECOK)
+      ret.push_back("DO");
+  }
+  return ret;
+}
+
+bool RecursorLua4::DNSQuestion::getEDNSFlag(string flag) const
+{
+  if (ednsFlags) {
+    if (flag == "DO" && (*ednsFlags & EDNSOpts::DNSSECOK))
+      return true;
+  }
+  return false;
+}
+
+vector<pair<uint16_t, string>> RecursorLua4::DNSQuestion::getEDNSOptions() const
+{
+  if (ednsOptions)
+    return *ednsOptions;
+  else
+    return vector<pair<uint16_t, string>>();
+}
+
+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>();
+}
+
+boost::optional<Netmask> RecursorLua4::DNSQuestion::getEDNSSubnet() const
+{
+  if (ednsOptions) {
+    for (const auto& o : *ednsOptions) {
+      if (o.first == EDNSOptionCode::ECS) {
+        EDNSSubnetOpts eso;
+        if (getEDNSSubnetOptsFromString(o.second, &eso))
+          return eso.source;
+        else
+          break;
+      }
+    }
+  }
+  return boost::optional<Netmask>();
+}
+
+std::vector<std::pair<int, ProxyProtocolValue>> RecursorLua4::DNSQuestion::getProxyProtocolValues() const
+{
+  std::vector<std::pair<int, ProxyProtocolValue>> result;
+  if (proxyProtocolValues) {
+    result.reserve(proxyProtocolValues->size());
+
+    int idx = 1;
+    for (const auto& value : *proxyProtocolValues) {
+      result.push_back({idx++, value});
+    }
+  }
+
+  return result;
+}
+
+vector<pair<int, DNSRecord>> RecursorLua4::DNSQuestion::getRecords() const
+{
+  vector<pair<int, DNSRecord>> ret;
+  int num = 1;
+  for (const auto& r : records) {
+    ret.push_back({num++, r});
+  }
+  return ret;
+}
+void RecursorLua4::DNSQuestion::setRecords(const vector<pair<int, DNSRecord>>& recs)
+{
+  records.clear();
+  for (const auto& p : recs) {
+    records.push_back(p.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::make(type, QClass::IN, content));
+  records.push_back(dr);
+}
+
+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, 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; }
+};
+
+// 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<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("rcode", &DNSQuestion::rcode);
+  d_lw->registerMember("tag", &DNSQuestion::tag);
+  d_lw->registerMember("requestorId", &DNSQuestion::requestorId);
+  d_lw->registerMember("deviceId", &DNSQuestion::deviceId);
+  d_lw->registerMember("deviceName", &DNSQuestion::deviceName);
+  d_lw->registerMember("followupFunction", &DNSQuestion::followupFunction);
+  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);
+      }
+      return 0;
+    },
+    [](DNSQuestion& dq, uint16_t newCode) {
+      if (dq.extendedErrorCode) {
+        *dq.extendedErrorCode = newCode;
+      }
+    });
+  d_lw->registerMember<std::string (DNSQuestion::*)>("extendedErrorExtra", [](const DNSQuestion& dq) -> std::string {
+      if (dq.extendedErrorExtra) {
+        return *dq.extendedErrorExtra;
+      }
+      return "";
+    },
+    [](DNSQuestion& dq, const std::string& newExtra) {
+      if (dq.extendedErrorExtra) {
+        *dq.extendedErrorExtra = newExtra;
+      }
+    });
+  d_lw->registerMember("udpQuery", &DNSQuestion::udpQuery);
+  d_lw->registerMember("udpAnswer", &DNSQuestion::udpAnswer);
+  d_lw->registerMember("udpQueryDest", &DNSQuestion::udpQueryDest);
+  d_lw->registerMember("udpCallback", &DNSQuestion::udpCallback);
+  d_lw->registerMember("appliedPolicy", &DNSQuestion::appliedPolicy);
+  d_lw->registerMember("queryTime", &DNSQuestion::queryTime);
+
+  d_lw->registerMember<DNSFilterEngine::Policy, std::string>("policyName",
+    [](const DNSFilterEngine::Policy& pol) -> std::string {
+      return pol.getName();
+    },
+    [](DNSFilterEngine::Policy& pol, const std::string& name) {
+      pol.setName(name);
+    });
+  d_lw->registerMember("policyKind", &DNSFilterEngine::Policy::d_kind);
+  d_lw->registerMember("policyType", &DNSFilterEngine::Policy::d_type);
+  d_lw->registerMember("policyTTL", &DNSFilterEngine::Policy::d_ttl);
+  d_lw->registerMember("policyTrigger", &DNSFilterEngine::Policy::d_trigger);
+  d_lw->registerMember("policyHit", &DNSFilterEngine::Policy::d_hit);
+  d_lw->registerMember<DNSFilterEngine::Policy, std::string>("policyCustom",
+    [](const DNSFilterEngine::Policy& pol) -> std::string {
+      std::string result;
+      if (pol.d_kind != DNSFilterEngine::PolicyKind::Custom) {
+        return result;
+      }
+
+      for (const auto& dr : pol.d_custom) {
+        if (!result.empty()) {
+          result += "\n";
+        }
+        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::make(QType::CNAME, QClass::IN, content));
+    }
+  );
+  d_lw->registerFunction("getDH", &DNSQuestion::getDH);
+  d_lw->registerFunction("getEDNSOptions", &DNSQuestion::getEDNSOptions);
+  d_lw->registerFunction("getEDNSOption", &DNSQuestion::getEDNSOption);
+  d_lw->registerFunction("getEDNSSubnet", &DNSQuestion::getEDNSSubnet);
+  d_lw->registerFunction("getProxyProtocolValues", &DNSQuestion::getProxyProtocolValues);
+  d_lw->registerFunction("getEDNSFlags", &DNSQuestion::getEDNSFlags);
+  d_lw->registerFunction("getEDNSFlag", &DNSQuestion::getEDNSFlag);
+
+  d_lw->registerMember("name", &DNSRecord::d_name);
+  d_lw->registerMember("type", &DNSRecord::d_type);
+  d_lw->registerMember("ttl", &DNSRecord::d_ttl);
+  d_lw->registerMember("place", &DNSRecord::d_place);
+
+  d_lw->registerMember("size", &EDNSOptionViewValue::size);
+  d_lw->registerFunction<std::string(EDNSOptionViewValue::*)()>("getContent", [](const EDNSOptionViewValue& value) { return std::string(value.content, value.size); });
+  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;
+      for (const auto& value : option.values) {
+        values.push_back(std::string(value.content, value.size));
+      }
+      return values;
+    });
+
+  /* pre 4.2 API compatibility, when we had only one value for a given EDNS option */
+  d_lw->registerMember<uint16_t(EDNSOptionView::*)>("size", [](const EDNSOptionView& option) -> uint16_t {
+      uint16_t result = 0;
+
+      if (!option.values.empty()) {
+        result = option.values.at(0).size;
+      }
+      return result;
+    },
+    [](EDNSOptionView& /* option */, uint16_t newSize) { (void) newSize; });
+  d_lw->registerFunction<std::string(EDNSOptionView::*)()>("getContent", [](const EDNSOptionView& option) {
+      if (option.values.empty()) {
+        return std::string();
+      }
+      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) { 
+      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);
+      return ret;
+    });
+
+  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::make(dr.d_type, QClass::IN, newContent)); });
+  d_lw->registerFunction("addAnswer", &DNSQuestion::addAnswer);
+  d_lw->registerFunction("addRecord", &DNSQuestion::addRecord);
+  d_lw->registerFunction("getRecords", &DNSQuestion::getRecords);
+  d_lw->registerFunction("setRecords", &DNSQuestion::setRecords);
+
+  d_lw->registerFunction<void(DNSQuestion::*)(const std::string&)>("addPolicyTag", [](DNSQuestion& dq, const std::string& tag) { if (dq.policyTags) { dq.policyTags->insert(tag); } });
+  d_lw->registerFunction<void(DNSQuestion::*)(const std::vector<std::pair<int, std::string> >&)>("setPolicyTags", [](DNSQuestion& dq, const std::vector<std::pair<int, std::string> >& tags) {
+      if (dq.policyTags) {
+        dq.policyTags->clear();
+        dq.policyTags->reserve(tags.size());
+        for (const auto& tag : tags) {
+          dq.policyTags->insert(tag.second);
+        }
+      }
+    });
+  d_lw->registerFunction<std::vector<std::pair<int, std::string> >(DNSQuestion::*)()>("getPolicyTags", [](const DNSQuestion& dq) {
+      std::vector<std::pair<int, std::string> > ret;
+      if (dq.policyTags) {
+        int count = 1;
+        ret.reserve(dq.policyTags->size());
+        for (const auto& tag : *dq.policyTags) {
+          ret.push_back({count++, tag});
+        }
+      }
+      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->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){
+      try {
+        if(auto s = boost::get<string>(&in)) {
+          smn.add(DNSName(*s));
+        }
+        else if(auto v = boost::get<vector<pair<unsigned int, string> > >(&in)) {
+          for(const auto& entry : *v)
+            smn.add(DNSName(entry.second));
+        }
+        else {
+          smn.add(boost::get<DNSName>(in));
+        }
+      }
+      catch(std::exception& e) {
+        SLOG(g_log <<Logger::Error<<e.what()<<endl,
+             g_slog->withName("lua")->error(Logr::Error, e.what(), "Error in call to DNSSuffixMatchGroup:add"));
+      }
+    }
+  );
+
+  d_lw->registerFunction("check",(bool (SuffixMatchNode::*)(const DNSName&) const) &SuffixMatchNode::check);
+  d_lw->registerFunction("toString",(string (SuffixMatchNode::*)() const) &SuffixMatchNode::toString);
+
+  d_pd.push_back({"policykinds", in_t {
+    {"NoAction", (int)DNSFilterEngine::PolicyKind::NoAction},
+    {"Drop",     (int)DNSFilterEngine::PolicyKind::Drop    },
+    {"NXDOMAIN", (int)DNSFilterEngine::PolicyKind::NXDOMAIN},
+    {"NODATA",   (int)DNSFilterEngine::PolicyKind::NODATA  },
+    {"Truncate", (int)DNSFilterEngine::PolicyKind::Truncate},
+    {"Custom",   (int)DNSFilterEngine::PolicyKind::Custom  }
+    }});
+
+  d_pd.push_back({"policytypes", in_t {
+    {"None",       (int)DNSFilterEngine::PolicyType::None       },
+    {"QName",      (int)DNSFilterEngine::PolicyType::QName      },
+    {"ClientIP",   (int)DNSFilterEngine::PolicyType::ClientIP   },
+    {"ResponseIP", (int)DNSFilterEngine::PolicyType::ResponseIP },
+    {"NSDName",    (int)DNSFilterEngine::PolicyType::NSDName    },
+    {"NSIP",       (int)DNSFilterEngine::PolicyType::NSIP       }
+    }});
+
+  for(const auto& n : QType::names)
+    d_pd.push_back({n.first, n.second});
+
+  d_pd.push_back({"validationstates", in_t{
+        {"Indeterminate", static_cast<unsigned int>(vState::Indeterminate) },
+        {"BogusNoValidDNSKEY", static_cast<unsigned int>(vState::BogusNoValidDNSKEY) },
+        {"BogusInvalidDenial", static_cast<unsigned int>(vState::BogusInvalidDenial) },
+        {"BogusUnableToGetDSs", static_cast<unsigned int>(vState::BogusUnableToGetDSs) },
+        {"BogusUnableToGetDNSKEYs", static_cast<unsigned int>(vState::BogusUnableToGetDNSKEYs) },
+        {"BogusSelfSignedDS", static_cast<unsigned int>(vState::BogusSelfSignedDS) },
+        {"BogusNoRRSIG", static_cast<unsigned int>(vState::BogusNoRRSIG) },
+        {"BogusNoValidRRSIG", static_cast<unsigned int>(vState::BogusNoValidRRSIG) },
+        {"BogusMissingNegativeIndication", static_cast<unsigned int>(vState::BogusMissingNegativeIndication) },
+        {"BogusSignatureNotYetValid", static_cast<unsigned int>(vState::BogusSignatureNotYetValid)},
+        {"BogusSignatureExpired", static_cast<unsigned int>(vState::BogusSignatureExpired)},
+        {"BogusUnsupportedDNSKEYAlgo", static_cast<unsigned int>(vState::BogusUnsupportedDNSKEYAlgo)},
+        {"BogusUnsupportedDSDigestType", static_cast<unsigned int>(vState::BogusUnsupportedDSDigestType)},
+        {"BogusNoZoneKeyBitSet", static_cast<unsigned int>(vState::BogusNoZoneKeyBitSet)},
+        {"BogusRevokedDNSKEY", static_cast<unsigned int>(vState::BogusRevokedDNSKEY)},
+        {"BogusInvalidDNSKEYProtocol", static_cast<unsigned int>(vState::BogusInvalidDNSKEYProtocol)},
+        {"Insecure", static_cast<unsigned int>(vState::Insecure) },
+        {"Secure", static_cast<unsigned int>(vState::Secure) },
+        /* in order not to break compatibility with older scripts: */
+        {"Bogus", static_cast<unsigned int>(255) },
+  }});
+
+  d_lw->writeFunction("isValidationStateBogus", [](vState state) {
+    return vStateIsBogus(state);
+  });
+
+  d_pd.push_back({"now", &g_now});
+
+  d_lw->writeFunction("getMetric", [](const std::string& str, boost::optional<std::string> prometheusName) {
+    return DynMetric{getDynMetric(str, prometheusName ? *prometheusName : "")};
+    });
+
+  d_lw->registerFunction("inc", &DynMetric::inc);
+  d_lw->registerFunction("incBy", &DynMetric::incBy);
+  d_lw->registerFunction("set", &DynMetric::set);
+  d_lw->registerFunction("get", &DynMetric::get);
+
+  d_lw->writeFunction("getStat", [](const std::string& str) {
+      uint64_t result = 0;
+      auto value = getStatByName(str);
+      if (value) {
+        result = *value;
+      }
+      return result;
+    });
+
+  d_lw->writeFunction("getRecursorThreadId", []() {
+    return RecThreadInfo::id();
+  });
+
+  d_lw->writeFunction("sendCustomSNMPTrap", [](const std::string& str) {
+      if (g_snmpAgent) {
+        g_snmpAgent->sendCustomTrap(str);
+      }
+    });
+
+  d_lw->writeFunction("getregisteredname", [](const DNSName &dname) {
+      return getRegisteredName(dname);
+  });
+
+  d_lw->registerMember<const DNSName (PolicyEvent::*)>("qname", [](const PolicyEvent& event) -> const DNSName& { return event.qname; }, [](PolicyEvent& /* event */, const DNSName& newName) { (void) newName; });
+  d_lw->registerMember<uint16_t (PolicyEvent::*)>("qtype", [](const PolicyEvent& event) -> uint16_t { return event.qtype.getCode(); }, [](PolicyEvent& /* event */, uint16_t newType) { (void) newType; });
+  d_lw->registerMember<bool (PolicyEvent::*)>("isTcp", [](const PolicyEvent& event) -> bool { return event.isTcp; }, [](PolicyEvent& /* event */, bool newTcp) { (void) newTcp; });
+  d_lw->registerMember<const ComboAddress (PolicyEvent::*)>("remote", [](const PolicyEvent& event) -> const ComboAddress& { return event.remote; }, [](PolicyEvent& /* event */, const ComboAddress& newRemote) { (void) newRemote; });
+  d_lw->registerMember("appliedPolicy", &PolicyEvent::appliedPolicy);
+  d_lw->registerFunction<void(PolicyEvent::*)(const std::string&)>("addPolicyTag", [](PolicyEvent& event, const std::string& tag) { if (event.policyTags) { event.policyTags->insert(tag); } });
+  d_lw->registerFunction<void(PolicyEvent::*)(const std::vector<std::pair<int, std::string> >&)>("setPolicyTags", [](PolicyEvent& event, const std::vector<std::pair<int, std::string> >& tags) {
+      if (event.policyTags) {
+        event.policyTags->clear();
+        event.policyTags->reserve(tags.size());
+        for (const auto& tag : tags) {
+          event.policyTags->insert(tag.second);
+        }
+      }
+    });
+  d_lw->registerFunction<std::vector<std::pair<int, std::string> >(PolicyEvent::*)()>("getPolicyTags", [](const PolicyEvent& event) {
+      std::vector<std::pair<int, std::string> > ret;
+      if (event.policyTags) {
+        int count = 1;
+        ret.reserve(event.policyTags->size());
+        for (const auto& tag : *event.policyTags) {
+          ret.push_back({count++, tag});
+        }
+      }
+      return ret;
+    });
+  d_lw->registerFunction<void(PolicyEvent::*)(const std::string&)>("discardPolicy", [](PolicyEvent& event, const std::string& policy) {
+    if (event.discardedPolicies) {
+      (*event.discardedPolicies)[policy] = true;
+    }
+  });
+}
+
+// clang-format on
+
+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_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_policyHitEventFilter = d_lw->readVariable<boost::optional<policyEventFilter_t>>("policyEventFilter").get_value_or(0);
+}
+
+void RecursorLua4::getFeatures(Features& features)
+{
+  // Add key-values pairs below.
+  // Make sure you add string values explicitly converted to string.
+  // e.g. features.emplace_back("somekey", string("stringvalue");
+  // Both int and double end up as a lua number type.
+  features.emplace_back("PR8001_devicename", true);
+}
+
+static void warnDrop(const RecursorLua4::DNSQuestion& dq)
+{
+  if (dq.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.
+  }
+}
+
+void RecursorLua4::maintenance() const
+{
+  if (d_maintenance) {
+    d_maintenance();
+  }
+}
+
+bool RecursorLua4::prerpz(DNSQuestion& dq, int& ret, RecEventTrace& et) 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;
+}
+
+bool RecursorLua4::preresolve(DNSQuestion& dq, int& ret, RecEventTrace& et) 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;
+}
+
+bool RecursorLua4::nxdomain(DNSQuestion& dq, int& ret, RecEventTrace& et) 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;
+}
+
+bool RecursorLua4::nodata(DNSQuestion& dq, int& ret, RecEventTrace& et) 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;
+}
+
+bool RecursorLua4::postresolve(DNSQuestion& dq, int& ret, RecEventTrace& et) 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;
+}
+
+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
+{
+  if (!d_preoutquery) {
+    return false;
+  }
+  bool variableAnswer = false;
+  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;
+}
+
+bool RecursorLua4::ipfilter(const ComboAddress& remote, const ComboAddress& local, const struct dnsheader& dh, RecEventTrace& et) 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;
+}
+
+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
+{
+  if (!d_policyHitEventFilter) {
+    return false;
+  }
+
+  PolicyEvent event(remote, qname, qtype, tcp);
+  event.appliedPolicy = &policy;
+  event.policyTags = &tags;
+  event.discardedPolicies = &discardedPolicies;
+
+  if (d_policyHitEventFilter(event)) {
+    return true;
+  }
+  else {
+    return false;
+  }
+}
+
+unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, const std::vector<ProxyProtocolValue>& proxyProtocolValues) const
+{
+  if (d_gettag) {
+    std::vector<std::pair<int, const ProxyProtocolValue*>> proxyProtocolValuesMap;
+    proxyProtocolValuesMap.reserve(proxyProtocolValues.size());
+    int num = 1;
+    for (const auto& value : proxyProtocolValues) {
+      proxyProtocolValuesMap.emplace_back(num++, &value);
+    }
+
+    auto ret = d_gettag(remote, ednssubnet, local, qname, qtype, ednsOptions, tcp, proxyProtocolValuesMap);
+
+    if (policyTags != nullptr) {
+      const auto& tags = std::get<1>(ret);
+      if (tags) {
+        policyTags->reserve(policyTags->size() + tags->size());
+        for (const auto& tag : *tags) {
+          policyTags->insert(tag.second);
+        }
+      }
+    }
+    const auto& dataret = std::get<2>(ret);
+    if (dataret) {
+      data = *dataret;
+    }
+    const auto& reqIdret = std::get<3>(ret);
+    if (reqIdret) {
+      requestorId = *reqIdret;
+    }
+    const auto& deviceIdret = std::get<4>(ret);
+    if (deviceIdret) {
+      deviceId = *deviceIdret;
+    }
+
+    const auto& deviceNameret = std::get<5>(ret);
+    if (deviceNameret) {
+      deviceName = *deviceNameret;
+    }
+
+    const auto& routingTarget = std::get<6>(ret);
+    if (routingTarget) {
+      routingTag = *routingTarget;
+    }
+
+    return std::get<0>(ret);
+  }
+  return 0;
+}
+
+struct pdns_ffi_param
+{
+public:
+  pdns_ffi_param(RecursorLua4::FFIParams& params_) :
+    params(params_)
+  {
+  }
+
+  RecursorLua4::FFIParams& params;
+  std::unique_ptr<std::string> qnameStr{nullptr};
+  std::unique_ptr<std::string> localStr{nullptr};
+  std::unique_ptr<std::string> remoteStr{nullptr};
+  std::unique_ptr<std::string> ednssubnetStr{nullptr};
+  std::vector<pdns_ednsoption_t> ednsOptionsVect;
+  std::vector<pdns_proxyprotocol_value_t> proxyProtocolValuesVect;
+};
+
+unsigned int RecursorLua4::gettag_ffi(RecursorLua4::FFIParams& params) const
+{
+  if (d_gettag_ffi) {
+    pdns_ffi_param_t param(params);
+
+    auto ret = d_gettag_ffi(&param);
+    if (ret) {
+      params.data = *ret;
+    }
+
+    return param.params.tag;
+  }
+  return 0;
+}
+
+bool RecursorLua4::genhook(const luacall_t& func, DNSQuestion& dq, int& ret) const
+{
+  if (!func)
+    return false;
+
+  if (dq.currentRecords) {
+    dq.records = *dq.currentRecords;
+  }
+  else {
+    dq.records.clear();
+  }
+
+  dq.followupFunction.clear();
+  dq.followupPrefix.clear();
+  dq.followupName.clear();
+  dq.udpQuery.clear();
+  dq.udpAnswer.clear();
+  dq.udpCallback.clear();
+
+  dq.rcode = ret;
+  bool handled = func(&dq);
+
+  if (handled) {
+  loop:;
+    ret = dq.rcode;
+
+    if (!dq.followupFunction.empty()) {
+      if (dq.followupFunction == "followCNAMERecords") {
+        ret = followCNAMERecords(dq.records, QType(dq.qtype), ret);
+      }
+      else if (dq.followupFunction == "getFakeAAAARecords") {
+        ret = getFakeAAAARecords(dq.followupName, ComboAddress(dq.followupPrefix), dq.records);
+      }
+      else if (dq.followupFunction == "getFakePTRRecords") {
+        ret = getFakePTRRecords(dq.followupName, dq.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());
+        // coverity[auto_causes_copy] not copying produces a dangling ref
+        const auto cbFunc = d_lw->readVariable<boost::optional<luacall_t>>(dq.udpCallback).get_value_or(nullptr);
+        if (!cbFunc) {
+          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);
+        if (!result) {
+          return false;
+        }
+        goto loop;
+      }
+    }
+    if (dq.currentRecords) {
+      *dq.currentRecords = dq.records;
+    }
+  }
+
+  // see if they added followup work for us too
+  return handled;
+}
+
+RecursorLua4::~RecursorLua4() {}
+
+const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref)
+{
+  if (!ref->qnameStr) {
+    ref->qnameStr = std::make_unique<std::string>(ref->params.qname.toStringNoDot());
+  }
+
+  return ref->qnameStr->c_str();
+}
+
+void pdns_ffi_param_get_qname_raw(pdns_ffi_param_t* ref, const char** qname, size_t* qnameSize)
+{
+  const auto& storage = ref->params.qname.getStorage();
+  *qname = storage.data();
+  *qnameSize = storage.size();
+}
+
+uint16_t pdns_ffi_param_get_qtype(const pdns_ffi_param_t* ref)
+{
+  return ref->params.qtype;
+}
+
+const char* pdns_ffi_param_get_remote(pdns_ffi_param_t* ref)
+{
+  if (!ref->remoteStr) {
+    ref->remoteStr = std::make_unique<std::string>(ref->params.remote.toString());
+  }
+
+  return ref->remoteStr->c_str();
+}
+
+static void pdns_ffi_comboaddress_to_raw(const ComboAddress& ca, const void** addr, size_t* addrSize)
+{
+  if (ca.isIPv4()) {
+    *addr = &ca.sin4.sin_addr.s_addr;
+    *addrSize = sizeof(ca.sin4.sin_addr.s_addr);
+  }
+  else {
+    *addr = &ca.sin6.sin6_addr.s6_addr;
+    *addrSize = sizeof(ca.sin6.sin6_addr.s6_addr);
+  }
+}
+
+void pdns_ffi_param_get_remote_raw(pdns_ffi_param_t* ref, const void** addr, size_t* addrSize)
+{
+  pdns_ffi_comboaddress_to_raw(ref->params.remote, addr, addrSize);
+}
+
+uint16_t pdns_ffi_param_get_remote_port(const pdns_ffi_param_t* ref)
+{
+  return ref->params.remote.getPort();
+}
+
+const char* pdns_ffi_param_get_local(pdns_ffi_param_t* ref)
+{
+  if (!ref->localStr) {
+    ref->localStr = std::make_unique<std::string>(ref->params.local.toString());
+  }
+
+  return ref->localStr->c_str();
+}
+
+void pdns_ffi_param_get_local_raw(pdns_ffi_param_t* ref, const void** addr, size_t* addrSize)
+{
+  pdns_ffi_comboaddress_to_raw(ref->params.local, addr, addrSize);
+}
+
+uint16_t pdns_ffi_param_get_local_port(const pdns_ffi_param_t* ref)
+{
+  return ref->params.local.getPort();
+}
+
+const char* pdns_ffi_param_get_edns_cs(pdns_ffi_param_t* ref)
+{
+  if (ref->params.ednssubnet.empty()) {
+    return nullptr;
+  }
+
+  if (!ref->ednssubnetStr) {
+    ref->ednssubnetStr = std::make_unique<std::string>(ref->params.ednssubnet.toStringNoMask());
+  }
+
+  return ref->ednssubnetStr->c_str();
+}
+
+void pdns_ffi_param_get_edns_cs_raw(pdns_ffi_param_t* ref, const void** net, size_t* netSize)
+{
+  if (ref->params.ednssubnet.empty()) {
+    *net = nullptr;
+    *netSize = 0;
+    return;
+  }
+
+  pdns_ffi_comboaddress_to_raw(ref->params.ednssubnet.getNetwork(), net, netSize);
+}
+
+uint8_t pdns_ffi_param_get_edns_cs_source_mask(const pdns_ffi_param_t* ref)
+{
+  return ref->params.ednssubnet.getBits();
+}
+
+static void fill_edns_option(const EDNSOptionViewValue& value, pdns_ednsoption_t& option)
+{
+  option.len = value.size;
+  option.data = nullptr;
+
+  if (value.size > 0) {
+    option.data = value.content;
+  }
+}
+
+size_t pdns_ffi_param_get_edns_options(pdns_ffi_param_t* ref, const pdns_ednsoption_t** out)
+{
+  if (ref->params.ednsOptions.empty()) {
+    return 0;
+  }
+
+  size_t totalCount = 0;
+  for (const auto& option : ref->params.ednsOptions) {
+    totalCount += option.second.values.size();
+  }
+
+  ref->ednsOptionsVect.resize(totalCount);
+
+  size_t pos = 0;
+  for (const auto& option : ref->params.ednsOptions) {
+    for (const auto& entry : option.second.values) {
+      fill_edns_option(entry, ref->ednsOptionsVect.at(pos));
+      ref->ednsOptionsVect.at(pos).optionCode = option.first;
+      pos++;
+    }
+  }
+
+  *out = ref->ednsOptionsVect.data();
+
+  return totalCount;
+}
+
+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()) {
+    return 0;
+  }
+
+  ref->ednsOptionsVect.resize(it->second.values.size());
+
+  size_t pos = 0;
+  for (const auto& entry : it->second.values) {
+    fill_edns_option(entry, ref->ednsOptionsVect.at(pos));
+    ref->ednsOptionsVect.at(pos).optionCode = optionCode;
+    pos++;
+  }
+
+  *out = ref->ednsOptionsVect.data();
+
+  return pos;
+}
+
+size_t pdns_ffi_param_get_proxy_protocol_values(pdns_ffi_param_t* ref, const pdns_proxyprotocol_value_t** out)
+{
+  if (ref->params.proxyProtocolValues.empty()) {
+    return 0;
+  }
+
+  ref->proxyProtocolValuesVect.resize(ref->params.proxyProtocolValues.size());
+
+  size_t pos = 0;
+  for (const auto& value : ref->params.proxyProtocolValues) {
+    auto& dest = ref->proxyProtocolValuesVect.at(pos);
+    dest.type = value.type;
+    dest.len = value.content.size();
+    if (dest.len > 0) {
+      dest.data = value.content.data();
+    }
+    pos++;
+  }
+
+  *out = ref->proxyProtocolValuesVect.data();
+
+  return ref->proxyProtocolValuesVect.size();
+}
+
+void pdns_ffi_param_set_tag(pdns_ffi_param_t* ref, unsigned int tag)
+{
+  ref->params.tag = tag;
+}
+
+void pdns_ffi_param_add_policytag(pdns_ffi_param_t* ref, const char* name)
+{
+  ref->params.policyTags.insert(std::string(name));
+}
+
+void pdns_ffi_param_set_requestorid(pdns_ffi_param_t* ref, const char* name)
+{
+  ref->params.requestorId = std::string(name);
+}
+
+void pdns_ffi_param_set_devicename(pdns_ffi_param_t* ref, const char* name)
+{
+  ref->params.deviceName = std::string(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);
+}
+
+void pdns_ffi_param_set_routingtag(pdns_ffi_param_t* ref, const char* rtag)
+{
+  ref->params.routingTag = std::string(rtag);
+}
+
+void pdns_ffi_param_set_variable(pdns_ffi_param_t* ref, bool variable)
+{
+  ref->params.variable = variable;
+}
+
+void pdns_ffi_param_set_ttl_cap(pdns_ffi_param_t* ref, uint32_t ttl)
+{
+  ref->params.ttlCap = ttl;
+}
+
+void pdns_ffi_param_set_log_query(pdns_ffi_param_t* ref, bool logQuery)
+{
+  ref->params.logQuery = logQuery;
+}
+
+void pdns_ffi_param_set_log_response(pdns_ffi_param_t* ref, bool logResponse)
+{
+  ref->params.logResponse = logResponse;
+}
+
+void pdns_ffi_param_set_rcode(pdns_ffi_param_t* ref, int rcode)
+{
+  ref->params.rcode = rcode;
+}
+
+void pdns_ffi_param_set_follow_cname_records(pdns_ffi_param_t* ref, bool follow)
+{
+  ref->params.followCNAMERecords = follow;
+}
+
+void pdns_ffi_param_set_extended_error_code(pdns_ffi_param_t* ref, uint16_t code)
+{
+  ref->params.extendedErrorCode = code;
+}
+
+void pdns_ffi_param_set_extended_error_extra(pdns_ffi_param_t* ref, size_t len, const char* extra)
+{
+  ref->params.extendedErrorExtra = std::string(extra, 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::make(type, QClass::IN, std::string(content, contentSize)));
+    ref->params.records.push_back(std::move(dr));
+
+    return true;
+  }
+  catch (const std::exception& e) {
+    g_log << Logger::Error << "Error attempting to add a record from Lua via pdns_ffi_param_add_record(): " << e.what() << endl;
+    return false;
+  }
+}
+
+void pdns_ffi_param_set_padding_disabled(pdns_ffi_param_t* ref, bool disabled)
+{
+  ref->params.disablePadding = disabled;
+}
+
+void pdns_ffi_param_add_meta_single_string_kv(pdns_ffi_param_t* ref, const char* key, const char* val)
+{
+  ref->params.meta[std::string(key)].stringVal.insert(std::string(val));
+}
+
+void pdns_ffi_param_add_meta_single_int64_kv(pdns_ffi_param_t* ref, const char* key, int64_t val)
+{
+  ref->params.meta[std::string(key)].intVal.insert(val);
+}
+
+struct pdns_postresolve_ffi_handle
+{
+public:
+  pdns_postresolve_ffi_handle(RecursorLua4::PostResolveFFIHandle& h) :
+    handle(h)
+  {
+  }
+  RecursorLua4::PostResolveFFIHandle& handle;
+  auto insert(std::string&& str)
+  {
+    const auto it = pool.insert(std::move(str)).first;
+    return it;
+  }
+
+private:
+  std::unordered_set<std::string> pool;
+};
+
+bool RecursorLua4::postresolve_ffi(RecursorLua4::PostResolveFFIHandle& h) const
+{
+  if (d_postresolve_ffi) {
+    pdns_postresolve_ffi_handle_t handle(h);
+
+    auto ret = d_postresolve_ffi(&handle);
+    return ret;
+  }
+  return false;
+}
+
+const char* pdns_postresolve_ffi_handle_get_qname(pdns_postresolve_ffi_handle_t* ref)
+{
+  auto str = ref->insert(ref->handle.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();
+  *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;
+}
+
+uint16_t pdns_postresolve_ffi_handle_get_rcode(const pdns_postresolve_ffi_handle_t* ref)
+{
+  return ref->handle.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;
+}
+
+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);
+}
+
+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);
+}
+
+bool pdns_postresolve_ffi_handle_get_record(pdns_postresolve_ffi_handle_t* ref, unsigned int i, pdns_ffi_record_t* record, bool raw)
+{
+  if (i >= ref->handle.d_dq.currentRecords->size()) {
+    return false;
+  }
+  try {
+    DNSRecord& r = ref->handle.d_dq.currentRecords->at(i);
+    if (raw) {
+      const auto& storage = r.d_name.getStorage();
+      record->name = storage.data();
+      record->name_len = storage.size();
+    }
+    else {
+      std::string name = r.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));
+      record->content = content->data();
+      record->content_len = content->size();
+    }
+    else {
+      auto content = ref->insert(r.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;
+  }
+  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;
+    return false;
+  }
+
+  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)
+{
+  if (i >= ref->handle.d_dq.currentRecords->size()) {
+    return false;
+  }
+  try {
+    DNSRecord& r = ref->handle.d_dq.currentRecords->at(i);
+    if (raw) {
+      r.setContent(DNSRecordContent::deserialize(r.d_name, r.d_type, string(content, contentLen)));
+    }
+    else {
+      r.setContent(DNSRecordContent::make(r.d_type, QClass::IN, string(content, contentLen)));
+    }
+
+    return true;
+  }
+  catch (const std::exception& e) {
+    g_log << Logger::Error << "Error attempting to set record content from Lua via pdns_postresolve_ffi_handle_set_record(): " << e.what() << endl;
+    return false;
+  }
+}
+
+void pdns_postresolve_ffi_handle_clear_records(pdns_postresolve_ffi_handle_t* ref)
+{
+  ref->handle.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);
+    if (raw) {
+      dr.setContent(DNSRecordContent::deserialize(dr.d_name, dr.d_type, string(content, contentLen)));
+    }
+    else {
+      dr.setContent(DNSRecordContent::make(type, QClass::IN, string(content, contentLen)));
+    }
+    ref->handle.d_dq.currentRecords->push_back(std::move(dr));
+
+    return true;
+  }
+  catch (const std::exception& e) {
+    g_log << Logger::Error << "Error attempting to add a record from Lua via pdns_postresolve_ffi_handle_add_record(): " << e.what() << endl;
+    return false;
+  }
+}
+
+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();
+}
+
+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);
+}
deleted file mode 120000 (symlink)
index debe6e7f938c1cada754f0ebe718ecb11c5daff7..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../lua-recursor4.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..dd1c117559e635e0a87fd34bc3abd9a91b10b96d
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "iputils.hh"
+#include "dnsname.hh"
+#include "namespaces.hh"
+#include "dnsrecords.hh"
+#include "filterpo.hh"
+#include "ednsoptions.hh"
+#include "validate.hh"
+#include "lua-base4.hh"
+#include "proxy-protocol.hh"
+#include "noinitvector.hh"
+#include "rec-eventtrace.hh"
+
+#include <unordered_map>
+
+#include "lua-recursor4-ffi.hh"
+
+// pdns_ffi_param_t is a lightuserdata
+template <>
+struct LuaContext::Pusher<pdns_ffi_param*>
+{
+  static const int minSize = 1;
+  static const int maxSize = 1;
+
+  static PushedObject push(lua_State* state, pdns_ffi_param* ptr) noexcept
+  {
+    lua_pushlightuserdata(state, ptr);
+    return PushedObject{state, 1};
+  }
+};
+
+// pdns_postresolve_ffi_handle is a lightuserdata
+template <>
+struct LuaContext::Pusher<pdns_postresolve_ffi_handle*>
+{
+  static const int minSize = 1;
+  static const int maxSize = 1;
+
+  static PushedObject push(lua_State* state, pdns_postresolve_ffi_handle* ptr) noexcept
+  {
+    lua_pushlightuserdata(state, ptr);
+    return PushedObject{state, 1};
+  }
+};
+
+class RecursorLua4 : public BaseLua4
+{
+public:
+  RecursorLua4();
+  ~RecursorLua4(); // this is so unique_ptr works with an incomplete type
+
+  struct MetaValue
+  {
+    std::unordered_set<std::string> stringVal;
+    std::unordered_set<int64_t> intVal;
+  };
+  struct DNSQuestion
+  {
+    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_)
+    {
+    }
+    const DNSName& qname;
+    const uint16_t qtype;
+    const ComboAddress& local;
+    const ComboAddress& remote;
+    const ComboAddress* fromAuthIP{nullptr};
+    const struct dnsheader* dh{nullptr};
+    const bool isTcp;
+    const std::vector<pair<uint16_t, string>>* ednsOptions{nullptr};
+    const uint16_t* ednsFlags{nullptr};
+    vector<DNSRecord>* currentRecords{nullptr};
+    DNSFilterEngine::Policy* appliedPolicy{nullptr};
+    std::unordered_set<std::string>* policyTags{nullptr};
+    const std::vector<ProxyProtocolValue>* proxyProtocolValues{nullptr};
+    std::unordered_map<std::string, bool>* discardedPolicies{nullptr};
+    std::string* extendedErrorExtra{nullptr};
+    boost::optional<uint16_t>* extendedErrorCode{nullptr};
+    std::string requestorId;
+    std::string deviceId;
+    std::string deviceName;
+    vState validationState{vState::Indeterminate};
+    bool& variable;
+    bool& wantsRPZ;
+    bool& logResponse;
+    bool& addPaddingToResponse;
+    unsigned int tag{0};
+    std::map<std::string, MetaValue> meta;
+    struct timeval queryTime;
+
+    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);
+
+    int rcode{0};
+    // struct dnsheader, packet length would be great
+    vector<DNSRecord> records;
+
+    string followupFunction;
+    string followupPrefix;
+
+    string udpQuery;
+    ComboAddress udpQueryDest;
+    string udpAnswer;
+    string udpCallback;
+
+    LuaContext::LuaObject data;
+    DNSName followupName;
+  };
+
+  struct PolicyEvent
+  {
+    PolicyEvent(const ComboAddress& rem, const DNSName& name, const QType& type, bool tcp) :
+      qname(name), qtype(type), remote(rem), isTcp(tcp)
+    {
+    }
+    const DNSName& qname;
+    const QType qtype;
+    const ComboAddress& remote;
+    const bool isTcp;
+    DNSFilterEngine::Policy* appliedPolicy{nullptr};
+    std::unordered_set<std::string>* policyTags{nullptr};
+    std::unordered_map<std::string, bool>* discardedPolicies{nullptr};
+  };
+
+  struct FFIParams
+  {
+  public:
+    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_)
+    {
+    }
+
+    LuaContext::LuaObject& data;
+    const DNSName& qname;
+    const ComboAddress& local;
+    const ComboAddress& remote;
+    const Netmask& ednssubnet;
+    std::unordered_set<std::string>& policyTags;
+    std::vector<DNSRecord>& records;
+    const EDNSOptionViewMap& ednsOptions;
+    const std::vector<ProxyProtocolValue>& proxyProtocolValues;
+    std::string& requestorId;
+    std::string& deviceId;
+    std::string& deviceName;
+    std::string& routingTag;
+    std::string& extendedErrorExtra;
+    boost::optional<int>& rcode;
+    boost::optional<uint16_t>& extendedErrorCode;
+    uint32_t& ttlCap;
+    bool& variable;
+    bool& logQuery;
+    bool& logResponse;
+    bool& followCNAMERecords;
+    bool& disablePadding;
+
+    unsigned int tag{0};
+    uint16_t qtype;
+    bool tcp;
+
+    std::map<std::string, MetaValue>& meta;
+  };
+
+  unsigned int gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap&, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, const std::vector<ProxyProtocolValue>& proxyProtocolValues) const;
+  unsigned int gettag_ffi(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 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 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
+  {
+    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)
+    {
+    }
+    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;
+
+protected:
+  virtual void postPrepareContext() override;
+  virtual void postLoad() override;
+  virtual void getFeatures(Features& features) override;
+
+private:
+  typedef std::function<void()> luamaintenance_t;
+  luamaintenance_t d_maintenance;
+  typedef std::function<bool(DNSQuestion*)> luacall_t;
+  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;
+  ipfilter_t d_ipfilter;
+  typedef std::function<bool(PolicyEvent&)> policyEventFilter_t;
+  policyEventFilter_t d_policyHitEventFilter;
+};
deleted file mode 120000 (symlink)
index 8e4bf7ede46734c9767e7dcb234c496f95be5e0d..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../lwres.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..28e142ed85b75ed286394ccc61e5fe4f9d4997b0
--- /dev/null
@@ -0,0 +1,659 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "utility.hh"
+#include "lwres.hh"
+#include <iostream>
+#include "dnsrecords.hh"
+#include <cerrno>
+#include "misc.hh"
+#include <algorithm>
+#include <sstream>
+#include <cstring>
+#include <string>
+#include <vector>
+#include "dns.hh"
+#include "qtype.hh"
+#include "pdnsexception.hh"
+#include "arguments.hh"
+#include "sstuff.hh"
+#include "syncres.hh"
+#include "dnswriter.hh"
+#include "dnsparser.hh"
+#include "logger.hh"
+#include "dns_random.hh"
+#include <boost/scoped_array.hpp>
+#include <boost/algorithm/string.hpp>
+#include "validate-recursor.hh"
+#include "ednssubnet.hh"
+#include "query-local-address.hh"
+#include "tcpiohandler.hh"
+#include "ednsoptions.hh"
+#include "ednspadding.hh"
+#include "rec-protozero.hh"
+#include "uuid-utils.hh"
+#include "rec-tcpout.hh"
+
+thread_local TCPOutConnectionManager t_tcp_manager;
+std::shared_ptr<Logr::Logger> g_slogout;
+bool g_paddingOutgoing;
+
+void remoteLoggerQueueData(RemoteLoggerInterface& rli, const std::string& data)
+{
+  auto ret = rli.queueData(data);
+
+  switch (ret) {
+  case RemoteLoggerInterface::Result::Queued:
+    break;
+  case RemoteLoggerInterface::Result::PipeFull: {
+    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);
+    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);
+    SLOG(g_log << Logger::Warning << rli.name() << ": " << msg << std::endl,
+         g_slog->withName(rli.name())->info(Logr::Warning, msg));
+    break;
+  }
+  }
+}
+
+#ifdef HAVE_FSTRM
+#include "dnstap.hh"
+#include "fstrm_logger.hh"
+
+static bool isEnabledForQueries(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers)
+{
+  if (fstreamLoggers == nullptr) {
+    return false;
+  }
+  for (auto& logger : *fstreamLoggers) {
+    if (logger->logQueries()) {
+      return true;
+    }
+  }
+  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& address, DnstapMessage::ProtocolType protocol, const boost::optional<const DNSName&>& auth, const vector<uint8_t>& packet)
+{
+  if (fstreamLoggers == nullptr)
+    return;
+
+  struct timespec ts;
+  TIMEVAL_TO_TIMESPEC(&queryTime, &ts);
+  std::string str;
+  // 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);
+  }
+}
+
+static bool isEnabledForResponses(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers)
+{
+  if (fstreamLoggers == nullptr) {
+    return false;
+  }
+  for (auto& logger : *fstreamLoggers) {
+    if (logger->logResponses()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+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;
+
+  struct timespec ts1, ts2;
+  TIMEVAL_TO_TIMESPEC(&queryTime, &ts1);
+  TIMEVAL_TO_TIMESPEC(&replyTime, &ts2);
+  std::string str;
+  // 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);
+  }
+}
+
+#endif // HAVE_FSTRM
+
+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;
+  }
+
+  bool log = false;
+  for (auto& logger : *outgoingLoggers) {
+    if (logger->logQueries()) {
+      log = true;
+      break;
+    }
+  }
+
+  if (!log) {
+    return;
+  }
+
+  static thread_local std::string buffer;
+  buffer.clear();
+  pdns::ProtoZero::Message m{buffer};
+  m.setType(pdns::ProtoZero::Message::MessageType::DNSOutgoingQueryType);
+  m.setMessageIdentity(uuid);
+  m.setSocketFamily(address.sin4.sin_family);
+  if (!doTCP) {
+    m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::UDP);
+  }
+  else if (!tls) {
+    m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::TCP);
+  }
+  else {
+    m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::DoT);
+  }
+
+  m.setTo(address);
+  m.setInBytes(bytes);
+  m.setTime();
+  m.setId(qid);
+  m.setQuestion(domain, type, QClass::IN);
+  m.setToPort(address.getPort());
+  m.setServerIdentity(SyncRes::s_serverID);
+
+  if (initialRequestId) {
+    m.setInitialRequestID(*initialRequestId);
+  }
+
+  if (srcmask) {
+    m.setEDNSSubnet(*srcmask, 128);
+  }
+
+  for (auto& logger : *outgoingLoggers) {
+    if (logger->logQueries()) {
+      remoteLoggerQueueData(*logger, buffer);
+    }
+  }
+}
+
+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;
+  }
+
+  bool log = false;
+  for (auto& logger : *outgoingLoggers) {
+    if (logger->logResponses()) {
+      log = true;
+      break;
+    }
+  }
+
+  if (!log) {
+    return;
+  }
+
+  static thread_local std::string buffer;
+  buffer.clear();
+  pdns::ProtoZero::RecMessage m{buffer};
+  m.setType(pdns::ProtoZero::Message::MessageType::DNSIncomingResponseType);
+  m.setMessageIdentity(uuid);
+  m.setSocketFamily(address.sin4.sin_family);
+  if (!doTCP) {
+    m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::UDP);
+  }
+  else if (!tls) {
+    m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::TCP);
+  }
+  else {
+    m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::DoT);
+  }
+  m.setTo(address);
+  m.setInBytes(bytes);
+  m.setTime();
+  m.setId(qid);
+  m.setQuestion(domain, type, QClass::IN);
+  m.setToPort(address.getPort());
+  m.setServerIdentity(SyncRes::s_serverID);
+
+  if (initialRequestId) {
+    m.setInitialRequestID(*initialRequestId);
+  }
+
+  if (srcmask) {
+    m.setEDNSSubnet(*srcmask, 128);
+  }
+
+  m.startResponse();
+  m.setQueryTime(queryTime.tv_sec, queryTime.tv_usec);
+  if (rcode == -1) {
+    m.setNetworkErrorResponseCode();
+  }
+  else {
+    m.setResponseCode(rcode);
+  }
+
+  for (const auto& record : records) {
+    m.addRR(record, exportTypes, false);
+  }
+  m.commitResponse();
+
+  for (auto& logger : *outgoingLoggers) {
+    if (logger->logResponses()) {
+      remoteLoggerQueueData(*logger, buffer);
+    }
+  }
+}
+
+static bool tcpconnect(const ComboAddress& ip, TCPOutConnectionManager::Connection& connection, bool& dnsOverTLS, const std::string& nsName)
+{
+  dnsOverTLS = SyncRes::s_dot_to_port_853 && ip.getPort() == 853;
+
+  connection = t_tcp_manager.get(ip);
+  if (connection.d_handler) {
+    return false;
+  }
+
+  const struct timeval timeout
+  {
+    g_networkTimeoutMsec / 1000, static_cast<suseconds_t>(g_networkTimeoutMsec) % 1000 * 1000
+  };
+  Socket s(ip.sin4.sin_family, SOCK_STREAM);
+  s.setNonBlocking();
+  setTCPNoDelay(s.getHandle());
+  ComboAddress localip = pdns::getQueryLocalAddress(ip.sin4.sin_family, 0);
+  s.bind(localip);
+
+  std::shared_ptr<TLSCtx> tlsCtx{nullptr};
+  if (dnsOverTLS) {
+    TLSContextParameters tlsParams;
+    tlsParams.d_provider = "openssl";
+    tlsParams.d_validateCertificates = false;
+    // tlsParams.d_caStore
+    tlsCtx = getTLSContext(tlsParams);
+    if (tlsCtx == nullptr) {
+      SLOG(g_log << Logger::Error << "DoT to " << ip << " requested but not available" << endl,
+           g_slogout->info(Logr::Error, "DoT requested but not available", "server", Logging::Loggable(ip)));
+      dnsOverTLS = false;
+    }
+  }
+  connection.d_handler = std::make_shared<TCPIOHandler>(nsName, false, s.releaseHandle(), timeout, tlsCtx);
+  // Returned state ignored
+  // This can throw an exception, retry will need to happen at higher level
+  connection.d_handler->tryConnect(SyncRes::s_tcp_fast_open_connect, ip);
+  return true;
+}
+
+static LWResult::Result tcpsendrecv(const ComboAddress& ip, TCPOutConnectionManager::Connection& connection,
+                                    ComboAddress& localip, const vector<uint8_t>& vpacket, size_t& len, PacketBuffer& buf)
+{
+  socklen_t slen = ip.getSocklen();
+  uint16_t tlen = htons(vpacket.size());
+  const char* lenP = reinterpret_cast<const char*>(&tlen);
+
+  len = 0; // in case of error
+  localip.sin4.sin_family = ip.sin4.sin_family;
+  if (getsockname(connection.d_handler->getDescriptor(), reinterpret_cast<sockaddr*>(&localip), &slen) != 0) {
+    return LWResult::Result::PermanentError;
+  }
+
+  PacketBuffer packet;
+  packet.reserve(2 + vpacket.size());
+  packet.insert(packet.end(), lenP, lenP + 2);
+  packet.insert(packet.end(), vpacket.begin(), vpacket.end());
+
+  LWResult::Result ret = asendtcp(packet, connection.d_handler);
+  if (ret != LWResult::Result::Success) {
+    return ret;
+  }
+
+  ret = arecvtcp(packet, 2, connection.d_handler, false);
+  if (ret != LWResult::Result::Success) {
+    return ret;
+  }
+
+  memcpy(&tlen, packet.data(), sizeof(tlen));
+  len = ntohs(tlen); // switch to the 'len' shared with the rest of the calling function
+
+  // XXX receive into buf directly?
+  packet.resize(len);
+  ret = arecvtcp(packet, len, connection.d_handler, false);
+  if (ret != LWResult::Result::Success) {
+    return ret;
+  }
+  buf.resize(len);
+  memcpy(buf.data(), packet.data(), len);
+  return LWResult::Result::Success;
+}
+
+static void addPadding(const DNSPacketWriter& pw, size_t bufsize, DNSPacketWriter::optvect_t& opts)
+{
+  const size_t currentSize = pw.getSizeWithOpts(opts);
+  if (currentSize < (bufsize - 4)) {
+    const size_t remaining = bufsize - (currentSize + 4);
+    /* from rfc8647, "4.1.  Recommended Strategy: Block-Length Padding":
+       Clients SHOULD pad queries to the closest multiple of 128 octets.
+       Note we are in the client role here.
+    */
+    const size_t blockSize = 128;
+    const size_t modulo = (currentSize + 4) % blockSize;
+    size_t padSize = 0;
+    if (modulo > 0) {
+      padSize = std::min(blockSize - modulo, remaining);
+    }
+    opts.emplace_back(EDNSOptionCode::PADDING, makeEDNSPaddingOptString(padSize));
+  }
+}
+
+/** lwr is only filled out in case 1 was returned, and even when returning 1 for 'success', lwr might contain DNS errors
+    Never throws!
+ */
+// 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;
+  PacketBuffer buf;
+  buf.resize(bufsize);
+  vector<uint8_t> vpacket;
+  //  string mapped0x20=dns0x20(domain);
+  uint16_t qid = dns_random_uint16();
+  DNSPacketWriter pw(vpacket, domain, type);
+  bool dnsOverTLS = SyncRes::s_dot_to_port_853 && address.getPort() == 853;
+
+  pw.getHeader()->rd = sendRDQuery;
+  pw.getHeader()->id = qid;
+  /* RFC 6840 section 5.9:
+   *  This document further specifies that validating resolvers SHOULD set
+   *  the CD bit on every upstream query.  This is regardless of whether
+   *  the CD bit was set on the incoming query [...]
+   *
+   * sendRDQuery is only true if the qname is part of a forward-zone-recurse (or
+   * set in the forward-zone-file), so we use this as an indicator for it being
+   * an "upstream query". To stay true to "dnssec=off means 3.X behaviour", we
+   * only set +CD on forwarded query in any mode other than dnssec=off.
+   */
+  pw.getHeader()->cd = (sendRDQuery && g_dnssecmode != DNSSECMode::Off);
+
+  string ping;
+  bool weWantEDNSSubnet = false;
+  uint8_t outgoingECSBits = 0;
+  ComboAddress outgoingECSAddr;
+  if (EDNS0Level > 0) {
+    DNSPacketWriter::optvect_t opts;
+    if (srcmask) {
+      EDNSSubnetOpts eo;
+      eo.source = *srcmask;
+      outgoingECSBits = srcmask->getBits();
+      outgoingECSAddr = srcmask->getNetwork();
+      opts.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(eo));
+      weWantEDNSSubnet = true;
+    }
+
+    if (dnsOverTLS && g_paddingOutgoing) {
+      addPadding(pw, bufsize, opts);
+    }
+
+    pw.addOpt(g_outgoingEDNSBufsize, 0, g_dnssecmode == DNSSECMode::Off ? 0 : EDNSOpts::DNSSECOK, opts);
+    pw.commit();
+  }
+  lwr->d_rcode = 0;
+  lwr->d_haveEDNS = false;
+  LWResult::Result ret;
+
+  DTime dt;
+  dt.set();
+  *now = dt.getTimeval();
+
+  boost::uuids::uuid uuid;
+  const struct timeval queryTime = *now;
+
+  if (outgoingLoggers) {
+    uuid = getUniqueID();
+    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
+
+  // We only store the localip if needed for fstrm logging
+  ComboAddress localip;
+#ifdef HAVE_FSTRM
+  bool fstrmQEnabled = false;
+  bool fstrmREnabled = false;
+
+  if (isEnabledForQueries(fstrmLoggers)) {
+    fstrmQEnabled = true;
+  }
+  if (isEnabledForResponses(fstrmLoggers)) {
+    fstrmREnabled = true;
+  }
+#endif
+
+  if (!doTCP) {
+    int queryfd;
+
+    ret = asendto(vpacket.data(), vpacket.size(), 0, address, qid, domain, type, weWantEDNSSubnet, &queryfd);
+
+    if (ret != LWResult::Result::Success) {
+      return ret;
+    }
+
+    if (queryfd == -1) {
+      *chained = true;
+    }
+
+#ifdef HAVE_FSTRM
+    if (!*chained) {
+      if (fstrmQEnabled || fstrmREnabled) {
+        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, 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, address, len, qid, domain, type, queryfd, *now);
+  }
+  else {
+    bool isNew;
+    do {
+      try {
+        // If we get a new (not re-used) TCP connection that does not
+        // work, we give up. For reused connections, we assume the
+        // peer has closed it on error, so we retry. At some point we
+        // *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.d_nsName.empty()) {
+          nsName = context.d_nsName.toStringNoDot();
+        }
+        isNew = tcpconnect(address, connection, dnsOverTLS, nsName);
+        ret = tcpsendrecv(address, connection, localip, vpacket, len, buf);
+#ifdef HAVE_FSTRM
+        if (fstrmQEnabled) {
+          logFstreamQuery(fstrmLoggers, queryTime, localip, address, !dnsOverTLS ? DnstapMessage::ProtocolType::DoTCP : DnstapMessage::ProtocolType::DoT, context.d_auth, vpacket);
+        }
+#endif /* HAVE_FSTRM */
+        if (ret == LWResult::Result::Success) {
+          break;
+        }
+        connection.d_handler->close();
+      }
+      catch (const NetworkError&) {
+        ret = LWResult::Result::OSLimitError; // OS limits error
+      }
+      catch (const runtime_error&) {
+        ret = LWResult::Result::OSLimitError; // OS limits error (PermanentError is transport related)
+      }
+    } while (!isNew);
+  }
+
+  lwr->d_usec = dt.udiff();
+  *now = dt.getTimeval();
+
+  if (ret != LWResult::Result::Success) { // includes 'timeout'
+    if (outgoingLoggers) {
+      logIncomingResponse(outgoingLoggers, context.d_initialRequestId, uuid, address, domain, type, qid, doTCP, dnsOverTLS, srcmask, 0, -1, {}, queryTime, exportTypes);
+    }
+    return ret;
+  }
+
+  buf.resize(len);
+
+#ifdef HAVE_FSTRM
+  if (fstrmREnabled && (!*chained || doTCP)) {
+    DnstapMessage::ProtocolType protocol = doTCP ? DnstapMessage::ProtocolType::DoTCP : DnstapMessage::ProtocolType::DoUDP;
+    if (dnsOverTLS) {
+      protocol = DnstapMessage::ProtocolType::DoT;
+    }
+    logFstreamResponse(fstrmLoggers, localip, address, protocol, context.d_auth, buf, queryTime, *now);
+  }
+#endif /* HAVE_FSTRM */
+
+  lwr->d_records.clear();
+  try {
+    lwr->d_tcbit = 0;
+    MOADNSParser mdp(false, reinterpret_cast<const char*>(buf.data()), buf.size());
+    lwr->d_aabit = mdp.d_header.aa;
+    lwr->d_tcbit = mdp.d_header.tc;
+    lwr->d_rcode = mdp.d_header.rcode;
+
+    if (mdp.d_header.rcode == RCode::FormErr && mdp.d_qname.empty() && mdp.d_qtype == 0 && mdp.d_qclass == 0) {
+      if (outgoingLoggers) {
+        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
+    }
+
+    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 " << 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(address),
+                             "qname", Logging::Loggable(domain),
+                             "onwire", Logging::Loggable(mdp.d_qname)));
+      }
+      // unexpected count has already been done @ pdns_recursor.cc
+      goto out;
+    }
+
+    lwr->d_records.reserve(mdp.d_answers.size());
+    for (const auto& a : mdp.d_answers)
+      lwr->d_records.push_back(a.first);
+
+    EDNSOpts edo;
+    if (EDNS0Level > 0 && getEDNSOpts(mdp, &edo)) {
+      lwr->d_haveEDNS = true;
+
+      if (weWantEDNSSubnet) {
+        for (const auto& opt : edo.d_options) {
+          if (opt.first == EDNSOptionCode::ECS) {
+            EDNSSubnetOpts reso;
+            if (getEDNSSubnetOptsFromString(opt.second, &reso)) {
+              /* rfc7871 states that 0 "indicate[s] that the answer is suitable for all addresses in FAMILY",
+                 so we might want to still pass the information along to be able to differentiate between
+                 IPv4 and IPv6. Still I'm pretty sure it doesn't matter in real life, so let's not duplicate
+                 entries in our cache. */
+              if (reso.scope.getBits()) {
+                uint8_t bits = std::min(reso.scope.getBits(), outgoingECSBits);
+                outgoingECSAddr.truncate(bits);
+                srcmask = Netmask(outgoingECSAddr, bits);
+              }
+            }
+          }
+        }
+      }
+    }
+
+    if (outgoingLoggers) {
+      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;
+  }
+  catch (const std::exception& mde) {
+    if (::arg().mustDo("log-common-errors")) {
+      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")));
+    }
+
+    lwr->d_rcode = RCode::FormErr;
+    lwr->d_validpacket = false;
+    t_Counters.at(rec::Counter::serverParseError)++;
+
+    if (outgoingLoggers) {
+      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(address)));
+  }
+
+  t_Counters.at(rec::Counter::serverParseError)++;
+
+out:
+  if (!lwr->d_rcode) {
+    lwr->d_rcode = RCode::ServFail;
+  }
+
+  return LWResult::Result::PermanentError;
+}
+
+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(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, address, std::move(connection));
+    }
+  }
+  return ret;
+}
deleted file mode 120000 (symlink)
index 0b8b0d14caf3c2be0293f106a0a80114419af987..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../lwres.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..8fca61fe15aff946206d374346bacf57e741ff20
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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 <vector>
+#include <sys/types.h>
+#include "misc.hh"
+#include "iputils.hh"
+#include <netdb.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include "dnsparser.hh"
+#include <arpa/inet.h>
+#undef res_mkquery
+
+#include "pdnsexception.hh"
+#include "dns.hh"
+#include "namespaces.hh"
+#include "remote_logger.hh"
+#include "fstrm_logger.hh"
+#include "resolve-context.hh"
+#include "noinitvector.hh"
+
+#include "logging.hh"
+
+// Helper to be defined by main program: queue data and log based on return value of queueData()
+void remoteLoggerQueueData(RemoteLoggerInterface&, const std::string&);
+
+extern std::shared_ptr<Logr::Logger> g_slogout;
+extern bool g_paddingOutgoing;
+
+class LWResException : public PDNSException
+{
+public:
+  LWResException(const string& reason_) :
+    PDNSException(reason_) {}
+};
+
+//! LWRes class
+class LWResult
+{
+public:
+  enum class Result : uint8_t
+  {
+    Timeout = 0,
+    Success = 1,
+    PermanentError = 2 /* not transport related */,
+    OSLimitError = 3,
+    Spoofed = 4 /* Spoofing attempt (too many near-misses) */
+  };
+
+  vector<DNSRecord> d_records;
+  int d_rcode{0};
+  bool d_validpacket{false};
+  bool d_aabit{false}, d_tcbit{false};
+  uint32_t d_usec{0};
+  bool d_haveEDNS{false};
+};
+
+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& 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
diff --git a/pdns/recursordist/m4/pdns_enable_lto.m4 b/pdns/recursordist/m4/pdns_enable_lto.m4
new file mode 120000 (symlink)
index 0000000..0165907
--- /dev/null
@@ -0,0 +1 @@
+../../../m4/pdns_enable_lto.m4
\ No newline at end of file
diff --git a/pdns/recursordist/m4/pdns_init_auto_vars.m4 b/pdns/recursordist/m4/pdns_init_auto_vars.m4
new file mode 120000 (symlink)
index 0000000..c4384ff
--- /dev/null
@@ -0,0 +1 @@
+../../../m4/pdns_init_auto_vars.m4
\ No newline at end of file
deleted file mode 120000 (symlink)
index 6fa017d20e6bb548a99a8baff659b254782d78a6..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../mkpubsuffixcc
\ No newline at end of file
new file mode 100755 (executable)
index 0000000000000000000000000000000000000000..54e3e819eafa277e53cc2700fa451eeba7634164
--- /dev/null
@@ -0,0 +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 "//" "$1" | grep \\. | egrep "^[.0-9a-z-]*$")
+       do 
+               echo \"$a\",
+       done 
+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;
+}
deleted file mode 120000 (symlink)
index c31cfc42b0c65d30cfad9b30e0088fa29bb71bc2..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../mtasker_context.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..1661f2dac5d5c50d140051e7695fb32292d5022f
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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
+
+#if defined(HAVE_BOOST_CONTEXT)
+#include "mtasker_fcontext.cc" // NOLINT(bugprone-suspicious-include)
+#else
+#include "mtasker_ucontext.cc" // NOLINT(bugprone-suspicious-include)
+#endif
deleted file mode 120000 (symlink)
index c737e07de6d818d9d7c192f453d83c7a7ac473c7..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../mtasker_context.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..dbc5683f2964c87ff0fc92da2ef779ce76273624
--- /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.
+ */
+#pragma once
+#include "lazy_allocator.hh"
+#include <vector>
+#include <exception>
+#include <functional>
+
+struct pdns_ucontext_t
+{
+  pdns_ucontext_t();
+  pdns_ucontext_t(pdns_ucontext_t const&) = delete;
+  pdns_ucontext_t& operator=(pdns_ucontext_t const&) = delete;
+  ~pdns_ucontext_t();
+
+  void* uc_mcontext;
+  pdns_ucontext_t* uc_link;
+  std::vector<char, lazy_allocator<char>> uc_stack;
+  std::exception_ptr exception;
+#ifdef PDNS_USE_VALGRIND
+  int valgrind_id;
+#endif /* PDNS_USE_VALGRIND */
+};
+
+void pdns_swapcontext(pdns_ucontext_t& __restrict octx, pdns_ucontext_t const& __restrict ctx);
+
+void pdns_makecontext(pdns_ucontext_t& ctx, std::function<void(void)>& start);
+
+#ifdef HAVE_FIBER_SANITIZER
+#include <sanitizer/common_interface_defs.h>
+#endif /* HAVE_FIBER_SANITIZER */
+
+#ifdef HAVE_FIBER_SANITIZER
+extern __thread void* t_mainStack;
+extern __thread size_t t_mainStackSize;
+#endif /* HAVE_FIBER_SANITIZER */
+
+#ifdef HAVE_FIBER_SANITIZER
+static inline void notifyStackSwitch(const void* startOfStack, size_t stackSize)
+{
+  __sanitizer_start_switch_fiber(nullptr, startOfStack, stackSize);
+}
+#else
+static inline void notifyStackSwitch(const void* /* startOfStack */, size_t /* stackSize */)
+{}
+#endif /* HAVE_FIBER_SANITIZER */
+
+static inline void notifyStackSwitchToKernel()
+{
+#ifdef HAVE_FIBER_SANITIZER
+  notifyStackSwitch(t_mainStack, t_mainStackSize);
+#endif /* HAVE_FIBER_SANITIZER */
+}
+
+static inline void notifyStackSwitchDone()
+{
+#ifdef HAVE_FIBER_SANITIZER
+#ifdef HAVE_SANITIZER_FINISH_SWITCH_FIBER_SINGLE_PTR
+  __sanitizer_finish_switch_fiber(nullptr);
+#else /* HAVE_SANITIZER_FINISH_SWITCH_FIBER_SINGLE_PTR */
+#ifdef HAVE_SANITIZER_FINISH_SWITCH_FIBER_THREE_PTRS
+  __sanitizer_finish_switch_fiber(nullptr, nullptr, nullptr);
+#endif /* HAVE_SANITIZER_FINISH_SWITCH_FIBER_THREE_PTRS */
+#endif /* HAVE_SANITIZER_FINISH_SWITCH_FIBER_SINGLE_PTR */
+#endif /* HAVE_FIBER_SANITIZER */
+}
deleted file mode 120000 (symlink)
index cac593d9bd6401cbde8273a5e66787d0341de541..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../mtasker_fcontext.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..e489b40e1515455811cbdae48bea2ecb5aa3e9e9
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * 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 "mtasker_context.hh"
+#include <exception>
+#include <cassert>
+#include <type_traits>
+#include <boost/version.hpp>
+#if BOOST_VERSION < 106100
+#include <boost/context/fcontext.hpp>
+using boost::context::make_fcontext;
+#else
+#include <boost/context/detail/fcontext.hpp>
+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 */
+
+#ifdef HAVE_FIBER_SANITIZER
+__thread void* t_mainStack{nullptr};
+__thread size_t t_mainStackSize{0};
+#endif /* HAVE_FIBER_SANITIZER */
+
+#if BOOST_VERSION < 105600
+/* Note: This typedef means functions taking fcontext_t*, like jump_fcontext(),
+ * now require a reinterpret_cast rather than a static_cast, since we're
+ * casting from pdns_context_t->uc_mcontext, which is void**, to
+ * some_opaque_struct**. In later versions fcontext_t is already void*. So if
+ * you remove this, then fix the ugly.
+ */
+using fcontext_t = boost::context::fcontext_t*;
+
+/* Emulate the >= 1.56 API for Boost 1.52 through 1.55 */
+static inline intptr_t
+jump_fcontext(fcontext_t* const ofc, fcontext_t const nfc,
+              intptr_t const arg)
+{
+  /* If the fcontext_t is preallocated then use it, otherwise allocate one
+   * on the stack ('self') and stash a pointer away in *ofc so the returning
+   * MThread can access it. This is safe because we're suspended, so the
+   * context object always outlives the jump.
+   */
+  if (*ofc) {
+    return boost::context::jump_fcontext(*ofc, nfc, arg);
+  }
+  else {
+    boost::context::fcontext_t self;
+    *ofc = &self;
+    auto ret = boost::context::jump_fcontext(*ofc, nfc, arg);
+    *ofc = nullptr;
+    return ret;
+  }
+}
+#else
+
+#if BOOST_VERSION < 106100
+using boost::context::fcontext_t;
+using boost::context::jump_fcontext;
+#else
+using boost::context::detail::fcontext_t;
+using boost::context::detail::jump_fcontext;
+using boost::context::detail::transfer_t;
+#endif /* BOOST_VERSION < 106100 */
+
+static_assert(std::is_pointer<fcontext_t>::value,
+              "Boost Context has changed the fcontext_t type again :-(");
+#endif
+
+/* Boost context only provides a means of passing a single argument across a
+ * jump. args_t simply provides a way to pass more by reference.
+ */
+struct args_t
+{
+#if BOOST_VERSION < 106100
+  fcontext_t prev_ctx = nullptr;
+#endif
+  pdns_ucontext_t* self = nullptr;
+  std::function<void(void)>* work = nullptr;
+};
+
+extern "C"
+{
+  static void
+#if BOOST_VERSION < 106100
+  threadWrapper(intptr_t const xargs)
+  {
+#else
+  threadWrapper(transfer_t const t)
+  {
+#endif
+    /* Access the args passed from pdns_makecontext, and copy them directly from
+     * the calling stack on to ours (we're now using the MThreads stack).
+     * This saves heap allocating an args object, at the cost of an extra
+     * context switch to fashion this constructor-like init phase. The work
+     * function object is still only moved after we're (re)started, so may
+     * still be set or changed after a call to pdns_makecontext. This matches
+     * the behaviour of the System V implementation, which can inherently only
+     * be passed ints and pointers.
+     */
+    notifyStackSwitchDone();
+#if BOOST_VERSION < 106100
+    auto args = reinterpret_cast<args_t*>(xargs);
+#else
+    auto args = reinterpret_cast<args_t*>(t.data);
+#endif
+    auto ctx = args->self;
+    auto work = args->work;
+    /* we switch back to pdns_makecontext() */
+    notifyStackSwitchToKernel();
+#if BOOST_VERSION < 106100
+    jump_fcontext(reinterpret_cast<fcontext_t*>(&ctx->uc_mcontext),
+                  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
+         we just switched from, and we need to fill it to be able to
+         switch back to it later. */
+      fcontext_t* ptr = static_cast<fcontext_t*>(res.data);
+      *ptr = res.fctx;
+    }
+#endif
+    notifyStackSwitchDone();
+    args = nullptr;
+
+    try {
+      auto start = std::move(*work);
+      start();
+    }
+    catch (...) {
+      ctx->exception = std::current_exception();
+    }
+
+    notifyStackSwitchToKernel();
+    /* Emulate the System V uc_link feature. */
+    auto const next_ctx = ctx->uc_link->uc_mcontext;
+#if BOOST_VERSION < 106100
+    jump_fcontext(reinterpret_cast<fcontext_t*>(&ctx->uc_mcontext),
+                  static_cast<fcontext_t>(next_ctx),
+                  reinterpret_cast<intptr_t>(ctx));
+#else
+    jump_fcontext(static_cast<fcontext_t>(next_ctx), 0);
+#endif
+
+#ifdef NDEBUG
+    __builtin_unreachable();
+#endif
+  }
+}
+
+pdns_ucontext_t::pdns_ucontext_t() :
+  uc_mcontext(nullptr), uc_link(nullptr)
+{
+#ifdef PDNS_USE_VALGRIND
+  valgrind_id = 0;
+#endif /* PDNS_USE_VALGRIND */
+}
+
+pdns_ucontext_t::~pdns_ucontext_t()
+{
+  /* There's nothing to delete here since fcontext doesn't require anything
+   * to be heap allocated.
+   */
+#ifdef PDNS_USE_VALGRIND
+  if (valgrind_id != 0) {
+    VALGRIND_STACK_DEREGISTER(valgrind_id);
+  }
+#endif /* PDNS_USE_VALGRIND */
+}
+
+void pdns_swapcontext(pdns_ucontext_t& __restrict octx, pdns_ucontext_t const& __restrict ctx)
+{
+  /* we either switch back to threadwrapper() if it's the first time,
+     or we switch back to pdns_swapcontext(),
+     in both case we will be returning from a call to jump_fcontext(). */
+#if BOOST_VERSION < 106100
+  intptr_t ptr = jump_fcontext(reinterpret_cast<fcontext_t*>(&octx.uc_mcontext),
+                               static_cast<fcontext_t>(ctx.uc_mcontext), 0);
+
+  auto origctx = reinterpret_cast<pdns_ucontext_t*>(ptr);
+  if (origctx && origctx->exception)
+    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
+       switch back to it later. */
+    fcontext_t* ptr = static_cast<fcontext_t*>(res.data);
+    *ptr = res.fctx;
+  }
+  if (ctx.exception) {
+    std::rethrow_exception(ctx.exception);
+  }
+#endif
+}
+
+void pdns_makecontext(pdns_ucontext_t& ctx, std::function<void(void)>& start)
+{
+  assert(ctx.uc_link);
+  assert(ctx.uc_stack.size() >= 8192);
+  assert(!ctx.uc_mcontext);
+  ctx.uc_mcontext = make_fcontext(&ctx.uc_stack[ctx.uc_stack.size() - 1],
+                                  ctx.uc_stack.size() - 1, &threadWrapper);
+  args_t args;
+  args.self = &ctx;
+  args.work = &start;
+  /* jumping to threadwrapper */
+  notifyStackSwitch(&ctx.uc_stack[ctx.uc_stack.size() - 1], ctx.uc_stack.size() - 1);
+#if BOOST_VERSION < 106100
+  jump_fcontext(reinterpret_cast<fcontext_t*>(&args.prev_ctx),
+                static_cast<fcontext_t>(ctx.uc_mcontext),
+                reinterpret_cast<intptr_t>(&args));
+#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
+  notifyStackSwitchDone();
+}
deleted file mode 120000 (symlink)
index 67d056f35f1773d5474cdf3463ed7cfe9c858df7..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../mtasker_ucontext.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..9fcb3daaa0d639568e037f033420cb60598ef21c
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * 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 "mtasker_context.hh"
+#include <system_error>
+#include <exception>
+#include <cstring>
+#include <cassert>
+#include <csignal>
+#include <cstdint>
+#include <ucontext.h>
+
+#ifdef PDNS_USE_VALGRIND
+#include <valgrind/valgrind.h>
+#endif /* PDNS_USE_VALGRIND */
+
+#ifdef HAVE_FIBER_SANITIZER
+__thread void* t_mainStack{nullptr};
+__thread size_t t_mainStackSize{0};
+#endif /* HAVE_FIBER_SANITIZER */
+
+template <typename Message>
+static __attribute__((noinline, cold, noreturn)) void
+throw_errno(Message&& msg)
+{
+  throw std::system_error(errno, std::system_category(), std::forward<Message>(msg));
+}
+
+static inline std::pair<int, int>
+splitPointer(void* const ptr) noexcept
+{
+  static_assert(sizeof(int) == 4, "splitPointer() requires an 4 byte 'int'");
+  // In theory, we need this assertion. In practice, it prevents compilation
+  // on EL6 i386. Without the assertion, everything works.
+  // If you ever run into trouble with this code, please heed the warnings at
+  // http://man7.org/linux/man-pages/man3/makecontext.3.html#NOTES
+  //    static_assert (sizeof(uintptr_t) == 8,
+  //                    "splitPointer() requires an 8 byte 'uintptr_t'");
+  std::pair<int, int> words;
+  auto rep = reinterpret_cast<uintptr_t>(ptr);
+  uint32_t const hw = rep >> 32;
+  auto const lw = static_cast<uint32_t>(rep);
+  std::memcpy(&words.first, &hw, 4);
+  std::memcpy(&words.second, &lw, 4);
+  return words;
+}
+
+template <typename T>
+static inline T*
+joinPtr(int const first, int const second) noexcept
+{
+  static_assert(sizeof(int) == 4, "joinPtr() requires an 4 byte 'int'");
+  // See above.
+  //    static_assert (sizeof(uintptr_t) == 8,
+  //                    "joinPtr() requires an 8 byte 'uintptr_t'");
+  uint32_t hw;
+  uint32_t lw;
+  std::memcpy(&hw, &first, 4);
+  std::memcpy(&lw, &second, 4);
+  return reinterpret_cast<T*>((static_cast<uintptr_t>(hw) << 32) | lw);
+}
+
+extern "C"
+{
+  static void
+  threadWrapper(int const ctx0, int const ctx1, int const fun0, int const fun1)
+  {
+    notifyStackSwitchDone();
+    auto ctx = joinPtr<pdns_ucontext_t>(ctx0, ctx1);
+    try {
+      auto start = std::move(*joinPtr<std::function<void()>>(fun0, fun1));
+      start();
+    }
+    catch (...) {
+      ctx->exception = std::current_exception();
+    }
+    notifyStackSwitchToKernel();
+  }
+} // extern "C"
+
+pdns_ucontext_t::pdns_ucontext_t()
+{
+  uc_mcontext = new ::ucontext_t();
+  uc_link = nullptr;
+#ifdef PDNS_USE_VALGRIND
+  valgrind_id = 0;
+#endif /* PDNS_USE_VALGRIND */
+}
+
+pdns_ucontext_t::~pdns_ucontext_t()
+{
+  delete static_cast<ucontext_t*>(uc_mcontext);
+#ifdef PDNS_USE_VALGRIND
+  if (valgrind_id != 0) {
+    VALGRIND_STACK_DEREGISTER(valgrind_id);
+  }
+#endif /* PDNS_USE_VALGRIND */
+}
+
+void pdns_swapcontext(pdns_ucontext_t& __restrict octx, pdns_ucontext_t const& __restrict ctx)
+{
+  if (::swapcontext(static_cast<ucontext_t*>(octx.uc_mcontext),
+                    static_cast<ucontext_t*>(ctx.uc_mcontext))) {
+    throw_errno("swapcontext() failed");
+  }
+  if (ctx.exception) {
+    std::rethrow_exception(ctx.exception);
+  }
+}
+
+void pdns_makecontext(pdns_ucontext_t& ctx, std::function<void(void)>& start)
+{
+  assert(ctx.uc_link);
+  assert(ctx.uc_stack.size());
+
+  auto const mcp = static_cast<ucontext_t*>(ctx.uc_mcontext);
+  auto const next = static_cast<ucontext_t*>(ctx.uc_link->uc_mcontext);
+  if (::getcontext(mcp)) {
+    throw_errno("getcontext() failed");
+  }
+  mcp->uc_link = next;
+  mcp->uc_stack.ss_sp = ctx.uc_stack.data();
+  mcp->uc_stack.ss_size = ctx.uc_stack.size() - 1;
+  mcp->uc_stack.ss_flags = 0;
+
+  auto ctxarg = splitPointer(&ctx);
+  auto funarg = splitPointer(&start);
+  return ::makecontext(mcp, reinterpret_cast<void (*)(void)>(&threadWrapper),
+                       4, ctxarg.first, ctxarg.second,
+                       funarg.first, funarg.second);
+}
index b11d4ea2651dc260da54a53525b27df983740614..5bea1a06b934290dbead810ee040370fc0bd7af7 100644 (file)
 #include "misc.hh"
 #include "cachecleaner.hh"
 #include "utility.hh"
+#include "rec-taskqueue.hh"
+
+// For a description on how ServeStale works, see recursor_cache.cc, the general structure is the same.
+uint16_t NegCache::s_maxServedStaleExtensions;
 
 NegCache::NegCache(size_t mapsCount) :
-  d_maps(mapsCount)
+  d_maps(mapsCount == 0 ? 1 : mapsCount)
 {
 }
 
@@ -35,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;
 }
@@ -49,32 +53,39 @@ 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 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();
+  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;
+}
 
-  auto& map = getMap(lastLabel);
-  auto content = map.lock();
-
-  negcache_t::const_iterator ni = content->d_map.find(std::tie(lastLabel, qtnull));
+void NegCache::updateStaleEntry(time_t now, negcache_t::iterator& entry, QType qtype)
+{
+  // We need to take care a infrequently access stale item cannot be extended past
+  // s_maxServedStaleExtension * s_serveStaleExtensionPeriod
+  // We we look how old the entry is, and increase d_servedStale accordingly, taking care not to overflow
+  const time_t howlong = std::max(static_cast<time_t>(1), now - entry->d_ttd);
+  const uint32_t extension = std::max(1U, std::min(entry->d_orig_ttl, s_serveStaleExtensionPeriod));
+  entry->d_servedStale = std::min(entry->d_servedStale + 1 + howlong / extension, static_cast<time_t>(s_maxServedStaleExtensions));
+  entry->d_ttd = now + std::min(entry->d_orig_ttl, s_serveStaleExtensionPeriod);
 
-  while (ni != content->d_map.end() && ni->d_name == lastLabel && ni->d_auth.isRoot() && ni->d_qtype == qtnull) {
-    // We have something
-    if (now.tv_sec < ni->d_ttd) {
-      ne = *ni;
-      moveCacheItemToBack<SequenceTag>(content->d_map, ni);
-      return true;
-    }
-    moveCacheItemToFront<SequenceTag>(content->d_map, ni);
-    ++ni;
+  if (qtype == QType::ENT) {
+    qtype = QType::A;
   }
-  return false;
+
+  pushAlmostExpiredTask(entry->d_name, qtype, entry->d_ttd, Netmask());
 }
 
 /*!
@@ -86,31 +97,39 @@ bool NegCache::getRootNXTrust(const DNSName& qname, const struct timeval& now, N
  * \param ne       A NegCacheEntry that is filled when there is a cache entry
  * \return         true if ne was filled out, false otherwise
  */
-bool NegCache::get(const DNSName& qname, const QType& qtype, const struct timeval& now, NegCacheEntry& ne, bool typeMustMatch)
+bool NegCache::get(const DNSName& qname, QType qtype, const struct timeval& now, NegCacheEntry& ne, bool typeMustMatch, bool serveStale, bool refresh)
 {
   auto& map = getMap(qname);
   auto content = map.lock();
 
   const auto& idx = content->d_map.get<NegCacheEntry>();
   auto range = idx.equal_range(qname);
-  auto ni = range.first;
 
-  while (ni != range.second) {
+  for (auto ni = range.first; ni != range.second; ++ni) {
     // We have an entry
-    if ((!typeMustMatch && ni->d_qtype.getCode() == 0) || ni->d_qtype == qtype) {
+    if ((!typeMustMatch && ni->d_qtype == QType::ENT) || ni->d_qtype == qtype) {
       // We match the QType or the whole name is denied
       auto firstIndexIterator = content->d_map.project<CompositeKey>(ni);
 
+      // this checks ttd, but also takes into account serve-stale
+      if (!ni->isEntryUsable(now.tv_sec, serveStale)) {
+        // Outdated
+        moveCacheItemToFront<SequenceTag>(content->d_map, firstIndexIterator);
+        continue;
+      }
+      // If we are serving this record stale (or *should*) and the ttd has passed increase ttd to
+      // the future and remember that we did. Also push a refresh task.
+      if ((serveStale || ni->d_servedStale > 0) && ni->d_ttd <= now.tv_sec && ni->d_servedStale < s_maxServedStaleExtensions) {
+        updateStaleEntry(now.tv_sec, firstIndexIterator, qtype);
+      }
       if (now.tv_sec < ni->d_ttd) {
         // Not expired
         ne = *ni;
         moveCacheItemToBack<SequenceTag>(content->d_map, firstIndexIterator);
-        return true;
+        // when refreshing, we consider served-stale entries outdated
+        return !(refresh && ni->d_servedStale > 0);
       }
-      // expired
-      moveCacheItemToFront<SequenceTag>(content->d_map, firstIndexIterator);
     }
-    ++ni;
   }
   return false;
 }
@@ -127,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();
   }
 }
 
@@ -138,7 +157,7 @@ void NegCache::add(const NegCacheEntry& ne)
  * \param qtype The type of the entry to replace
  * \param newState The new validation state
  */
-void NegCache::updateValidationStatus(const DNSName& qname, const QType& qtype, const vState newState, boost::optional<time_t> capTTD)
+void NegCache::updateValidationStatus(const DNSName& qname, const QType qtype, const vState newState, boost::optional<time_t> capTTD)
 {
   auto& mc = getMap(qname);
   auto map = mc.lock();
@@ -195,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;
@@ -208,7 +227,27 @@ 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;
+}
+
+size_t NegCache::wipeTyped(const DNSName& qname, QType qtype)
+{
+  size_t ret = 0;
+  auto& map = getMap(qname);
+  auto content = map.lock();
+  auto range = content->d_map.equal_range(std::tie(qname));
+  auto i = range.first;
+  while (i != range.second) {
+    if (i->d_qtype == QType::ENT || i->d_qtype == qtype) {
+      i = content->d_map.erase(i);
+      ++ret;
+      map.decEntriesCount();
+    }
+    else {
+      ++i;
+    }
   }
   return ret;
 }
@@ -221,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();
   }
 }
 
@@ -230,10 +269,10 @@ 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);
 }
 
 /*!
@@ -241,30 +280,50 @@ void NegCache::prune(size_t maxEntries)
  *
  * \param fp A pointer to an open FILE object
  */
-size_t NegCache::dumpToFile(FILE* fp, const struct timeval& now)
+size_t NegCache::doDump(int fd, size_t maxCacheEntries, time_t now)
 {
+  int newfd = dup(fd);
+  if (newfd == -1) {
+    return 0;
+  }
+  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
+  if (!fp) {
+    close(newfd);
+    return 0;
+  }
+  fprintf(fp.get(), "; negcache dump follows\n;\n");
+
   size_t ret = 0;
 
+  size_t shard = 0;
+  size_t min = std::numeric_limits<size_t>::max();
+  size_t max = 0;
   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);
+    min = std::min(min, shardSize);
+    max = std::max(max, shardSize);
+    shard++;
     auto& sidx = m->d_map.get<SequenceTag>();
     for (const NegCacheEntry& ne : sidx) {
       ret++;
-      int64_t ttl = ne.d_ttd - now.tv_sec;
-      fprintf(fp, "%s %" PRId64 " IN %s VIA %s ; (%s)\n", ne.d_name.toString().c_str(), ttl, ne.d_qtype.toString().c_str(), ne.d_auth.toString().c_str(), vStateToString(ne.d_validationState).c_str());
+      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);
       for (const auto& rec : ne.authoritySOA.records) {
-        fprintf(fp, "%s %" PRId64 " IN %s %s ; (%s)\n", rec.d_name.toString().c_str(), ttl, DNSRecordContent::NumberToType(rec.d_type).c_str(), rec.d_content->getZoneRepresentation().c_str(), vStateToString(ne.d_validationState).c_str());
+        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());
       }
       for (const auto& sig : ne.authoritySOA.signatures) {
-        fprintf(fp, "%s %" PRId64 " IN RRSIG %s ;\n", sig.d_name.toString().c_str(), ttl, sig.d_content->getZoneRepresentation().c_str());
+        fprintf(fp.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, "%s %" PRId64 " IN %s %s ; (%s)\n", rec.d_name.toString().c_str(), ttl, DNSRecordContent::NumberToType(rec.d_type).c_str(), rec.d_content->getZoneRepresentation().c_str(), vStateToString(ne.d_validationState).c_str());
+        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());
       }
       for (const auto& sig : ne.DNSSECRecords.signatures) {
-        fprintf(fp, "%s %" PRId64 " IN RRSIG %s ;\n", sig.d_name.toString().c_str(), ttl, sig.d_content->getZoneRepresentation().c_str());
+        fprintf(fp.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);
   return ret;
 }
index 204406cbd20b403c2dd509b9e5481c1becde964b..d0d87ee4004180359af5c72c1f8fabf4379e5a86 100644 (file)
@@ -54,7 +54,13 @@ typedef struct
 class NegCache : public boost::noncopyable
 {
 public:
-  NegCache(size_t mapsCount = 1024);
+  NegCache(size_t mapsCount = 128);
+
+  // For a description on how ServeStale works, see recursor_cache.cc, the general structure is the same.
+  // The number of times a stale cache entry is extended
+  static uint16_t s_maxServedStaleExtensions;
+  // The time a stale cache entry is extended
+  static constexpr uint32_t s_serveStaleExtensionPeriod = 30;
 
   struct NegCacheEntry
   {
@@ -63,24 +69,40 @@ public:
     DNSName d_name; // The denied name
     DNSName d_auth; // The denying name (aka auth)
     mutable time_t d_ttd; // Timestamp when this entry should die
+    uint32_t d_orig_ttl;
+    mutable uint16_t d_servedStale{0};
     mutable vState d_validationState{vState::Indeterminate};
     QType d_qtype; // The denied type
-    time_t getTTD() const
+
+    bool isStale(time_t now) const
     {
-      return d_ttd;
+      // We like to keep things in cache when we (potentially) should serve stale
+      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;
+      }
     };
+
+    bool isEntryUsable(time_t now, bool serveStale) const
+    {
+      // When serving stale, we consider expired records
+      return d_ttd > now || serveStale || d_servedStale != 0;
+    }
   };
 
   void add(const NegCacheEntry& ne);
-  void updateValidationStatus(const DNSName& qname, const QType& qtype, const vState newState, boost::optional<time_t> capTTD);
-  bool get(const DNSName& qname, const QType& qtype, const struct timeval& now, NegCacheEntry& ne, bool typeMustMatch = false);
-  bool getRootNXTrust(const DNSName& qname, const struct timeval& now, NegCacheEntry& ne);
+  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& negEntry, bool serveStale, bool refresh);
   size_t count(const DNSName& qname);
-  size_t count(const DNSName& qname, const QType qtype);
-  void prune(size_t maxEntries);
+  size_t count(const DNSName& qname, QType qtype);
+  void prune(time_t now, size_t maxEntries);
   void clear();
-  size_t dumpToFile(FILE* fd, const struct timeval& now);
+  size_t doDump(int fd, size_t maxCacheEntries, time_t now = time(nullptr));
   size_t wipe(const DNSName& name, bool subtree = false);
+  size_t wipeTyped(const DNSName& name, QType qtype);
   size_t size() const;
 
 private:
@@ -105,6 +127,8 @@ private:
                         member<NegCacheEntry, DNSName, &NegCacheEntry::d_name>>>>
     negcache_t;
 
+  void updateStaleEntry(time_t now, negcache_t::iterator& entry, QType qtype);
+
   struct MapCombo
   {
     MapCombo() {}
@@ -116,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()
     {
@@ -130,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;
@@ -144,9 +189,4 @@ private:
   {
     return d_maps.at(qname.hash() % d_maps.size());
   }
-
-public:
-  void preRemoval(MapCombo::LockedContent& map, const NegCacheEntry& entry)
-  {
-  }
 };
deleted file mode 120000 (symlink)
index fde5c091f02093ff7896c162709c0b767828a32c..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../nod.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..811811ca6ac8413d11e985d70df525e997b0f409
--- /dev/null
@@ -0,0 +1,269 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "nod.hh"
+#include <fstream>
+#include "pdnsexception.hh"
+#include <iostream>
+#include <iomanip>
+#include <ctime>
+#include <thread>
+#include "threadname.hh"
+#include <stdlib.h>
+#include "logger.hh"
+#include "logging.hh"
+#include "misc.hh"
+
+using namespace nod;
+namespace filesystem = boost::filesystem;
+
+// PersistentSBF Implementation
+
+std::mutex PersistentSBF::d_cachedir_mutex;
+
+void PersistentSBF::remove_tmp_files(const filesystem::path& p, std::lock_guard<std::mutex>& /* lock */)
+{
+  Regex file_regex(d_prefix + ".*\\." + bf_suffix + "\\..{8}$");
+  for (filesystem::directory_iterator i(p); i != filesystem::directory_iterator(); ++i) {
+    if (filesystem::is_regular_file(i->path()) && file_regex.match(i->path().filename().string())) {
+      filesystem::remove(*i);
+    }
+  }
+}
+
+// This looks for an old (per-thread) snapshot. The first one it finds,
+// it restores from that. Then immediately snapshots with the current thread id,// before removing the old snapshot
+// In this way, we can have per-thread SBFs, but still snapshot and restore.
+// The mutex has to be static because we can't have multiple (i.e. per-thread)
+// instances iterating and writing to the cache dir at the same time
+bool PersistentSBF::init(bool ignore_pid)
+{
+  auto log = g_slog->withName("nod");
+  std::lock_guard<std::mutex> lock(d_cachedir_mutex);
+  if (d_cachedir.length()) {
+    filesystem::path p(d_cachedir);
+    try {
+      if (filesystem::exists(p) && filesystem::is_directory(p)) {
+        remove_tmp_files(p, lock);
+        filesystem::path newest_file;
+        std::time_t newest_time = time(nullptr);
+        Regex file_regex(d_prefix + ".*\\." + bf_suffix + "$");
+        for (filesystem::directory_iterator i(p); i != filesystem::directory_iterator(); ++i) {
+          if (filesystem::is_regular_file(i->path()) && file_regex.match(i->path().filename().string())) {
+            if (ignore_pid || (i->path().filename().string().find(std::to_string(getpid())) == std::string::npos)) {
+              // look for the newest file matching the regex
+              if ((last_write_time(i->path()) < newest_time) || newest_file.empty()) {
+                newest_time = last_write_time(i->path());
+                newest_file = i->path();
+              }
+            }
+          }
+        }
+        if (filesystem::exists(newest_file)) {
+          std::string filename = newest_file.string();
+          std::ifstream infile;
+          try {
+            infile.open(filename, std::ios::in | std::ios::binary);
+            SLOG(g_log << Logger::Warning << "Found SBF file " << filename << endl,
+                 log->info(Logr::Warning, "Found SBF File", "file", Logging::Loggable(filename)));
+            // read the file into the sbf
+            d_sbf.lock()->restore(infile);
+            infile.close();
+            // now dump it out again with new thread id & process id
+            snapshotCurrent(std::this_thread::get_id());
+            // Remove the old file we just read to stop proliferation
+            filesystem::remove(newest_file);
+          }
+          catch (const std::runtime_error& e) {
+            infile.close();
+            filesystem::remove(newest_file);
+            SLOG(g_log << Logger::Warning << "NODDB init: Cannot parse file: " << filename << ": " << e.what() << "; removed" << endl,
+                 log->error(Logr::Warning, e.what(), "NODDB init: Cannot parse file, removed", "file", Logging::Loggable(filename)));
+          }
+        }
+      }
+    }
+    catch (const filesystem::filesystem_error& e) {
+      SLOG(g_log << Logger::Warning << "NODDB init failed: " << e.what() << endl,
+           log->error(Logr::Warning, e.what(), "NODDB init failed", "exception", Logging::Loggable("filesystem::filesystem_error")));
+      return false;
+    }
+  }
+  return true;
+}
+
+void PersistentSBF::setCacheDir(const std::string& 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
+// To spend the least amount of time inside the mutex, we dump to an
+// intermediate stringstream, otherwise the lock would be waiting for
+// file IO to complete
+bool PersistentSBF::snapshotCurrent(std::thread::id tid)
+{
+  auto log = g_slog->withName("nod");
+  if (d_cachedir.length()) {
+    filesystem::path p(d_cachedir);
+    filesystem::path f(d_cachedir);
+    std::stringstream ss;
+    ss << d_prefix << "_" << tid;
+    f /= ss.str() + "_" + std::to_string(getpid()) + "." + bf_suffix;
+    if (filesystem::exists(p) && filesystem::is_directory(p)) {
+      try {
+        std::ofstream ofile;
+        std::stringstream iss;
+        {
+          // only lock while dumping to a stringstream
+          d_sbf.lock()->dump(iss);
+        }
+        // Now write it out to the file
+        std::string ftmp = f.string() + ".XXXXXXXX";
+        int fd = mkstemp(&ftmp.at(0));
+        if (fd == -1) {
+          throw std::runtime_error("Cannot create temp file: " + stringerror());
+        }
+        std::string str = iss.str();
+        ssize_t len = write(fd, str.data(), str.length());
+        if (len != static_cast<ssize_t>(str.length())) {
+          close(fd);
+          filesystem::remove(ftmp.c_str());
+          throw std::runtime_error("Failed to write to file:" + ftmp);
+        }
+        if (close(fd) != 0) {
+          filesystem::remove(ftmp);
+          throw std::runtime_error("Failed to write to file:" + ftmp);
+        }
+        try {
+          filesystem::rename(ftmp, f);
+        }
+        catch (const std::runtime_error& e) {
+          SLOG(g_log << Logger::Warning << "NODDB snapshot: Cannot rename file: " << e.what() << endl,
+               log->error(Logr::Warning, e.what(), "NODDB snapshot: Cannot rename file", "exception", Logging::Loggable("std::runtime_error")));
+          filesystem::remove(ftmp);
+          throw;
+        }
+        return true;
+      }
+      catch (const std::runtime_error& e) {
+        SLOG(g_log << Logger::Warning << "NODDB snapshot: Cannot write file: " << e.what() << endl,
+             log->error(Logr::Warning, e.what(), "NODDB snapshot: Cannot write file", "exception", Logging::Loggable("std::runtime_error")));
+      }
+    }
+    else {
+      SLOG(g_log << Logger::Warning << "NODDB snapshot: Cannot write file: " << f.string() << endl,
+           log->info(Logr::Warning, "NODDB snapshot: Cannot write file", "file", Logging::Loggable(f.string())));
+    }
+  }
+  return false;
+}
+
+// NODDB Implementation
+
+void NODDB::housekeepingThread(std::thread::id tid)
+{
+  setThreadName("rec/nod-hk");
+  for (;;) {
+    sleep(d_snapshot_interval);
+    {
+      snapshotCurrent(tid);
+    }
+  }
+}
+
+bool NODDB::isNewDomain(const std::string& domain)
+{
+  DNSName dname(domain);
+  return isNewDomain(dname);
+}
+
+bool NODDB::isNewDomain(const DNSName& dname)
+{
+  std::string dname_lc = dname.toDNSStringLC();
+  // The only time this should block is when snapshotting from the
+  // housekeeping thread
+  // the result is always the inverse of what is returned by the SBF
+  return !d_psbf.testAndAdd(dname_lc);
+}
+
+bool NODDB::isNewDomainWithParent(const std::string& domain, std::string& observed)
+{
+  DNSName dname(domain);
+  return isNewDomainWithParent(dname, observed);
+}
+
+bool NODDB::isNewDomainWithParent(const DNSName& dname, std::string& observed)
+{
+  bool ret = isNewDomain(dname);
+  if (ret == true) {
+    DNSName mdname = dname;
+    while (mdname.chopOff()) {
+      if (!isNewDomain(mdname)) {
+        observed = mdname.toString();
+        break;
+      }
+    }
+  }
+  return ret;
+}
+
+void NODDB::addDomain(const DNSName& dname)
+{
+  std::string native_domain = dname.toDNSStringLC();
+  d_psbf.add(native_domain);
+}
+
+void NODDB::addDomain(const std::string& domain)
+{
+  DNSName dname(domain);
+  addDomain(dname);
+}
+
+// UniqueResponseDB Implementation
+bool UniqueResponseDB::isUniqueResponse(const std::string& response)
+{
+  return !d_psbf.testAndAdd(response);
+}
+
+void UniqueResponseDB::addResponse(const std::string& response)
+{
+  d_psbf.add(response);
+}
+
+void UniqueResponseDB::housekeepingThread(std::thread::id tid)
+{
+  setThreadName("rec/udr-hk");
+  for (;;) {
+    sleep(d_snapshot_interval);
+    {
+      snapshotCurrent(tid);
+    }
+  }
+}
deleted file mode 120000 (symlink)
index a56167aa87c6206e6655253494302e17c1aeb569..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../nod.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..a27b1d77fd0cba671e0af36602d26d25a2fdfb0a
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * 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 <mutex>
+#include <thread>
+#include <boost/filesystem.hpp>
+#include "dnsname.hh"
+#include "lock.hh"
+#include "stable-bloom.hh"
+
+namespace nod
+{
+const float c_fp_rate = 0.01;
+const size_t c_num_cells = 67108864;
+const uint8_t c_num_dec = 10;
+const unsigned int snapshot_interval_default = 600;
+const std::string bf_suffix = "bf";
+const std::string sbf_prefix = "sbf";
+
+// Theses classes are not designed to be shared between threads
+// Use a new instance per-thread, e.g. using thread local storage
+// Synchronization (at the class level) is still needed for reading from
+// and writing to the cache dir
+// Synchronization (at the instance level) is needed when snapshotting
+class PersistentSBF
+{
+public:
+  PersistentSBF() :
+    d_sbf(bf::stableBF(c_fp_rate, c_num_cells, c_num_dec)) {}
+  PersistentSBF(uint32_t num_cells) :
+    d_sbf(bf::stableBF(c_fp_rate, num_cells, c_num_dec)) {}
+  bool init(bool ignore_pid = false);
+  void setPrefix(const std::string& prefix) { d_prefix = prefix; } // Added to filenames in cachedir
+  void setCacheDir(const std::string& cachedir);
+  bool snapshotCurrent(std::thread::id tid); // Write the current file out to disk
+  void add(const std::string& data)
+  {
+    // The only time this should block is when snapshotting
+    d_sbf.lock()->add(data);
+  }
+  bool test(const std::string& data) { return d_sbf.lock()->test(data); }
+  bool testAndAdd(const std::string& data)
+  {
+    // The only time this should block is when snapshotting
+    return d_sbf.lock()->testAndAdd(data);
+  }
+
+private:
+  void remove_tmp_files(const boost::filesystem::path&, std::lock_guard<std::mutex>&);
+
+  LockGuarded<bf::stableBF> d_sbf; // Stable Bloom Filter
+  std::string d_cachedir;
+  std::string d_prefix = sbf_prefix;
+  static std::mutex d_cachedir_mutex; // One mutex for all instances of this class
+};
+
+class NODDB
+{
+public:
+  NODDB() :
+    d_psbf{} {}
+  NODDB(uint32_t num_cells) :
+    d_psbf{num_cells} {}
+  // Set ignore_pid to true if you don't mind loading files
+  // created by the current process
+  bool init(bool ignore_pid = false)
+  {
+    d_psbf.setPrefix("nod");
+    return d_psbf.init(ignore_pid);
+  }
+  bool isNewDomain(const std::string& domain); // Returns true if newly observed domain
+  bool isNewDomain(const DNSName& dname); // As above
+  bool isNewDomainWithParent(const std::string& domain, std::string& observed); // Returns true if newly observed domain, in which case "observed" contains the parent domain which *was* observed (or "" if domain is . or no parent domains observed)
+  bool isNewDomainWithParent(const DNSName& dname, std::string& observed); // As above
+  void addDomain(const DNSName& dname); // You need to add this to refresh frequently used domains
+  void addDomain(const std::string& domain); // As above
+  void setSnapshotInterval(unsigned int secs) { d_snapshot_interval = secs; }
+  void setCacheDir(const std::string& cachedir) { d_psbf.setCacheDir(cachedir); }
+  bool snapshotCurrent(std::thread::id tid) { return d_psbf.snapshotCurrent(tid); }
+  static void startHousekeepingThread(std::shared_ptr<NODDB> noddbp, std::thread::id tid)
+  {
+    noddbp->housekeepingThread(tid);
+  }
+
+private:
+  PersistentSBF d_psbf;
+  unsigned int d_snapshot_interval{snapshot_interval_default}; // Number seconds between snapshots
+  void housekeepingThread(std::thread::id tid);
+};
+
+class UniqueResponseDB
+{
+public:
+  UniqueResponseDB() :
+    d_psbf{} {}
+  UniqueResponseDB(uint32_t num_cells) :
+    d_psbf{num_cells} {}
+  bool init(bool ignore_pid = false)
+  {
+    d_psbf.setPrefix("udr");
+    return d_psbf.init(ignore_pid);
+  }
+  bool isUniqueResponse(const std::string& response);
+  void addResponse(const std::string& response);
+  void setSnapshotInterval(unsigned int secs) { d_snapshot_interval = secs; }
+  void setCacheDir(const std::string& cachedir) { d_psbf.setCacheDir(cachedir); }
+  bool snapshotCurrent(std::thread::id tid) { return d_psbf.snapshotCurrent(tid); }
+  static void startHousekeepingThread(std::shared_ptr<UniqueResponseDB> udrdbp, std::thread::id tid)
+  {
+    udrdbp->housekeepingThread(tid);
+  }
+
+private:
+  PersistentSBF d_psbf;
+  unsigned int d_snapshot_interval{snapshot_interval_default}; // Number seconds between snapshots
+  void housekeepingThread(std::thread::id tid);
+};
+
+}
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 886b90655da0fa4befbb49f94095886abe4c7c1c..91d81fe1968d7466912c2c341b92d9a2091f5df4 100644 (file)
@@ -2,9 +2,8 @@
 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
-After=network-online.target
+Wants=network-online.target
+After=network-online.target time-sync.target
 
 [Service]
 ExecStart=@sbindir@/pdns_recursor --daemon=no --write-pid=no --disable-syslog --log-timestamp=no
@@ -41,6 +40,12 @@ RestrictRealtime=true
 RestrictSUIDSGID=true
 SystemCallArchitectures=native
 SystemCallFilter=~ @clock @debug @module @mount @raw-io @reboot @swap @cpu-emulation @obsolete
+ProtectProc=invisible
+PrivateIPC=true
+RemoveIPC=true
+DevicePolicy=closed
+# Not enabled by default because it does not play well with LuaJIT
+# MemoryDenyWriteExecute=true
 
 [Install]
 WantedBy=multi-user.target
deleted file mode 120000 (symlink)
index 1bdc30068a2acc8af7858c36b3852f0adef13012..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../pdns_recursor.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..5071a38787c5ee4b1351d2f0430dd2d5354292e2
--- /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 "rec-main.hh"
+
+#include "arguments.hh"
+#include "dns_random.hh"
+#include "ednsextendederror.hh"
+#include "ednspadding.hh"
+#include "query-local-address.hh"
+#include "rec-taskqueue.hh"
+#include "shuffle.hh"
+#include "validate-recursor.hh"
+
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-daemon.h>
+#endif
+
+#ifdef NOD_ENABLED
+#include "nod.hh"
+#include "logging.hh"
+#endif /* NOD_ENABLED */
+
+thread_local std::shared_ptr<RecursorLua4> t_pdl;
+thread_local std::shared_ptr<Regex> t_traceRegex;
+thread_local FDWrapper t_tracefd = -1;
+thread_local ProtobufServersInfo t_protobufServers;
+thread_local ProtobufServersInfo t_outgoingProtobufServers;
+
+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;
+
+thread_local std::unique_ptr<FDMultiplexer> t_fdm;
+thread_local std::unique_ptr<addrringbuf_t> t_remotes, t_servfailremotes, t_largeanswerremotes, t_bogusremotes;
+thread_local std::unique_ptr<boost::circular_buffer<pair<DNSName, uint16_t>>> t_queryring, t_servfailqueryring, t_bogusqueryring;
+thread_local std::shared_ptr<NetmaskGroup> t_allowFrom;
+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
+
+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())
+NetmaskGroup g_paddingFrom;
+size_t g_proxyProtocolMaximumSize;
+size_t g_maxUDPQueriesPerRound;
+unsigned int g_maxMThreads;
+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};
+bool g_useKernelTimestamp;
+std::atomic<uint32_t> g_maxCacheEntries, g_maxPacketCacheEntries;
+#ifdef HAVE_BOOST_CONTAINER_FLAT_SET_HPP
+boost::container::flat_set<uint16_t> g_avoidUdpSourcePorts;
+#else
+std::set<uint16_t> g_avoidUdpSourcePorts;
+#endif
+uint16_t g_minUdpSourcePort;
+uint16_t g_maxUdpSourcePort;
+double g_balancingFactor;
+
+bool g_lowercaseOutgoing;
+unsigned int g_networkTimeoutMsec;
+uint16_t g_outgoingEDNSBufsize;
+
+// Used in Syncres to counts DNSSEC stats for names in a different "universe"
+GlobalStateHolder<SuffixMatchNode> g_xdnssec;
+// Used in the Syncres to not throttle certain servers
+GlobalStateHolder<SuffixMatchNode> g_dontThrottleNames;
+GlobalStateHolder<NetmaskGroup> g_dontThrottleNetmasks;
+GlobalStateHolder<SuffixMatchNode> g_DoTToAuthNames;
+uint64_t g_latencyStatSize;
+
+LWResult::Result UDPClientSocks::getSocket(const ComboAddress& toaddr, int* fileDesc)
+{
+  *fileDesc = makeClientSocket(toaddr.sin4.sin_family);
+  if (*fileDesc < 0) { // temporary error - receive exception otherwise
+    return LWResult::Result::OSLimitError;
+  }
+
+  if (connect(*fileDesc, reinterpret_cast<const struct sockaddr*>(&toaddr), toaddr.getSocklen()) < 0) { // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast))
+    int err = errno;
+    try {
+      closesocket(*fileDesc);
+    }
+    catch (const PDNSException& e) {
+      SLOG(g_log << Logger::Error << "Error closing UDP socket after connect() failed: " << e.reason << endl,
+           g_slogout->error(Logr::Error, e.reason, "Error closing UDP socket after connect() failed", "exception", Logging::Loggable("PDNSException")));
+    }
+
+    if (err == ENETUNREACH) { // Seth "My Interfaces Are Like A Yo Yo" Arnold special
+      return LWResult::Result::OSLimitError;
+    }
+
+    return LWResult::Result::PermanentError;
+  }
+
+  d_numsocks++;
+  return LWResult::Result::Success;
+}
+
+// return a socket to the pool, or simply erase it
+void UDPClientSocks::returnSocket(int fileDesc)
+{
+  try {
+    t_fdm->removeReadFD(fileDesc);
+  }
+  catch (const FDMultiplexerException& e) {
+    // we sometimes return a socket that has not yet been assigned to t_fdm
+  }
+
+  try {
+    closesocket(fileDesc);
+  }
+  catch (const PDNSException& e) {
+    SLOG(g_log << Logger::Error << "Error closing returned UDP socket: " << e.reason << endl,
+         g_slogout->error(Logr::Error, e.reason, "Error closing returned UDP socket", "exception", Logging::Loggable("PDNSException")));
+  }
+
+  --d_numsocks;
+}
+
+// returns -1 for errors which might go away, throws for ones that won't
+int UDPClientSocks::makeClientSocket(int family)
+{
+  int ret = socket(family, SOCK_DGRAM, 0); // turns out that setting CLO_EXEC and NONBLOCK from here is not a performance win on Linux (oddly enough)
+
+  if (ret < 0 && errno == EMFILE) { // this is not a catastrophic error
+    return ret;
+  }
+  if (ret < 0) {
+    int err = errno;
+    throw PDNSException("Making a socket for resolver (family = " + std::to_string(family) + "): " + stringerror(err));
+  }
+
+  // The loop below runs the body with [tries-1 tries-2 ... 1]. Last iteration with tries == 1 is special: it uses a kernel
+  // allocated UDP port.
+#if !defined(__OpenBSD__)
+  int tries = 10;
+#else
+  int tries = 2; // hit the reliable kernel random case for OpenBSD immediately (because it will match tries==1 below), using sysctl net.inet.udp.baddynamic to exclude ports
+#endif
+  ComboAddress sin;
+  while (--tries != 0) {
+    in_port_t port = 0;
+
+    if (tries == 1) { // last iteration: fall back to kernel 'random'
+      port = 0;
+    }
+    else {
+      do {
+        port = g_minUdpSourcePort + dns_random(g_maxUdpSourcePort - g_minUdpSourcePort + 1);
+      } while (g_avoidUdpSourcePorts.count(port) != 0);
+    }
+
+    sin = pdns::getQueryLocalAddress(family, port); // does htons for us
+    if (::bind(ret, reinterpret_cast<struct sockaddr*>(&sin), sin.getSocklen()) >= 0) { // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+      break;
+    }
+  }
+
+  int err = errno;
+
+  if (tries == 0) {
+    closesocket(ret);
+    throw PDNSException("Resolver binding to local query client socket on " + sin.toString() + ": " + stringerror(err));
+  }
+
+  try {
+    setReceiveSocketErrors(ret, family);
+    setNonBlocking(ret);
+  }
+  catch (...) {
+    closesocket(ret);
+    throw;
+  }
+  return ret;
+}
+
+static void handleGenUDPQueryResponse(int fileDesc, FDMultiplexer::funcparam_t& 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(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(fileDesc);
+  if (ret >= 0) {
+    resp.resize(ret);
+    g_multiTasker->sendEvent(pident, &resp);
+  }
+  else {
+    PacketBuffer empty;
+    g_multiTasker->sendEvent(pident, &empty);
+    //    cerr<<"Had some kind of error: "<<ret<<", "<<stringerror()<<endl;
+  }
+}
+
+PacketBuffer GenUDPQueryResponse(const ComboAddress& dest, const string& query)
+{
+  Socket socket(dest.sin4.sin_family, SOCK_DGRAM);
+  socket.setNonBlocking();
+  ComboAddress local = pdns::getQueryLocalAddress(dest.sin4.sin_family, 0);
+
+  socket.bind(local);
+  socket.connect(dest);
+  socket.send(query);
+
+  std::shared_ptr<PacketID> pident = std::make_shared<PacketID>();
+  pident->fd = socket.getHandle();
+  pident->remote = dest;
+  pident->type = 0;
+  t_fdm->addReadFD(socket.getHandle(), handleGenUDPQueryResponse, pident);
+
+  PacketBuffer data;
+  int ret = g_multiTasker->waitEvent(pident, &data, g_networkTimeoutMsec);
+
+  if (ret == 0 || ret == -1) { // timeout
+    t_fdm->removeReadFD(socket.getHandle());
+  }
+  else if (data.empty()) { // error, EOF or other
+    // we could special case this
+    return data;
+  }
+  return data;
+}
+
+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 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 = toAddress;
+  pident->type = qtype;
+
+  // We cannot merge ECS-enabled queries based on the ECS source only, as the scope
+  // of the response might be narrower, so instead we do not chain ECS-enabled queries
+  // at all.
+  if (!ecs) {
+    // 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 = 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); // 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(qid); // we can chain
+        *fileDesc = -1; // gets used in waitEvent / sendEvent later on
+        return LWResult::Result::Success;
+      }
+    }
+  }
+
+  auto ret = t_udpclientsocks->getSocket(toAddress, fileDesc);
+  if (ret != LWResult::Result::Success) {
+    return ret;
+  }
+
+  pident->fd = *fileDesc;
+  pident->id = qid;
+
+  t_fdm->addReadFD(*fileDesc, handleUDPServerResponse, pident);
+  ssize_t sent = send(*fileDesc, data, len, 0);
+
+  int tmp = errno;
+
+  if (sent < 0) {
+    t_udpclientsocks->returnSocket(*fileDesc);
+    errno = tmp; // this is for logging purposes only
+    return LWResult::Result::PermanentError;
+  }
+
+  return LWResult::Result::Success;
+}
+
+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 = fileDesc;
+  pident->id = qid;
+  pident->domain = domain;
+  pident->type = qtype;
+  pident->remote = fromAddr;
+
+  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) {
+    /* handleUDPServerResponse() will close the socket for us no matter what */
+    if (packet.empty()) { // means "error"
+      return LWResult::Result::PermanentError;
+    }
+
+    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,
+           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)));
+      t_Counters.at(rec::Counter::spoofCount)++;
+      return LWResult::Result::Spoofed;
+    }
+
+    return LWResult::Result::Success;
+  }
+  /* 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;
+}
+
+// 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) {
+    t_largeanswerremotes->push_back(remote);
+  }
+  switch (res) {
+  case RCode::ServFail:
+    if (t_servfailremotes) {
+      t_servfailremotes->push_back(remote);
+      if (query != nullptr && t_servfailqueryring) { // packet cache
+        t_servfailqueryring->push_back({*query, qtype});
+      }
+    }
+    ++t_Counters.at(rec::Counter::servFails);
+    break;
+  case RCode::NXDomain:
+    ++t_Counters.at(rec::Counter::nxDomains);
+    break;
+  case RCode::NoError:
+    t_Counters.at(rec::Counter::noErrors)++;
+    break;
+  }
+}
+
+static string makeLoginfo(const std::unique_ptr<DNSComboWriter>& comboWriter)
+try {
+  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";
+}
+
+/**
+ * Chases the CNAME provided by the PolicyCustom RPZ policy.
+ *
+ * @param spoofed: The DNSRecord that was created by the policy, should already be added to ret
+ * @param qtype: The QType of the original query
+ * @param sr: A SyncRes
+ * @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& resolver, int& res, vector<DNSRecord>& ret)
+{
+  if (spoofed.d_type == QType::CNAME) {
+    bool oldWantsRPZ = resolver.getWantsRPZ();
+    resolver.setWantsRPZ(false);
+    vector<DNSRecord> 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
+    resolver.setWantsRPZ(oldWantsRPZ);
+  }
+}
+
+static bool addRecordToPacket(DNSPacketWriter& packetWritewr, const DNSRecord& rec, uint32_t& minTTL, uint32_t ttlCap, const uint16_t maxAnswerSize, bool& seenAuthSOA)
+{
+  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
+    minTTL = min(minTTL, rec.d_ttl);
+  }
+
+  rec.getContent()->toPacket(packetWritewr);
+  if (packetWritewr.size() > static_cast<size_t>(maxAnswerSize)) {
+    packetWritewr.rollback();
+    if (rec.d_place != DNSResourceRecord::ADDITIONAL) {
+      packetWritewr.getHeader()->tc = 1;
+      packetWritewr.truncate();
+    }
+    return false;
+  }
+
+  return true;
+}
+
+/**
+ * A helper class that handles the TCP in-flight bookkeeping on
+ * destruct. This class ise used by startDoResolve() to not forget
+ * that. You can also signal that the TCP connection must be closed
+ * once the in-flight connections drop to zero.
+ **/
+class RunningResolveGuard
+{
+public:
+  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");
+    }
+  }
+  ~RunningResolveGuard()
+  {
+    if (!d_handled && d_dc->d_tcp) {
+      try {
+        finishTCPReply(d_dc, false, true);
+      }
+      catch (const FDMultiplexerException&) {
+      }
+    }
+  }
+  void setHandled()
+  {
+    d_handled = true;
+  }
+  void setDropOnIdle()
+  {
+    if (d_dc->d_tcp) {
+      d_dc->d_tcpConnection->setDropOnIdle();
+    }
+  }
+
+private:
+  std::unique_ptr<DNSComboWriter>& d_dc;
+  bool d_handled{false};
+};
+
+enum class PolicyResult : uint8_t
+{
+  NoAction,
+  HaveAnswer,
+  Drop
+};
+
+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 || !comboWriter->d_tcp) {
+    ++t_Counters.at(rec::PolicyHistogram::policy).at(appliedPolicy.d_kind);
+    ++t_Counters.at(rec::PolicyNameHits::policyName).counts[appliedPolicy.getName()];
+  }
+
+  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) {
+    comboWriter->d_extendedErrorCode = *appliedPolicy.d_zoneData->d_extendedErrorCode;
+    comboWriter->d_extendedErrorExtra = appliedPolicy.d_zoneData->d_extendedErrorExtra;
+  }
+
+  switch (appliedPolicy.d_kind) {
+
+  case DNSFilterEngine::PolicyKind::NoAction:
+    return PolicyResult::NoAction;
+
+  case DNSFilterEngine::PolicyKind::Drop:
+    tcpGuard.setDropOnIdle();
+    ++t_Counters.at(rec::Counter::policyDrops);
+    return PolicyResult::Drop;
+
+  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 (!comboWriter->d_tcp) {
+      ret.clear();
+      appliedPolicy.addSOAtoRPZResult(ret);
+      res = RCode::NoError;
+      packetWriter.getHeader()->tc = 1;
+      return PolicyResult::HaveAnswer;
+    }
+    return PolicyResult::NoAction;
+
+  case DNSFilterEngine::PolicyKind::Custom:
+    res = RCode::NoError;
+    {
+      auto spoofed = appliedPolicy.getCustomRecords(comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype);
+      for (auto& record : spoofed) {
+        ret.push_back(record);
+        try {
+          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 " << 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) {
+            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")));
+          }
+          res = RCode::ServFail;
+          break;
+        }
+        catch (const PolicyHitException& e) {
+          if (g_logCommonErrors) {
+            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;
+    }
+  }
+
+  return PolicyResult::NoAction;
+}
+
+#ifdef NOD_ENABLED
+static bool nodCheckNewDomain(Logr::log_t nodlogger, const DNSName& dname)
+{
+  bool ret = false;
+  // First check the (sub)domain isn't ignored for NOD purposes
+  if (!g_nodDomainWL.check(dname)) {
+    // Now check the NODDB (note this is probabilistic so can have FNs/FPs)
+    if (t_nodDBp && t_nodDBp->isNewDomain(dname)) {
+      if (g_nodLog) {
+        // This should probably log to a dedicated log file
+        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;
+    }
+  }
+  return ret;
+}
+
+static void sendNODLookup(Logr::log_t nodlogger, const DNSName& dname)
+{
+  if (!(g_nodLookupDomain.isRoot())) {
+    // Send a DNS A query to <domain>.g_nodLookupDomain
+    DNSName qname;
+    try {
+      qname = dname + g_nodLookupDomain;
+    }
+    catch (const std::range_error& e) {
+      if (g_logCommonErrors) {
+        nodlogger->v(10)->error(Logr::Error, "DNSName too long", "Unable to send NOD lookup");
+      }
+      ++t_Counters.at(rec::Counter::nodLookupsDroppedOversize);
+      return;
+    }
+    nodlogger->v(10)->info(Logr::Debug, "Sending NOD lookup", "nodqname", Logging::Loggable(qname));
+    vector<DNSRecord> dummy;
+    directResolve(qname, QType::A, QClass::IN, dummy, nullptr, false, nodlogger);
+  }
+}
+
+static bool udrCheckUniqueDNSRecord(Logr::log_t nodlogger, const DNSName& dname, uint16_t qtype, const DNSRecord& record)
+{
+  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 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::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;
+    }
+  }
+  return ret;
+}
+#endif /* NOD_ENABLED */
+
+static bool dns64Candidate(uint16_t requestedType, int rcode, const std::vector<DNSRecord>& records);
+
+int followCNAMERecords(vector<DNSRecord>& ret, const QType qtype, int rcode)
+{
+  vector<DNSRecord> resolved;
+  DNSName target;
+  for (const DNSRecord& record : ret) {
+    if (record.d_type == QType::CNAME) {
+      auto rec = getRR<CNAMERecordContent>(record);
+      if (rec) {
+        target = rec->getTarget();
+        break;
+      }
+    }
+  }
+
+  if (target.empty()) {
+    return rcode;
+  }
+
+  auto log = g_slog->withName("lua")->withValues("method", Logging::Loggable("followCNAMERecords"));
+  rcode = directResolve(target, qtype, QClass::IN, resolved, t_pdl, log);
+
+  if (g_dns64Prefix && qtype == QType::AAAA && dns64Candidate(qtype, rcode, resolved)) {
+    rcode = getFakeAAAARecords(target, *g_dns64Prefix, resolved);
+  }
+
+  for (DNSRecord& record : resolved) {
+    if (record.d_place == DNSResourceRecord::ANSWER) {
+      ret.push_back(std::move(record));
+    }
+  }
+  return rcode;
+}
+
+int getFakeAAAARecords(const DNSName& qname, ComboAddress prefix, vector<DNSRecord>& ret)
+{
+  auto log = g_slog->withName("dns64")->withValues("method", Logging::Loggable("getAAAA"));
+  /* we pass a separate vector of records because we will be resolving the initial qname
+     again, possibly encountering the same CNAME(s), and we don't want to trigger the CNAME
+     loop detection. */
+  vector<DNSRecord> newRecords;
+  int rcode = directResolve(qname, QType::A, QClass::IN, newRecords, t_pdl, log);
+
+  ret.reserve(ret.size() + newRecords.size());
+  for (auto& record : newRecords) {
+    ret.push_back(std::move(record));
+  }
+
+  // Remove double CNAME records
+  std::set<DNSName> seenCNAMEs;
+  ret.erase(std::remove_if(
+              ret.begin(),
+              ret.end(),
+              [&seenCNAMEs](DNSRecord& record) {
+                if (record.d_type == QType::CNAME) {
+                  auto target = getRR<CNAMERecordContent>(record);
+                  if (target == nullptr) {
+                    return false;
+                  }
+                  if (seenCNAMEs.count(target->getTarget()) > 0) {
+                    // We've had this CNAME before, remove it
+                    return true;
+                  }
+                  seenCNAMEs.insert(target->getTarget());
+                }
+                return false;
+              }),
+            ret.end());
+
+  bool seenA = false;
+  for (DNSRecord& 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));
+        record.setContent(std::make_shared<AAAARecordContent>(prefix));
+        record.d_type = QType::AAAA;
+      }
+      seenA = true;
+    }
+  }
+
+  if (seenA) {
+    // We've seen an A in the ANSWER section, so there is no need to keep any
+    // SOA in the AUTHORITY section as this is not a NODATA response.
+    ret.erase(std::remove_if(
+                ret.begin(),
+                ret.end(),
+                [](DNSRecord& record) {
+                  return (record.d_type == QType::SOA && record.d_place == DNSResourceRecord::AUTHORITY);
+                }),
+              ret.end());
+  }
+  t_Counters.at(rec::Counter::dns64prefixanswers)++;
+  return rcode;
+}
+
+int getFakePTRRecords(const DNSName& qname, vector<DNSRecord>& ret)
+{
+  /* qname has a reverse ordered IPv6 address, need to extract the underlying IPv4 address from it
+     and turn it into an IPv4 in-addr.arpa query */
+  ret.clear();
+  vector<string> parts = qname.getRawLabels();
+
+  if (parts.size() < 8) {
+    return -1;
+  }
+
+  string newquery;
+  for (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.";
+
+  auto log = g_slog->withName("dns64")->withValues("method", Logging::Loggable("getPTR"));
+  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;
+}
+
+// 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 SyncRes::answerIsNOData(requestedType, rcode, records);
+  }
+  return rcode != RCode::NXDomain;
+}
+
+bool isAllowNotifyForZone(DNSName qname)
+{
+  if (t_allowNotifyFor->empty()) {
+    return false;
+  }
+
+  notifyset_t::const_iterator ret;
+  do {
+    ret = t_allowNotifyFor->find(qname);
+    if (ret != t_allowNotifyFor->end()) {
+      return true;
+    }
+  } while (qname.chopOff());
+  return false;
+}
+
+#if defined(HAVE_FSTRM) && defined(NOD_ENABLED)
+#include "dnstap.hh"
+#include "fstrm_logger.hh"
+
+static bool isEnabledForNODs(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers)
+{
+  if (fstreamLoggers == nullptr) {
+    return false;
+  }
+  for (auto& logger : *fstreamLoggers) {
+    if (logger->logNODs()) {
+      return true;
+    }
+  }
+  return false;
+}
+static bool isEnabledForUDRs(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers)
+{
+  if (fstreamLoggers == nullptr) {
+    return false;
+  }
+  for (auto& logger : *fstreamLoggers) {
+    if (logger->logUDRs()) {
+      return true;
+    }
+  }
+  return false;
+}
+#endif // HAVE_FSTRM
+
+static void dumpTrace(const string& trace, const timeval& timev)
+{
+  if (trace.empty()) {
+    return;
+  }
+  timeval now{};
+  Utility::gettimeofday(&now);
+  int traceFd = dup(t_tracefd);
+  if (traceFd == -1) {
+    int err = errno;
+    SLOG(g_log << Logger::Error << "Could not dup trace file: " << stringerror(err) << endl,
+         g_slog->withName("trace")->error(Logr::Error, err, "Could not dup trace file"));
+    return;
+  }
+  setNonBlocking(traceFd);
+  auto filep = std::unique_ptr<FILE, decltype(&fclose)>(fdopen(traceFd, "a"), &fclose);
+  if (!filep) {
+    int err = errno;
+    SLOG(g_log << Logger::Error << "Could not write to trace file: " << stringerror(err) << endl,
+         g_slog->withName("trace")->error(Logr::Error, err, "Could not write to trace file"));
+    close(traceFd);
+    return;
+  }
+  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);
+  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
+}
+
+static uint32_t capPacketCacheTTL(const struct dnsheader& hdr, uint32_t ttl, bool seenAuthSOA)
+{
+  if (hdr.rcode == RCode::NXDomain || (hdr.rcode == RCode::NoError && hdr.ancount == 0 && seenAuthSOA)) {
+    ttl = std::min(ttl, SyncRes::s_packetcachenegativettl);
+  }
+  else if ((hdr.rcode != RCode::NoError && hdr.rcode != RCode::NXDomain) || (hdr.ancount == 0 && hdr.nscount == 0)) {
+    ttl = min(ttl, SyncRes::s_packetcacheservfailttl);
+  }
+  else {
+    ttl = std::min(ttl, SyncRes::s_packetcachettl);
+  }
+  return ttl;
+}
+
+static void addPolicyTagsToPBMessageIfNeeded(DNSComboWriter& comboWriter, pdns::ProtoZero::RecMessage& pbMessage)
+{
+  /* 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({comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype});
+    }
+
+    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 = comboWriter->d_variable;
+    bool haveEDNS = false;
+    bool paddingAllowed = false;
+    bool addPaddingToResponse = false;
+#ifdef NOD_ENABLED
+    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(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(comboWriter->d_mdp, &edo)) {
+      haveEDNS = true;
+      if (edo.d_version != 0) {
+        ednsExtRCode = ERCode::BADVERS;
+      }
+
+      if (!comboWriter->d_tcp) {
+        /* rfc6891 6.2.3:
+           "Values lower than 512 MUST be treated as equal to 512."
+        */
+        maxanswersize = min(static_cast<uint16_t>(edo.d_packetsize >= 512 ? edo.d_packetsize : 512), g_udpTruncationThreshold);
+      }
+      ednsOpts = edo.d_options;
+      maxanswersize -= 11; // EDNS header size
+
+      if (!comboWriter->d_responsePaddingDisabled && g_paddingFrom.match(comboWriter->d_remote)) {
+        paddingAllowed = true;
+        if (g_paddingMode == PaddingMode::Always) {
+          addPaddingToResponse = true;
+        }
+      }
+
+      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 (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);
+            variableAnswer = true; // Can't packetcache an answer with NSID
+            maxanswersize -= EDNSOptionCodeSize + EDNSOptionLengthSize + mode_server_id.size();
+          }
+        }
+        else if (paddingAllowed && !addPaddingToResponse && g_paddingMode == PaddingMode::PaddedQueries && option.first == EDNSOptionCode::PADDING) {
+          addPaddingToResponse = true;
+        }
+      }
+    }
+
+    /* 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 && comboWriter->d_tag == 0) {
+      comboWriter->d_tag = g_paddingTag;
+    }
+
+    /* perhaps there was no EDNS or no ECS but by now we looked */
+    comboWriter->d_ecsParsed = true;
+    vector<DNSRecord> ret;
+    vector<uint8_t> packet;
+
+    auto luaconfsLocal = g_luaconfs.getLocal();
+    // Used to tell syncres later on if we should apply NSDNAME and NSIP RPZ triggers for this query
+    bool wantsRPZ(true);
+    RecursorPacketCache::OptPBData pbDataForCache;
+    pdns::ProtoZero::RecMessage pbMessage;
+    if (checkProtobufExport(luaconfsLocal)) {
+      pbMessage.reserve(128, 128); // It's a bit of a guess...
+      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
+    }
+    checkOutgoingProtobufExport(luaconfsLocal); // to pick up changed configs
+#ifdef HAVE_FSTRM
+    checkFrameStreamExport(luaconfsLocal, luaconfsLocal->frameStreamExportConfig, t_frameStreamServersInfo);
+    checkFrameStreamExport(luaconfsLocal, luaconfsLocal->nodFrameStreamExportConfig, t_nodFrameStreamServersInfo);
+#endif
+
+    DNSPacketWriter packetWriter(packet, comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype, comboWriter->d_mdp.d_qclass, comboWriter->d_mdp.d_header.opcode);
+
+    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 = comboWriter->d_ttlCap;
+    bool seenAuthSOA = false;
+
+    resolver.d_eventTrace = std::move(comboWriter->d_eventTrace);
+    resolver.setId(g_multiTasker->getTid());
+
+    bool DNSSECOK = false;
+    if (comboWriter->d_luaContext) {
+      resolver.setLuaEngine(comboWriter->d_luaContext);
+    }
+    if (g_dnssecmode != DNSSECMode::Off) {
+      resolver.setDoDNSSEC(true);
+
+      // Does the requestor want DNSSEC records?
+      if ((edo.d_extFlags & EDNSOpts::DNSSECOK) != 0) {
+        DNSSECOK = true;
+        t_Counters.at(rec::Counter::dnssecQueries)++;
+      }
+      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 (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
+           indicate that it understands the AD bit without also requesting
+           DNSSEC data via the DO bit. */
+        ++t_Counters.at(rec::Counter::dnssecAuthenticDataQueries);
+      }
+    }
+    else {
+      // Ignore the client-set CD flag
+      packetWriter.getHeader()->cd = 0;
+    }
+    resolver.setDNSSECValidationRequested(g_dnssecmode == DNSSECMode::ValidateAll || g_dnssecmode == DNSSECMode::ValidateForLog || ((comboWriter->d_mdp.d_header.ad || DNSSECOK) && g_dnssecmode == DNSSECMode::Process));
+
+    resolver.setInitialRequestId(comboWriter->d_uuid);
+    resolver.setOutgoingProtobufServers(t_outgoingProtobufServers.servers);
+#ifdef HAVE_FSTRM
+    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 && 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 {
+            ++iter->second.stats.suffixMatches;
+          }
+        }
+        // No suffix match node defined, use mapped address
+      }
+      // lookup failing cannot happen as dc->d_source != dc->d_mappedSource
+    }
+    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);
+
+    resolver.setQueryReceivedOverTCP(comboWriter->d_tcp);
+
+    bool tracedQuery = false; // we could consider letting Lua know about this too
+    bool shouldNotValidate = false;
+
+    /* preresolve expects res (dq.rcode) to be set to RCode::NoError by default */
+    int res = RCode::NoError;
+
+    DNSFilterEngine::Policy appliedPolicy;
+    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; // NOLINT(cppcoreguidelines-avoid-goto)
+    }
+
+    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() << " [" << 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 {
+        resolver.d_slog->info(Logr::Info, "Question");
+      }
+    }
+
+    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 (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(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 (comboWriter->d_rcode != boost::none) {
+
+      bool policyOverride = false;
+      /* Unless we already matched on the client IP, time to check the qname.
+         We normally check it in beginResolve() but it will be bypassed since we already have an answer */
+      if (wantsRPZ && appliedPolicy.policyOverridesGettag()) {
+        if (appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) {
+          // Client IP already matched
+        }
+        else {
+          // no match on the client IP, check the qname
+          if (luaconfsLocal->dfe.getQueryPolicy(comboWriter->d_mdp.d_qname, resolver.d_discardedPolicies, appliedPolicy)) {
+            // got a match
+            mergePolicyTags(comboWriter->d_policyTags, appliedPolicy.getTags());
+          }
+        }
+
+        if (appliedPolicy.wasHit()) {
+          policyOverride = true;
+        }
+      }
+
+      if (!policyOverride) {
+        /* No RPZ or gettag overrides it anyway */
+        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; // NOLINT(cppcoreguidelines-avoid-goto)
+      }
+    }
+
+    // if there is a RecursorLua active, and it 'took' the query in preResolve, we don't launch beginResolve
+    if (!comboWriter->d_luaContext || !comboWriter->d_luaContext->preresolve(dnsQuestion, res, resolver.d_eventTrace)) {
+
+      if (!g_dns64PrefixReverse.empty() && dnsQuestion.qtype == QType::PTR && dnsQuestion.qname.isPartOf(g_dns64PrefixReverse)) {
+        res = getFakePTRRecords(dnsQuestion.qname, ret);
+        goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto)
+      }
+
+      resolver.setWantsRPZ(wantsRPZ);
+
+      if (wantsRPZ && appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) {
+
+        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, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
+          if (policyResult == PolicyResult::HaveAnswer) {
+            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; // NOLINT(cppcoreguidelines-avoid-goto)
+          }
+          else if (policyResult == PolicyResult::Drop) {
+            return;
+          }
+        }
+      }
+
+      // Query did not get handled for Client IP or QNAME Policy reasons, now actually go out to find an answer
+      try {
+        resolver.d_appliedPolicy = appliedPolicy;
+        resolver.d_policyTags = std::move(comboWriter->d_policyTags);
+
+        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 = 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(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 " << 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"));
+        }
+        res = RCode::ServFail;
+      }
+      catch (const SendTruncatedAnswerException& e) {
+        ret.clear();
+        resolver.d_appliedPolicy.addSOAtoRPZResult(ret);
+        res = RCode::NoError;
+        packetWriter.getHeader()->tc = 1;
+      }
+      catch (const PolicyHitException& e) {
+        res = -2;
+      }
+      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) {
+        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
+      if (res == -2) { // XXX This block should be macro'd, it is repeated post-resolve.
+        if (appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction) {
+          throw PDNSException("NoAction policy returned while a NSDNAME or NSIP trigger was hit");
+        }
+        auto policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
+        if (policyResult == PolicyResult::HaveAnswer) {
+          goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto)
+        }
+        else if (policyResult == PolicyResult::Drop) {
+          return;
+        }
+      }
+
+      bool luaHookHandled = false;
+      if (comboWriter->d_luaContext) {
+        PolicyResult policyResult = PolicyResult::NoAction;
+        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, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
+          }
+        }
+        else if (res == RCode::NXDomain && comboWriter->d_luaContext->nxdomain(dnsQuestion, res, resolver.d_eventTrace)) {
+          luaHookHandled = true;
+          shouldNotValidate = true;
+          policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
+        }
+        if (policyResult == PolicyResult::HaveAnswer) {
+          goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto)
+        }
+        else if (policyResult == PolicyResult::Drop) {
+          return;
+        }
+      } // dc->d_luaContext
+
+      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 (comboWriter->d_luaContext) {
+        PolicyResult policyResult = PolicyResult::NoAction;
+        if (comboWriter->d_luaContext->d_postresolve_ffi) {
+          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, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
+          }
+        }
+        else if (comboWriter->d_luaContext->postresolve(dnsQuestion, res, resolver.d_eventTrace)) {
+          shouldNotValidate = true;
+          policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
+        }
+        if (policyResult == PolicyResult::HaveAnswer) {
+          goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto)
+        }
+        else if (policyResult == PolicyResult::Drop) {
+          return;
+        }
+      } // dc->d_luaContext
+    }
+    else if (comboWriter->d_luaContext) {
+      // preresolve returned true
+      shouldNotValidate = true;
+      auto policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
+      // haveAnswer case redundant
+      if (policyResult == PolicyResult::Drop) {
+        return;
+      }
+    }
+
+  haveAnswer:;
+    if (tracedQuery || res == -1 || res == RCode::ServFail || packetWriter.getHeader()->rcode == static_cast<unsigned>(RCode::ServFail)) {
+      dumpTrace(resolver.getTrace(), resolver.d_fixednow);
+    }
+
+    if (res == -1) {
+      packetWriter.getHeader()->rcode = RCode::ServFail;
+      // no commit here, because no record
+      ++t_Counters.at(rec::Counter::servFails);
+    }
+    else {
+      packetWriter.getHeader()->rcode = res;
+
+      // Does the validation mode or query demand validation?
+      if (!shouldNotValidate && resolver.isDNSSECValidationRequested()) {
+        try {
+          auto state = resolver.getValidationState();
+
+          string x_marker;
+          std::shared_ptr<Logr::Logger> log;
+          if (resolver.doLog() || vStateIsBogus(state)) {
+            // Only create logging object if needed below, beware if you change the logging logic!
+            log = resolver.d_slog->withValues("vstate", Logging::Loggable(state));
+            auto xdnssec = g_xdnssec.getLocal();
+            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 (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 (comboWriter->d_mdp.d_header.ad || DNSSECOK) {
+              packetWriter.getHeader()->ad = 1;
+            }
+          }
+          else if (state == vState::Insecure) {
+            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"));
+            }
+
+            packetWriter.getHeader()->ad = 0;
+          }
+          else if (vStateIsBogus(state)) {
+            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 (!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"));
+              }
+
+              packetWriter.getHeader()->rcode = RCode::ServFail;
+              goto sendit; // NOLINT(cppcoreguidelines-avoid-goto)
+            }
+            else {
+              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 " << 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) {
+            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")));
+          }
+          goto sendit; // NOLINT(cppcoreguidelines-avoid-goto)
+        }
+      }
+
+      if (!ret.empty()) {
+        pdns::orderAndShuffle(ret, false);
+        if (auto listToSort = luaconfsLocal->sortlist.getOrderCmp(comboWriter->d_source)) {
+          stable_sort(ret.begin(), ret.end(), *listToSort);
+          variableAnswer = true;
+        }
+      }
+
+      bool needCommit = false;
+      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(packetWriter, record, minTTL, comboWriter->d_ttlCap, maxanswersize, seenAuthSOA)) {
+          needCommit = false;
+          break;
+        }
+        needCommit = true;
+
+        bool udr = false;
+#ifdef NOD_ENABLED
+        if (g_udrEnabled) {
+          udr = udrCheckUniqueDNSRecord(nodlogger, comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype, record);
+          if (!hasUDR && udr) {
+            hasUDR = true;
+          }
+        }
+#endif /* NOD ENABLED */
+
+        if (t_protobufServers.servers) {
+          // Max size is 64k, but we're conservative here, as other fields are added after the answers have been added
+          // 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(record, luaconfsLocal->protobufExportConfig.exportTypes, udr);
+          }
+        }
+      }
+      if (needCommit) {
+        packetWriter.commit();
+      }
+#ifdef NOD_ENABLED
+#ifdef HAVE_FSTRM
+      if (hasUDR) {
+        if (isEnabledForUDRs(t_nodFrameStreamServersInfo.servers)) {
+          struct timespec timeSpec
+          {
+          };
+          std::string str;
+          if (g_useKernelTimestamp && comboWriter->d_kernelTimestamp.tv_sec != 0) {
+            TIMEVAL_TO_TIMESPEC(&comboWriter->d_kernelTimestamp, &timeSpec); // NOLINT
+          }
+          else {
+            TIMEVAL_TO_TIMESPEC(&comboWriter->d_now, &timeSpec); // NOLINT
+          }
+          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);
+            }
+          }
+        }
+      }
+#endif // HAVE_FSTRM
+#endif // NOD_ENABLED
+    }
+  sendit:;
+
+    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 (packetWriter.size() < maxanswersize && (maxanswersize - packetWriter.size()) >= (EDNSOptionCodeSize + EDNSOptionLengthSize + ecsPayload.size())) {
+
+        maxanswersize -= EDNSOptionCodeSize + EDNSOptionLengthSize + ecsPayload.size();
+
+        returnedEdnsOptions.emplace_back(EDNSOptionCode::ECS, std::move(ecsPayload));
+      }
+    }
+
+    if (haveEDNS && addPaddingToResponse) {
+      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);
+
+      if (currentSize < (maxSize - 4)) {
+        size_t remaining = maxSize - (currentSize + 4);
+        /* from rfc8647, "4.1.  Recommended Strategy: Block-Length Padding":
+           If a server receives a query that includes the EDNS(0) "Padding"
+           option, it MUST pad the corresponding response (see Section 4 of
+           RFC 7830) and SHOULD pad the corresponding response to a
+           multiple of 468 octets (see below).
+        */
+        const size_t blockSize = 468;
+        size_t modulo = (currentSize + 4) % blockSize;
+        size_t padSize = 0;
+        if (modulo > 0) {
+          padSize = std::min(blockSize - modulo, remaining);
+        }
+        returnedEdnsOptions.emplace_back(EDNSOptionCode::PADDING, makeEDNSPaddingOptString(padSize));
+      }
+    }
+
+    if (haveEDNS) {
+      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 (comboWriter->d_extendedErrorCode) {
+          code = static_cast<EDNSExtendedError::code>(*comboWriter->d_extendedErrorCode);
+          extra = std::move(comboWriter->d_extendedErrorExtra);
+        }
+        else if (resolver.d_extendedError) {
+          code = static_cast<EDNSExtendedError::code>(resolver.d_extendedError->infoCode);
+          extra = std::move(resolver.d_extendedError->extraText);
+        }
+        else {
+          switch (state) {
+          case vState::BogusNoValidDNSKEY:
+            code = EDNSExtendedError::code::DNSKEYMissing;
+            break;
+          case vState::BogusInvalidDenial:
+            code = EDNSExtendedError::code::NSECMissing;
+            break;
+          case vState::BogusUnableToGetDSs:
+            code = EDNSExtendedError::code::DNSSECBogus;
+            break;
+          case vState::BogusUnableToGetDNSKEYs:
+            code = EDNSExtendedError::code::DNSKEYMissing;
+            break;
+          case vState::BogusSelfSignedDS:
+            code = EDNSExtendedError::code::DNSSECBogus;
+            break;
+          case vState::BogusNoRRSIG:
+            code = EDNSExtendedError::code::RRSIGsMissing;
+            break;
+          case vState::BogusNoValidRRSIG:
+            code = EDNSExtendedError::code::DNSSECBogus;
+            break;
+          case vState::BogusMissingNegativeIndication:
+            code = EDNSExtendedError::code::NSECMissing;
+            break;
+          case vState::BogusSignatureNotYetValid:
+            code = EDNSExtendedError::code::SignatureNotYetValid;
+            break;
+          case vState::BogusSignatureExpired:
+            code = EDNSExtendedError::code::SignatureExpired;
+            break;
+          case vState::BogusUnsupportedDNSKEYAlgo:
+            code = EDNSExtendedError::code::UnsupportedDNSKEYAlgorithm;
+            break;
+          case vState::BogusUnsupportedDSDigestType:
+            code = EDNSExtendedError::code::UnsupportedDSDigestType;
+            break;
+          case vState::BogusNoZoneKeyBitSet:
+            code = EDNSExtendedError::code::NoZoneKeyBitSet;
+            break;
+          case vState::BogusRevokedDNSKEY:
+          case vState::BogusInvalidDNSKEYProtocol:
+            code = EDNSExtendedError::code::DNSSECBogus;
+            break;
+          default:
+            throw std::runtime_error("Bogus validation state not handled: " + vStateToString(state));
+          }
+        }
+
+        EDNSExtendedError eee;
+        eee.infoCode = static_cast<uint16_t>(code);
+        eee.extraText = std::move(extra);
+
+        if (packetWriter.size() < maxanswersize && (maxanswersize - packetWriter.size()) >= (EDNSOptionCodeSize + EDNSOptionLengthSize + sizeof(eee.infoCode) + eee.extraText.size())) {
+          returnedEdnsOptions.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(eee));
+        }
+      }
+
+      /* 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."
+      */
+      packetWriter.addOpt(512, ednsExtRCode, DNSSECOK ? EDNSOpts::DNSSECOK : 0, returnedEdnsOptions);
+      packetWriter.commit();
+    }
+
+    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, comboWriter->d_mdp.d_qname)) {
+        nod = true;
+#ifdef HAVE_FSTRM
+        if (isEnabledForNODs(t_nodFrameStreamServersInfo.servers)) {
+          struct timespec timeSpec
+          {
+          };
+          std::string str;
+          if (g_useKernelTimestamp && comboWriter->d_kernelTimestamp.tv_sec != 0) {
+            TIMEVAL_TO_TIMESPEC(&comboWriter->d_kernelTimestamp, &timeSpec); // NOLINT
+          }
+          else {
+            TIMEVAL_TO_TIMESPEC(&comboWriter->d_now, &timeSpec); // NOLINT
+          }
+          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()) {
+              remoteLoggerQueueData(*logger, str);
+            }
+          }
+        }
+#endif // HAVE_FSTRM
+      }
+    }
+#endif /* NOD_ENABLED */
+
+    if (variableAnswer || resolver.wasVariable()) {
+      t_Counters.at(rec::Counter::variableResponses)++;
+    }
+
+    if (t_protobufServers.servers && !(luaconfsLocal->protobufExportConfig.taggedOnly && appliedPolicy.getName().empty() && comboWriter->d_policyTags.empty())) {
+      // Start constructing embedded DNSResponse object
+      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.setAppliedPolicyKind(appliedPolicy.d_kind);
+      }
+      pbMessage.setInBytes(packet.size());
+      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() || !comboWriter->d_policyTags.empty()});
+#ifdef NOD_ENABLED
+      // if (g_udrEnabled) ??
+      pbMessage.clearUDR(pbDataForCache->d_response);
+#endif
+    }
+
+    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,
+                                          dnsQuestion.validationState,
+                                          std::move(pbDataForCache), comboWriter->d_tcp);
+    }
+
+    if (g_regressionTestMode) {
+      t_Counters.updateSnap(g_regressionTestMode);
+    }
+
+    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(comboWriter, packet);
+      finishTCPReply(comboWriter, hadError, true);
+      tcpGuard.setHandled();
+    }
+
+    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() && 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 && comboWriter->d_kernelTimestamp.tv_sec != 0) {
+        pbMessage.setQueryTime(comboWriter->d_kernelTimestamp.tv_sec, comboWriter->d_kernelTimestamp.tv_usec);
+      }
+      else {
+        pbMessage.setQueryTime(comboWriter->d_now.tv_sec, comboWriter->d_now.tv_usec);
+      }
+      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(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(comboWriter->d_source.getPort());
+      }
+      else {
+        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(comboWriter->d_mappedSource.getPort());
+      }
+
+      pbMessage.setTo(comboWriter->d_destination);
+      pbMessage.setId(comboWriter->d_mdp.d_header.id);
+
+      pbMessage.setTime();
+      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& metaValue : dnsQuestion.meta) {
+        pbMessage.setMeta(metaValue.first, metaValue.second.stringVal, metaValue.second.intVal);
+      }
+#ifdef NOD_ENABLED
+      if (g_nodEnabled) {
+        if (nod) {
+          pbMessage.setNewlyObservedDomain(true);
+          pbMessage.addPolicyTag(g_nod_pbtag);
+        }
+        if (hasUDR) {
+          pbMessage.addPolicyTag(g_udr_pbtag);
+        }
+      }
+#endif /* NOD_ENABLED */
+      if (resolver.d_eventTrace.enabled() && (SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_pb) != 0) {
+        pbMessage.addEvents(resolver.d_eventTrace);
+      }
+      if (comboWriter->d_logResponse) {
+        protobufLogResponse(pbMessage);
+      }
+    }
+
+    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(resolver.getNow() - comboWriter->d_now);
+    if (!g_quiet) {
+      if (!g_slogStructured) {
+        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 && 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 {
+        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->incCacheHits();
+      }
+    }
+
+    t_Counters.at(rec::Histogram::answers)(spentUsec);
+    t_Counters.at(rec::Histogram::cumulativeAnswers)(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 >= resolver.d_totUsec) {
+      uint64_t ourtime = spentUsec - resolver.d_totUsec;
+      t_Counters.at(rec::Histogram::ourtime)(ourtime);
+      newLat = static_cast<double>(ourtime); // usec
+      t_Counters.at(rec::DoubleWAvgCounter::avgLatencyOursUsec).addToRollingAvg(newLat, g_latencyStatSize);
+    }
+
+#ifdef NOD_ENABLED
+    if (nod) {
+      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(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(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(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 {
+      std::rethrow_if_nested(e);
+    }
+    catch (const std::exception& ne) {
+      SLOG(g_log << ". Extra info: " << ne.what(),
+           resolver.d_slog->error(Logr::Error, ne.what(), "Nested exception in resolver context", Logging::Loggable("std::exception")));
+    }
+    catch (...) {
+    }
+    if (!g_slogStructured) {
+      g_log << endl;
+    }
+  }
+  catch (...) {
+    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 (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);
+}
+
+void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass,
+                       bool& foundECS, EDNSSubnetOpts* ednssubnet, EDNSOptionViewMap* options)
+{
+  const bool lookForECS = ednssubnet != nullptr;
+  const dnsheader_aligned dnshead(question.data());
+  const dnsheader* dhPointer = dnshead.get();
+  size_t questionLen = question.length();
+  unsigned int consumed = 0;
+  *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(dhPointer->arcount);
+
+  for (uint16_t arpos = 0; arpos < arcount && questionLen > (pos + headerSize) && (lookForECS && !foundECS); arpos++) {
+    if (question.at(pos) != 0) {
+      /* not an OPT, bye. */
+      return;
+    }
+
+    pos += 1;
+    const auto* drh = reinterpret_cast<const dnsrecordheader*>(&question.at(pos)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+    pos += sizeof(dnsrecordheader);
+
+    if (pos >= questionLen) {
+      return;
+    }
+
+    /* OPT root label (1) followed by type (2) */
+    if (lookForECS && ntohs(drh->d_type) == QType::OPT) {
+      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); // 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)) {
+            *ednssubnet = eso;
+            foundECS = true;
+          }
+        }
+      }
+      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); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+        if (res == 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(iter->second.values.at(0).content, iter->second.values.at(0).size, &eso)) {
+              *ednssubnet = eso;
+              foundECS = true;
+            }
+          }
+        }
+      }
+    }
+
+    pos += ntohs(drh->d_clen);
+  }
+}
+
+bool checkForCacheHit(bool qnameParsed, unsigned int tag, const string& data,
+                      DNSName& qname, uint16_t& qtype, uint16_t& qclass,
+                      const struct timeval& now,
+                      string& response, uint32_t& qhash,
+                      RecursorPacketCache::OptPBData& pbData, bool tcp, const ComboAddress& source, const ComboAddress& mappedSource)
+{
+  if (!g_packetCache) {
+    return false;
+  }
+  bool cacheHit = false;
+  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);
+  }
+  else {
+    cacheHit = g_packetCache->getResponsePacket(tag, data, qname, &qtype, &qclass, now.tv_sec, &response, &age, &valState, &qhash, &pbData, tcp);
+  }
+
+  if (cacheHit) {
+    if (vStateIsBogus(valState)) {
+      if (t_bogusremotes) {
+        t_bogusremotes->push_back(source);
+      }
+      if (t_bogusqueryring) {
+        t_bogusqueryring->push_back({qname, qtype});
+      }
+    }
+
+    // This is only to get the proxyMapping suffixMatch stats right i the case of a PC hit
+    if (t_proxyMapping && source != mappedSource) {
+      if (const auto* found = t_proxyMapping->lookup(source)) {
+        if (found->second.suffixMatchNode) {
+          if (found->second.suffixMatchNode->check(qname)) {
+            ++found->second.stats.suffixMatches;
+          }
+        }
+      }
+    }
+
+    t_Counters.at(rec::Counter::packetCacheHits)++;
+    t_Counters.at(rec::Counter::syncresqueries)++; // XXX
+    if (response.length() >= sizeof(struct dnsheader)) {
+      dnsheader_aligned dh_aligned(response.data());
+      ageDNSPacket(response, age, dh_aligned);
+      const auto* dhp = dh_aligned.get();
+      updateResponseStats(dhp->rcode, source, response.length(), nullptr, 0);
+      t_Counters.at(rec::ResponseStats::responseStats).submitResponse(qtype, response.length(), dhp->rcode);
+    }
+
+    // we assume 0 usec
+    t_Counters.at(rec::DoubleWAvgCounter::avgLatencyUsec).addToRollingAvg(0.0, g_latencyStatSize);
+    t_Counters.at(rec::DoubleWAvgCounter::avgLatencyOursUsec).addToRollingAvg(0.0, g_latencyStatSize);
+#if 0
+    // XXX changes behaviour compared to old code!
+    t_Counters.at(rec::Counter::answers)(0);
+    t_Counters.at(rec::Counter::ourtime)(0);
+#endif
+  }
+
+  return cacheHit;
+}
+
+static void* pleaseWipeCaches(const DNSName& canon, bool subtree, uint16_t qtype)
+{
+  auto res = wipeCaches(canon, subtree, qtype);
+  SLOG(g_log << Logger::Info << "Wiped caches for " << canon << ": " << res.record_count << " records; " << res.negative_record_count << " negative records; " << res.packet_count << " packets" << endl,
+       g_slog->withName("runtime")->info(Logr::Info, "Wiped cache", "qname", Logging::Loggable(canon), "records", Logging::Loggable(res.record_count), "negrecords", Logging::Loggable(res.negative_record_count), "packets", Logging::Loggable(res.packet_count)));
+  return nullptr;
+}
+
+void requestWipeCaches(const DNSName& canon)
+{
+  // send a message to the handler thread asking it
+  // to wipe all of the caches
+  ThreadMSG* tmsg = new ThreadMSG(); // NOLINT: pointer owner
+  tmsg->func = [=] { return pleaseWipeCaches(canon, true, 0xffff); };
+  tmsg->wantAnswer = false;
+  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");
+  }
+  // coverity[leaked_storage]
+}
+
+bool expectProxyProtocol(const ComboAddress& from)
+{
+  return g_proxyProtocolACL.match(from);
+}
+
+// fromaddr: the address the query is coming from
+// destaddr: the address the query was received on
+// 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 tval, int fileDesc, std::vector<ProxyProtocolValue>& proxyProtocolValues, RecEventTrace& eventTrace) // NOLINT(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
+{
+  RecThreadInfo::self().incNumberOfDistributedQueries();
+  gettimeofday(&g_now, nullptr);
+  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)++;
+      return nullptr;
+    }
+  }
+
+  ++t_Counters.at(rec::Counter::qcounter);
+
+  if (fromaddr.sin4.sin_family == AF_INET6) {
+    t_Counters.at(rec::Counter::ipv6qcounter)++;
+  }
+
+  string response;
+  const dnsheader_aligned headerdata(question.data());
+  const dnsheader* dnsheader = headerdata.get();
+  unsigned int ctag = 0;
+  uint32_t qhash = 0;
+  bool needECS = false;
+  std::unordered_set<std::string> policyTags;
+  std::map<std::string, RecursorLua4::MetaValue> meta;
+  LuaContext::LuaObject data;
+  string requestorId;
+  string deviceId;
+  string deviceName;
+  string routingTag;
+  bool logQuery = false;
+  bool logResponse = false;
+  boost::uuids::uuid uniqueId{};
+  auto luaconfsLocal = g_luaconfs.getLocal();
+  const auto pbExport = checkProtobufExport(luaconfsLocal);
+  const auto outgoingbExport = checkOutgoingProtobufExport(luaconfsLocal);
+  if (pbExport || outgoingbExport) {
+    if (pbExport) {
+      needECS = true;
+    }
+    uniqueId = getUniqueID();
+  }
+  logQuery = t_protobufServers.servers && luaconfsLocal->protobufExportConfig.logQueries;
+  logResponse = t_protobufServers.servers && luaconfsLocal->protobufExportConfig.logResponses;
+#ifdef HAVE_FSTRM
+  checkFrameStreamExport(luaconfsLocal, luaconfsLocal->frameStreamExportConfig, t_frameStreamServersInfo);
+#endif
+  EDNSSubnetOpts ednssubnet;
+  bool ecsFound = false;
+  bool ecsParsed = false;
+  std::vector<DNSRecord> records;
+  std::string extendedErrorExtra;
+  boost::optional<int> rcode = boost::none;
+  boost::optional<uint16_t> extendedErrorCode{boost::none};
+  uint32_t ttlCap = std::numeric_limits<uint32_t>::max();
+  bool variable = false;
+  bool followCNAMEs = false;
+  bool responsePaddingDisabled = false;
+  DNSName qname;
+  try {
+    uint16_t qtype = 0;
+    uint16_t qclass = 0;
+    bool qnameParsed = false;
+#ifdef MALLOC_TRACE
+    /*
+    static uint64_t last=0;
+    if(!last)
+      g_mtracer->clearAllocators();
+    cout<<g_mtracer->getAllocs()-last<<" "<<g_mtracer->getNumOut()<<" -- BEGIN TRACE"<<endl;
+    last=g_mtracer->getAllocs();
+    cout<<g_mtracer->topAllocatorsString()<<endl;
+    g_mtracer->clearAllocators();
+    */
+#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)) || dnsheader->opcode == static_cast<unsigned>(Opcode::Notify)) {
+      try {
+        EDNSOptionViewMap ednsOptions;
+
+        ecsFound = false;
+
+        getQNameAndSubnet(question, &qname, &qtype, &qclass,
+                          ecsFound, &ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr);
+
+        qnameParsed = true;
+        ecsParsed = true;
+
+        if (t_pdl) {
+          try {
+            if (t_pdl->d_gettag_ffi) {
+              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) {
+              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);
+            }
+          }
+          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_slogudpin->error(Logr::Warning, e.what(), "Error parsing a query packet for tag determination, setting tag=0", "qname", Logging::Loggable(qname), "remote", Logging::Loggable(fromaddr), "exception", Logging::Loggable("std;:exception")));
+            }
+          }
+        }
+      }
+      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_slogudpin->error(Logr::Warning, e.what(), "Error parsing a query packet for tag determination, setting tag=0", "remote", Logging::Loggable(fromaddr), "exception", Logging::Loggable("std;:exception")));
+        }
+      }
+    }
+
+    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, dnsheader->id, question.size(), qname, qtype, qclass, policyTags, requestorId, deviceId, deviceName, meta);
+      }
+    }
+
+    if (ctag == 0 && !responsePaddingDisabled && g_paddingFrom.match(fromaddr)) {
+      ctag = g_paddingTag;
+    }
+
+    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. */
+      eventTrace.add(RecEventTrace::PCacheCheck);
+      bool cacheHit = checkForCacheHit(qnameParsed, ctag, question, qname, qtype, qclass, g_now, response, qhash, pbData, false, source, mappedSource);
+      eventTrace.add(RecEventTrace::PCacheCheck, cacheHit, false);
+      if (cacheHit) {
+        if (!g_quiet) {
+          SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " question answered from packet cache tag=" << ctag << " from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << endl,
+               g_slogudpin->info(Logr::Notice, "Question answered from packet cache", "tag", Logging::Loggable(ctag),
+                                 "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, 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(fileDesc, &msgh);
+        eventTrace.add(RecEventTrace::AnswerSent);
+
+        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) != 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 != 0 && g_logCommonErrors) {
+          SLOG(g_log << Logger::Warning << "Sending UDP reply to client " << source.toStringWithPort()
+                     << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << " failed with: "
+                     << 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
+        {
+        };
+        Utility::gettimeofday(&now, nullptr);
+        uint64_t spentUsec = uSec(now - tval);
+        t_Counters.at(rec::Histogram::cumulativeAnswers)(spentUsec);
+        t_Counters.updateSnap(g_regressionTestMode);
+        return nullptr;
+      }
+    }
+  }
+  catch (const std::exception& e) {
+    if (g_logCommonErrors) {
+      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 nullptr;
+  }
+
+  if (t_pdl) {
+    bool ipf = t_pdl->ipfilter(source, destination, *dnsheader, eventTrace);
+    if (ipf) {
+      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() + ")" : "") << " 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 nullptr;
+    }
+  }
+
+  if (dnsheader->opcode == static_cast<unsigned>(Opcode::Notify)) {
+    if (!isAllowNotifyForZone(qname)) {
+      if (!g_quiet) {
+        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 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)));
+    }
+    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
+    // check dh->opcode, but we need to ensure that the response
+    // to this request does not get put into the packet cache
+    variable = true;
+  }
+
+  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 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) {
+    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 nullptr;
+}
+
+static void handleNewUDPQuestion(int fileDesc, FDMultiplexer::funcparam_t& /* var */) // NOLINT(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
+{
+  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
+  {
+  };
+  cmsgbuf_aligned cbuf;
+  bool firstQuery = true;
+  std::vector<ProxyProtocolValue> proxyProtocolValues;
+  RecEventTrace eventTrace;
+
+  for (size_t queriesCounter = 0; queriesCounter < g_maxUDPQueriesPerRound; queriesCounter++) {
+    bool proxyProto = false;
+    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.data(), data.size(), &fromaddr);
+
+    if (ssize_t len = recvmsg(fileDesc, &msgh, 0); len >= 0) {
+      eventTrace.clear();
+      eventTrace.setEnabled(SyncRes::s_event_trace_enabled != 0);
+      eventTrace.add(RecEventTrace::ReqRecv);
+
+      firstQuery = false;
+
+      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,
+               g_slogudpin->info(Logr::Error, "Ignoring truncated query", "remote", Logging::Loggable(fromaddr)));
+        }
+        return;
+      }
+
+      data.resize(static_cast<size_t>(len));
+
+      if (expectProxyProtocol(fromaddr)) {
+        bool tcp = false;
+        ssize_t used = parseProxyHeader(data, proxyProto, source, destination, tcp, proxyProtocolValues);
+        if (used <= 0) {
+          ++t_Counters.at(rec::Counter::proxyProtocolInvalidCount);
+          if (!g_quiet) {
+            SLOG(g_log << Logger::Error << "Ignoring invalid proxy protocol (" << std::to_string(len) << ", " << std::to_string(used) << ") query from " << fromaddr.toStringWithPort() << endl,
+                 g_slogudpin->info(Logr::Error, "Ignoring invalid proxy protocol query", "length", Logging::Loggable(len),
+                                   "used", Logging::Loggable(used), "remote", Logging::Loggable(fromaddr)));
+          }
+          return;
+        }
+        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",
+                                   "used", Logging::Loggable(used), "remote", Logging::Loggable(fromaddr)));
+          }
+          ++t_Counters.at(rec::Counter::proxyProtocolInvalidCount);
+          return;
+        }
+
+        data.erase(0, used);
+      }
+      else if (len > 512) {
+        /* we only allow UDP packets larger than 512 for those with a proxy protocol header */
+        t_Counters.at(rec::Counter::truncatedDrops)++;
+        if (!g_quiet) {
+          SLOG(g_log << Logger::Error << "Ignoring truncated query from " << fromaddr.toStringWithPort() << endl,
+               g_slogudpin->info(Logr::Error, "Ignoring truncated query", "remote", Logging::Loggable(fromaddr)));
+        }
+        return;
+      }
+
+      if (data.size() < sizeof(dnsheader)) {
+        t_Counters.at(rec::Counter::ignoredCount)++;
+        if (!g_quiet) {
+          SLOG(g_log << Logger::Error << "Ignoring too-short (" << std::to_string(data.size()) << ") query from " << fromaddr.toString() << endl,
+               g_slogudpin->info(Logr::Error, "Ignoring too-short query", "length", Logging::Loggable(data.size()),
+                                 "remote", Logging::Loggable(fromaddr)));
+        }
+        return;
+      }
+
+      if (!proxyProto) {
+        source = fromaddr;
+      }
+      ComboAddress mappedSource = source;
+      if (t_proxyMapping) {
+        if (const auto* iter = t_proxyMapping->lookup(source)) {
+          mappedSource = iter->second.address;
+          ++iter->second.stats.netmaskMatches;
+        }
+      }
+      if (t_remotes) {
+        t_remotes->push_back(fromaddr);
+      }
+
+      if (t_allowFrom && !t_allowFrom->match(&mappedSource)) {
+        if (!g_quiet) {
+          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)));
+        }
+
+        t_Counters.at(rec::Counter::unauthorizedUDP)++;
+        return;
+      }
+
+      BOOST_STATIC_ASSERT(offsetof(sockaddr_in, sin_port) == offsetof(sockaddr_in6, sin6_port));
+      if (fromaddr.sin4.sin_port == 0) { // also works for IPv6
+        if (!g_quiet) {
+          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)));
+        }
+
+        t_Counters.at(rec::Counter::clientParseError)++; // not quite the best place to put it, but needs to go somewhere
+        return;
+      }
+
+      try {
+        const dnsheader_aligned headerdata(data.data());
+        const dnsheader* dnsheader = headerdata.get();
+
+        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 (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(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 (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,
+                 g_slogudpin->info(Logr::Error, "Ignoring empty (qdcount == 0) query on server socket!", "remote", Logging::Loggable(fromaddr)));
+          }
+        }
+        else {
+          if (dnsheader->opcode == static_cast<unsigned>(Opcode::Notify)) {
+            if (!t_allowNotifyFrom || !t_allowNotifyFrom->match(&mappedSource)) {
+              if (!g_quiet) {
+                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)));
+              }
+
+              t_Counters.at(rec::Counter::sourceDisallowedNotify)++;
+              return;
+            }
+          }
+
+          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 != nullptr) {
+              destaddr.sin4.sin_port = loc->sin4.sin_port;
+            }
+          }
+          else {
+            if (loc != nullptr) {
+              destaddr = *loc;
+            }
+            else {
+              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 = destaddr;
+          }
+
+          if (RecThreadInfo::weDistributeQueries()) {
+            std::string localdata = data;
+            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, destaddr, source, destination, mappedSource, tval, fileDesc, proxyProtocolValues, eventTrace);
+          }
+        }
+      }
+      catch (const MOADNSException& mde) {
+        t_Counters.at(rec::Counter::clientParseError)++;
+        if (g_logCommonErrors) {
+          SLOG(g_log << Logger::Error << "Unable to parse packet from remote UDP client " << fromaddr.toString() << ": " << mde.what() << endl,
+               g_slogudpin->error(Logr::Error, mde.what(), "Unable to parse packet from remote UDP client", "remote", Logging::Loggable(fromaddr), "exception", Logging::Loggable("MOADNSException")));
+        }
+      }
+      catch (const std::runtime_error& e) {
+        t_Counters.at(rec::Counter::clientParseError)++;
+        if (g_logCommonErrors) {
+          SLOG(g_log << Logger::Error << "Unable to parse packet from remote UDP client " << fromaddr.toString() << ": " << e.what() << endl,
+               g_slogudpin->error(Logr::Error, e.what(), "Unable to parse packet from remote UDP client", "remote", Logging::Loggable(fromaddr), "exception", Logging::Loggable("std::runtime_error")));
+        }
+      }
+    }
+    else {
+      // cerr<<t_id<<" had error: "<<stringerror()<<endl;
+      if (firstQuery && errno == EAGAIN) {
+        t_Counters.at(rec::Counter::noPacketError)++;
+      }
+
+      break;
+    }
+  }
+  t_Counters.updateSnap(g_regressionTestMode);
+}
+
+void makeUDPServerSockets(deferredAdd_t& deferredAdds, Logr::log_t log)
+{
+  int one = 1;
+  vector<string> localAddresses;
+  stringtok(localAddresses, ::arg()["local-address"], " ,");
+
+  if (localAddresses.empty()) {
+    throw PDNSException("No local address specified");
+  }
+
+  const uint16_t defaultLocalPort = ::arg().asNum("local-port");
+  for (const auto& localAddress : localAddresses) {
+    ComboAddress address{localAddress, defaultLocalPort};
+    const int socketFd = socket(address.sin4.sin_family, SOCK_DGRAM, 0);
+    if (socketFd < 0) {
+      throw PDNSException("Making a UDP server socket for resolver: " + stringerror());
+    }
+    if (!setSocketTimestamps(socketFd)) {
+      SLOG(g_log << Logger::Warning << "Unable to enable timestamp reporting for socket" << endl,
+           log->info(Logr::Warning, "Unable to enable timestamp reporting for socket"));
+    }
+    if (IsAnyAddress(address)) {
+      if (address.sin4.sin_family == AF_INET) {
+        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)) == 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::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")) {
+      Utility::setBindAny(AF_INET6, socketFd);
+    }
+
+    setCloseOnExec(socketFd);
+
+    try {
+      setSocketReceiveBuffer(socketFd, 250000);
+    }
+    catch (const std::exception& e) {
+      SLOG(g_log << Logger::Error << e.what() << endl,
+           log->error(Logr::Error, e.what(), "Exception while setting socket buffer size"));
+    }
+
+    if (g_reusePort) {
+#if defined(SO_REUSEPORT_LB)
+      try {
+        SSetsockopt(socketFd, SOL_SOCKET, SO_REUSEPORT_LB, 1);
+      }
+      catch (const std::exception& e) {
+        throw PDNSException(std::string("SO_REUSEPORT_LB: ") + e.what());
+      }
+#elif defined(SO_REUSEPORT)
+      try {
+        SSetsockopt(socketFd, SOL_SOCKET, SO_REUSEPORT, 1);
+      }
+      catch (const std::exception& e) {
+        throw PDNSException(std::string("SO_REUSEPORT: ") + e.what());
+      }
+#endif
+    }
+
+    try {
+      setSocketIgnorePMTU(socketFd, address.sin4.sin_family);
+    }
+    catch (const std::exception& e) {
+      SLOG(g_log << Logger::Warning << "Failed to set IP_MTU_DISCOVER on UDP server socket: " << e.what() << endl,
+           log->error(Logr::Warning, e.what(), "Failed to set IP_MTU_DISCOVER on UDP server socket"));
+    }
+
+    socklen_t socklen = address.getSocklen();
+    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());
+    }
+
+    setNonBlocking(socketFd);
+
+    deferredAdds.emplace_back(socketFd, handleNewUDPQuestion);
+    g_listenSocketsAddresses[socketFd] = address; // this is written to only from the startup thread, not from the workers
+    SLOG(g_log << Logger::Info << "Listening for UDP queries on " << address.toStringWithPort() << endl,
+         log->info(Logr::Info, "Listening for queries", "proto", Logging::Loggable("UDP"), "address", Logging::Loggable(address)));
+  }
+}
+
+static bool trySendingQueryToWorker(unsigned int target, ThreadMSG* tmsg)
+{
+  auto& targetInfo = RecThreadInfo::info(target);
+  if (!targetInfo.isWorker()) {
+    SLOG(g_log << Logger::Error << "distributeAsyncFunction() tried to assign a query to a non-worker thread" << endl,
+         g_slog->withName("runtime")->info(Logr::Error, "distributeAsyncFunction() tried to assign a query to a non-worker thread"));
+    _exit(1);
+  }
+
+  const auto& tps = targetInfo.getPipes();
+
+  ssize_t written = write(tps.writeQueriesToThread, &tmsg, sizeof(tmsg)); // NOLINT: correct sizeof
+  if (written > 0) {
+    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");
+    }
+  }
+  else {
+    int error = errno;
+    if (error == EAGAIN || error == EWOULDBLOCK) {
+      return false;
+    }
+    delete tmsg; // NOLINT: pointer ownership
+    unixDie("write to thread pipe returned wrong size or error:" + std::to_string(error));
+  }
+
+  return true;
+}
+
+static unsigned int getWorkerLoad(size_t workerIdx)
+{
+  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::numUDPWorkers() != 0); // NOLINT: assert implementation
+  if (g_balancingFactor == 0) {
+    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::numUDPWorkers());
+  for (size_t idx = 0; idx < RecThreadInfo::numUDPWorkers(); idx++) {
+    load[idx] = getWorkerLoad(idx);
+    currentLoad += load[idx];
+  }
+
+  double targetLoad = (currentLoad / RecThreadInfo::numUDPWorkers()) * g_balancingFactor;
+
+  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::numUDPWorkers();
+    } while (load[worker] > targetLoad);
+  }
+
+  return RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + worker;
+}
+
+// This function is only called by the distributor threads, when pdns-distributes-queries is set
+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->withName("runtime")->info(Logr::Error, "distributeAsyncFunction() has been called by a worker")); // tid will be added
+    _exit(1);
+  }
+
+  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(); // NOLINT: pointer ownership
+  tmsg->func = func;
+  tmsg->wantAnswer = false;
+
+  if (!trySendingQueryToWorker(target, tmsg)) {
+    /* if this function failed but did not raise an exception, it means that the pipe
+       was full, let's try another one */
+    unsigned int newTarget = 0;
+    do {
+      newTarget = RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + dns_random(RecThreadInfo::numUDPWorkers());
+    } while (newTarget == target);
+
+    if (!trySendingQueryToWorker(newTarget, tmsg)) {
+      t_Counters.at(rec::Counter::queryPipeFullDrops)++;
+      delete tmsg; // NOLINT: pointer ownership
+    }
+  }
+  // coverity[leaked_storage]
+}
+
+// resend event to everybody chained onto it
+static void doResends(MT_t::waiters_t::iterator& iter, const std::shared_ptr<PacketID>& resend, const PacketBuffer& content)
+{
+  // We close the chain for new entries, since they won't be processed anyway
+  iter->key->closed = true;
+
+  if (iter->key->chain.empty()) {
+    return;
+  }
+  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 fileDesc, FDMultiplexer::funcparam_t& 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(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(fileDesc);
+
+    PacketBuffer empty;
+    auto iter = g_multiTasker->getWaiters().find(pid);
+    if (iter != g_multiTasker->getWaiters().end()) {
+      doResends(iter, pid, empty);
+    }
+    g_multiTasker->sendEvent(pid, &empty); // this denotes error (does retry lookup using other NS)
+    return;
+  }
+
+  if (len < signed_sizeof_sdnsheader) {
+    // We have received a packet that cannot be a valid DNS packet, as it has no complete header
+    // Drop it, but continue to wait for other packets
+    t_Counters.at(rec::Counter::serverParseError)++;
+    if (g_logCommonErrors) {
+      SLOG(g_log << Logger::Error << "Unable to parse too short packet from remote UDP server " << fromaddr.toString() << ": packet smaller than DNS header" << endl,
+           g_slogout->info(Logr::Error, "Unable to parse too short packet from remote UDP server", "from", Logging::Loggable(fromaddr)));
+    }
+    return;
+  }
+
+  // We have at least a full header
+  packet.resize(len);
+  dnsheader dnsheader{};
+  memcpy(&dnsheader, &packet.at(0), sizeof(dnsheader));
+
+  auto pident = std::make_shared<PacketID>();
+  pident->remote = fromaddr;
+  pident->id = dnsheader.id;
+  pident->fd = fileDesc;
+
+  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 (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()), 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
+        // We will do a full scan search later to see if we can match this reply even without a domain
+        pident->domain.clear();
+        pident->type = 0;
+      }
+    }
+    catch (std::exception& e) {
+      // Parse error, continue waiting for other packets
+      t_Counters.at(rec::Counter::serverParseError)++; // won't be fed to lwres.cc, so we have to increment
+      SLOG(g_log << Logger::Warning << "Error in packet from remote nameserver " << fromaddr.toStringWithPort() << ": " << e.what() << endl,
+           g_slogudpin->error(Logr::Warning, e.what(), "Error in packet from remote nameserver", "from", Logging::Loggable(fromaddr)));
+      return;
+    }
+  }
+
+  if (!pident->domain.empty()) {
+    auto iter = g_multiTasker->getWaiters().find(pident);
+    if (iter != g_multiTasker->getWaiters().end()) {
+      doResends(iter, pident, packet);
+    }
+  }
+
+retryWithName:
+
+  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 (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. */
+        d_waiter.key->nearMisses++;
+      }
+
+      // be a bit paranoid here since we're weakening our matching
+      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 = 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 << ", " << 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(g_multiTasker->getWaiters().size())));
+    }
+  }
+  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(fileDesc);
+  }
+}
deleted file mode 120000 (symlink)
index f533308613c1ad81c2d79abb70a845fcd8ae4228..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../pubsuffix.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..cc5f7c4272ca529677bc23c92d000f5ce24cb6c0
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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 <vector>
+
+extern std::vector<std::vector<std::string>> g_pubs;
+
+/* initialize the g_pubs variable with the public suffix list,
+   using the file passed in parameter if any, or the built-in
+   list otherwise.
+*/
+void initPublicSuffixList(const std::string& file);
index e9a25bfc16ec9a1b51d72837c1d6ac98dcc94f88..3d413d66c9a307400a981c21dde700581b9c214c 100644 (file)
@@ -25,6 +25,7 @@
 
 #include "dnsname.hh"
 #include "logger.hh"
+#include "logging.hh"
 #include "misc.hh"
 #include "pubsuffix.hh"
 
@@ -66,11 +67,13 @@ void initPublicSuffixList(const std::string& file)
         }
       }
 
-      g_log << Logger::Info << "Loaded the Public Suffix List from '" << file << "'" << endl;
+      SLOG(g_log << Logger::Info << "Loaded the Public Suffix List from '" << file << "'" << endl,
+           g_slog->withName("runtime")->info(Logr::Info, "Loaded the Public Suffix List", "file", Logging::Loggable(file)));
       loaded = true;
     }
     catch (const std::exception& e) {
-      g_log << Logger::Warning << "Error while loading the Public Suffix List from '" << file << "', falling back to the built-in list: " << e.what() << endl;
+      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(), "Error while loading the Public Suffix List", "file", Logging::Loggable(file)));
     }
   }
 
deleted file mode 120000 (symlink)
index 65e27892694a14f0afb7e111019644c256c1aff7..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../rec-carbon.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..d0f77742cc755fde6e0864a967817746061cec91
--- /dev/null
@@ -0,0 +1,93 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "mtasker.hh"
+#include "syncres.hh"
+#include "rec_channel.hh"
+#include "iputils.hh"
+#include "logger.hh"
+#include "logging.hh"
+#include "arguments.hh"
+#include "lock.hh"
+
+GlobalStateHolder<CarbonConfig> g_carbonConfig;
+
+void doCarbonDump(void*)
+{
+  auto log = g_slog->withName("carbon");
+  try {
+    static thread_local auto configHolder = g_carbonConfig.getLocal();
+
+    auto config = *configHolder;
+    if (config.servers.empty()) {
+      return;
+    }
+
+    if (config.namespace_name.empty()) {
+      config.namespace_name = "pdns";
+    }
+
+    if (config.hostname.empty()) {
+      try {
+        config.hostname = getCarbonHostName();
+      }
+      catch (const std::exception& e) {
+        throw std::runtime_error(std::string("The 'carbon-ourname' setting has not been set and we are unable to determine the system's hostname: ") + e.what());
+      }
+    }
+    if (config.instance_name.empty()) {
+      config.instance_name = "recursor";
+    }
+
+    PacketBuffer msg;
+    for (const auto& carbonServer : config.servers) {
+      ComboAddress remote(carbonServer, 2003);
+      Socket s(remote.sin4.sin_family, SOCK_STREAM);
+      s.setNonBlocking();
+      std::shared_ptr<TLSCtx> tlsCtx{nullptr};
+      const struct timeval timeout
+      {
+        g_networkTimeoutMsec / 1000, static_cast<suseconds_t>(g_networkTimeoutMsec) % 1000 * 1000
+      };
+      auto handler = std::make_shared<TCPIOHandler>("", false, s.releaseHandle(), timeout, tlsCtx);
+      handler->tryConnect(SyncRes::s_tcp_fast_open_connect, remote); // we do the connect so the first attempt happens while we gather stats
+
+      if (msg.empty()) {
+        auto all = getAllStatsMap(StatComponent::Carbon);
+
+        ostringstream str;
+        time_t now = time(0);
+
+        for (const auto& val : all) {
+          str << config.namespace_name << '.' << config.hostname << '.' << config.instance_name << '.' << val.first << ' ' << val.second.d_value << ' ' << now << "\r\n";
+        }
+        const string& x = str.str();
+        msg.insert(msg.end(), x.cbegin(), x.cend());
+      }
+
+      auto ret = asendtcp(msg, handler); // this will actually do the right thing waiting on the connect
+      if (ret == LWResult::Result::Timeout) {
+        SLOG(g_log << Logger::Warning << "Timeout connecting/writing carbon data to " << remote.toStringWithPort() << endl,
+             log->info(Logr::Warning, "Timeout connecting/writing carbon data", "address", Logging::Loggable(remote)));
+      }
+      else if (ret != LWResult::Result::Success) {
+        int err = errno;
+        SLOG(g_log << Logger::Warning << "Error writing carbon data to " << remote.toStringWithPort() << ": " << stringerror(err) << endl,
+             log->error(Logr::Warning, err, "Error writing carbon data", "address", Logging::Loggable(remote)));
+      }
+      handler->close();
+    }
+  }
+  catch (const PDNSException& e) {
+    SLOG(g_log << Logger::Error << "Error in carbon thread: " << e.reason << endl,
+         log->error(Logr::Error, e.reason, "Error in carbon thread", "exception", Logging::Loggable("PDNSException")));
+  }
+  catch (const std::exception& e) {
+    SLOG(g_log << Logger::Error << "Error in carbon thread: " << e.what() << endl,
+         log->error(Logr::Error, e.what(), "Error in carbon thread", "exception", Logging::Loggable("std::exception")));
+  }
+  catch (...) {
+    SLOG(g_log << Logger::Error << "Unknown error in carbon thread" << endl,
+         log->info(Logr::Error, "Error in carbon thread"));
+  }
+}
deleted file mode 120000 (symlink)
index d5125d7a0d58d2889ccf299f442b3c9579d4424c..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../rec-lua-conf.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..e862da35f90443e4892c60f86c0562f594eb34f2
--- /dev/null
@@ -0,0 +1,921 @@
+#include "config.h"
+#include "ext/luawrapper/include/LuaContext.hpp"
+
+#include <fstream>
+#include <thread>
+#include "namespaces.hh"
+#include "logger.hh"
+#include "lua-base4.hh"
+#include "rec-lua-conf.hh"
+#include "sortlist.hh"
+#include "filterpo.hh"
+#include "syncres.hh"
+#include "rpzloader.hh"
+#include "base64.hh"
+#include "remote_logger.hh"
+#include "validate.hh"
+#include "validate-recursor.hh"
+#include "root-dnssec.hh"
+
+GlobalStateHolder<LuaConfigItems> g_luaconfs;
+
+/* SO HOW DOES THIS WORK! AND PLEASE PAY ATTENTION!
+   This function can be called at any time. It is expected to overwrite all the contents
+   of LuaConfigItems, which is held in a GlobalStateHolder for RCU properties.
+
+   This function can be called again at a later date, so you must make sure that anything you
+   allow to be configured from here lives in g_luaconfs AND NOWHERE ELSE.
+
+   If someone loads an empty Lua file, the default LuaConfigItems struct MUST MAKE SENSE.
+
+   To make this easy on you, here is a LuaConfigItems constructor where you
+   can set sane defaults:
+*/
+
+LuaConfigItems::LuaConfigItems()
+{
+  DNSName root("."); // don't use g_rootdnsname here, it might not exist yet
+  for (const auto& dsRecord : rootDSs) {
+    auto ds = std::dynamic_pointer_cast<DSRecordContent>(DSRecordContent::make(dsRecord));
+    dsAnchors[root].insert(*ds);
+  }
+}
+
+/* DID YOU READ THE STORY ABOVE? */
+
+bool operator==(const ProtobufExportConfig& configA, const ProtobufExportConfig& configB)
+{
+  // clang-format off
+  return configA.exportTypes          == configB.exportTypes       &&
+         configA.servers              == configB.servers           &&
+         configA.maxQueuedEntries     == configB.maxQueuedEntries  &&
+         configA.timeout              == configB.timeout           &&
+         configA.reconnectWaitTime    == configB.reconnectWaitTime &&
+         configA.asyncConnect         == configB.asyncConnect      &&
+         configA.enabled              == configB.enabled           &&
+         configA.logQueries           == configB.logQueries        &&
+         configA.logResponses         == configB.logResponses      &&
+         configA.taggedOnly           == configB.taggedOnly        &&
+         configA.logMappedFrom        == configB.logMappedFrom;
+  // clang-format on
+}
+
+bool operator!=(const ProtobufExportConfig& configA, const ProtobufExportConfig& configB)
+{
+  return !(configA == configB);
+}
+
+bool operator==(const FrameStreamExportConfig& configA, const FrameStreamExportConfig& configB)
+{
+  // clang-format off
+  return configA.enabled              == configB.enabled              &&
+         configA.logQueries           == configB.logQueries           &&
+         configA.logResponses         == configB.logResponses         &&
+         configA.logNODs              == configB.logNODs              &&
+         configA.logUDRs              == configB.logUDRs              &&
+         configA.bufferHint           == configB.bufferHint           &&
+         configA.flushTimeout         == configB.flushTimeout         &&
+         configA.inputQueueSize       == configB.inputQueueSize       &&
+         configA.outputQueueSize      == configB.outputQueueSize      &&
+         configA.queueNotifyThreshold == configB.queueNotifyThreshold &&
+         configA.reopenInterval       == configB.reopenInterval       &&
+         configA.servers              == configB.servers;
+  // clang-format on
+}
+
+bool operator!=(const FrameStreamExportConfig& configA, const FrameStreamExportConfig& configB)
+{
+  return !(configA == configB);
+}
+
+template <typename C>
+typename C::value_type::second_type constGet(const C& c, const std::string& name)
+{
+  auto iter = c.find(name);
+  if (iter == c.end())
+    return 0;
+  return iter->second;
+}
+
+typedef std::unordered_map<std::string, boost::variant<bool, uint32_t, std::string, std::vector<std::pair<int, std::string>>>> rpzOptions_t;
+
+static void parseRPZParameters(rpzOptions_t& have, std::shared_ptr<DNSFilterEngine::Zone>& zone, std::string& polName, boost::optional<DNSFilterEngine::Policy>& defpol, bool& defpolOverrideLocal, uint32_t& maxTTL)
+{
+  if (have.count("policyName") != 0) {
+    polName = boost::get<std::string>(have["policyName"]);
+  }
+  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::make(QType::CNAME, QClass::IN,
+                                                        boost::get<string>(have["defcontent"])));
+
+      if (have.count("defttl") != 0) {
+        defpol->d_ttl = static_cast<int32_t>(boost::get<uint32_t>(have["defttl"]));
+      }
+      else {
+        defpol->d_ttl = -1; // get it from the zone
+      }
+    }
+
+    if (have.count("defpolOverrideLocalData") != 0) {
+      defpolOverrideLocal = boost::get<bool>(have["defpolOverrideLocalData"]);
+    }
+  }
+  if (have.count("maxTTL") != 0) {
+    maxTTL = boost::get<uint32_t>(have["maxTTL"]);
+  }
+  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") != 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") != 0) {
+    zone->setPolicyOverridesGettag(boost::get<bool>(have["overridesGettag"]));
+  }
+  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") != 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;
+
+static void parseProtobufOptions(boost::optional<protobufOptions_t> vars, ProtobufExportConfig& config)
+{
+  if (!vars) {
+    return;
+  }
+
+  if (vars->count("timeout")) {
+    config.timeout = boost::get<uint64_t>((*vars)["timeout"]);
+  }
+
+  if (vars->count("maxQueuedEntries")) {
+    config.maxQueuedEntries = boost::get<uint64_t>((*vars)["maxQueuedEntries"]);
+  }
+
+  if (vars->count("reconnectWaitTime")) {
+    config.reconnectWaitTime = boost::get<uint64_t>((*vars)["reconnectWaitTime"]);
+  }
+
+  if (vars->count("asyncConnect")) {
+    config.asyncConnect = boost::get<bool>((*vars)["asyncConnect"]);
+  }
+
+  if (vars->count("taggedOnly")) {
+    config.taggedOnly = boost::get<bool>((*vars)["taggedOnly"]);
+  }
+
+  if (vars->count("logQueries")) {
+    config.logQueries = boost::get<bool>((*vars)["logQueries"]);
+  }
+
+  if (vars->count("logResponses")) {
+    config.logResponses = boost::get<bool>((*vars)["logResponses"]);
+  }
+
+  if (vars->count("logMappedFrom")) {
+    config.logMappedFrom = boost::get<bool>((*vars)["logMappedFrom"]);
+  }
+
+  if (vars->count("exportTypes")) {
+    config.exportTypes.clear();
+
+    auto types = boost::get<std::vector<std::pair<int, std::string>>>((*vars)["exportTypes"]);
+    for (const auto& pair : types) {
+      const auto& type = pair.second;
+
+      QType qtype;
+      try {
+        qtype = std::stoul(type);
+      }
+      catch (const std::exception& ex) {
+        qtype = QType::chartocode(type.c_str());
+        if (qtype == 0) {
+          throw std::runtime_error("Unknown QType '" + type + "' in protobuf's export types");
+        }
+      }
+      config.exportTypes.insert(qtype);
+    }
+  }
+}
+
+#ifdef HAVE_FSTRM
+typedef std::unordered_map<std::string, boost::variant<bool, uint64_t, std::string, std::vector<std::pair<int, std::string>>>> frameStreamOptions_t;
+
+static void parseFrameStreamOptions(boost::optional<frameStreamOptions_t> vars, FrameStreamExportConfig& config)
+{
+  if (!vars) {
+    return;
+  }
+
+  if (vars->count("logQueries")) {
+    config.logQueries = boost::get<bool>((*vars)["logQueries"]);
+  }
+  if (vars->count("logResponses")) {
+    config.logResponses = boost::get<bool>((*vars)["logResponses"]);
+  }
+  if (vars->count("logNODs")) {
+    config.logNODs = boost::get<bool>((*vars)["logNODs"]);
+  }
+  if (vars->count("logUDRs")) {
+    config.logUDRs = boost::get<bool>((*vars)["logUDRs"]);
+  }
+
+  if (vars->count("bufferHint")) {
+    config.bufferHint = boost::get<uint64_t>((*vars)["bufferHint"]);
+  }
+  if (vars->count("flushTimeout")) {
+    config.flushTimeout = boost::get<uint64_t>((*vars)["flushTimeout"]);
+  }
+  if (vars->count("inputQueueSize")) {
+    config.inputQueueSize = boost::get<uint64_t>((*vars)["inputQueueSize"]);
+  }
+  if (vars->count("outputQueueSize")) {
+    config.outputQueueSize = boost::get<uint64_t>((*vars)["outputQueueSize"]);
+  }
+  if (vars->count("queueNotifyThreshold")) {
+    config.queueNotifyThreshold = boost::get<uint64_t>((*vars)["queueNotifyThreshold"]);
+  }
+  if (vars->count("reopenInterval")) {
+    config.reopenInterval = boost::get<uint64_t>((*vars)["reopenInterval"]);
+  }
+}
+#endif /* HAVE_FSTRM */
+
+static void rpzPrimary(LuaConfigItems& lci, luaConfigDelayedThreads& delayedThreads, const boost::variant<string, std::vector<std::pair<int, string>>>& primaries_, const string& zoneName, boost::optional<rpzOptions_t> options)
+{
+  boost::optional<DNSFilterEngine::Policy> defpol;
+  bool defpolOverrideLocal = true;
+  std::shared_ptr<DNSFilterEngine::Zone> zone = std::make_shared<DNSFilterEngine::Zone>();
+  TSIGTriplet tt;
+  uint32_t refresh = 0;
+  size_t maxReceivedXFRMBytes = 0;
+  uint16_t axfrTimeout = 20;
+  uint32_t maxTTL = std::numeric_limits<uint32_t>::max();
+  ComboAddress localAddress;
+  std::vector<ComboAddress> primaries;
+  if (primaries_.type() == typeid(string)) {
+    primaries.push_back(ComboAddress(boost::get<std::string>(primaries_), 53));
+  }
+  else {
+    for (const auto& primary : boost::get<std::vector<std::pair<int, std::string>>>(primaries_)) {
+      primaries.push_back(ComboAddress(primary.second, 53));
+    }
+  }
+
+  size_t zoneIdx;
+  std::string dumpFile;
+  std::shared_ptr<const SOARecordContent> sr = nullptr;
+
+  try {
+    std::string seedFile;
+    std::string polName(zoneName);
+
+    if (options) {
+      auto& have = *options;
+      parseRPZParameters(have, zone, polName, defpol, defpolOverrideLocal, maxTTL);
+
+      if (have.count("tsigname")) {
+        tt.name = DNSName(toLower(boost::get<string>(have["tsigname"])));
+        tt.algo = DNSName(toLower(boost::get<string>(have["tsigalgo"])));
+        if (B64Decode(boost::get<string>(have["tsigsecret"]), tt.secret))
+          throw std::runtime_error("TSIG secret is not valid Base-64 encoded");
+      }
+
+      if (have.count("refresh")) {
+        refresh = boost::get<uint32_t>(have["refresh"]);
+        if (refresh == 0) {
+          SLOG(g_log << Logger::Warning << "rpzPrimary refresh value of 0 ignored" << endl,
+               lci.d_slog->info(Logr::Warning, "rpzPrimary refresh value of 0 ignored"));
+        }
+      }
+
+      if (have.count("maxReceivedMBytes")) {
+        maxReceivedXFRMBytes = static_cast<size_t>(boost::get<uint32_t>(have["maxReceivedMBytes"]));
+      }
+
+      if (have.count("localAddress")) {
+        localAddress = ComboAddress(boost::get<string>(have["localAddress"]));
+      }
+
+      if (have.count("axfrTimeout")) {
+        axfrTimeout = static_cast<uint16_t>(boost::get<uint32_t>(have["axfrTimeout"]));
+      }
+
+      if (have.count("seedFile")) {
+        seedFile = boost::get<std::string>(have["seedFile"]);
+      }
+
+      if (have.count("dumpFile")) {
+        dumpFile = boost::get<std::string>(have["dumpFile"]);
+      }
+    }
+
+    if (localAddress != ComboAddress()) {
+      // We were passed a localAddress, check if its AF matches the primaries'
+      for (const auto& primary : primaries) {
+        if (localAddress.sin4.sin_family != primary.sin4.sin_family) {
+          throw PDNSException("Primary address(" + primary.toString() + ") is not of the same Address Family as the local address (" + localAddress.toString() + ").");
+        }
+      }
+    }
+
+    DNSName domain(zoneName);
+    zone->setDomain(domain);
+    zone->setName(polName);
+    zoneIdx = lci.dfe.addZone(zone);
+
+    auto log = lci.d_slog->withValues("seedfile", Logging::Loggable(seedFile), "zone", Logging::Loggable(zoneName));
+    if (!seedFile.empty()) {
+      SLOG(g_log << Logger::Info << "Pre-loading RPZ zone " << zoneName << " from seed file '" << seedFile << "'" << endl,
+           log->info(Logr::Info, "Pre-loading RPZ zone from seed file"));
+      try {
+        sr = loadRPZFromFile(seedFile, zone, defpol, defpolOverrideLocal, maxTTL);
+
+        if (zone->getDomain() != domain) {
+          throw PDNSException("The RPZ zone " + zoneName + " loaded from the seed file (" + zone->getDomain().toString() + ") does not match the one passed in parameter (" + domain.toString() + ")");
+        }
+
+        if (sr == nullptr) {
+          throw PDNSException("The RPZ zone " + zoneName + " loaded from the seed file (" + zone->getDomain().toString() + ") has no SOA record");
+        }
+      }
+      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-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-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::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::Error, e.reason, "Exception configuring 'rpzPrimary'", Logging::Loggable("PDNSException")));
+  }
+
+  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
+class RecLuaConfigContext : public BaseLua4
+{
+public:
+  RecLuaConfigContext()
+  {
+    prepareContext();
+  }
+  void postPrepareContext() override
+  {
+    // clang-format off
+    d_pd.push_back({"AdditionalMode", in_t{
+          {"Ignore", static_cast<int>(AdditionalMode::Ignore)},
+          {"CacheOnly", static_cast<int>(AdditionalMode::CacheOnly)},
+          {"CacheOnlyRequireAuth", static_cast<int>(AdditionalMode::CacheOnlyRequireAuth)},
+          {"ResolveImmediately", static_cast<int>(AdditionalMode::ResolveImmediately)},
+          {"ResolveDeferred", static_cast<int>(AdditionalMode::ResolveDeferred)}
+        }});
+  }
+  void postLoad() override
+  {
+  }
+  LuaContext* operator->()
+  {
+    return d_lw.get();
+  }
+};
+
+void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& delayedThreads, ProxyMapping& proxyMapping)
+{
+  LuaConfigItems lci;
+  lci.d_slog = g_slog->withName("luaconfig");
+
+  RecLuaConfigContext Lua;
+
+  if (fname.empty())
+    return;
+  ifstream ifs(fname);
+  if (!ifs)
+    throw PDNSException("Cannot open file '" + fname + "': " + stringerror());
+
+  auto luaconfsLocal = g_luaconfs.getLocal();
+  lci.generation = luaconfsLocal->generation + 1;
+
+  Lua->writeFunction("clearSortlist", [&lci]() { lci.sortlist.clear(); });
+
+  /* we can get: "1.2.3.4"
+                 {"1.2.3.4", "4.5.6.7"}
+                 {"1.2.3.4", {"4.5.6.7", "8.9.10.11"}}
+  */
+
+  map<string, DNSFilterEngine::PolicyKind> pmap{
+    {"NoAction", DNSFilterEngine::PolicyKind::NoAction},
+    {"Drop", DNSFilterEngine::PolicyKind::Drop},
+    {"NXDOMAIN", DNSFilterEngine::PolicyKind::NXDOMAIN},
+    {"NODATA", DNSFilterEngine::PolicyKind::NODATA},
+    {"Truncate", DNSFilterEngine::PolicyKind::Truncate},
+    {"Custom", DNSFilterEngine::PolicyKind::Custom}};
+  Lua->writeVariable("Policy", pmap);
+
+  Lua->writeFunction("rpzFile", [&lci](const string& filename, boost::optional<rpzOptions_t> options) {
+    auto log = lci.d_slog->withValues("file", Logging::Loggable(filename));
+    try {
+      boost::optional<DNSFilterEngine::Policy> defpol;
+      bool defpolOverrideLocal = true;
+      std::string polName("rpzFile");
+      std::shared_ptr<DNSFilterEngine::Zone> zone = std::make_shared<DNSFilterEngine::Zone>();
+      uint32_t maxTTL = std::numeric_limits<uint32_t>::max();
+      if (options) {
+        auto& have = *options;
+        parseRPZParameters(have, zone, polName, defpol, defpolOverrideLocal, maxTTL);
+      }
+      SLOG(g_log << Logger::Warning << "Loading RPZ from file '" << filename << "'" << endl,
+           log->info(Logr::Info, "Loading RPZ from file"));
+      zone->setName(polName);
+      loadRPZFromFile(filename, zone, defpol, defpolOverrideLocal, maxTTL);
+      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"));
+    }
+    catch (const std::exception& e) {
+      SLOG(g_log << Logger::Error << "Unable to load RPZ zone from '" << filename << "': " << e.what() << endl,
+           log->error(Logr::Error, e.what(), "Exception while loading RPZ zone from file"));
+    }
+  });
+
+  Lua->writeFunction("rpzMaster", [&lci, &delayedThreads](const boost::variant<string, std::vector<std::pair<int, string>>>& primaries_, const string& zoneName, boost::optional<rpzOptions_t> options) {
+    SLOG(g_log << Logger::Warning << "'rpzMaster' is deprecated and will be removed in a future release, use 'rpzPrimary' instead" << endl,
+         lci.d_slog->info(Logr::Warning, "'rpzMaster' is deprecated and will be removed in a future release, use 'rpzPrimary' instead"));
+    rpzPrimary(lci, delayedThreads, primaries_, zoneName, options);
+  });
+  Lua->writeFunction("rpzPrimary", [&lci, &delayedThreads](const boost::variant<string, std::vector<std::pair<int, string>>>& primaries_, const string& zoneName, boost::optional<rpzOptions_t> options) {
+    rpzPrimary(lci, delayedThreads, primaries_, zoneName, options);
+  });
+
+  typedef std::unordered_map<std::string, boost::variant<uint32_t, std::string>> zoneToCacheOptions_t;
+
+  Lua->writeFunction("zoneToCache", [&lci](const string& zoneName, const string& method, const boost::variant<string, std::vector<std::pair<int, string>>>& srcs, boost::optional<zoneToCacheOptions_t> options) {
+    try {
+      RecZoneToCache::Config conf;
+      DNSName validZoneName(zoneName);
+      conf.d_zone = zoneName;
+      const set<string> methods = {"axfr", "url", "file"};
+      if (methods.count(method) == 0) {
+        throw std::runtime_error("unknwon method '" + method + "'");
+      }
+      conf.d_method = method;
+      if (srcs.type() == typeid(std::string)) {
+        conf.d_sources.push_back(boost::get<std::string>(srcs));
+      }
+      else {
+        for (const auto& src : boost::get<std::vector<std::pair<int, std::string>>>(srcs)) {
+          conf.d_sources.push_back(src.second);
+        }
+      }
+      if (conf.d_sources.size() == 0) {
+        throw std::runtime_error("at least one source required");
+      }
+      if (options) {
+        auto& have = *options;
+        if (have.count("timeout")) {
+          conf.d_timeout = boost::get<uint32_t>(have.at("timeout"));
+        }
+        if (have.count("tsigname")) {
+          conf.d_tt.name = DNSName(toLower(boost::get<string>(have.at("tsigname"))));
+          conf.d_tt.algo = DNSName(toLower(boost::get<string>(have.at("tsigalgo"))));
+          if (B64Decode(boost::get<string>(have.at("tsigsecret")), conf.d_tt.secret)) {
+            throw std::runtime_error("TSIG secret is not valid Base-64 encoded");
+          }
+        }
+        if (have.count("maxReceivedMBytes")) {
+          conf.d_maxReceivedBytes = static_cast<size_t>(boost::get<uint32_t>(have.at("maxReceivedMBytes")));
+          conf.d_maxReceivedBytes *= 1024 * 1024;
+        }
+        if (have.count("localAddress")) {
+          conf.d_local = ComboAddress(boost::get<string>(have.at("localAddress")));
+        }
+        if (have.count("refreshPeriod")) {
+          conf.d_refreshPeriod = boost::get<uint32_t>(have.at("refreshPeriod"));
+        }
+        if (have.count("retryOnErrorPeriod")) {
+          conf.d_retryOnError = boost::get<uint32_t>(have.at("retryOnErrorPeriod"));
+        }
+        const map<string, pdns::ZoneMD::Config> nameToVal = {
+          {"ignore", pdns::ZoneMD::Config::Ignore},
+          {"validate", pdns::ZoneMD::Config::Validate},
+          {"require", pdns::ZoneMD::Config::Require},
+        };
+        if (have.count("zonemd")) {
+          string zonemdValidation = boost::get<string>(have.at("zonemd"));
+          auto it = nameToVal.find(zonemdValidation);
+          if (it == nameToVal.end()) {
+            throw std::runtime_error(zonemdValidation + " is not a valid value for `zonemd`");
+          }
+          else {
+            conf.d_zonemd = it->second;
+          }
+        }
+        if (have.count("dnssec")) {
+          string dnssec = boost::get<string>(have.at("dnssec"));
+          auto it = nameToVal.find(dnssec);
+          if (it == nameToVal.end()) {
+            throw std::runtime_error(dnssec + " is not a valid value for `dnssec`");
+          }
+          else {
+            conf.d_dnssec = it->second;
+          }
+        }
+      }
+
+      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,
+           lci.d_slog->error(Logr::Error, e.what(), "Problem configuring zoneToCache", "zone", Logging::Loggable(zoneName),
+                             "exception", Logging::Loggable("std::exception")));
+    }
+  });
+
+  typedef vector<pair<int, boost::variant<string, vector<pair<int, string>>>>> argvec_t;
+  Lua->writeFunction("addSortList",
+                     [&lci](const std::string& formask_,
+                            const boost::variant<string, argvec_t>& masks,
+                            boost::optional<int> order_) {
+                       try {
+                         Netmask formask(formask_);
+                         int order = order_ ? (*order_) : lci.sortlist.getMaxOrder(formask) + 1;
+                         if (auto str = boost::get<string>(&masks))
+                           lci.sortlist.addEntry(formask, Netmask(*str), order);
+                         else {
+
+                           auto vec = boost::get<argvec_t>(&masks);
+                           for (const auto& e : *vec) {
+                             if (auto s = boost::get<string>(&e.second)) {
+                               lci.sortlist.addEntry(formask, Netmask(*s), order);
+                             }
+                             else {
+                               const auto& v = boost::get<vector<pair<int, string>>>(e.second);
+                               for (const auto& entry : v)
+                                 lci.sortlist.addEntry(formask, Netmask(entry.second), order);
+                             }
+                             ++order;
+                           }
+                         }
+                       }
+                       catch (std::exception& e) {
+                         SLOG(g_log << Logger::Error << "Error in addSortList: " << e.what() << endl,
+                              lci.d_slog->error(Logr::Error, e.what(), "Error in addSortList", "exception",  Logging::Loggable("std::exception")));
+                       }
+                     });
+
+  Lua->writeFunction("addTA", [&lci](const std::string& who, const std::string& what) {
+    warnIfDNSSECDisabled("Warning: adding Trust Anchor for DNSSEC (addTA), but dnssec is set to 'off'!");
+    DNSName zone(who);
+    auto ds = std::dynamic_pointer_cast<DSRecordContent>(DSRecordContent::make(what));
+    lci.dsAnchors[zone].insert(*ds);
+  });
+
+  Lua->writeFunction("clearTA", [&lci](boost::optional<string> who) {
+    warnIfDNSSECDisabled("Warning: removing Trust Anchor for DNSSEC (clearTA), but dnssec is set to 'off'!");
+    if (who)
+      lci.dsAnchors.erase(DNSName(*who));
+    else
+      lci.dsAnchors.clear();
+  });
+
+  /* Remove in 4.3 */
+  Lua->writeFunction("addDS", [&lci](const std::string& who, const std::string& what) {
+    warnIfDNSSECDisabled("Warning: adding Trust Anchor for DNSSEC (addDS), but dnssec is set to 'off'!");
+    SLOG(g_log << Logger::Warning << "addDS is deprecated and will be removed in the future, switch to addTA" << endl,
+         lci.d_slog->info(Logr::Warning, "addDS is deprecated and will be removed in the future, switch to addTA"));
+    DNSName zone(who);
+    auto ds = std::dynamic_pointer_cast<DSRecordContent>(DSRecordContent::make(what));
+    lci.dsAnchors[zone].insert(*ds);
+  });
+
+  /* Remove in 4.3 */
+  Lua->writeFunction("clearDS", [&lci](boost::optional<string> who) {
+    SLOG(g_log << Logger::Warning << "clearDS is deprecated and will be removed in the future, switch to clearTA" << endl,
+         lci.d_slog->info(Logr::Warning, "clearDS is deprecated and will be removed in the future, switch to clearTA"));
+    warnIfDNSSECDisabled("Warning: removing Trust Anchor for DNSSEC (clearDS), but dnssec is set to 'off'!");
+    if (who)
+      lci.dsAnchors.erase(DNSName(*who));
+    else
+      lci.dsAnchors.clear();
+  });
+
+  Lua->writeFunction("addNTA", [&lci](const std::string& who, const boost::optional<std::string> why) {
+    warnIfDNSSECDisabled("Warning: adding Negative Trust Anchor for DNSSEC (addNTA), but dnssec is set to 'off'!");
+    if (why)
+      lci.negAnchors[DNSName(who)] = static_cast<string>(*why);
+    else
+      lci.negAnchors[DNSName(who)] = "";
+  });
+
+  Lua->writeFunction("clearNTA", [&lci](boost::optional<string> who) {
+    warnIfDNSSECDisabled("Warning: removing Negative Trust Anchor for DNSSEC (clearNTA), but dnssec is set to 'off'!");
+    if (who)
+      lci.negAnchors.erase(DNSName(*who));
+    else
+      lci.negAnchors.clear();
+  });
+
+  Lua->writeFunction("readTrustAnchorsFromFile", [&lci](const std::string& fnamearg, const boost::optional<uint32_t> interval) {
+    uint32_t realInterval = 24;
+    if (interval) {
+      realInterval = static_cast<uint32_t>(*interval);
+    }
+    warnIfDNSSECDisabled("Warning: reading Trust Anchors from file (readTrustAnchorsFromFile), but dnssec is set to 'off'!");
+    lci.trustAnchorFileInfo.fname = fnamearg;
+    lci.trustAnchorFileInfo.interval = realInterval;
+    updateTrustAnchorsFromFile(fnamearg, lci.dsAnchors, lci.d_slog);
+  });
+
+  Lua->writeFunction("setProtobufMasks", [&lci](const uint8_t maskV4, uint8_t maskV6) {
+    lci.protobufMaskV4 = maskV4;
+    lci.protobufMaskV6 = maskV6;
+  });
+
+  Lua->writeFunction("protobufServer", [&lci](boost::variant<const std::string, const std::unordered_map<int, std::string>> servers, boost::optional<protobufOptions_t> vars) {
+    if (!lci.protobufExportConfig.enabled) {
+
+      lci.protobufExportConfig.enabled = true;
+
+      try {
+        if (servers.type() == typeid(std::string)) {
+          auto server = boost::get<const std::string>(servers);
+
+          lci.protobufExportConfig.servers.emplace_back(server);
+        }
+        else {
+          auto serversMap = boost::get<const std::unordered_map<int, std::string>>(servers);
+          for (const auto& serverPair : serversMap) {
+            lci.protobufExportConfig.servers.emplace_back(serverPair.second);
+          }
+        }
+
+        parseProtobufOptions(vars, lci.protobufExportConfig);
+      }
+      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")));
+      }
+      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")));
+      }
+    }
+    else {
+      SLOG(g_log << Logger::Error << "Only one protobufServer() directive can be configured, we already have " << lci.protobufExportConfig.servers.at(0).toString() << endl,
+           lci.d_slog->info(Logr::Error, "Only one protobufServer() directive can be configured", "existing", Logging::Loggable(lci.protobufExportConfig.servers.at(0).toString())));
+    }
+  });
+
+  Lua->writeFunction("outgoingProtobufServer", [&lci](boost::variant<const std::string, const std::unordered_map<int, std::string>> servers, boost::optional<protobufOptions_t> vars) {
+    if (!lci.outgoingProtobufExportConfig.enabled) {
+
+      lci.outgoingProtobufExportConfig.enabled = true;
+
+      try {
+        if (servers.type() == typeid(std::string)) {
+          auto server = boost::get<const std::string>(servers);
+
+          lci.outgoingProtobufExportConfig.servers.emplace_back(server);
+        }
+        else {
+          auto serversMap = boost::get<const std::unordered_map<int, std::string>>(servers);
+          for (const auto& serverPair : serversMap) {
+            lci.outgoingProtobufExportConfig.servers.emplace_back(serverPair.second);
+          }
+        }
+
+        parseProtobufOptions(vars, lci.outgoingProtobufExportConfig);
+      }
+      catch (std::exception& e) {
+        SLOG(g_log << Logger::Error << "Error while starting outgoing protobuf logger: " << e.what() << endl,
+             lci.d_slog->error(Logr::Error, "Exception while starting outgoing protobuf logger", "exception", Logging::Loggable("std::exception")));
+      }
+      catch (PDNSException& e) {
+        SLOG(g_log << Logger::Error << "Error while starting outgoing protobuf logger: " << e.reason << endl,
+             lci.d_slog->error(Logr::Error, "Exception while starting outgoing protobuf logger", "exception", Logging::Loggable("PDNSException")));
+      }
+    }
+    else {
+      SLOG(g_log << Logger::Error << "Only one outgoingProtobufServer() directive can be configured, we already have " << lci.outgoingProtobufExportConfig.servers.at(0).toString() << endl,
+           lci.d_slog->info(Logr::Error, "Only one outgoingProtobufServer() directive can be configured", "existing", Logging::Loggable(lci.outgoingProtobufExportConfig.servers.at(0).toString())));
+    }
+  });
+
+#ifdef HAVE_FSTRM
+  Lua->writeFunction("dnstapFrameStreamServer", [&lci](boost::variant<const std::string, const std::unordered_map<int, std::string>> servers, boost::optional<frameStreamOptions_t> vars) {
+    if (!lci.frameStreamExportConfig.enabled) {
+
+      lci.frameStreamExportConfig.enabled = true;
+
+      try {
+        if (servers.type() == typeid(std::string)) {
+          auto server = boost::get<const std::string>(servers);
+          if (!boost::starts_with(server, "/")) {
+            ComboAddress parsecheck(server);
+          }
+          lci.frameStreamExportConfig.servers.emplace_back(server);
+        }
+        else {
+          auto serversMap = boost::get<const std::unordered_map<int, std::string>>(servers);
+          for (const auto& serverPair : serversMap) {
+            lci.frameStreamExportConfig.servers.emplace_back(serverPair.second);
+          }
+        }
+
+        parseFrameStreamOptions(vars, lci.frameStreamExportConfig);
+      }
+      catch (std::exception& e) {
+        SLOG(g_log << Logger::Error << "Error reading config for dnstap framestream logger: " << e.what() << endl,
+              lci.d_slog->error(Logr::Error, "Exception reading config for dnstap framestream logger", "exception", Logging::Loggable("std::exception")));
+      }
+      catch (PDNSException& e) {
+        SLOG(g_log << Logger::Error << "Error reading config for dnstap framestream logger: " << e.reason << endl,
+             lci.d_slog->error(Logr::Error, "Exception reading config for dnstap framestream logger", "exception", Logging::Loggable("PDNSException")));
+      }
+    }
+    else {
+      SLOG(g_log << Logger::Error << "Only one dnstapFrameStreamServer() directive can be configured, we already have " << lci.frameStreamExportConfig.servers.at(0) << endl,
+           lci.d_slog->info(Logr::Error,  "Only one dnstapFrameStreamServer() directive can be configured",  "existing", Logging::Loggable(lci.frameStreamExportConfig.servers.at(0))));
+    }
+  });
+  Lua->writeFunction("dnstapNODFrameStreamServer", [&lci](boost::variant<const std::string, const std::unordered_map<int, std::string>> servers, boost::optional<frameStreamOptions_t> vars) {
+    if (!lci.nodFrameStreamExportConfig.enabled) {
+      lci.nodFrameStreamExportConfig.enabled = true;
+
+      try {
+        if (servers.type() == typeid(std::string)) {
+          auto server = boost::get<const std::string>(servers);
+          if (!boost::starts_with(server, "/")) {
+            ComboAddress parsecheck(server);
+          }
+          lci.nodFrameStreamExportConfig.servers.emplace_back(server);
+        }
+        else {
+          auto serversMap = boost::get<const std::unordered_map<int, std::string>>(servers);
+          for (const auto& serverPair : serversMap) {
+            lci.nodFrameStreamExportConfig.servers.emplace_back(serverPair.second);
+          }
+        }
+
+        parseFrameStreamOptions(vars, lci.nodFrameStreamExportConfig);
+      }
+      catch (std::exception& e) {
+        SLOG(g_log << Logger::Error << "Error reading config for dnstap NOD framestream logger: " << e.what() << endl,
+              lci.d_slog->error(Logr::Error, "Exception reading config for dnstap NOD framestream logger", "exception", Logging::Loggable("std::exception")));
+      }
+      catch (PDNSException& e) {
+        SLOG(g_log << Logger::Error << "Error reading config for dnstap NOD framestream logger: " << e.reason << endl,
+             lci.d_slog->error(Logr::Error, "Exception reading config for dnstap NOD framestream logger", "exception", Logging::Loggable("PDNSException")));
+      }
+    }
+    else {
+      SLOG(g_log << Logger::Error << "Only one dnstapNODFrameStreamServer() directive can be configured, we already have " << lci.nodFrameStreamExportConfig.servers.at(0) << endl,
+           lci.d_slog->info(Logr::Error,  "Only one dnstapNODFrameStreamServer() directive can be configured",  "existing", Logging::Loggable(lci.nodFrameStreamExportConfig.servers.at(0))));
+    }
+  });
+#endif /* HAVE_FSTRM */
+
+  Lua->writeFunction("addAllowedAdditionalQType", [&lci](int qtype, std::unordered_map<int, int> targetqtypes, boost::optional<std::map<std::string, int>> options) {
+    switch (qtype) {
+    case QType::MX:
+    case QType::SRV:
+    case QType::SVCB:
+    case QType::HTTPS:
+    case QType::NAPTR:
+      break;
+    default:
+      SLOG(g_log << Logger::Error << "addAllowedAdditionalQType does not support " << QType(qtype).toString() << endl,
+           lci.d_slog->info(Logr::Error, "addAllowedAdditionalQType does not support this qtype", "qtype", Logging::Loggable(QType(qtype).toString())));
+      return;
+    }
+
+    std::set<QType> targets;
+    for (const auto& t : targetqtypes) {
+      targets.emplace(QType(t.second));
+    }
+
+    AdditionalMode mode = AdditionalMode::CacheOnlyRequireAuth; // Always cheap and should be safe
+
+    if (options) {
+      if (const auto it = options->find("mode"); it != options->end()) {
+        mode = static_cast<AdditionalMode>(it->second);
+        if (mode > AdditionalMode::ResolveDeferred) {
+          SLOG(g_log << Logger::Error << "addAllowedAdditionalQType: unknown mode " << it->second << endl,
+               lci.d_slog->info(Logr::Error, "addAllowedAdditionalQType: unknown mode", "mode", Logging::Loggable( it->second)));
+        }
+      }
+    }
+    lci.allowAdditionalQTypes.insert_or_assign(qtype, pair(targets, mode));
+  });
+
+  Lua->writeFunction("addProxyMapping", [&proxyMapping,&lci](const string& netmaskArg, const string& addressArg, boost::optional<std::vector<pair<int,std::string>>> smnStrings) {
+    try {
+      Netmask netmask(netmaskArg);
+      ComboAddress address(addressArg);
+      boost::optional<SuffixMatchNode> smn;
+      if (smnStrings) {
+        smn = boost::make_optional(SuffixMatchNode{});
+        for (const auto& el : *smnStrings) {
+          smn->add(el.second);
+        }
+      }
+      proxyMapping.insert_or_assign(netmask, {address, smn});
+    }
+    catch (std::exception& e) {
+      SLOG(g_log << Logger::Error << "Error processing addProxyMapping: " << e.what() << endl,
+           lci.d_slog->error(Logr::Error, e.what(), "Exception processing addProxyMapping", "exception", Logging::Loggable("std::exception")));
+    }
+    catch (PDNSException& e) {
+      SLOG(g_log << Logger::Error << "Error processing addProxyMapping: " << e.reason << endl,
+           lci.d_slog->error(Logr::Error, e.reason, "Exception processing addProxyMapping", "exception", Logging::Loggable("PDNSException")));
+    }
+  });
+
+  try {
+    Lua->executeCode(ifs);
+    g_luaconfs.setState(std::move(lci));
+  }
+  catch (const LuaContext::ExecutionErrorException& e) {
+    SLOG(g_log << Logger::Error << "Unable to load Lua script from '" + fname + "': ",
+         lci.d_slog->error(Logr::Error, e.what(),  "Unable to load Lua script", "file", Logging::Loggable(fname)));
+    try {
+      std::rethrow_if_nested(e);
+    }
+    catch (const std::exception& exp) {
+      // exp is the exception that was thrown from inside the lambda
+      SLOG(g_log << exp.what() << std::endl,
+           lci.d_slog->error(Logr::Error, exp.what(), "Exception loading Lua", "exception", Logging::Loggable("std::exception")));
+    }
+    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")));
+    }
+    throw;
+  }
+  catch (std::exception& err) {
+    SLOG(g_log << Logger::Error << "Unable to load Lua script from '" + fname + "': " << err.what() << endl,
+         lci.d_slog->error(Logr::Error, err.what(),  "Unable to load Lua script", "file", Logging::Loggable(fname), "exception", Logging::Loggable("std::exception")));
+    throw;
+  }
+}
+
+void startLuaConfigDelayedThreads(const luaConfigDelayedThreads& delayedThreads, uint64_t generation)
+{
+  for (const auto& rpzPrimary : delayedThreads.rpzPrimaryThreads) {
+    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 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 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 starting RPZIXFRTracker thread", "exception", Logging::Loggable("PDNSException")));
+      exit(1);
+    }
+  }
+}
deleted file mode 120000 (symlink)
index 769f8653fc19e9b002549525057e34eebd4c7831..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../rec-lua-conf.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..440068ec6ea61c358d43d9c46e6c596854f7055e
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * 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 <set>
+
+#include "sholder.hh"
+#include "sortlist.hh"
+#include "filterpo.hh"
+#include "validate.hh"
+#include "rec-zonetocache.hh"
+#include "logging.hh"
+#include "fstrm_logger.hh"
+#include "rpzloader.hh"
+
+struct ProtobufExportConfig
+{
+  std::set<uint16_t> exportTypes = {QType::A, QType::AAAA, QType::CNAME};
+  std::vector<ComboAddress> servers;
+  uint64_t maxQueuedEntries{100};
+  uint16_t timeout{2};
+  uint16_t reconnectWaitTime{1};
+  bool asyncConnect{false};
+  bool enabled{false};
+  bool logQueries{true};
+  bool logResponses{true};
+  bool taggedOnly{false};
+  bool logMappedFrom{false};
+};
+
+bool operator==(const ProtobufExportConfig& configA, const ProtobufExportConfig& configB);
+bool operator!=(const ProtobufExportConfig& configA, const ProtobufExportConfig& configB);
+
+struct FrameStreamExportConfig
+{
+  std::vector<string> servers;
+  bool enabled{false};
+  bool logQueries{true};
+  bool logResponses{true};
+  bool logNODs{true};
+  bool logUDRs{false};
+  unsigned bufferHint{0};
+  unsigned flushTimeout{0};
+  unsigned inputQueueSize{0};
+  unsigned outputQueueSize{0};
+  unsigned queueNotifyThreshold{0};
+  unsigned reopenInterval{0};
+};
+
+bool operator==(const FrameStreamExportConfig& configA, const FrameStreamExportConfig& configB);
+bool operator!=(const FrameStreamExportConfig& configA, const FrameStreamExportConfig& configB);
+
+struct TrustAnchorFileInfo
+{
+  uint32_t interval{24};
+  std::string fname;
+};
+
+enum class AdditionalMode : uint8_t
+{
+  Ignore,
+  CacheOnly,
+  CacheOnlyRequireAuth,
+  ResolveImmediately,
+  ResolveDeferred
+};
+
+struct ProxyMappingCounts
+{
+  uint64_t netmaskMatches{};
+  uint64_t suffixMatches{};
+};
+
+struct ProxyByTableValue
+{
+  ComboAddress address;
+  boost::optional<SuffixMatchNode> suffixMatchNode;
+  mutable ProxyMappingCounts stats{};
+};
+
+using ProxyMapping = NetmaskTree<ProxyByTableValue, Netmask>;
+
+class LuaConfigItems
+{
+public:
+  LuaConfigItems();
+  SortList sortlist;
+  DNSFilterEngine dfe;
+  TrustAnchorFileInfo trustAnchorFileInfo; // Used to update the Trust Anchors from file periodically
+  map<DNSName, dsmap_t> dsAnchors;
+  map<DNSName, std::string> negAnchors;
+  map<DNSName, RecZoneToCache::Config> ztcConfigs;
+  std::map<QType, std::pair<std::set<QType>, AdditionalMode>> allowAdditionalQTypes;
+  ProtobufExportConfig protobufExportConfig;
+  ProtobufExportConfig outgoingProtobufExportConfig;
+  FrameStreamExportConfig frameStreamExportConfig;
+  FrameStreamExportConfig nodFrameStreamExportConfig;
+  std::shared_ptr<Logr::Logger> d_slog;
+  /* we need to increment this every time the configuration
+     is reloaded, so we know if we need to reload the protobuf
+     remote loggers */
+  uint64_t generation{0};
+  uint8_t protobufMaskV4{32};
+  uint8_t protobufMaskV6{128};
+};
+
+extern GlobalStateHolder<LuaConfigItems> g_luaconfs;
+
+struct luaConfigDelayedThreads
+{
+  std::vector<RPZTrackerParams> rpzPrimaryThreads;
+};
+
+void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& delayedThreads, ProxyMapping&);
+void startLuaConfigDelayedThreads(const luaConfigDelayedThreads& delayedThreads, uint64_t generation);
index 3ade92299f868e62c92e580802567442ea44a267..54d117bd7315869e5a976fdbe5714bf68c2792b6 100644 (file)
@@ -38,6 +38,8 @@
 #include "rec-taskqueue.hh"
 #include "secpoll-recursor.hh"
 #include "logging.hh"
+#include "dnsseckeeper.hh"
+#include "settings/cxxsettings.hh"
 
 #ifdef NOD_ENABLED
 #include "nod.hh"
 
 #ifdef HAVE_LIBSODIUM
 #include <sodium.h>
+
+#include <cstddef>
+#include <utility>
 #endif
 
 #ifdef HAVE_SYSTEMD
+// All calls are coming form the same function, so no use for CODE_LINE, CODE_FUNC etc
+#define SD_JOURNAL_SUPPRESS_LOCATION
 #include <systemd/sd-daemon.h>
+#include <systemd/sd-journal.h>
 #endif
 
-static thread_local uint64_t t_protobufServersGeneration;
-static thread_local uint64_t t_outgoingProtobufServersGeneration;
-
 #ifdef HAVE_FSTRM
-thread_local std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>> t_frameStreamServers{nullptr};
-thread_local uint64_t t_frameStreamServersGeneration;
+thread_local FrameStreamServersInfo t_frameStreamServersInfo;
+thread_local FrameStreamServersInfo t_nodFrameStreamServersInfo;
 #endif /* HAVE_FSTRM */
 
 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;
@@ -79,7 +86,6 @@ thread_local std::shared_ptr<nod::UniqueResponseDB> t_udrDBp;
 std::atomic<bool> statsWanted;
 uint32_t g_disthashseed;
 bool g_useIncomingECS;
-uint16_t g_xpfRRCode{0};
 NetmaskGroup g_proxyProtocolACL;
 boost::optional<ComboAddress> g_dns64Prefix{boost::none};
 DNSName g_dns64PrefixReverse;
@@ -89,13 +95,19 @@ std::shared_ptr<NetmaskGroup> g_initialAllowNotifyFrom; // new threads need this
 std::shared_ptr<notifyset_t> g_initialAllowNotifyFor; // new threads need this to be setup
 bool g_logRPZChanges{false};
 static time_t s_statisticsInterval;
-bool g_addExtendedResolutionDNSErrors;
 static std::atomic<uint32_t> s_counter;
 int g_argc;
 char** g_argv;
+static string s_structured_logger_backend;
+static Logger::Urgency s_logUrgency;
+
+std::shared_ptr<Logr::Logger> g_slogtcpin;
+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)
@@ -103,19 +115,24 @@ deferredAdd_t g_deferredAdds;
    and finally the workers */
 std::vector<RecThreadInfo> RecThreadInfo::s_threadInfos;
 
+std::unique_ptr<ProxyMapping> g_proxyMapping; // new threads needs this to be setup
+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()
+static std::map<unsigned int, std::set<int>> parseCPUMap(Logr::log_t log)
 {
   std::map<unsigned int, std::set<int>> result;
 
   const std::string value = ::arg()["cpu-map"];
 
   if (!value.empty() && !isSettingThreadCPUAffinitySupported()) {
-    g_log << Logger::Warning << "CPU mapping requested but not supported, skipping" << endl;
+    SLOG(g_log << Logger::Warning << "CPU mapping requested but not supported, skipping" << endl,
+         log->info(Logr::Warning, "CPU mapping requested but not supported, skipping"));
     return result;
   }
 
@@ -124,8 +141,9 @@ static std::map<unsigned int, std::set<int>> parseCPUMap()
   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, '=');
@@ -144,186 +162,227 @@ static std::map<unsigned int, std::set<int>> parseCPUMap()
       }
     }
     catch (const std::exception& e) {
-      g_log << Logger::Error << "Error parsing cpu-map entry '" << part << "': " << e.what() << endl;
+      SLOG(g_log << Logger::Error << "Error parsing cpu-map entry '" << part << "': " << e.what() << endl,
+           log->error(Logr::Error, e.what(), "Error parsing cpu-map entry", "entry", Logging::Loggable(part)));
     }
   }
 
   return result;
 }
 
-static void setCPUMap(const std::map<unsigned int, std::set<int>>& cpusMap, unsigned int n, pthread_t tid)
+static void setCPUMap(const std::map<unsigned int, std::set<int>>& cpusMap, unsigned int n, pthread_t tid, Logr::log_t log)
 {
   const auto& cpuMapping = cpusMap.find(n);
   if (cpuMapping == cpusMap.cend()) {
     return;
   }
-  int rc = mapThreadToCPUList(tid, cpuMapping->second);
-  if (rc == 0) {
-    g_log << Logger::Info << "CPU affinity for thread " << n << " has been set to CPU map:";
-    for (const auto cpu : cpuMapping->second) {
-      g_log << Logger::Info << " " << cpu;
+  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) {
+        g_log << Logger::Info << " " << cpu;
+      }
+      g_log << Logger::Info << endl;
+    }
+    else {
+      log->info(Logr::Info, "CPU affinity has been set", "thread", Logging::Loggable(n), "cpumap", Logging::IterLoggable(cpuMapping->second.begin(), cpuMapping->second.end()));
     }
-    g_log << Logger::Info << endl;
   }
   else {
-    g_log << Logger::Warning << "Error setting CPU affinity for thread " << n << " to CPU map:";
-    for (const auto cpu : cpuMapping->second) {
-      g_log << Logger::Info << " " << cpu;
+    if (!g_slogStructured) {
+      g_log << Logger::Warning << "Error setting CPU affinity for thread " << n << " to CPU map:";
+      for (const auto cpu : cpuMapping->second) {
+        g_log << Logger::Info << " " << cpu;
+      }
+      g_log << Logger::Info << ' ' << stringerror(ret) << endl;
+    }
+    else {
+      log->error(Logr::Warning, ret, "Error setting CPU affinity", "thread", Logging::Loggable(n), "cpumap", Logging::IterLoggable(cpuMapping->second.begin(), cpuMapping->second.end()));
     }
-    g_log << Logger::Info << ' ' << strerror(rc) << endl;
   }
 }
 
 static void recursorThread();
 
-void RecThreadInfo::start(unsigned int id, const string& tname, const std::map<unsigned int, std::set<int>>& cpusMap)
+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());
+  setCPUMap(cpusMap, tid, thread.native_handle(), log);
 }
 
-int RecThreadInfo::runThreads()
+int RecThreadInfo::runThreads(Logr::log_t log)
 {
   int ret = EXIT_SUCCESS;
-  unsigned int currentThreadId = 1;
-  const auto cpusMap = parseCPUMap();
-
-  if (RecThreadInfo::numDistributors() + RecThreadInfo::numWorkers() == 1) {
-    g_log << Logger::Warning << "Operating with single distributor/worker thread" << endl;
+  const auto cpusMap = parseCPUMap(log);
 
-#ifdef HAVE_SYSTEMD
-    sd_notify(0, "READY=1");
-#endif
+  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);
-    auto& taskInfo = RecThreadInfo::info(2);
-    taskInfo.setTaskThread();
-    taskInfo.start(2, "taskThread", cpusMap);
+    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()) {
-      g_log << Logger::Warning << "Launching " << RecThreadInfo::numDistributors() << " distributor threads" << endl;
-      for (unsigned int n = 0; n < RecThreadInfo::numDistributors(); ++n) {
+      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 thread = 0; thread < RecThreadInfo::numDistributors(); thread++, currentThreadId++) {
         auto& info = RecThreadInfo::info(currentThreadId);
-        info.start(currentThreadId++, "distr", cpusMap);
+        info.start(currentThreadId, "distr", cpusMap, log);
       }
     }
+    SLOG(g_log << Logger::Warning << "Launching " << RecThreadInfo::numUDPWorkers() << " worker threads" << endl,
+         log->info(Logr::Notice, "Launching worker threads", "count", Logging::Loggable(RecThreadInfo::numUDPWorkers())));
 
-    g_log << Logger::Warning << "Launching " << RecThreadInfo::numWorkers() << " worker threads" << endl;
-
-    for (unsigned int n = 0; n < RecThreadInfo::numWorkers(); ++n) {
+    for (unsigned int thread = 0; thread < RecThreadInfo::numUDPWorkers(); thread++, currentThreadId++) {
       auto& info = RecThreadInfo::info(currentThreadId);
-      info.start(currentThreadId++, "worker", cpusMap);
+      info.start(currentThreadId, "worker", cpusMap, log);
     }
 
-    for (unsigned int n = 0; n < RecThreadInfo::numTaskThreads(); ++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++, "taskThread", cpusMap);
+      info.start(currentThreadId, "tcpworker", cpusMap, log);
     }
 
-#ifdef HAVE_SYSTEMD
-    sd_notify(0, "READY=1");
-#endif
+    for (unsigned int thread = 0; thread < RecThreadInfo::numTaskThreads(); thread++, currentThreadId++) {
+      auto& info = RecThreadInfo::info(currentThreadId);
+      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);
+    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;
       }
     }
   }
   return ret;
 }
 
-void RecThreadInfo::makeThreadPipes()
+void RecThreadInfo::makeThreadPipes(Logr::log_t log)
 {
   auto pipeBufferSize = ::arg().asNum("distribution-pipe-buffer-size");
   if (pipeBufferSize > 0) {
-    g_log << Logger::Info << "Resizing the buffer of the distribution pipe to " << pipeBufferSize << endl;
+    SLOG(g_log << Logger::Info << "Resizing the buffer of the distribution pipe to " << pipeBufferSize << endl,
+         log->info(Logr::Info, "Resizing the buffer of the distribution pipe", "size", Logging::Loggable(pipeBufferSize)));
   }
 
   /* 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;
-        g_log << Logger::Warning << "Error resizing the buffer of the distribution pipe for thread " << n << " to " << pipeBufferSize << ": " << strerror(err) << endl;
+        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) {
-          g_log << Logger::Warning << "The current size of the distribution pipe's buffer for thread " << n << " is " << existingSize << endl;
+          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)));
         }
       }
     }
@@ -340,26 +399,29 @@ ArgvMap& arg()
   return theArg;
 }
 
-static FDMultiplexer* getMultiplexer()
+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();
+      ret = mplexer.second(FDMultiplexer::s_maxevents);
       return ret;
     }
     catch (FDMultiplexerException& fe) {
-      g_log << Logger::Error << "Non-fatal error initializing possible multiplexer (" << fe.what() << "), falling back" << endl;
+      SLOG(g_log << Logger::Warning << "Non-fatal error initializing possible multiplexer (" << fe.what() << "), falling back" << endl,
+           log->error(Logr::Warning, fe.what(), "Non-fatal error initializing possible multiplexer, falling back"));
     }
     catch (...) {
-      g_log << Logger::Error << "Non-fatal error initializing possible multiplexer" << endl;
+      SLOG(g_log << Logger::Warning << "Non-fatal error initializing possible multiplexer" << endl,
+           log->info(Logr::Warning, "Non-fatal error initializing possible multiplexer"));
     }
   }
-  g_log << Logger::Error << "No working multiplexer found!" << endl;
+  SLOG(g_log << Logger::Error << "No working multiplexer found!" << endl,
+       log->info(Logr::Error, "No working multiplexer found!"));
   _exit(1);
 }
 
-static std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>> startProtobufServers(const ProtobufExportConfig& config)
+static std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>> startProtobufServers(const ProtobufExportConfig& config, Logr::log_t log)
 {
   auto result = std::make_shared<std::vector<std::unique_ptr<RemoteLogger>>>();
 
@@ -371,10 +433,12 @@ static std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>> startProtobuf
       result->emplace_back(std::move(logger));
     }
     catch (const std::exception& e) {
-      g_log << Logger::Error << "Error while starting protobuf logger to '" << server << ": " << e.what() << endl;
+      SLOG(g_log << Logger::Error << "Error while starting protobuf logger to '" << server << ": " << e.what() << endl,
+           log->error(Logr::Error, e.what(), "Exception while starting protobuf logger", "exception", Logging::Loggable("std::exception"), "server", Logging::Loggable(server)));
     }
     catch (const PDNSException& e) {
-      g_log << Logger::Error << "Error while starting protobuf logger to '" << server << ": " << e.reason << endl;
+      SLOG(g_log << Logger::Error << "Error while starting protobuf logger to '" << server << ": " << e.reason << endl,
+           log->error(Logr::Error, e.reason, "Exception while starting protobuf logger", "exception", Logging::Loggable("PDNSException"), "server", Logging::Loggable(server)));
     }
   }
 
@@ -384,11 +448,9 @@ static std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>> startProtobuf
 bool checkProtobufExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal)
 {
   if (!luaconfsLocal->protobufExportConfig.enabled) {
-    if (t_protobufServers) {
-      for (auto& server : *t_protobufServers) {
-        server->stop();
-      }
-      t_protobufServers.reset();
+    if (t_protobufServers.servers) {
+      t_protobufServers.servers.reset();
+      t_protobufServers.config = luaconfsLocal->protobufExportConfig;
     }
 
     return false;
@@ -396,17 +458,15 @@ bool checkProtobufExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal)
 
   /* if the server was not running, or if it was running according to a
      previous configuration */
-  if (!t_protobufServers || t_protobufServersGeneration < luaconfsLocal->generation) {
+  if (t_protobufServers.generation < luaconfsLocal->generation && t_protobufServers.config != luaconfsLocal->protobufExportConfig) {
 
-    if (t_protobufServers) {
-      for (auto& server : *t_protobufServers) {
-        server->stop();
-      }
+    if (t_protobufServers.servers) {
+      t_protobufServers.servers.reset();
     }
-    t_protobufServers.reset();
-
-    t_protobufServers = startProtobufServers(luaconfsLocal->protobufExportConfig);
-    t_protobufServersGeneration = luaconfsLocal->generation;
+    auto log = g_slog->withName("protobuf");
+    t_protobufServers.servers = startProtobufServers(luaconfsLocal->protobufExportConfig, log);
+    t_protobufServers.config = luaconfsLocal->protobufExportConfig;
+    t_protobufServers.generation = luaconfsLocal->generation;
   }
 
   return true;
@@ -415,85 +475,93 @@ bool checkProtobufExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal)
 bool checkOutgoingProtobufExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal)
 {
   if (!luaconfsLocal->outgoingProtobufExportConfig.enabled) {
-    if (t_outgoingProtobufServers) {
-      for (auto& server : *t_outgoingProtobufServers) {
-        server->stop();
-      }
+    if (t_outgoingProtobufServers.servers) {
+      t_outgoingProtobufServers.servers.reset();
+      t_outgoingProtobufServers.config = luaconfsLocal->outgoingProtobufExportConfig;
     }
-    t_outgoingProtobufServers.reset();
 
     return false;
   }
 
   /* if the server was not running, or if it was running according to a
      previous configuration */
-  if (!t_outgoingProtobufServers || t_outgoingProtobufServersGeneration < luaconfsLocal->generation) {
+  if (t_outgoingProtobufServers.generation < luaconfsLocal->generation && t_outgoingProtobufServers.config != luaconfsLocal->outgoingProtobufExportConfig) {
 
-    if (t_outgoingProtobufServers) {
-      for (auto& server : *t_outgoingProtobufServers) {
-        server->stop();
-      }
+    if (t_outgoingProtobufServers.servers) {
+      t_outgoingProtobufServers.servers.reset();
     }
-    t_outgoingProtobufServers.reset();
-
-    t_outgoingProtobufServers = startProtobufServers(luaconfsLocal->outgoingProtobufExportConfig);
-    t_outgoingProtobufServersGeneration = luaconfsLocal->generation;
+    auto log = g_slog->withName("protobuf");
+    t_outgoingProtobufServers.servers = startProtobufServers(luaconfsLocal->outgoingProtobufExportConfig, log);
+    t_outgoingProtobufServers.config = luaconfsLocal->outgoingProtobufExportConfig;
+    t_outgoingProtobufServers.generation = luaconfsLocal->generation;
   }
 
   return true;
 }
 
-void protobufLogQuery(LocalStateHolder<LuaConfigItems>& luaconfsLocal, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const Netmask& ednssubnet, bool tcp, uint16_t id, size_t len, const DNSName& qname, uint16_t qtype, uint16_t qclass, const std::unordered_set<std::string>& policyTags, const std::string& requestorId, const std::string& deviceId, const std::string& deviceName, 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)
 {
-  if (!t_protobufServers) {
+  auto log = g_slog->withName("pblq");
+
+  if (!t_protobufServers.servers) {
     return;
   }
 
-  Netmask requestorNM(remote, remote.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
-  ComboAddress requestor = requestorNM.getMaskedNetwork();
-  requestor.setPort(remote.getPort());
+  ComboAddress requestor;
+  if (!luaconfsLocal->protobufExportConfig.logMappedFrom) {
+    Netmask requestorNM(remote, remote.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+    requestor = requestorNM.getMaskedNetwork();
+    requestor.setPort(remote.getPort());
+  }
+  else {
+    Netmask requestorNM(mappedSource, mappedSource.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+    requestor = requestorNM.getMaskedNetwork();
+    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());
-  for (auto& server : *t_protobufServers) {
-    server->queueData(msg);
+  std::string strMsg(msg.finishAndMoveBuf());
+  for (auto& server : *t_protobufServers.servers) {
+    remoteLoggerQueueData(*server, strMsg);
   }
 }
 
 void protobufLogResponse(pdns::ProtoZero::RecMessage& message)
 {
-  if (!t_protobufServers) {
+  if (!t_protobufServers.servers) {
     return;
   }
 
   std::string msg(message.finishAndMoveBuf());
-  for (auto& server : *t_protobufServers) {
-    server->queueData(msg);
+  for (auto& server : *t_protobufServers.servers) {
+    remoteLoggerQueueData(*server, msg);
   }
 }
 
-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?)
@@ -504,46 +572,58 @@ 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);
   }
 
   // In message part
-  Netmask requestorNM(source, source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
-  ComboAddress requestor = requestorNM.getMaskedNetwork();
+  if (!luaconfsLocal->protobufExportConfig.logMappedFrom) {
+    pbMessage.setSocketFamily(source.sin4.sin_family);
+    Netmask requestorNM(source, source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+    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);
+    const auto& requestor = requestorNM.getMaskedNetwork();
+    pbMessage.setFrom(requestor);
+    pbMessage.setFromPort(mappedSource.getPort());
+  }
   pbMessage.setMessageIdentity(uniqueId);
-  pbMessage.setFrom(requestor);
   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);
   pbMessage.setRequestorId(requestorId);
   pbMessage.setDeviceId(deviceId);
   pbMessage.setDeviceName(deviceName);
-  pbMessage.setFromPort(source.getPort());
   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);
 }
 
 #ifdef HAVE_FSTRM
 
-static std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>> startFrameStreamServers(const FrameStreamExportConfig& config)
+static std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>> startFrameStreamServers(const FrameStreamExportConfig& config, Logr::log_t log)
 {
   auto result = std::make_shared<std::vector<std::unique_ptr<FrameStreamLogger>>>();
 
@@ -556,74 +636,93 @@ 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);
-      result->emplace_back(fsl);
+      fsl->setLogNODs(config.logNODs);
+      fsl->setLogUDRs(config.logUDRs);
+      result->emplace_back(std::move(fsl));
     }
     catch (const std::exception& e) {
-      g_log << Logger::Error << "Error while starting dnstap framestream logger to '" << server << ": " << e.what() << endl;
+      SLOG(g_log << Logger::Error << "Error while starting dnstap framestream logger to '" << server << ": " << e.what() << endl,
+           log->error(Logr::Error, e.what(), "Exception while starting dnstap framestream logger", "exception", Logging::Loggable("std::exception"), "server", Logging::Loggable(server)));
     }
     catch (const PDNSException& e) {
-      g_log << Logger::Error << "Error while starting dnstap framestream logger to '" << server << ": " << e.reason << endl;
+      SLOG(g_log << Logger::Error << "Error while starting dnstap framestream logger to '" << server << ": " << e.reason << endl,
+           log->error(Logr::Error, e.reason, "Exception while starting dnstap framestream logger", "exception", Logging::Loggable("PDNSException"), "server", Logging::Loggable(server)));
     }
   }
 
   return result;
 }
 
-bool checkFrameStreamExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal)
+static void asyncFrameStreamLoggersCleanup(std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>&& servers)
+{
+  auto thread = std::thread([&] {
+    servers.reset();
+  });
+  thread.detach();
+}
+
+bool checkFrameStreamExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal, const FrameStreamExportConfig& config, FrameStreamServersInfo& serverInfos)
 {
-  if (!luaconfsLocal->frameStreamExportConfig.enabled) {
-    if (t_frameStreamServers) {
+  if (!config.enabled) {
+    if (serverInfos.servers) {
       // dt's take care of cleanup
-      t_frameStreamServers.reset();
+      asyncFrameStreamLoggersCleanup(std::move(serverInfos.servers));
+      serverInfos.config = config;
     }
 
     return false;
   }
 
-  /* if the server was not running, or if it was running according to a
-     previous configuration */
-  if (!t_frameStreamServers || t_frameStreamServersGeneration < luaconfsLocal->generation) {
-
-    if (t_frameStreamServers) {
+  /* if the server was not running, or if it was running according to a previous
+   * configuration
+   */
+  if (serverInfos.generation < luaconfsLocal->generation && serverInfos.config != config) {
+    if (serverInfos.servers) {
       // dt's take care of cleanup
-      t_frameStreamServers.reset();
+      asyncFrameStreamLoggersCleanup(std::move(serverInfos.servers));
     }
 
-    t_frameStreamServers = startFrameStreamServers(luaconfsLocal->frameStreamExportConfig);
-    t_frameStreamServersGeneration = luaconfsLocal->generation;
+    auto dnsTapLog = g_slog->withName("dnstap");
+    serverInfos.servers = startFrameStreamServers(config, dnsTapLog);
+    serverInfos.config = config;
+    serverInfos.generation = luaconfsLocal->generation;
   }
 
   return true;
 }
+
 #endif /* HAVE_FSTRM */
 
 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");
     }
@@ -638,30 +737,32 @@ static void makeControlChannelSocket(int processNum = -1)
   }
 }
 
-static void writePid(void)
+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;
-    g_log << Logger::Error << "Writing pid for " << Utility::getpid() << " to " << g_pidfname << " failed: "
-          << stringerror(err) << endl;
+    SLOG(g_log << Logger::Error << "Writing pid for " << Utility::getpid() << " to " << g_pidfname << " failed: " << stringerror(err) << endl,
+         log->error(Logr::Error, err, "Writing pid failed", "pid", Logging::Loggable(Utility::getpid()), "file", Logging::Loggable(g_pidfname)));
   }
 }
 
-static void checkSocketDir(void)
+static void checkSocketDir(Logr::log_t log)
 {
-  struct stat st;
   string dir(::arg()["socket-dir"]);
   string msg;
 
-  if (stat(dir.c_str(), &st) == -1) {
+  struct stat dirStat = {};
+  if (stat(dir.c_str(), &dirStat) == -1) {
     msg = "it does not exist or cannot access";
   }
-  else if (!S_ISDIR(st.st_mode)) {
+  else if (!S_ISDIR(dirStat.st_mode)) {
     msg = "it is not a directory";
   }
   else if (access(dir.c_str(), R_OK | W_OK | X_OK) != 0) {
@@ -670,12 +771,14 @@ static void checkSocketDir(void)
   else {
     return;
   }
-  g_log << Logger::Error << "Problem with socket directory " << dir << ": " << msg << "; see https://docs.powerdns.com/recursor/upgrade.html#x-to-4-3-0" << endl;
+  dir = ::arg()["chroot"] + dir;
+  SLOG(g_log << Logger::Error << "Problem with socket directory " << dir << ": " << msg << "; see https://docs.powerdns.com/recursor/upgrade.html#x-to-4-3-0" << endl,
+       log->error(Logr::Error, msg, "Problem with socket directory, see https://docs.powerdns.com/recursor/upgrade.html#x-to-4-3-0", "dir", Logging::Loggable(dir)));
   _exit(1);
 }
 
 #ifdef NOD_ENABLED
-static void setupNODThread()
+static void setupNODThread(Logr::log_t log)
 {
   if (g_nodEnabled) {
     uint32_t num_cells = ::arg().asNum("new-domain-db-size");
@@ -684,16 +787,17 @@ static void setupNODThread()
       t_nodDBp->setCacheDir(::arg()["new-domain-history-dir"]);
     }
     catch (const PDNSException& e) {
-      g_log << Logger::Error << "new-domain-history-dir (" << ::arg()["new-domain-history-dir"] << ") is not readable or does not exist" << endl;
+      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 readable or does not exists", "dir", Logging::Loggable(::arg()["new-domain-history-dir"])));
       _exit(1);
     }
     if (!t_nodDBp->init()) {
-      g_log << Logger::Error << "Could not initialize domain tracking" << endl;
+      SLOG(g_log << Logger::Error << "Could not initialize domain tracking" << endl,
+           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();
-    g_nod_pbtag = ::arg()["new-domain-pb-tag"];
+    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");
@@ -702,16 +806,17 @@ static void setupNODThread()
       t_udrDBp->setCacheDir(::arg()["unique-response-history-dir"]);
     }
     catch (const PDNSException& e) {
-      g_log << Logger::Error << "unique-response-history-dir (" << ::arg()["unique-response-history-dir"] << ") is not readable or does not exist" << endl;
+      SLOG(g_log << Logger::Error << "unique-response-history-dir (" << ::arg()["unique-response-history-dir"] << ") is not readable or does not exist" << endl,
+           log->info(Logr::Error, "unique-response-history-dir is not readable or does not exist", "dir", Logging::Loggable(::arg()["unique-response-history-dir"])));
       _exit(1);
     }
     if (!t_udrDBp->init()) {
-      g_log << Logger::Error << "Could not initialize unique response tracking" << endl;
+      SLOG(g_log << Logger::Error << "Could not initialize unique response tracking" << endl,
+           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();
-    g_udr_pbtag = ::arg()["unique-response-pb-tag"];
+    std::thread thread(nod::UniqueResponseDB::startHousekeepingThread, t_udrDBp, std::this_thread::get_id());
+    thread.detach();
   }
 }
 
@@ -719,8 +824,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));
   }
 }
 
@@ -736,72 +841,87 @@ static void setupNODGlobal()
   // Setup Unique DNS Response subsystem
   g_udrEnabled = ::arg().mustDo("unique-response-tracking");
   g_udrLog = ::arg().mustDo("unique-response-log");
+  g_nod_pbtag = ::arg()["new-domain-pb-tag"];
+  g_udr_pbtag = ::arg()["unique-response-pb-tag"];
 }
 #endif /* NOD_ENABLED */
 
-static void daemonize(void)
+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)
-    g_log << Logger::Critical << "Unable to open /dev/null: " << stringerror() << endl;
+  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()
+static void checkLinuxIPv6Limits([[maybe_unused]] Logr::log_t log)
 {
 #ifdef __linux__
   string line;
   if (readFileIfThere("/proc/sys/net/ipv6/route/max_size", &line)) {
     int lim = std::stoi(line);
     if (lim < 16384) {
-      g_log << Logger::Error << "If using IPv6, please raise sysctl net.ipv6.route.max_size, currently set to " << lim << " which is < 16384" << endl;
+      SLOG(g_log << Logger::Error << "If using IPv6, please raise sysctl net.ipv6.route.max_size, currently set to " << lim << " which is < 16384" << endl,
+           log->info(Logr::Error, "If using IPv6, please raise sysctl net.ipv6.route.max_size to a size >= 16384", "current", Logging::Loggable(lim)));
     }
   }
 #endif
 }
 
-static void checkOrFixFDS()
+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);
     if (hardlimit >= wantFDs) {
       setFilenumLimit(wantFDs);
-      g_log << Logger::Warning << "Raised soft limit on number of filedescriptors to " << wantFDs << " to match max-mthreads and threads settings" << endl;
+      SLOG(g_log << Logger::Warning << "Raised soft limit on number of filedescriptors to " << wantFDs << " to match max-mthreads and threads settings" << endl,
+           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();
-      g_log << Logger::Warning << "Insufficient number of filedescriptors available for max-mthreads*threads setting! (" << hardlimit << " < " << wantFDs << "), reducing max-mthreads to " << newval << endl;
+      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;
       setFilenumLimit(hardlimit);
     }
@@ -811,43 +931,132 @@ static void checkOrFixFDS()
 // 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)
 {
-  struct tm tm;
-  size_t len = strftime(buf, sz, s_timestampFormat.c_str(), localtime_r(&tv.tv_sec, &tm));
+  size_t len = 0;
+  if (s_timestampFormat != "%s") {
+    // strftime is not thread safe, it can access locale information
+    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.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 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& key, const string& value) {
+    strings.emplace_back(key + "=" + value);
+  };
+  appendKeyAndVal("MESSAGE", entry.message);
+  if (entry.error) {
+    appendKeyAndVal("ERROR", entry.error.get());
+  }
+  appendKeyAndVal("LEVEL", std::to_string(entry.level));
+  appendKeyAndVal("PRIORITY", std::to_string(entry.d_priority));
+  if (entry.name) {
+    appendKeyAndVal("SUBSYSTEM", entry.name.get());
+  }
+  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.
+  appendKeyAndVal("TID", std::to_string(RecThreadInfo::id()));
 
-  snprintf(buf + len, sz - len, ".%03ld", static_cast<long>(tv.tv_usec) / 1000);
-  return buf;
+  vector<iovec> iov;
+  iov.reserve(strings.size());
+  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*>(str.data())), str.size()}); // NOLINT: it's the API
+  }
+  sd_journal_sendv(iov.data(), static_cast<int>(iov.size()));
 }
+#endif
 
 static void loggerBackend(const Logging::Entry& entry)
 {
   static thread_local std::stringstream buf;
 
+  // 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;
+  }
   buf.str("");
   buf << "msg=" << std::quoted(entry.message);
   if (entry.error) {
-    buf << " oserror=" << std::quoted(entry.error.get());
+    buf << " error=" << std::quoted(entry.error.get());
   }
 
   if (entry.name) {
     buf << " subsystem=" << std::quoted(entry.name.get());
   }
-  buf << " level=" << entry.level;
-  if (entry.d_priority) {
-    buf << " prio=" << static_cast<int>(entry.d_priority);
+  buf << " level=" << std::quoted(std::to_string(entry.level));
+  if (entry.d_priority != 0) {
+    buf << " prio=" << std::quoted(Logr::Logger::toString(entry.d_priority));
   }
-  char timebuf[64];
-  buf << " ts=" << std::quoted(toTimestampStringMilli(entry.d_timestamp, timebuf, sizeof(timebuf)));
-  for (auto const& v : entry.values) {
+  // 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()));
+  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);
   }
-  Logger::Urgency u = entry.d_priority ? Logger::Urgency(entry.d_priority) : Logger::Info;
-  g_log << u << buf.str() << endl;
+
+  g_log << urg << buf.str() << endl;
 }
 
 static int ratePercentage(uint64_t nom, uint64_t denom)
@@ -855,185 +1064,297 @@ 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();
-  double r = rc_stats.second == 0 ? 0.0 : (100.0 * rc_stats.first / rc_stats.second);
+  auto pc_stats = g_packetCache ? g_packetCache->stats() : std::pair<uint64_t, uint64_t>{0, 0};
+  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();
   auto taskSize = getTaskSize();
-
-  if (g_stats.qcounter && (cacheHits + cacheMisses) && SyncRes::s_queries && SyncRes::s_outqueries) {
-    g_log << Logger::Notice << "stats: " << g_stats.qcounter << " questions, " << cacheSize << " cache entries, " << negCacheSize << " negative entries, " << ratePercentage(cacheHits, cacheHits + cacheMisses) << "% cache hits" << endl;
-    g_log << Logger::Notice << "stats: cache contended/acquired " << rc_stats.first << '/' << rc_stats.second << " = " << r << '%' << endl;
-
-    g_log << Logger::Notice << "stats: throttle map: "
-          << broadcastAccFunction<uint64_t>(pleaseGetThrottleSize) << ", ns speeds: "
-          << broadcastAccFunction<uint64_t>(pleaseGetNsSpeedsSize) << ", failed ns: "
-          << SyncRes::getFailedServersSize() << ", ednsmap: "
-          << broadcastAccFunction<uint64_t>(pleaseGetEDNSStatusesSize) << endl;
-    g_log << Logger::Notice << "stats: outpacket/query ratio " << ratePercentage(SyncRes::s_outqueries, SyncRes::s_queries) << "%";
-    g_log << Logger::Notice << ", " << ratePercentage(SyncRes::s_throttledqueries, SyncRes::s_outqueries + SyncRes::s_throttledqueries) << "% throttled" << endl;
-    g_log << Logger::Notice << "stats: " << SyncRes::s_tcpoutqueries << "/" << SyncRes::s_dotoutqueries << "/" << getCurrentIdleTCPConnections() << " outgoing tcp/dot/idle connections, " << broadcastAccFunction<uint64_t>(pleaseGetConcurrentQueries) << " queries running, " << SyncRes::s_outgoingtimeouts << " outgoing timeouts " << endl;
-
-    uint64_t pcSize = broadcastAccFunction<uint64_t>(pleaseGetPacketCacheSize);
-    uint64_t pcHits = broadcastAccFunction<uint64_t>(pleaseGetPacketCacheHits);
-    g_log << Logger::Notice << "stats: " << pcSize << " packet cache entries, " << ratePercentage(pcHits, SyncRes::s_queries) << "% packet cache hits" << endl;
-
+  uint64_t pcSize = g_packetCache ? g_packetCache->size() : 0;
+  uint64_t pcHits = g_packetCache ? g_packetCache->getHits() : 0;
+
+  auto log = g_slog->withName("stats");
+
+  auto qcounter = g_Counters.sum(rec::Counter::qcounter);
+  auto syncresqueries = g_Counters.sum(rec::Counter::syncresqueries);
+  auto outqueries = g_Counters.sum(rec::Counter::outqueries);
+  auto throttledqueries = g_Counters.sum(rec::Counter::throttledqueries);
+  auto tcpoutqueries = g_Counters.sum(rec::Counter::tcpoutqueries);
+  auto dotoutqueries = g_Counters.sum(rec::Counter::dotoutqueries);
+  auto outgoingtimeouts = g_Counters.sum(rec::Counter::outgoingtimeouts);
+  if (qcounter > 0 && (cacheHits + cacheMisses) > 0 && syncresqueries > 0 && outqueries > 0) {
+    if (!g_slogStructured) {
+      g_log << Logger::Notice << "stats: " << qcounter << " questions, " << cacheSize << " cache entries, " << negCacheSize << " negative entries, " << ratePercentage(cacheHits, cacheHits + cacheMisses) << "% cache hits" << endl;
+      g_log << Logger::Notice << "stats: record cache contended/acquired " << rc_stats.first << '/' << rc_stats.second << " = " << rrc << '%' << endl;
+      g_log << Logger::Notice << "stats: packet cache contended/acquired " << pc_stats.first << '/' << pc_stats.second << " = " << rpc << '%' << endl;
+
+      g_log << Logger::Notice << "stats: throttle map: "
+            << SyncRes::getThrottledServersSize() << ", ns speeds: "
+            << SyncRes::getNSSpeedsSize() << ", failed ns: "
+            << SyncRes::getFailedServersSize() << ", ednsmap: "
+            << SyncRes::getEDNSStatusesSize() << ", non-resolving: "
+            << SyncRes::getNonResolvingNSSize() << ", saved-parentsets: "
+            << SyncRes::getSaveParentsNSSetsSize()
+            << endl;
+      g_log << Logger::Notice << "stats: outpacket/query ratio " << ratePercentage(outqueries, syncresqueries) << "%";
+      g_log << Logger::Notice << ", " << ratePercentage(throttledqueries, outqueries + throttledqueries) << "% throttled" << endl;
+      g_log << Logger::Notice << "stats: " << tcpoutqueries << "/" << dotoutqueries << "/" << getCurrentIdleTCPConnections() << " outgoing tcp/dot/idle connections, " << broadcastAccFunction<uint64_t>(pleaseGetConcurrentQueries) << " queries running, " << outgoingtimeouts << " outgoing timeouts " << endl;
+
+      g_log << Logger::Notice << "stats: " << pcSize << " packet cache entries, " << ratePercentage(pcHits, qcounter) << "% packet cache hits" << endl;
+
+      g_log << Logger::Notice << "stats: tasks pushed/expired/queuesize: " << taskPushes << '/' << taskExpired << '/' << taskSize << endl;
+    }
+    else {
+      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),
+                "record-cache-hitratio-perc", Logging::Loggable(ratePercentage(cacheHits, cacheHits + cacheMisses)),
+                "record-cache-contended", Logging::Loggable(rc_stats.first),
+                "record-cache-acquired", Logging::Loggable(rc_stats.second),
+                "record-cache-contended-perc", Logging::Loggable(rrc),
+                "packetcache-contended", Logging::Loggable(pc_stats.first),
+                "packetcache-acquired", Logging::Loggable(pc_stats.second),
+                "packetcache-contended-perc", Logging::Loggable(rpc));
+      log->info(Logr::Info, report,
+                "throttle-entries", Logging::Loggable(SyncRes::getThrottledServersSize()),
+                "nsspeed-entries", Logging::Loggable(SyncRes::getNSSpeedsSize()),
+                "failed-host-entries", Logging::Loggable(SyncRes::getFailedServersSize()),
+                "edns-entries", Logging::Loggable(SyncRes::getEDNSStatusesSize()),
+                "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, 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, report,
+                "packetcache-entries", Logging::Loggable(pcSize),
+                "packetcache-hitratio-perc", Logging::Loggable(ratePercentage(pcHits, qcounter)),
+                "taskqueue-pushed", Logging::Loggable(taskPushes),
+                "taskqueue-expired", Logging::Loggable(taskExpired),
+                "taskqueue-size", Logging::Loggable(taskSize));
+    }
     size_t idx = 0;
     for (const auto& threadInfo : RecThreadInfo::infos()) {
       if (threadInfo.isWorker()) {
-        g_log << Logger::Notice << "stats: thread " << idx << " has been distributed " << threadInfo.numberOfDistributedQueries << " queries" << endl;
+        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;
       }
     }
-
-    g_log << Logger::Notice << "stats: tasks pushed/expired/queuesize: " << taskPushes << '/' << taskExpired << '/' << taskSize << endl;
-    time_t now = time(0);
-    if (lastOutputTime && lastQueryCount && now != lastOutputTime) {
-      g_log << Logger::Notice << "stats: " << (SyncRes::s_queries - lastQueryCount) / (now - lastOutputTime) << " qps (average over " << (now - lastOutputTime) << " seconds)" << endl;
+    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)));
     }
     lastOutputTime = now;
-    lastQueryCount = SyncRes::s_queries;
+    lastQueryCount = qcounter;
+  }
+  else if (statsWanted) {
+    SLOG(g_log << Logger::Notice << "stats: no stats yet!" << endl,
+         log->info(Logr::Notice, "No stats yet"));
   }
-  else if (statsWanted)
-    g_log << Logger::Notice << "stats: no stats yet!" << endl;
 
   statsWanted = false;
 }
 
-static std::shared_ptr<NetmaskGroup> parseACL(const std::string& aclFile, const std::string& aclSetting)
+static std::shared_ptr<NetmaskGroup> parseACL(const std::string& aclFile, const std::string& aclSetting, Logr::log_t log)
 {
   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);
+      }
     }
-    g_log << Logger::Info << "Done parsing " << result->size() << " " << aclSetting << " ranges from file '" << ::arg()[aclFile] << "' - overriding '" << aclSetting << "' setting" << endl;
-
-    return result;
+    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(file)));
   }
   else if (!::arg()[aclSetting].empty()) {
     vector<string> ips;
     stringtok(ips, ::arg()[aclSetting], ", ");
 
-    g_log << Logger::Info << aclSetting << ": ";
-    for (vector<string>::const_iterator i = ips.begin(); i != ips.end(); ++i) {
-      result->addMask(*i);
-      if (i != ips.begin())
-        g_log << Logger::Info << ", ";
-      g_log << Logger::Info << *i;
+    for (const auto& address : ips) {
+      result->addMask(address);
+    }
+    if (!g_slogStructured) {
+      g_log << Logger::Info << aclSetting << ": ";
+      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;
+    }
+    else {
+      log->info(Logr::Info, "Setting access control", "acl", Logging::Loggable(aclSetting), "addresses", Logging::IterLoggable(ips.begin(), ips.end()));
     }
-    g_log << Logger::Info << endl;
-
-    return result;
   }
 
-  return nullptr;
+  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;
 }
 
 void parseACLs()
 {
+  auto log = g_slog->withName("config");
+
   static bool l_initialized;
 
   if (l_initialized) { // only reload configuration file on second call
-    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(), "allow-from-file"))
-      throw runtime_error("Unable to re-parse configuration file '" + configname + "'");
-    ::arg().preParseFile(configname.c_str(), "allow-from", LOCAL_NETS);
+    string configName = ::arg()["config-dir"] + "/recursor";
+    if (!::arg()["config-name"].empty()) {
+      configName = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"];
+    }
+    cleanSlashes(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;
+      }
+    }
+    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& fn : extraConfigs) {
-      if (!::arg().preParseFile(fn.c_str(), "allow-from-file", ::arg()["allow-from-file"]))
-        throw runtime_error("Unable to re-parse configuration file include '" + fn + "'");
-      if (!::arg().preParseFile(fn.c_str(), "allow-from", ::arg()["allow-from"]))
-        throw runtime_error("Unable to re-parse configuration file include '" + fn + "'");
+      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(fn.c_str(), "allow-notify-from-file", ::arg()["allow-notify-from-file"]))
-        throw runtime_error("Unable to re-parse configuration file include '" + fn + "'");
-      if (!::arg().preParseFile(fn.c_str(), "allow-notify-from", ::arg()["allow-notify-from"]))
-        throw runtime_error("Unable to re-parse configuration file include '" + fn + "'");
+        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");
+  auto allowFrom = parseACL("allow-from-file", "allow-from", log);
 
-  if (allowFrom->size() == 0) {
-    if (::arg()["local-address"] != "127.0.0.1" && ::arg().asNum("local-port") == 53)
-      g_log << Logger::Warning << "WARNING: Allowing queries from all IP addresses - this can be a security risk!" << endl;
+  if (allowFrom->empty()) {
+    if (::arg()["local-address"] != "127.0.0.1" && ::arg().asNum("local-port") == 53) {
+      SLOG(g_log << Logger::Warning << "WARNING: Allowing queries from all IP addresses - this can be a security risk!" << endl,
+           log->info(Logr::Warning, "WARNING: Allowing queries from all IP addresses - this can be a security risk!"));
+    }
     allowFrom = nullptr;
   }
 
   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");
+  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;
@@ -1054,49 +1375,68 @@ 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]
   }
 }
 
 template <class T>
-void* voider(const boost::function<T*()>& func)
+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)
+{
+  lhs.insert(lhs.end(), rhs.begin(), rhs.end());
+  return lhs;
+}
+
+static vector<pair<DNSName, uint16_t>>& operator+=(vector<pair<DNSName, uint16_t>>& lhs, const vector<pair<DNSName, uint16_t>>& rhs)
+{
+  lhs.insert(lhs.end(), rhs.begin(), rhs.end());
+  return lhs;
+}
+
+static ProxyMappingStats_t& operator+=(ProxyMappingStats_t& lhs, const ProxyMappingStats_t& rhs)
 {
-  a.insert(a.end(), b.begin(), b.end());
-  return a;
+  for (const auto& [key, entry] : rhs) {
+    lhs[key].netmaskMatches += entry.netmaskMatches;
+    lhs[key].suffixMatches += entry.suffixMatches;
+  }
+  return lhs;
 }
 
-static vector<pair<DNSName, uint16_t>>& operator+=(vector<pair<DNSName, uint16_t>>& a, const vector<pair<DNSName, uint16_t>>& b)
+static RemoteLoggerStats_t& operator+=(RemoteLoggerStats_t& lhs, const RemoteLoggerStats_t& rhs)
 {
-  a.insert(a.end(), b.begin(), b.end());
-  return a;
+  for (const auto& [key, entry] : rhs) {
+    lhs[key] += entry;
+  }
+  return lhs;
 }
 
 // This function should only be called by the handler to gather
@@ -1105,218 +1445,247 @@ static vector<pair<DNSName, uint16_t>>& operator+=(vector<pair<DNSName, uint16_t
 // metrics.
 // Note that this currently skips the handler, but includes the taskThread(s).
 template <class T>
-T broadcastAccFunction(const boost::function<T*()>& func)
+T broadcastAccFunction(const std::function<T*()>& func)
 {
   if (!RecThreadInfo::self().isHandler()) {
-    g_log << Logger::Error << "broadcastAccFunction has been called by a worker (" << RecThreadInfo::id() << ")" << endl;
+    SLOG(g_log << Logger::Error << "broadcastAccFunction has been called by a worker (" << RecThreadInfo::id() << ")" << endl,
+         g_slog->withName("runtime")->info(Logr::Critical, "broadcastAccFunction has been called by a worker")); // tid will be added
     _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]
   }
   return ret;
 }
 
-template string broadcastAccFunction(const boost::function<string*()>& fun); // explicit instantiation
-template RecursorControlChannel::Answer broadcastAccFunction(const boost::function<RecursorControlChannel::Answer*()>& fun); // explicit instantiation
-template uint64_t broadcastAccFunction(const boost::function<uint64_t*()>& fun); // explicit instantiation
-template vector<ComboAddress> broadcastAccFunction(const boost::function<vector<ComboAddress>*()>& fun); // explicit instantiation
-template vector<pair<DNSName, uint16_t>> broadcastAccFunction(const boost::function<vector<pair<DNSName, uint16_t>>*()>& fun); // explicit instantiation
-template ThreadTimes broadcastAccFunction(const boost::function<ThreadTimes*()>& fun);
+template string broadcastAccFunction(const std::function<string*()>& fun); // explicit instantiation
+template RecursorControlChannel::Answer broadcastAccFunction(const std::function<RecursorControlChannel::Answer*()>& fun); // explicit instantiation
+template uint64_t broadcastAccFunction(const std::function<uint64_t*()>& fun); // explicit instantiation
+template vector<ComboAddress> broadcastAccFunction(const std::function<vector<ComboAddress>*()>& fun); // explicit instantiation
+template vector<pair<DNSName, uint16_t>> broadcastAccFunction(const std::function<vector<pair<DNSName, uint16_t>>*()>& fun); // explicit instantiation
+template ThreadTimes broadcastAccFunction(const std::function<ThreadTimes*()>& fun);
+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[])
+static int initNet(Logr::log_t log)
 {
-  g_slogStructured = ::arg().mustDo("structured-logging");
-
-  g_log.setName(g_programname);
-  g_log.disableSyslog(::arg().mustDo("disable-syslog"));
-  g_log.setTimestamps(::arg().mustDo("log-timestamp"));
-
-  if (!::arg()["logging-facility"].empty()) {
-    int val = logFacilityToLOG(::arg().asNum("logging-facility"));
-    if (val >= 0)
-      g_log.setFacility(val);
-    else
-      g_log << Logger::Error << "Unknown logging facility " << ::arg().asNum("logging-facility") << endl;
-  }
-
-  showProductVersion();
-
-  g_disthashseed = dns_random(0xffffffff);
-
-  checkLinuxIPv6Limits();
+  checkLinuxIPv6Limits(log);
   try {
     pdns::parseQueryLocalAddress(::arg()["query-local-address"]);
   }
   catch (std::exception& e) {
-    g_log << Logger::Error << "Assigning local query addresses: " << e.what();
-    exit(99);
+    SLOG(g_log << Logger::Error << "Assigning local query addresses: " << e.what(),
+         log->error(Logr::Error, e.what(), "Unable to assign local query address"));
+    return 99;
   }
 
   if (pdns::isQueryLocalAddressFamilyEnabled(AF_INET)) {
     SyncRes::s_doIPv4 = true;
-    g_log << Logger::Warning << "Enabling IPv4 transport for outgoing queries" << endl;
+    SLOG(g_log << Logger::Warning << "Enabling IPv4 transport for outgoing queries" << endl,
+         log->info(Logr::Notice, "Enabling IPv4 transport for outgoing queries"));
   }
   else {
-    g_log << Logger::Warning << "NOT using IPv4 for outgoing queries - add an IPv4 address (like '0.0.0.0') to query-local-address to enable" << endl;
+    SLOG(g_log << Logger::Warning << "NOT using IPv4 for outgoing queries - add an IPv4 address (like '0.0.0.0') to query-local-address to enable" << endl,
+         log->info(Logr::Warning, "NOT using IPv4 for outgoing queries - add an IPv4 address (like '0.0.0.0') to query-local-address to enable"));
   }
 
   if (pdns::isQueryLocalAddressFamilyEnabled(AF_INET6)) {
     SyncRes::s_doIPv6 = true;
-    g_log << Logger::Warning << "Enabling IPv6 transport for outgoing queries" << endl;
+    SLOG(g_log << Logger::Warning << "Enabling IPv6 transport for outgoing queries" << endl,
+         log->info(Logr::Notice, "Enabling IPv6 transport for outgoing queries"));
   }
   else {
-    g_log << Logger::Warning << "NOT using IPv6 for outgoing queries - add an IPv6 address (like '::') to query-local-address to enable" << endl;
+    SLOG(g_log << Logger::Warning << "NOT using IPv6 for outgoing queries - add an IPv6 address (like '::') to query-local-address to enable" << endl,
+         log->info(Logr::Warning, "NOT using IPv6 for outgoing queries - add an IPv6 address (like '::') to query-local-address to enable"));
   }
 
   if (!SyncRes::s_doIPv6 && !SyncRes::s_doIPv4) {
-    g_log << Logger::Error << "No outgoing addresses configured! Can not continue" << endl;
-    exit(99);
+    SLOG(g_log << Logger::Error << "No outgoing addresses configured! Can not continue" << endl,
+         log->info(Logr::Error, "No outgoing addresses configured! Can not continue"));
+    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 {
-    g_log << Logger::Error << "Unknown DNSSEC mode " << ::arg()["dnssec"] << endl;
-    exit(1);
+    SLOG(g_log << Logger::Error << "Unknown DNSSEC mode " << ::arg()["dnssec"] << endl,
+         log->info(Logr::Error, "Unknown DNSSEC mode", "dnssec", Logging::Loggable(::arg()["dnssec"])));
+    return 1;
   }
 
   g_signatureInceptionSkew = ::arg().asNum("signature-inception-skew");
   if (g_signatureInceptionSkew < 0) {
-    g_log << Logger::Error << "A negative value for 'signature-inception-skew' is not allowed" << endl;
-    exit(1);
+    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"));
+    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");
-  g_maxPacketCacheEntries = ::arg().asNum("max-packetcache-entries");
-
-  luaConfigDelayedThreads delayedLuaThreads;
-  try {
-    loadRecursorLuaConfig(::arg()["lua-config-file"], delayedLuaThreads);
+  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) {
-    g_log << Logger::Error << "Cannot load Lua configuration: " << e.reason << endl;
-    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("::");
 
-    g_log << Logger::Warning << "Will not send queries to: ";
-    for (vector<string>::const_iterator i = ips.begin(); i != ips.end(); ++i) {
-      SyncRes::addDontQuery(*i);
-      if (i != ips.begin())
-        g_log << Logger::Warning << ", ";
-      g_log << Logger::Warning << *i;
+    for (const auto& anIP : ips) {
+      SyncRes::addDontQuery(anIP);
+    }
+    if (!g_slogStructured) {
+      g_log << Logger::Warning << "Will not send queries to: ";
+      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;
+    }
+    else {
+      log->info(Logr::Notice, "Will not send queries to", "addresses", Logging::IterLoggable(ips.begin(), ips.end()));
     }
-    g_log << Logger::Warning << endl;
-  }
-
-  /* this needs to be done before parseACLs(), which call broadcastFunction() */
-  RecThreadInfo::setWeDistributeQueries(::arg().mustDo("pdns-distributes-queries"));
-  if (RecThreadInfo::weDistributeQueries()) {
-    g_log << Logger::Warning << "PowerDNS Recursor itself will distribute queries over threads" << endl;
-  }
-
-  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;
-    g_dnssecLOG = true;
-  }
-  string myHostname = getHostname();
-  if (myHostname == "UNKNOWN") {
-    g_log << Logger::Warning << "Unable to get the hostname, NSID and id.server values will be empty" << endl;
-    myHostname = "";
   }
+}
 
+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_nopacketcache = ::arg().mustDo("disable-packetcache");
-
   SyncRes::s_maxnegttl = ::arg().asNum("max-negative-ttl");
   SyncRes::s_maxbogusttl = ::arg().asNum("max-cache-bogus-ttl");
   SyncRes::s_maxcachettl = max(::arg().asNum("max-cache-ttl"), 15);
+
   SyncRes::s_packetcachettl = ::arg().asNum("packetcache-ttl");
-  // Cap the packetcache-servfail-ttl to the packetcache-ttl
-  uint32_t packetCacheServFailTTL = ::arg().asNum("packetcache-servfail-ttl");
-  SyncRes::s_packetcacheservfailttl = (packetCacheServFailTTL > SyncRes::s_packetcachettl) ? SyncRes::s_packetcachettl : packetCacheServFailTTL;
+  // Cap the packetcache-servfail-ttl and packetcache-negative-ttl to packetcache-ttl
+  SyncRes::s_packetcacheservfailttl = std::min(static_cast<unsigned int>(::arg().asNum("packetcache-servfail-ttl")), SyncRes::s_packetcachettl);
+  SyncRes::s_packetcachenegativettl = std::min(static_cast<unsigned int>(::arg().asNum("packetcache-negative-ttl")), SyncRes::s_packetcachettl);
+
   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");
   RecursorPacketCache::s_refresh_ttlperc = SyncRes::s_refresh_ttlperc;
   SyncRes::s_tcp_fast_open = ::arg().asNum("tcp-fast-open");
   SyncRes::s_tcp_fast_open_connect = ::arg().mustDo("tcp-fast-open-connect");
 
   SyncRes::s_dot_to_port_853 = ::arg().mustDo("dot-to-port-853");
   SyncRes::s_event_trace_enabled = ::arg().asNum("event-trace-enabled");
-
-  if (SyncRes::s_tcp_fast_open_connect) {
-    checkFastOpenSysctl(true);
-    checkTFOconnect();
+  SyncRes::s_save_parent_ns_set = ::arg().mustDo("save-parent-ns-set");
+  SyncRes::s_max_busy_dot_probes = ::arg().asNum("max-busy-dot-probes");
+  {
+    uint64_t sse = ::arg().asNum("serve-stale-extensions");
+    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)));
+      return 1;
+    }
+    MemRecursorCache::s_maxServedStaleExtensions = sse;
+    NegCache::s_maxServedStaleExtensions = sse;
   }
 
-  if (SyncRes::s_serverID.empty()) {
-    SyncRes::s_serverID = myHostname;
+  if (SyncRes::s_tcp_fast_open_connect) {
+    checkFastOpenSysctl(true, log);
+    checkTFOconnect(log);
   }
-
   SyncRes::s_ecsipv4limit = ::arg().asNum("ecs-ipv4-bits");
   SyncRes::s_ecsipv6limit = ::arg().asNum("ecs-ipv6-bits");
   SyncRes::clearECSStats();
@@ -1327,12 +1696,8 @@ static int serviceMain(int argc, char* argv[])
   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"];
@@ -1343,8 +1708,9 @@ static int serviceMain(int argc, char* argv[])
     SyncRes::s_hardenNXD = SyncRes::HardenNXD::No;
   }
   else if (value != "dnssec") {
-    g_log << Logger::Error << "Unknown nothing-below-nxdomain mode: " << value << endl;
-    exit(1);
+    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)));
+    return 1;
   }
 
   if (!::arg().isEmpty("ecs-scope-zero-address")) {
@@ -1352,138 +1718,241 @@ static int serviceMain(int argc, char* argv[])
     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_XPFAcl.toMasks(::arg()["xpf-allow-from"]);
-  g_xpfRRCode = ::arg().asNum("xpf-rr-code");
+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"));
+  }
 
-  g_proxyProtocolACL.toMasks(::arg()["proxy-protocol-from"]);
-  g_proxyProtocolMaximumSize = ::arg().asNum("proxy-protocol-maximum-size");
+#ifdef SO_REUSEPORT
+  g_reusePort = ::arg().mustDo("reuseport");
+#endif
 
-  if (!::arg()["dns64-prefix"].empty()) {
-    try {
-      auto dns64Prefix = Netmask(::arg()["dns64-prefix"]);
-      if (dns64Prefix.getBits() != 96) {
-        g_log << Logger::Error << "Invalid prefix for 'dns64-prefix', the current implementation only supports /96 prefixes: " << ::arg()["dns64-prefix"] << endl;
-        exit(1);
+  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) {
-      g_log << Logger::Error << "Invalid prefix '" << ::arg()["dns64-prefix"] << "' for 'dns64-prefix': " << ne.reason << endl;
-      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");
-
-  std::tie(g_initialDomainMap, g_initialAllowNotifyFor) = parseZoneConfiguration();
-
-  g_latencyStatSize = ::arg().asNum("latency-statistic-size");
+    // 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);
+    }
+  }
+}
 
-  g_logCommonErrors = ::arg().mustDo("log-common-errors");
-  g_logRPZChanges = ::arg().mustDo("log-rpz-changes");
+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_anyToTcp = ::arg().mustDo("any-to-tcp");
-  g_udpTruncationThreshold = ::arg().asNum("udp-truncation-threshold");
+  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_lowercaseOutgoing = ::arg().mustDo("lowercase-outgoing");
+  if (Utility::getpid() == 1) {
+    /* We are running as pid 1, register sigterm and sigint handler
 
-  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 {
-    g_log << Logger::Error << "Unknown edns-padding-mode: " << ::arg()["edns-padding-mode"] << endl;
-    exit(1);
-  }
-  g_paddingTag = ::arg().asNum("edns-padding-tag");
+      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".
 
-  RecThreadInfo::setNumDistributorThreads(::arg().asNum("distributor-threads"));
-  RecThreadInfo::setNumWorkerThreads(::arg().asNum("threads"));
-  if (RecThreadInfo::numWorkers() < 1) {
-    g_log << Logger::Warning << "Asked to run with 0 threads, raising to 1 instead" << endl;
-    RecThreadInfo::setNumWorkerThreads(1);
+      So TL;DR: If we're running pid 1 (container), we should handle SIGTERM and SIGINT ourselves */
+
+    signal(SIGTERM, termIntHandler);
+    signal(SIGINT, termIntHandler);
   }
 
-  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) {
-    g_log << Logger::Warning << "Asked to run with illegal max-concurrent-requests-per-tcp-connection, setting to default (10)" << endl;
-    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");
+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
+  }
+}
 
-  g_gettagNeedsEDNSOptions = ::arg().mustDo("gettag-needs-edns-options");
+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;
+    }
+#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"])));
+  }
 
-  s_statisticsInterval = ::arg().asNum("statistics-interval");
+  checkSocketDir(log);
 
-  g_addExtendedResolutionDNSErrors = ::arg().mustDo("extended-resolution-errors");
+  g_pidfname = ::arg()["socket-dir"] + "/" + g_programname + ".pid";
+  if (!g_pidfname.empty()) {
+    unlink(g_pidfname.c_str()); // remove possible old pid file
+  }
+  writePid(log);
 
-  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 {
-      g_log << Logger::Warning << "Aggressive NSEC/NSEC3 caching is enabled but DNSSEC validation is not set to 'validate', 'log-fail' or 'process', ignoring" << endl;
-    }
+  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));
+    for (const auto& part : parts) {
+      dontThrottleNetmasks.addMask(Netmask(part));
     }
     g_dontThrottleNetmasks.setState(std::move(dontThrottleNetmasks));
   }
@@ -1492,8 +1961,8 @@ static int serviceMain(int argc, char* argv[])
     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));
   }
@@ -1503,190 +1972,259 @@ static int serviceMain(int argc, char* argv[])
     vector<string> parts;
     stringtok(parts, ::arg()["dot-to-auth-names"], " ,");
 #ifndef HAVE_DNS_OVER_TLS
-    if (parts.size()) {
-      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;
+    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;
-    g_log << Logger::Warning << "Asked to run with a distribution-load-factor below 1.0, disabling it instead" << endl;
-  }
-
-#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);
-        makeTCPServerSockets(deferredAdds, tcpSockets);
+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);
-        makeTCPServerSockets(deferredAdds, tcpSockets);
+      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);
-    makeTCPServerSockets(g_deferredAdds, tcpSockets);
+  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")) {
-    g_log << Logger::Warning << "Calling daemonize, going to background" << endl;
-    g_log.toConsole(Logger::Critical);
-    daemonize();
+  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();
+  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) {
-    g_log << Logger::Error << "Unable to initialize sodium crypto library" << endl;
-    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;
+  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) {
-      g_log << Logger::Error << "Unable to chroot when running from systemd. Please disable chroot= or set the 'Type' for this service to 'simple'" << endl;
-      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"));
     }
-#endif
-    if (chroot(::arg()["chroot"].c_str()) < 0 || chdir("/") < 0) {
-      int err = errno;
-      g_log << Logger::Error << "Unable to chroot to '" + ::arg()["chroot"] + "': " << strerror(err) << ", exiting" << endl;
-      exit(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"));
     }
-    else
-      g_log << Logger::Info << "Chrooted to '" << ::arg()["chroot"] << "'" << endl;
   }
 
-  checkSocketDir();
+  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();
+  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) {
-    g_log << Logger::Warning << e.what() << endl;
+#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);
   delayedLuaThreads.rpzPrimaryThreads.clear(); // no longer needed
 
-  RecThreadInfo::makeThreadPipes();
+  RecThreadInfo::makeThreadPipes(log);
 
   g_tcpTimeout = ::arg().asNum("client-tcp-timeout");
   g_maxTCPPerClient = ::arg().asNum("max-tcp-per-client");
@@ -1705,118 +2243,109 @@ static int serviceMain(int argc, char* argv[])
   disableStats(StatComponent::RecControl, ::arg()["stats-rec-control-disabled-list"]);
   disableStats(StatComponent::SNMP, ::arg()["stats-snmp-disabled-list"]);
 
-  if (::arg().mustDo("snmp-agent")) {
-    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();
-  }
+  // Run before any thread doing stats related things
+  registerAllStats();
 
-  int port = ::arg().asNum("udp-source-port-min");
-  if (port < 1024 || port > 65535) {
-    g_log << Logger::Error << "Unable to launch, udp-source-port-min is not a valid port number" << endl;
-    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) {
-    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;
-    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) {
-      g_log << Logger::Error << "Unable to launch, udp-source-port-avoid contains an invalid port number: " << part << endl;
-      exit(99); // this isn't going to fix itself either
-    }
-    g_avoidUdpSourcePorts.insert(port);
+  initSNMP(log);
+
+  ret = initPorts(log);
+  if (ret != 0) {
+    return ret;
   }
 
-  return RecThreadInfo::runThreads();
+  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();
   }
   catch (std::exception& e) {
-    if (g_logCommonErrors)
-      g_log << Logger::Error << "PIPE function we executed created exception: " << e.what() << endl; // but what if they wanted an answer.. we send 0
+    if (g_logCommonErrors) {
+      SLOG(g_log << Logger::Error << "PIPE function we executed created exception: " << e.what() << endl, // but what if they wanted an answer.. we send 0
+           g_slog->withName("runtime")->error(Logr::Error, e.what(), "PIPE function we executed created exception", "exception", Logging::Loggable("std::exception")));
+    }
   }
   catch (PDNSException& e) {
-    if (g_logCommonErrors)
-      g_log << Logger::Error << "PIPE function we executed created PDNS exception: " << e.reason << endl; // but what if they wanted an answer.. we send 0
+    if (g_logCommonErrors) {
+      SLOG(g_log << Logger::Error << "PIPE function we executed created PDNS exception: " << e.reason << endl, // but what if they wanted an answer.. we send 0
+           g_slog->withName("runtime")->error(Logr::Error, e.reason, "PIPE function we executed created exception", "exception", Logging::Loggable("PDNSException")));
+    }
   }
   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");
     }
     string msg = g_rcc.recv(clientfd).d_str;
-    g_log << Logger::Info << "Received rec_control command '" << msg << "' via controlsocket" << endl;
+    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();
   }
   catch (const std::exception& e) {
-    g_log << Logger::Error << "Error dealing with control socket request: " << e.what() << endl;
+    SLOG(g_log << Logger::Error << "Error dealing with control socket request: " << e.what() << endl,
+         log->error(Logr::Error, e.what(), "Exception while dealing with control socket request", "exception", Logging::Loggable("std::exception")));
   }
   catch (const PDNSException& ae) {
-    g_log << Logger::Error << "Error dealing with control socket request: " << ae.reason << endl;
+    SLOG(g_log << Logger::Error << "Error dealing with control socket request: " << ae.reason << endl,
+         log->error(Logr::Error, ae.reason, "Exception while dealing with control socket request", "exception", Logging::Loggable("PDNSException")));
   }
 }
 
 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;
     }
   }
-  void setPeriod(time_t p)
+
+  [[nodiscard]] time_t getPeriod() const
   {
-    period.tv_sec = p;
+    return period.tv_sec;
+  }
+
+  void setPeriod(time_t newperiod)
+  {
+    period.tv_sec = newperiod;
   }
 
   void updateLastRun()
@@ -1824,7 +2353,7 @@ public:
     Utility::gettimeofday(&last_run);
   }
 
-  bool hasRun() const
+  [[nodiscard]] bool hasRun() const
   {
     return last_run.tv_sec != 0 || last_run.tv_usec != 0;
   }
@@ -1835,185 +2364,342 @@ private:
     0, 0
   };
   struct timeval period;
-  const string name;
+  string name;
 };
 
-static void houseKeeping(void*)
+static void houseKeepingWork(Logr::log_t log)
 {
-  static thread_local bool t_running; // houseKeeping can get suspended in secpoll, and be restarted, which makes us do duplicate work
+  struct timeval now
+  {
+  };
+  Utility::gettimeofday(&now);
+  t_Counters.updateSnap(now, g_regressionTestMode);
 
-  try {
-    if (t_running) {
-      return;
-    }
-    t_running = true;
+  // 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);
+  });
 
-    struct timeval now;
-    Utility::gettimeofday(&now);
+  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);
+#ifdef HAVE_FSTRM
+    checkFrameStreamExport(luaconfsLocal, luaconfsLocal->frameStreamExportConfig, t_frameStreamServersInfo);
+    checkFrameStreamExport(luaconfsLocal, luaconfsLocal->nodFrameStreamExportConfig, t_nodFrameStreamServersInfo);
+#endif
+  });
 
-    // Below are the tasks that run for every recursorThread, including handler and taskThread
-    static thread_local PeriodicTask packetCacheTask{"packetCacheTask", 5};
-    packetCacheTask.runIfDue(now, []() {
-      t_packetCache->doPruneTo(g_maxPacketCacheEntries / (RecThreadInfo::numDistributors() + RecThreadInfo::numWorkers()));
+  // 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));
+      }
+    });
+  }
+  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);
     });
 
-    // This is a full scan
-    static thread_local PeriodicTask pruneNSpeedTask{"pruneNSSpeedTask", 100};
-    pruneNSpeedTask.runIfDue(now, [now]() {
-      SyncRes::pruneNSSpeeds(now.tv_sec - 300);
+    static PeriodicTask negCachePruneTask{"NegCachePrunteTask", 5};
+    negCachePruneTask.runIfDue(now, [now]() {
+      g_negCache->prune(now.tv_sec, g_maxCacheEntries / 8);
     });
 
-    static thread_local PeriodicTask pruneEDNSTask{"pruneEDNSTask", 5}; // period could likely be longer
-    pruneEDNSTask.runIfDue(now, [now]() {
-      SyncRes::pruneEDNSStatuses(now.tv_sec - 2 * 3600);
+    static PeriodicTask aggrNSECPruneTask{"AggrNSECPruneTask", 5};
+    aggrNSECPruneTask.runIfDue(now, [now]() {
+      if (g_aggressiveNSECCache) {
+        g_aggressiveNSECCache->prune(now.tv_sec);
+      }
     });
 
-    static thread_local PeriodicTask pruneThrottledTask{"pruneThrottledTask", 5};
-    pruneThrottledTask.runIfDue(now, []() {
-      SyncRes::pruneThrottledServers();
+    static PeriodicTask pruneNSpeedTask{"pruneNSSpeedTask", 30};
+    pruneNSpeedTask.runIfDue(now, [now]() {
+      SyncRes::pruneNSSpeeds(now.tv_sec - 300);
     });
 
-    static thread_local PeriodicTask pruneTCPTask{"pruneTCPTask", 5};
-    pruneTCPTask.runIfDue(now, [now]() {
-      t_tcp_manager.cleanup(now);
+    static PeriodicTask pruneEDNSTask{"pruneEDNSTask", 60};
+    pruneEDNSTask.runIfDue(now, [now]() {
+      SyncRes::pruneEDNSStatuses(now.tv_sec);
     });
 
-    const auto& info = RecThreadInfo::self();
-
-    // 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
-      runTaskOnce(g_logCommonErrors);
-
-      static PeriodicTask ztcTask{"ZTC", 60};
-      static map<DNSName, RecZoneToCache::State> ztcStates;
-      auto luaconfsLocal = g_luaconfs.getLocal();
-      ztcTask.runIfDue(now, [&luaconfsLocal]() {
-        RecZoneToCache::maintainStates(luaconfsLocal->ztcConfigs, ztcStates, luaconfsLocal->generation);
-        for (auto& ztc : luaconfsLocal->ztcConfigs) {
-          RecZoneToCache::ZoneToCache(ztc.second, ztcStates.at(ztc.first));
-        }
+    if (SyncRes::s_max_busy_dot_probes > 0) {
+      static PeriodicTask pruneDoTProbeMap{"pruneDoTProbeMapTask", 60};
+      pruneDoTProbeMap.runIfDue(now, [now]() {
+        SyncRes::pruneDoTProbeMap(now.tv_sec);
       });
     }
-    else if (info.isHandler()) {
-      static PeriodicTask recordCachePruneTask{"RecordCachePruneTask", 5};
-      recordCachePruneTask.runIfDue(now, []() {
-        g_recCache->doPrune(g_maxCacheEntries);
-      });
 
-      static PeriodicTask negCachePruneTask{"NegCachePrunteTask", 5};
-      negCachePruneTask.runIfDue(now, []() {
-        g_negCache->prune(g_maxCacheEntries / 10);
-      });
+    static PeriodicTask pruneThrottledTask{"pruneThrottledTask", 5};
+    pruneThrottledTask.runIfDue(now, [now]() {
+      SyncRes::pruneThrottledServers(now.tv_sec);
+    });
 
-      static PeriodicTask aggrNSECPruneTask{"AggrNSECPruneTask", 5};
-      aggrNSECPruneTask.runIfDue(now, [now]() {
-        if (g_aggressiveNSECCache) {
-          g_aggressiveNSECCache->prune(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);
+    });
 
-      // Divide by 12 to get the original 2 hour cycle if s_maxcachettl is default (1 day)
-      static PeriodicTask rootUpdateTask{"rootUpdateTask", std::max(SyncRes::s_maxcachettl / 12, 10U)};
-      rootUpdateTask.runIfDue(now, [now]() {
-        int res = SyncRes::getRootNS(now, nullptr, 0);
-        if (res == 0) {
-          try {
-            primeRootNSZones(g_dnssecmode, 0);
-          }
-          catch (const std::exception& e) {
-            g_log << Logger::Error << "Exception while priming the root NS zones: " << e.what() << endl;
-          }
-          catch (const PDNSException& e) {
-            g_log << Logger::Error << "Exception while priming the root NS zones: " << e.reason << endl;
-          }
-          catch (const ImmediateServFailException& e) {
-            g_log << Logger::Error << "Exception while priming the root NS zones: " << e.reason << endl;
-          }
-          catch (const PolicyHitException& e) {
-            g_log << Logger::Error << "Policy hit while priming the root NS zones" << endl;
-          }
-          catch (...) {
-            g_log << Logger::Error << "Exception while priming the root NS zones" << endl;
-          }
-        }
-      });
+    // 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, []() {
+    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);
-        }
-        catch (const std::exception& e) {
-          g_log << Logger::Error << "Exception while performing security poll: " << e.what() << endl;
-        }
-        catch (const PDNSException& e) {
-          g_log << Logger::Error << "Exception while performing security poll: " << e.reason << endl;
-        }
-        catch (const ImmediateServFailException& e) {
-          g_log << Logger::Error << "Exception while performing security poll: " << e.reason << endl;
-        }
-        catch (const PolicyHitException& e) {
-          g_log << Logger::Error << "Policy hit while performing security poll" << endl;
+          map<DNSName, dsmap_t> dsAnchors;
+          if (updateTrustAnchorsFromFile(luaconfsLocal->trustAnchorFileInfo.fname, dsAnchors, log)) {
+            g_luaconfs.modify([&dsAnchors](LuaConfigItems& lci) {
+              lci.dsAnchors = dsAnchors;
+            });
+          }
         }
-        catch (...) {
-          g_log << Logger::Error << "Exception while performing security poll" << endl;
+        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);
+}
 
-      auto luaconfsLocal = g_luaconfs.getLocal();
-      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]() {
-        if (!luaconfsLocal->trustAnchorFileInfo.fname.empty() && luaconfsLocal->trustAnchorFileInfo.interval != 0) {
-          g_log << Logger::Debug << "Refreshing Trust Anchors from file" << endl;
-          try {
-            map<DNSName, dsmap_t> dsAnchors;
-            if (updateTrustAnchorsFromFile(luaconfsLocal->trustAnchorFileInfo.fname, dsAnchors)) {
-              g_luaconfs.modify([&dsAnchors](LuaConfigItems& lci) {
-                lci.dsAnchors = dsAnchors;
-              });
-            }
-          }
-          catch (const PDNSException& pe) {
-            g_log << Logger::Error << "Unable to update Trust Anchors: " << pe.reason << endl;
-          }
-        }
-      });
+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_running = true;
+    houseKeepingWork(log);
     t_running = false;
   }
   catch (const PDNSException& ae) {
     t_running = false;
-    g_log << Logger::Error << "Fatal error in housekeeping thread: " << ae.reason << endl;
+    SLOG(g_log << Logger::Error << "Fatal error in housekeeping thread: " << ae.reason << endl,
+         log->error(Logr::Error, ae.reason, "Fatal error in housekeeping thread"));
     throw;
   }
   catch (...) {
     t_running = false;
-    g_log << Logger::Error << "Uncaught exception in housekeeping thread" << endl;
+    SLOG(g_log << Logger::Error << "Uncaught exception in housekeeping thread" << endl,
+         log->info(Logr::Error, "Uncaught exception in housekeeping thread"));
     throw;
   }
 }
 
+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");
+  t_Counters.updateSnap(true);
   try {
     auto& threadInfo = RecThreadInfo::self();
     {
@@ -2024,22 +2710,29 @@ static void recursorThread()
       t_allowNotifyFor = g_initialAllowNotifyFor;
       t_udpclientsocks = std::make_unique<UDPClientSocks>();
       t_tcpClientCounts = std::make_unique<tcpClientCounts_t>();
+      if (g_proxyMapping) {
+        t_proxyMapping = make_unique<ProxyMapping>(*g_proxyMapping);
+      }
+      else {
+        t_proxyMapping = nullptr;
+      }
 
       if (threadInfo.isHandler()) {
         if (!primeHints()) {
           threadInfo.setExitCode(EXIT_FAILURE);
-          RecursorControlChannel::stop = 1;
-          g_log << Logger::Critical << "Priming cache failed, stopping" << endl;
+          RecursorControlChannel::stop = true;
+          SLOG(g_log << Logger::Critical << "Priming cache failed, stopping" << endl,
+               log->info(Logr::Critical, "Priming cache failed, stopping"));
         }
-        g_log << Logger::Debug << "Done priming cache with root hints" << endl;
+        SLOG(g_log << Logger::Debug << "Done priming cache with root hints" << endl,
+             log->info(Logr::Debug, "Done priming cache with root hints"));
       }
     }
 
-    t_packetCache = std::make_unique<RecursorPacketCache>();
-
 #ifdef NOD_ENABLED
-    if (threadInfo.isWorker())
-      setupNODThread();
+    if (threadInfo.isWorker()) {
+      setupNODThread(log);
+    }
 #endif /* NOD_ENABLED */
 
     /* the listener threads handle TCP queries */
@@ -2048,22 +2741,26 @@ static void recursorThread()
         if (!::arg()["lua-dns-script"].empty()) {
           t_pdl = std::make_shared<RecursorLua4>();
           t_pdl->loadFile(::arg()["lua-dns-script"]);
-          g_log << Logger::Warning << "Loaded 'lua' script from '" << ::arg()["lua-dns-script"] << "'" << endl;
+          SLOG(g_log << Logger::Warning << "Loaded 'lua' script from '" << ::arg()["lua-dns-script"] << "'" << endl,
+               log->info(Logr::Warning, "Loading Lua script from file", "name", Logging::Loggable(::arg()["lua-dns-script"])));
         }
       }
       catch (std::exception& e) {
-        g_log << Logger::Error << "Failed to load 'lua' script from '" << ::arg()["lua-dns-script"] << "': " << e.what() << endl;
+        SLOG(g_log << Logger::Error << "Failed to load 'lua' script from '" << ::arg()["lua-dns-script"] << "': " << e.what() << endl,
+             log->error(Logr::Error, e.what(), "Failed to load Lua script from file", "name", Logging::Loggable(::arg()["lua-dns-script"])));
         _exit(99);
       }
     }
 
-    unsigned int ringsize = ::arg().asNum("stats-ringbuffer-entries") / RecThreadInfo::numWorkers();
-    if (ringsize) {
+    unsigned int ringsize = ::arg().asNum("stats-ringbuffer-entries") / RecThreadInfo::numUDPWorkers();
+    if (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>();
@@ -2080,151 +2777,211 @@ 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"));
-    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);
 #ifdef HAVE_FSTRM
-    checkFrameStreamExport(luaconfsLocal);
+    checkFrameStreamExport(luaconfsLocal, luaconfsLocal->frameStreamExportConfig, t_frameStreamServersInfo);
+    checkFrameStreamExport(luaconfsLocal, luaconfsLocal->nodFrameStreamExportConfig, t_nodFrameStreamServersInfo);
 #endif
 
-    t_fdm = unique_ptr<FDMultiplexer>(getMultiplexer());
+    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")) {
-        g_log << Logger::Warning << "Enabling web server" << endl;
+        SLOG(g_log << Logger::Warning << "Enabling web server" << endl,
+             log->info(Logr::Info, "Enabling web server"));
         try {
           rws = make_unique<RecursorWebServer>(t_fdm.get());
         }
         catch (const PDNSException& e) {
-          g_log << Logger::Error << "Unable to start the internal web server: " << e.reason << endl;
+          SLOG(g_log << Logger::Error << "Unable to start the internal web server: " << e.reason << endl,
+               log->error(Logr::Critical, e.reason, "Exception while starting internal web server"));
           _exit(99);
         }
       }
-      g_log << Logger::Info << "Enabled '" << t_fdm->getName() << "' multiplexer" << endl;
+      SLOG(g_log << Logger::Info << "Enabled '" << t_fdm->getName() << "' multiplexer" << endl,
+           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);
           }
         }
       }
     }
 
-    registerAllStats();
-
     if (threadInfo.isHandler()) {
       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");
-    s_counter.store(0); // used to periodically execute certain tasks
-
-    while (!RecursorControlChannel::stop) {
-      while (MT->schedule(&g_now))
-        ; // MTasker letting the mthreads do their thing
+#ifdef HAVE_SYSTEMD
+    if (threadInfo.isHandler()) {
+      // There is a race, as some threads might not be ready yet to do work.
+      // To solve that, threads should notify RecThreadInfo they are done initializing.
+      // But we lack a mechanism for that at this point in time.
+      sd_notify(0, "READY=1");
+    }
+#endif
 
-      // 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) {
-        MT->makeThread(houseKeeping, 0);
-      }
+    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")));
+  }
+  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")));
+  }
+  catch (...) {
+    SLOG(g_log << Logger::Error << "any other exception in main: " << endl,
+         log->info(Logr::Error, "Exception in RecursorThread"));
+  }
+}
 
-      if (!(s_counter % 55)) {
-        typedef vector<pair<int, FDMultiplexer::funcparam_t>> expired_t;
-        expired_t expired = t_fdm->getTimeouts(g_now);
+static pair<int, bool> doYamlConfig(Logr::log_t /* startupLog */, int argc, char* argv[]) // NOLINT: Posix API
+{
+  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};
+}
 
-        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)
-            g_log << Logger::Warning << "Timeout from remote TCP client " << conn->d_remote.toStringWithPort() << endl;
-          t_fdm->removeReadFD(i->first);
+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};
+}
 
-      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;
-        }
+static void handleRuntimeDefaults(Logr::log_t log)
+{
+#if 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";
+  }
+#endif
 
-        Utility::gettimeofday(&g_now, nullptr);
+  const string RUNTIME = "*runtime determined*";
+  if (::arg()["version-string"] == RUNTIME) { // i.e. not set explicitly
+    ::arg().set("version-string") = fullVersionString();
+  }
 
-        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) {
-            t_pdl->maintenance();
-            last_lua_maintenance = g_now.tv_sec;
-          }
-        }
-      }
+  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 : "";
+  }
 
-      t_fdm->run(&g_now);
-      // 'run' updates g_now for us
+  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;
+    }
+  }
 
-      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;
-          }
-        }
-      }
+  if (::arg()["socket-dir"].empty()) {
+    if (::arg()["chroot"].empty()) {
+      ::arg().set("socket-dir") = std::string(LOCALSTATEDIR) + "/pdns-recursor";
+    }
+    else {
+      ::arg().set("socket-dir") = "/";
     }
   }
-  catch (PDNSException& ae) {
-    g_log << Logger::Error << "Exception: " << ae.reason << endl;
+
+  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";
+    }
   }
-  catch (std::exception& e) {
-    g_log << Logger::Error << "STL Exception: " << e.what() << endl;
+
+  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";
   }
-  catch (...) {
-    g_log << Logger::Error << "any other exception in main: " << endl;
+
+  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";
   }
 }
 
@@ -2232,8 +2989,6 @@ int main(int argc, char** argv)
 {
   g_argc = argc;
   g_argv = argv;
-  g_stats.startupTime = time(0);
-  Utility::srandom();
   versionSetProduct(ProductRecursor);
   reportBasicTypes();
   reportOtherTypes();
@@ -2241,257 +2996,7 @@ int main(int argc, char** argv)
   int ret = EXIT_SUCCESS;
 
   try {
-#if HAVE_FIBER_SANITIZER
-    // Asan needs more stack
-    ::arg().set("stack-size", "stack size per mthread") = "400000";
-#else
-    ::arg().set("stack-size", "stack size per mthread") = "200000";
-#endif
-    ::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");
-    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") = "3600";
-    ::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("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") = "yes";
-    ::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-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.";
-
-    ::arg().setSwitch("reuseport", "Enable SO_REUSEPORT allowing multiple recursors processes to listen to 1 address") = "no";
-
-    ::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);
-    }
-    for (size_t idx = 0; idx < 128; idx++) {
-      defaultAPIDisabledStats += ", ecs-v6-response-bits-" + std::to_string(idx + 1);
-    }
-    std::string defaultDisabledStats = defaultAPIDisabledStats + ", cumul-clientanswers, cumul-authanswers, policy-hits";
-
-    ::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("xpf-allow-from", "XPF information is only processed from these subnets") = "";
-    ::arg().set("xpf-rr-code", "XPF option code to use") = "0";
-
-    ::arg().set("proxy-protocol-from", "A Proxy Protocol header is only allowed from these subnets") = "";
-    ::arg().set("proxy-protocol-maximum-size", "The maximum size of a proxy protocol payload, including the TLV values") = "512";
-
-    ::arg().set("dns64-prefix", "DNS64 prefix") = "";
-
-    ::arg().set("udp-source-port-min", "Minimum UDP port to bind on") = "1024";
-    ::arg().set("udp-source-port-max", "Maximum UDP port to bind on") = "65535";
-    ::arg().set("udp-source-port-avoid", "List of comma separated UDP port number to avoid") = "11211";
-    ::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";
-
-    ::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";
-    ::arg().set("record-cache-shards", "Number of shards in the record cache") = "1024";
-    ::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("x-dnssec-names", "Collect DNSSEC statistics for names or suffixes in this list in separate x-dnssec counters") = "";
-
-#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 */
-
-    ::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("edns-padding-from", "List of netmasks (proxy IP in case of XPF or 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("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().setCmd("help", "Provide a helpful message");
-    ::arg().setCmd("version", "Print version string");
-    ::arg().setCmd("config", "Output blank configuration");
+    pdns::settings::rec::defineOldStyleSettings();
     ::arg().setDefaults();
     g_log.toConsole(Logger::Info);
     ::arg().laxParse(argc, argv); // do a lax parse
@@ -2499,12 +3004,35 @@ 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;
+      return 0;
+    }
+
+    // Pick up options given on command line to setup logging asap.
+    g_quiet = ::arg().mustDo("quiet");
+    s_logUrgency = (Logger::Urgency)::arg().asNum("loglevel");
+    g_slogStructured = ::arg().mustDo("structured-logging");
+    s_structured_logger_backend = ::arg()["structured-logging-backend"];
+
+    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";
-    if (::arg()["config-name"] != "") {
-      configname = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"] + ".conf";
+    g_yamlSettings = false;
+    string configname = ::arg()["config-dir"] + "/recursor";
+    if (!::arg()["config-name"].empty()) {
+      configname = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"];
       g_programname += "-" + ::arg()["config-name"];
     }
     cleanSlashes(configname);
@@ -2516,95 +3044,133 @@ 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);
+      return 99;
     }
 
-    if (::arg().mustDo("config")) {
-      cout << ::arg().configstring(false, true);
-      exit(0);
+    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;
+      }
     }
 
-    g_slog = Logging::Logger::create(loggerBackend);
-    auto startupLog = g_slog->withName("startup");
-
-    if (!::arg().file(configname.c_str())) {
-      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)));
+    if (g_slog == nullptr) {
+      g_slog = Logging::Logger::create(loggerBackend);
     }
 
-    ::arg().parse(argc, argv);
+    // Missing: a mechanism to call setVerbosity(x)
+    auto startupLog = g_slog->withName("config");
+    g_slogtcpin = g_slog->withName("in")->withValues("proto", Logging::Loggable("tcp"));
+    g_slogudpin = g_slog->withName("in")->withValues("proto", Logging::Loggable("udp"));
+    g_slogout = g_slog->withName("out");
 
-    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("Cannot use chroot and enable the API at the same time"));
-      exit(EXIT_FAILURE);
-    }
+    ::arg().setSLog(startupLog);
+
+    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);
 
-    if (::arg()["socket-dir"].empty()) {
-      if (::arg()["chroot"].empty())
-        ::arg().set("socket-dir") = std::string(LOCALSTATEDIR) + "/pdns-recursor";
-      else
-        ::arg().set("socket-dir") = "/";
+    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 (::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";
+    if (g_yamlSettings) {
+      bool mustExit = false;
+      std::tie(ret, mustExit) = doYamlConfig(startupLog, argc, argv);
+      if (ret != 0 || mustExit) {
+        return ret;
       }
     }
 
-    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 (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)));
+      }
     }
 
-    if (!::arg().mustDo("pdns-distributes-queries")) {
-      ::arg().set("distributor-threads") = "0";
+    // Reparse, now with config file as well, both for old-style as for YAML settings
+    ::arg().parse(argc, argv);
+
+    g_quiet = ::arg().mustDo("quiet");
+    s_logUrgency = (Logger::Urgency)::arg().asNum("loglevel");
+    g_slogStructured = ::arg().mustDo("structured-logging");
+
+    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);
 
-    if (::arg().mustDo("help")) {
-      cout << "syntax:" << endl
-           << endl;
-      cout << ::arg().helpstring(::arg()["help"]) << endl;
-      exit(0);
+    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"));
+      return EXIT_FAILURE;
     }
-    g_recCache = std::make_unique<MemRecursorCache>(::arg().asNum("record-cache-shards"));
-    g_negCache = std::make_unique<NegCache>(::arg().asNum("record-cache-shards"));
 
-    g_quiet = ::arg().mustDo("quiet");
-    Logger::Urgency logUrgency = (Logger::Urgency)::arg().asNum("loglevel");
+    handleRuntimeDefaults(startupLog);
 
-    if (logUrgency < Logger::Error)
-      logUrgency = Logger::Error;
-    if (!g_quiet && logUrgency < Logger::Info) { // Logger::Info=6, Logger::Debug=7
-      logUrgency = Logger::Info; // if you do --quiet=no, you need Info to also see the query log
+    g_recCache = std::make_unique<MemRecursorCache>(::arg().asNum("record-cache-shards"));
+    g_negCache = std::make_unique<NegCache>(::arg().asNum("record-cache-shards") / 8);
+    if (!::arg().mustDo("disable-packetcache")) {
+      g_maxPacketCacheEntries = ::arg().asNum("max-packetcache-entries");
+      g_packetCache = std::make_unique<RecursorPacketCache>(g_maxPacketCacheEntries, ::arg().asNum("packetcache-shards"));
     }
-    g_log.setLoglevel(logUrgency);
-    g_log.toConsole(logUrgency);
 
-    ret = serviceMain(argc, argv);
+    ret = serviceMain(startupLog);
   }
-  catch (PDNSException& ae) {
-    g_log << Logger::Error << "Exception: " << ae.reason << endl;
+  catch (const PDNSException& ae) {
+    SLOG(g_log << Logger::Error << "Exception: " << ae.reason << endl,
+         g_slog->withName("config")->error(Logr::Critical, ae.reason, "Fatal error", "exception", Logging::Loggable("PDNSException")));
     ret = EXIT_FAILURE;
   }
-  catch (std::exception& e) {
-    g_log << Logger::Error << "STL Exception: " << e.what() << endl;
+  catch (const std::exception& e) {
+    SLOG(g_log << Logger::Error << "STL Exception: " << e.what() << endl,
+         g_slog->withName("config")->error(Logr::Critical, e.what(), "Fatal error", "exception", Logging::Loggable("std::exception")));
     ret = EXIT_FAILURE;
   }
   catch (...) {
-    g_log << Logger::Error << "any other exception in main: " << endl;
+    SLOG(g_log << Logger::Error << "any other exception in main: " << endl,
+         g_slog->withName("config")->info(Logr::Critical, "Fatal error"));
     ret = EXIT_FAILURE;
   }
 
@@ -2614,62 +3180,71 @@ int main(int argc, char** argv)
 static RecursorControlChannel::Answer* doReloadLuaScript()
 {
   string fname = ::arg()["lua-dns-script"];
+  auto log = g_slog->withName("runtime")->withValues("name", Logging::Loggable(fname));
   try {
     if (fname.empty()) {
       t_pdl.reset();
-      g_log << Logger::Info << RecThreadInfo::id() << " Unloaded current lua script" << endl;
+      SLOG(g_log << Logger::Info << RecThreadInfo::id() << " Unloaded current lua script" << endl,
+           log->info(Logr::Info, "Unloaded current lua script"));
       return new RecursorControlChannel::Answer{0, string("unloaded\n")};
     }
-    else {
-      t_pdl = std::make_shared<RecursorLua4>();
-      int err = t_pdl->loadFile(fname);
-      if (err != 0) {
-        string msg = std::to_string(RecThreadInfo::id()) + " Retaining current script, could not read '" + fname + "': " + stringerror(err);
-        g_log << Logger::Error << msg << endl;
-        return new RecursorControlChannel::Answer{1, msg + "\n"};
-      }
+
+    t_pdl = std::make_shared<RecursorLua4>();
+    try {
+      t_pdl->loadFile(fname);
+    }
+    catch (std::runtime_error& ex) {
+      string msg = std::to_string(RecThreadInfo::id()) + " Retaining current script, could not read '" + fname + "': " + ex.what();
+      SLOG(g_log << Logger::Error << msg << endl,
+           log->error(Logr::Error, ex.what(), "Retaining current script, could not read new script"));
+      return new RecursorControlChannel::Answer{1, msg + "\n"};
     }
   }
   catch (std::exception& e) {
-    g_log << Logger::Error << RecThreadInfo::id() << " Retaining current script, error from '" << fname << "': " << e.what() << endl;
+    SLOG(g_log << Logger::Error << RecThreadInfo::id() << " Retaining current script, error from '" << fname << "': " << e.what() << endl,
+         log->error(Logr::Error, e.what(), "Retaining current script, error in new script"));
     return new RecursorControlChannel::Answer{1, string("retaining current script, error from '" + fname + "': " + e.what() + "\n")};
   }
 
-  g_log << Logger::Warning << RecThreadInfo::id() << " (Re)loaded lua script from '" << fname << "'" << endl;
+  SLOG(g_log << Logger::Warning << RecThreadInfo::id() << " (Re)loaded lua script from '" << fname << "'" << endl,
+       log->info(Logr::Warning, "(Re)loaded lua script"));
   return new RecursorControlChannel::Answer{0, string("(re)loaded '" + fname + "'\n")};
 }
 
 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);
 }
 
-static string* pleaseUseNewTraceRegex(const std::string& newRegex)
-try {
-  if (newRegex.empty()) {
-    t_traceRegex.reset();
-    return new string("unset\n");
-  }
-  else {
+static string* pleaseUseNewTraceRegex(const std::string& newRegex, int file)
+{
+  try {
+    if (newRegex.empty()) {
+      t_traceRegex.reset();
+      t_tracefd = FDWrapper();
+      return new string("unset\n");
+    }
+    if (file == -1) {
+      return new string("could not dup file\n");
+    }
     t_traceRegex = std::make_shared<Regex>(newRegex);
-    return new string("ok\n");
+    t_tracefd = file;
+    return new string("ok\n"); // NOLINT(cppcoreguidelines-owning-memory): it's the API
+  }
+  catch (const PDNSException& ae) {
+    return new string(ae.reason + "\n"); // NOLINT(cppcoreguidelines-owning-memory): it's the API
   }
-}
-catch (PDNSException& ae) {
-  return new string(ae.reason + "\n");
-}
-
-string doTraceRegex(vector<string>::const_iterator begin, vector<string>::const_iterator end)
-{
-  return broadcastAccFunction<string>([=] { return pleaseUseNewTraceRegex(begin != end ? *begin : ""); });
 }
 
-static uint64_t* pleaseWipePacketCache(const DNSName& canon, bool subtree, uint16_t qtype)
+string doTraceRegex(FDWrapper file, vector<string>::const_iterator begin, vector<string>::const_iterator end)
 {
-  return new uint64_t(t_packetCache->doWipePacketCache(canon, qtype, subtree));
+  int fileno = dup(file);
+  // Potential dup failure handled in pleaseUseNewTraceRegex()
+  return broadcastAccFunction<string>([=] { return pleaseUseNewTraceRegex(begin != end ? *begin : "", fileno); });
 }
 
 struct WipeCacheResult wipeCaches(const DNSName& canon, bool subtree, uint16_t qtype)
@@ -2677,15 +3252,20 @@ 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.packet_count = broadcastAccFunction<uint64_t>([=] { return pleaseWipePacketCache(canon, subtree, qtype); });
-    res.negative_record_count = g_negCache->wipe(canon, subtree);
+    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 = static_cast<int>(g_packetCache->doWipePacketCache(canon, qtype, subtree));
+    }
+    res.negative_record_count = static_cast<int>(g_negCache->wipe(canon, subtree));
     if (g_aggressiveNSECCache) {
       g_aggressiveNSECCache->removeZoneInfo(canon, subtree);
     }
   }
   catch (const std::exception& e) {
-    g_log << Logger::Warning << ", failed: " << e.what() << endl;
+    auto log = g_slog->withName("runtime");
+    SLOG(g_log << Logger::Warning << ", failed: " << e.what() << endl,
+         log->error(Logr::Warning, e.what(), "Wipecache failed"));
   }
 
   return res;
index da1b8d82fd4b023c1ec6a694e3540b9262155fbc..9fc1d96bc8f0be2808a89688f2b116ab16ed2c90 100644 (file)
@@ -27,6 +27,7 @@
 #endif
 
 #include "logger.hh"
+#include "logr.hh"
 #include "lua-recursor4.hh"
 #include "mplexer.hh"
 #include "namespaces.hh"
@@ -36,6 +37,7 @@
 #include "rec-snmp.hh"
 #include "rec_channel.hh"
 #include "threadname.hh"
+#include "recpacketcache.hh"
 
 #ifdef NOD_ENABLED
 #include "nod.hh"
 #include <boost/container/flat_set.hpp>
 #endif
 
+extern std::shared_ptr<Logr::Logger> g_slogtcpin;
+extern std::shared_ptr<Logr::Logger> g_slogudpin;
+
 //! used to send information to a newborn mthread
 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))
   {
   }
 
+  // The address the query is coming from
   void setRemote(const ComboAddress& sa)
   {
     d_remote = sa;
   }
 
+  // The address we assume the query is coming from, might be set by proxy protocol
   void setSource(const ComboAddress& sa)
   {
     d_source = sa;
   }
 
+  void setMappedSource(const ComboAddress& sa)
+  {
+    d_mappedSource = sa;
+  }
+
   void setLocal(const ComboAddress& sa)
   {
     d_local = sa;
   }
 
+  // The address we assume the query is sent to, might be set by proxy protocol
   void setDestination(const ComboAddress& sa)
   {
     d_destination = sa;
@@ -83,6 +96,7 @@ struct DNSComboWriter
     d_socket = sock;
   }
 
+  // get a string repesentation of the client address, including proxy info if applicable
   string getRemote() const
   {
     if (d_source == d_remote) {
@@ -94,18 +108,12 @@ struct DNSComboWriter
   std::vector<ProxyProtocolValue> d_proxyProtocolValues;
   MOADNSParser d_mdp;
   struct timeval d_now;
-  /* Remote client, might differ from d_source
-     in case of XPF, in which case d_source holds
-     the IP of the client and d_remote of the proxy
-  */
-  ComboAddress d_remote;
-  ComboAddress d_source;
-  /* Destination address, might differ from
-     d_destination in case of XPF, in which case
-     d_destination holds the IP of the proxy and
-     d_local holds our own. */
-  ComboAddress d_local;
-  ComboAddress d_destination;
+
+  ComboAddress d_remote; // the address the query is coming from
+  ComboAddress d_source; // the address we assume the query is coming from, might be set by proxy protocol
+  ComboAddress d_local; // the address we received the query on
+  ComboAddress d_destination; // the address we assume the query is sent to, might be set by proxy protocol
+  ComboAddress d_mappedSource; // the source address after being mapped by table based proxy mapping
   RecEventTrace d_eventTrace;
   boost::uuids::uuid d_uuid;
   string d_requestorId;
@@ -117,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;
 
@@ -146,6 +155,7 @@ struct DNSComboWriter
 extern thread_local unique_ptr<FDMultiplexer> t_fdm;
 extern uint16_t g_minUdpSourcePort;
 extern uint16_t g_maxUdpSourcePort;
+extern bool g_regressionTestMode;
 
 // you can ask this class for a UDP socket to send a query from
 // this socket is not yours, don't even think about deleting it
@@ -160,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
@@ -177,19 +187,20 @@ 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;
-extern NetmaskGroup g_XPFAcl;
-extern thread_local std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>> t_protobufServers;
 extern thread_local std::shared_ptr<RecursorLua4> t_pdl;
 extern bool g_gettagNeedsEDNSOptions;
 extern NetmaskGroup g_paddingFrom;
 extern unsigned int g_paddingTag;
 extern PaddingMode g_paddingMode;
-extern thread_local std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>> t_outgoingProtobufServers;
 extern unsigned int g_maxMThreads;
 extern bool g_reusePort;
 extern bool g_anyToTcp;
@@ -200,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;
@@ -208,8 +220,6 @@ extern bool g_useIncomingECS;
 extern boost::optional<ComboAddress> g_dns64Prefix;
 extern DNSName g_dns64PrefixReverse;
 extern uint64_t g_latencyStatSize;
-extern bool g_addExtendedResolutionDNSErrors;
-extern uint16_t g_xpfRRCode;
 extern NetmaskGroup g_proxyProtocolACL;
 extern std::atomic<bool> g_statsWanted;
 extern uint32_t g_disthashseed;
@@ -220,10 +230,14 @@ extern std::shared_ptr<NetmaskGroup> g_initialAllowFrom; // new thread needs to
 extern std::shared_ptr<NetmaskGroup> g_initialAllowNotifyFrom; // new threads need this to be setup
 extern std::shared_ptr<notifyset_t> g_initialAllowNotifyFor; // new threads need this to be setup
 extern thread_local std::shared_ptr<Regex> t_traceRegex;
+extern thread_local FDWrapper t_tracefd;
 extern string g_programname;
 extern string g_pidfname;
 extern RecursorControlChannel g_rcc; // only active in the handler thread
 
+extern thread_local std::unique_ptr<ProxyMapping> t_proxyMapping;
+using ProxyMappingStats_t = std::unordered_map<Netmask, ProxyMappingCounts>;
+
 #ifdef NOD_ENABLED
 extern bool g_nodEnabled;
 extern DNSName g_nodLookupDomain;
@@ -237,9 +251,25 @@ extern thread_local std::shared_ptr<nod::NODDB> t_nodDBp;
 extern thread_local std::shared_ptr<nod::UniqueResponseDB> t_udrDBp;
 #endif
 
+struct ProtobufServersInfo
+{
+  std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>> servers;
+  uint64_t generation;
+  ProtobufExportConfig config;
+};
+extern thread_local ProtobufServersInfo t_protobufServers;
+extern thread_local ProtobufServersInfo t_outgoingProtobufServers;
+
 #ifdef HAVE_FSTRM
-extern thread_local std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>> t_frameStreamServers;
-extern thread_local uint64_t t_frameStreamServersGeneration;
+struct FrameStreamServersInfo
+{
+  std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>> servers;
+  uint64_t generation;
+  FrameStreamExportConfig config;
+};
+
+extern thread_local FrameStreamServersInfo t_frameStreamServersInfo;
+extern thread_local FrameStreamServersInfo t_nodFrameStreamServersInfo;
 #endif /* HAVE_FSTRM */
 
 #ifdef HAVE_BOOST_CONTAINER_FLAT_SET_HPP
@@ -249,15 +279,14 @@ extern std::set<uint16_t> g_avoidUdpSourcePorts;
 #endif
 
 /* without reuseport, all listeners share the same sockets */
-typedef vector<pair<int, boost::function<void(int, boost::any&)>>> deferredAdd_t;
-extern deferredAdd_t g_deferredAdds;
+typedef vector<pair<int, std::function<void(int, boost::any&)>>> deferredAdd_t;
 
 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 */
@@ -317,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()
@@ -327,7 +356,7 @@ public:
     return s_threadInfos;
   }
 
-  bool isDistributor() const
+  [[nodiscard]] bool isDistributor() const
   {
     if (t_id == 0) {
       return false;
@@ -335,7 +364,7 @@ public:
     return s_weDistributeQueries && listener;
   }
 
-  bool isHandler() const
+  [[nodiscard]] bool isHandler() const
   {
     if (t_id == 0) {
       return true;
@@ -343,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;
   }
@@ -373,6 +409,12 @@ public:
     listener = flag;
   }
 
+  void setTCPListener(bool flag = true)
+  {
+    setListener(flag);
+    tcplistener = flag;
+  }
+
   void setTaskThread()
   {
     taskThread = true;
@@ -383,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;
   }
@@ -403,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()
@@ -423,9 +470,14 @@ public:
     s_weDistributeQueries = flag;
   }
 
-  static void setNumWorkerThreads(unsigned int n)
+  static void setNumUDPWorkerThreads(unsigned int n)
+  {
+    s_numUDPWorkerThreads = n;
+  }
+
+  static void setNumTCPWorkerThreads(unsigned int n)
   {
-    s_numWorkerThreads = n;
+    s_numTCPWorkerThreads = n;
   }
 
   static void setNumDistributorThreads(unsigned int n)
@@ -435,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 n)
+  {
+    exitCode = n;
+  }
+
+  std::set<int>& getTCPSockets()
+  {
+    return tcpSockets;
+  }
+
+  void setTCPSockets(std::set<int>& socks)
+  {
+    tcpSockets = socks;
   }
 
-  static int runThreads();
-  static void makeThreadPipes();
+  deferredAdd_t& getDeferredAdds()
+  {
+    return deferredAdds;
+  }
 
-  void setExitCode(int e)
+  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.
@@ -459,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);
+  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;
@@ -470,16 +562,19 @@ 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 TastQueue and ZoneToCache
+  // run async tasks: from TaskQueue and ZoneToCache
   bool taskThread{false};
 
   static thread_local unsigned int t_id;
   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
@@ -488,38 +583,42 @@ struct ThreadMSG
   bool wantAnswer;
 };
 
+void parseACLs();
 PacketBuffer GenUDPQueryResponse(const ComboAddress& dest, const string& query);
 bool checkProtobufExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal);
 bool checkOutgoingProtobufExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal);
-bool checkFrameStreamExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal);
+#ifdef HAVE_FSTRM
+bool checkFrameStreamExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal, const FrameStreamExportConfig& config, FrameStreamServersInfo& serverInfos);
+#endif
 void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass,
-                       bool& foundECS, EDNSSubnetOpts* ednssubnet, EDNSOptionViewMap* options,
-                       bool& foundXPF, ComboAddress* xpfSource, ComboAddress* xpfDest);
-void protobufLogQuery(LocalStateHolder<LuaConfigItems>& luaconfsLocal, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const Netmask& ednssubnet, bool tcp, uint16_t id, size_t len, const DNSName& qname, uint16_t qtype, uint16_t qclass, const std::unordered_set<std::string>& policyTags, const std::string& requestorId, const std::string& deviceId, const std::string& deviceName, const std::map<std::string, RecursorLua4::MetaValue>& meta);
+                       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 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,
                       const struct timeval& now,
                       string& response, uint32_t& qhash,
-                      RecursorPacketCache::OptPBData& pbData, bool tcp, const ComboAddress& source);
+                      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 EDNSSubnetOpts& ednssubnet,
+                         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 checkFastOpenSysctl(bool active);
-void checkTFOconnect();
-void makeTCPServerSockets(deferredAdd_t& deferredAdds, std::set<int>& tcpSockets);
-void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t&);
-
-void makeUDPServerSockets(deferredAdd_t& deferredAdds);
+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 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);
 
 #define LOCAL_NETS "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"
 #define LOCAL_NETS_INVERSE "!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"
index 8a698093d36a96924b071ab5d0866663671797c7..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;
@@ -41,52 +41,85 @@ void pdns::ProtoZero::RecMessage::addRR(const DNSRecord& record, const std::set<
   pbf_rr.add_uint32(static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::RRField::class_), record.d_class);
   pbf_rr.add_uint32(static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::RRField::ttl), record.d_ttl);
 
+  auto add = [&](const std::string& str) {
+    if (size() + str.length() < std::numeric_limits<uint16_t>::max() / 2) {
+      pbf_rr.add_string(static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::RRField::rdata), str);
+    }
+  };
+
   switch (record.d_type) {
   case QType::A: {
-    const auto& content = dynamic_cast<const ARecordContent&>(*(record.d_content));
-    ComboAddress data = content.getCA();
+    const auto& content = getRR<ARecordContent>(record);
+    if (!content) {
+      return;
+    }
+    ComboAddress data = content->getCA();
     pbf_rr.add_bytes(static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::RRField::rdata), reinterpret_cast<const char*>(&data.sin4.sin_addr.s_addr), sizeof(data.sin4.sin_addr.s_addr));
     break;
   }
   case QType::AAAA: {
-    const auto& content = dynamic_cast<const AAAARecordContent&>(*(record.d_content));
-    ComboAddress data = content.getCA();
+    const auto& content = getRR<AAAARecordContent>(record);
+    if (!content) {
+      return;
+    }
+    ComboAddress data = content->getCA();
     pbf_rr.add_bytes(static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::RRField::rdata), reinterpret_cast<const char*>(&data.sin6.sin6_addr.s6_addr), sizeof(data.sin6.sin6_addr.s6_addr));
     break;
   }
   case QType::CNAME: {
-    const auto& content = dynamic_cast<const CNAMERecordContent&>(*(record.d_content));
-    pbf_rr.add_string(static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::RRField::rdata), content.getTarget().toString());
+    const auto& content = getRR<CNAMERecordContent>(record);
+    if (!content) {
+      return;
+    }
+    add(content->getTarget().toString());
     break;
   }
   case QType::TXT: {
-    const auto& content = dynamic_cast<const TXTRecordContent&>(*(record.d_content));
-    pbf_rr.add_string(static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::RRField::rdata), content.d_text);
+    const auto& content = getRR<TXTRecordContent>(record);
+    if (!content) {
+      return;
+    }
+    add(content->d_text);
     break;
   }
   case QType::NS: {
-    const auto& content = dynamic_cast<const NSRecordContent&>(*(record.d_content));
-    pbf_rr.add_string(static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::RRField::rdata), content.getNS().toString());
+    const auto& content = getRR<NSRecordContent>(record);
+    if (!content) {
+      return;
+    }
+    add(content->getNS().toString());
     break;
   }
   case QType::PTR: {
-    const auto& content = dynamic_cast<const PTRRecordContent&>(*(record.d_content));
-    pbf_rr.add_string(static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::RRField::rdata), content.getContent().toString());
+    const auto& content = getRR<PTRRecordContent>(record);
+    if (!content) {
+      return;
+    }
+    add(content->getContent().toString());
     break;
   }
   case QType::MX: {
-    const auto& content = dynamic_cast<const MXRecordContent&>(*(record.d_content));
-    pbf_rr.add_string(static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::RRField::rdata), content.d_mxname.toString());
+    const auto& content = getRR<MXRecordContent>(record);
+    if (!content) {
+      return;
+    }
+    add(content->d_mxname.toString());
     break;
   }
   case QType::SPF: {
-    const auto& content = dynamic_cast<const SPFRecordContent&>(*(record.d_content));
-    pbf_rr.add_string(static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::RRField::rdata), content.getText());
+    const auto& content = getRR<SPFRecordContent>(record);
+    if (!content) {
+      return;
+    }
+    add(content->getText());
     break;
   }
   case QType::SRV: {
-    const auto& content = dynamic_cast<const SRVRecordContent&>(*(record.d_content));
-    pbf_rr.add_string(static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::RRField::rdata), content.d_target.toString());
+    const auto& content = getRR<SRVRecordContent>(record);
+    if (!content) {
+      return;
+    }
+    add(content->d_target.toString());
     break;
   }
   default:
index 6502fd71a7ab1303242c3a61cf6474b967a590b0..926982a20c8e757f16f3233c982eaac614610901 100644 (file)
@@ -92,6 +92,11 @@ namespace ProtoZero
       return d_rspbuf;
     }
 
+    [[nodiscard]] size_t size() const
+    {
+      return d_msgbuf.size() + d_rspbuf.size();
+    }
+
     std::string&& finishAndMoveBuf()
     {
       if (!d_rspbuf.empty()) {
diff --git a/pdns/recursordist/rec-responsestats.cc b/pdns/recursordist/rec-responsestats.cc
new file mode 100644 (file)
index 0000000..ea283c3
--- /dev/null
@@ -0,0 +1,96 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "rec-responsestats.hh"
+
+#include <limits>
+
+#include "namespaces.hh"
+#include "logger.hh"
+
+#include "dnsparser.hh"
+
+static auto sizeBounds()
+{
+  std::vector<uint64_t> bounds;
+
+  bounds.push_back(20);
+  bounds.push_back(40);
+  bounds.push_back(60);
+  bounds.push_back(80);
+  bounds.push_back(100);
+  bounds.push_back(150);
+  for (uint64_t n = 200; n < 65000; n += 200) {
+    bounds.push_back(n);
+  }
+  return bounds;
+}
+
+RecResponseStats::RecResponseStats() :
+  d_sizecounters("SizeCounters", sizeBounds())
+{
+  for (auto& entry : d_qtypecounters) {
+    entry = 0;
+  }
+  for (auto& entry : d_rcodecounters) {
+    entry = 0;
+  }
+}
+
+RecResponseStats& RecResponseStats::operator+=(const RecResponseStats& rhs)
+{
+  for (size_t i = 0; i < d_qtypecounters.size(); i++) {
+    d_qtypecounters.at(i) += rhs.d_qtypecounters.at(i);
+  }
+  for (size_t i = 0; i < d_rcodecounters.size(); i++) {
+    d_rcodecounters.at(i) += rhs.d_rcodecounters.at(i);
+  }
+  d_sizecounters += rhs.d_sizecounters;
+  return *this;
+}
+
+map<uint16_t, uint64_t> RecResponseStats::getQTypeResponseCounts() const
+{
+  map<uint16_t, uint64_t> ret;
+  for (size_t i = 0; i < d_qtypecounters.size(); ++i) {
+    auto count = d_qtypecounters.at(i);
+    if (count != 0) {
+      ret[i] = count;
+    }
+  }
+  return ret;
+}
+
+map<uint16_t, uint64_t> RecResponseStats::getSizeResponseCounts() const
+{
+  map<uint16_t, uint64_t> ret;
+  for (const auto& sizecounter : d_sizecounters.getRawData()) {
+    if (sizecounter.d_count > 0) {
+      ret[sizecounter.d_boundary] = sizecounter.d_count;
+    }
+  }
+  return ret;
+}
+
+map<uint8_t, uint64_t> RecResponseStats::getRCodeResponseCounts() const
+{
+  map<uint8_t, uint64_t> ret;
+  for (size_t i = 0; i < d_rcodecounters.size(); ++i) {
+    auto count = d_rcodecounters.at(i);
+    if (count != 0) {
+      ret[i] = count;
+    }
+  }
+  return ret;
+}
+
+string RecResponseStats::getQTypeReport() const
+{
+  auto qtypenums = getQTypeResponseCounts();
+  ostringstream ostr;
+  for (const auto& val : qtypenums) {
+    ostr << DNSRecordContent::NumberToType(val.first) << '\t' << std::to_string(val.second) << endl;
+  }
+  return ostr.str();
+}
diff --git a/pdns/recursordist/rec-responsestats.hh b/pdns/recursordist/rec-responsestats.hh
new file mode 100644 (file)
index 0000000..00046bc
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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 "histogram.hh"
+#include "dnspacket.hh"
+
+class RecResponseStats
+{
+public:
+  RecResponseStats();
+
+  RecResponseStats& operator+=(const RecResponseStats&);
+
+  // To limit the size of this object, we cap the rcodes and qtypes,
+  // in line with
+  // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
+
+  // This is to reduce the memory used and the amount of work to be
+  // done by TCounters. As this class is part of the TCounter object,
+  // growing it too much would cause large objects on the stack. A
+  // full QType array would take 64k * sizeof(uint64_t) = 512k.
+  // Having such an object on a thread stack does not work well on
+  // e.g. macOS or OpenBSD, where the default thread stack size is
+  // limited. Additionally, C++ has no platform independent way to
+  // enlarge the thread stack size.
+
+  // We could allocate parts of this on the heap, but this would still
+  // mean having to manipulate large amounts of data by the TCounter
+  // classes
+
+  static const uint16_t maxRCode = 23; // BADCOOKIE
+  static const uint16_t maxQType = 260; // AMTRELAY
+
+  void submitResponse(uint16_t qtype, uint16_t respsize, uint8_t rcode)
+  {
+    if (rcode <= maxRCode) {
+      d_rcodecounters.at(rcode)++;
+    }
+    if (qtype <= maxQType) {
+      d_qtypecounters.at(qtype)++;
+    }
+    d_sizecounters(respsize);
+  }
+  map<uint16_t, uint64_t> getQTypeResponseCounts() const;
+  map<uint16_t, uint64_t> getSizeResponseCounts() const;
+  map<uint8_t, uint64_t> getRCodeResponseCounts() const;
+  string getQTypeReport() const;
+
+private:
+  std::array<uint64_t, maxQType + 1> d_qtypecounters{};
+  std::array<uint64_t, maxRCode + 1> d_rcodecounters{};
+  pdns::Histogram d_sizecounters;
+};
deleted file mode 120000 (symlink)
index 9883d66bc9a20833443fdd8cf3e24a4ac20588de..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../rec-snmp.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..19b875beb5b392c66ac210da5c33109b5e28b0a7
--- /dev/null
@@ -0,0 +1,456 @@
+/*
+ * 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>
+
+#include "rec-snmp.hh"
+#include "rec_channel.hh"
+
+#include "logger.hh"
+#include "logging.hh"
+
+#ifdef HAVE_NET_SNMP
+
+#define RECURSOR_OID 1, 3, 6, 1, 4, 1, 43315, 2
+#define RECURSOR_STATS_OID RECURSOR_OID, 1
+#define RECURSOR_TRAPS_OID RECURSOR_OID, 10, 0
+#define RECURSOR_TRAP_OBJECTS_OID RECURSOR_OID, 11
+
+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 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 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 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 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;
+
+/* 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(questionsOID) + 1) {
+    return SNMP_ERR_GENERR;
+  }
+
+  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(iter->second);
+  if (value) {
+    return RecursorSNMPAgent::setCounter64Value(requests, *value);
+  }
+  return RecursorSNMPAgent::setCounter64Value(requests, 0);
+}
+
+static int handleDisabledCounter64Stats(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(questionsOID) + 1) {
+    return SNMP_ERR_GENERR;
+  }
+
+  return RecursorSNMPAgent::setCounter64Value(requests, 0);
+}
+
+static void registerCounter64Stat(const std::string& name, const oid10& statOID)
+{
+  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.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.at(statOID.size() - 1)] = name;
+  netsnmp_register_scalar(netsnmp_create_handler_registration(name.c_str(),
+                                                              isStatDisabled(StatComponent::SNMP, name) ? handleDisabledCounter64Stats : handleCounter64Stats,
+                                                              statOID.data(),
+                                                              statOID.size(),
+                                                              HANDLER_CAN_RONLY));
+}
+
+#endif /* HAVE_NET_SNMP */
+
+std::shared_ptr<RecursorSNMPAgent> g_snmpAgent{nullptr};
+
+bool RecursorSNMPAgent::sendCustomTrap([[maybe_unused]] 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);
+#endif /* HAVE_NET_SNMP */
+  return true;
+}
+
+RecursorSNMPAgent::RecursorSNMPAgent(const std::string& name, const std::string& daemonSocket) :
+  SNMPAgent(name, daemonSocket)
+{
+#ifdef HAVE_NET_SNMP
+  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);
+  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);
+  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);
+  RCODE(3);
+  RCODE(4);
+  RCODE(5);
+  RCODE(6);
+  RCODE(7);
+  RCODE(8);
+  RCODE(9);
+  RCODE(10);
+  RCODE(11);
+  RCODE(12);
+  RCODE(13);
+  RCODE(14);
+  RCODE(15);
+
+  registerCounter64Stat("packetcache-contended", packetCacheContendedOID);
+  registerCounter64Stat("packetcache-acquired", packetCacheAcquiredOID);
+  registerCounter64Stat("nod-events", nodEventsOID);
+  registerCounter64Stat("udr-events", udrEventsOID);
+
+#endif /* HAVE_NET_SNMP */
+}
deleted file mode 120000 (symlink)
index b34bdacfcbce5f2cf07b44fffa89b86bdff05f8a..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../rec-snmp.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..7f067211c42044ae245909d1b4e8533b7bd7ab02
--- /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 "snmp-agent.hh"
+
+class RecursorSNMPAgent;
+
+class RecursorSNMPAgent : public SNMPAgent
+{
+public:
+  RecursorSNMPAgent(const std::string& name, const std::string& daemonSocket);
+  bool sendCustomTrap(const std::string& reason);
+};
+
+extern std::shared_ptr<RecursorSNMPAgent> g_snmpAgent;
index 22f03df4041d10fd353f7ef358dfad3cf4f9cad8..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,40 +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()));
+  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);
-  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");
-    int res = sr.beginResolve(task.d_qname, QType(task.d_qtype), QClass::IN, ret);
-    ex = false;
+    log->info(Logr::Debug, "resolving", "refresh", Logging::Loggable(task.d_refreshMode));
+    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;
     }
@@ -163,13 +170,74 @@ static void resolve(const struct timeval& now, bool logErrors, const pdns::Resol
   }
 }
 
-void runTaskOnce(bool logErrors)
+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 resolver(now);
+  vector<DNSRecord> ret;
+  resolver.setRefreshAlmostExpired(false);
+  bool exceptionOccurred = true;
+  try {
+    log->info(Logr::Debug, "trying DoT");
+    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::Warning, msg, e.what());
+  }
+  catch (const PDNSException& e) {
+    log->error(Logr::Warning, msg, e.reason);
+  }
+  catch (const ImmediateServFailException& e) {
+    if (logErrors) {
+      log->error(Logr::Warning, msg, e.reason);
+    }
+  }
+  catch (const PolicyHitException& e) {
+    if (logErrors) {
+      log->error(Logr::Notice, msg, "PolicyHit");
+    }
+  }
+  catch (...) {
+    log->error(Logr::Warning, msg, "Unexpected exception");
+  }
+  if (exceptionOccurred) {
+    ++s_resolve_tasks.exceptions;
+  }
+  else {
+    ++s_resolve_tasks.run;
+  }
+}
+
+void runTasks(size_t max, bool logErrors)
+{
+  for (size_t count = 0; count < max; count++) {
+    if (!runTaskOnce(logErrors)) {
+      // No more tasks in queue
+      break;
+    }
+  }
+}
+
+bool runTaskOnce(bool logErrors)
 {
   pdns::ResolveTask task;
   {
     auto lock = s_taskQueue.lock();
     if (lock->queue.empty()) {
-      return;
+      return false;
     }
     task = lock->queue.pop();
   }
@@ -177,36 +245,56 @@ void runTaskOnce(bool logErrors)
   if (expired) {
     s_taskQueue.lock()->queue.incExpired();
   }
+  return true;
 }
 
-void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline)
+void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline, const Netmask& netmask)
 {
   if (SyncRes::isUnsupported(qtype)) {
-    auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype).toString()));
+    auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype).toString()), "netmask", Logging::Loggable(netmask.empty() ? "" : netmask.toString()));
     log->error(Logr::Error, "Cannot push task", "qtype unsupported");
     return;
   }
-  pdns::ResolveTask task{qname, qtype, deadline, true, resolve};
-  s_taskQueue.lock()->queue.push(std::move(task));
-  ++s_almost_expired_tasks.pushed;
+  pdns::ResolveTask task{qname, qtype, deadline, true, resolve, {}, {}, netmask};
+  if (s_taskQueue.lock()->queue.push(std::move(task))) {
+    ++s_almost_expired_tasks.pushed;
+  }
 }
 
-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) {
-    lock->queue.push(std::move(task));
-    ++s_resolve_tasks.pushed;
+    if (lock->queue.push(std::move(task))) {
+      ++s_resolve_tasks.pushed;
+    }
   }
 }
 
+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()));
+    log->error(Logr::Error, "Cannot push task", "qtype unsupported");
+    return false;
+  }
+
+  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;
+  }
+  return pushed;
+}
+
 uint64_t getTaskPushes()
 {
   return s_taskQueue.lock()->queue.getPushes();
@@ -263,3 +351,8 @@ uint64_t getResolveTaskExceptions()
 {
   return s_almost_expired_tasks.exceptions;
 }
+
+bool taskQTypeIsSupported(QType qtype)
+{
+  return !SyncRes::isUnsupported(qtype);
+}
index 84f165b7e1a3ad13d18c5f9f775b38ba9f30d597..e7bc855bd761a97eb0edb6aef8f142dc113d4fd1 100644 (file)
 #pragma once
 
 #include <cstdint>
-#include <time.h>
+#include <ctime>
+#include <qtype.hh>
 
 class DNSName;
+union ComboAddress;
+class Netmask;
+
 namespace pdns
 {
 struct ResolveTask;
 }
-void runTaskOnce(bool logErrors);
-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 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 forceQMOff);
+bool pushTryDoTTask(const DNSName& qname, uint16_t qtype, const ComboAddress& ipAddress, time_t deadline, const DNSName& nsname);
 void taskQueueClear();
 pdns::ResolveTask taskQueuePop();
 
@@ -49,3 +55,5 @@ uint64_t getResolveTaskExceptions();
 uint64_t getAlmostExpiredTasksPushed();
 uint64_t getAlmostExpiredTasksRun();
 uint64_t getAlmostExpiredTaskExceptions();
+
+bool taskQTypeIsSupported(QType qtype);
diff --git a/pdns/recursordist/rec-tcounters.cc b/pdns/recursordist/rec-tcounters.cc
new file mode 100644 (file)
index 0000000..4f91bde
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * 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 "rec-tcounters.hh"
+
+#include <sstream>
+
+namespace rec
+{
+
+Counters& Counters::merge(const Counters& data)
+{
+  // Counters are simply added
+  for (size_t i = 0; i < uint64Count.size(); i++) {
+    uint64Count.at(i) += data.uint64Count.at(i);
+  }
+  // Averages: take weight into account
+  for (size_t i = 0; i < doubleWAvg.size(); i++) {
+    auto& lhs = doubleWAvg.at(i);
+    const auto& rhs = data.doubleWAvg.at(i);
+    auto weight = lhs.weight + rhs.weight;
+    auto avg = lhs.avg * static_cast<double>(lhs.weight) + rhs.avg * static_cast<double>(rhs.weight);
+    avg = weight == 0 ? 0 : avg / static_cast<double>(weight);
+    lhs.avg = avg;
+    lhs.weight = weight;
+  }
+
+  // Rcode Counters are simply added
+  for (size_t i = 0; i < auth.rcodeCounters.size(); i++) {
+    auth.rcodeCounters.at(i) += data.auth.rcodeCounters.at(i);
+  }
+
+  // Histograms counts are added by += operator on Histograms
+  for (size_t i = 0; i < histograms.size(); i++) {
+    histograms.at(i) += data.histograms.at(i);
+  }
+
+  // ResponseStats knows how to add
+  responseStats += data.responseStats;
+
+  // DNSSEC histograms: add individual entries
+  for (size_t i = 0; i < dnssecCounters.size(); i++) {
+    auto& lhs = dnssecCounters.at(i);
+    const auto& rhs = data.dnssecCounters.at(i);
+    for (size_t j = 0; j < lhs.counts.size(); j++) {
+      lhs.counts.at(j) += rhs.counts.at(j);
+    }
+  }
+
+  // policy kind counters: add individual entries
+  for (size_t i = 0; i < policyCounters.counts.size(); i++) {
+    policyCounters.counts.at(i) += data.policyCounters.counts.at(i);
+  }
+
+  // Policy name counts knows how to add
+  policyNameHits += data.policyNameHits;
+
+  return *this;
+}
+
+std::string Counters::toString() const
+{
+  std::ostringstream stream;
+
+  for (auto element : uint64Count) {
+    stream << element << ' ';
+  }
+  stream << std::endl;
+  for (auto element : doubleWAvg) {
+    stream << '(' << element.avg << ' ' << element.weight << ')';
+  }
+  stream << " RCodes: ";
+  for (auto element : auth.rcodeCounters) {
+    stream << element << ' ';
+  }
+  stream << "Histograms: ";
+  for (const auto& element : histograms) {
+    stream << element.getName() << ": NYI ";
+  }
+  stream << "DNSSEC Histograms: ";
+  stream << "NYI ";
+  stream << "Policy Counters: ";
+  stream << "NYI ";
+  stream << "Policy Name Counters: ";
+  stream << "NYI ";
+
+  stream << std::endl;
+  return stream.str();
+}
+
+}
+
+// Compile with:
+// c++ -DTEST_TCOUNTER_TIMING -Wall -std=c++17 -O2 rec-tcounters.cc -pthread
+
+#if TEST_TCOUNTER_TIMING
+
+#include <iostream>
+#include <vector>
+#include <atomic>
+#include <thread>
+#include <ctime>
+
+rec::GlobalCounters g_counters;
+thread_local rec::TCounters t_counters(g_counters);
+
+std::atomic<uint64_t> atomicCounter;
+
+size_t iterations;
+
+void atomicThread()
+{
+  for (size_t i = 0; i < iterations; i++) {
+    ++atomicCounter;
+  }
+}
+
+void tcounterThread()
+{
+  for (size_t i = 0; i < iterations; i++) {
+    ++t_counters.at(rec::Counter::qcounter);
+    if (i % 100 == 0) {
+      t_counters.updateSnap();
+    }
+  }
+}
+
+int main(int argc, char* argv[])
+{
+  size_t threads = std::atoi(argv[1]);
+  iterations = std::atoi(argv[2]);
+
+  std::cout << "Starting " << threads << " threads doing " << iterations << " iterations using atomics" << std::endl;
+  std::vector<std::thread> thr;
+  thr.resize(threads);
+
+  timeval start;
+  gettimeofday(&start, nullptr);
+  for (size_t i = 0; i < threads; i++) {
+    thr[i] = std::thread(atomicThread);
+  }
+  for (size_t i = 0; i < threads; i++) {
+    thr[i].join();
+  }
+  timeval stop;
+  gettimeofday(&stop, nullptr);
+  timeval diff;
+  timersub(&stop, &start, &diff);
+  auto elapsed = (diff.tv_sec + diff.tv_usec / 1e6);
+  std::cout << "Sum is " << atomicCounter << " elapsed is " << elapsed << std::endl;
+
+  std::cout << "Now doing the same with tcounters" << std::endl;
+  gettimeofday(&start, nullptr);
+  for (size_t i = 0; i < threads; i++) {
+    thr[i] = std::thread(tcounterThread);
+  }
+  for (size_t i = 0; i < threads; i++) {
+    thr[i].join();
+  }
+  gettimeofday(&stop, nullptr);
+  timersub(&stop, &start, &diff);
+  elapsed = (diff.tv_sec + diff.tv_usec / 1e6);
+  std::cout << "Sum is " << g_counters.sum(rec::Counter::qcounter) << " elapsed is " << elapsed << std::endl;
+}
+
+#endif
diff --git a/pdns/recursordist/rec-tcounters.hh b/pdns/recursordist/rec-tcounters.hh
new file mode 100644 (file)
index 0000000..23e17ef
--- /dev/null
@@ -0,0 +1,342 @@
+/*
+ * 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 "tcounters.hh"
+
+#include <string>
+
+#include "histogram.hh"
+#include "rec-responsestats.hh"
+#include "validate.hh"
+#include "filterpo.hh"
+
+namespace rec
+{
+
+// Simple counters
+enum class Counter : uint8_t
+{
+  syncresqueries,
+  outgoingtimeouts,
+  outgoing4timeouts,
+  outgoing6timeouts,
+  throttledqueries,
+  dontqueries,
+  qnameminfallbacksuccess,
+  authzonequeries,
+  outqueries,
+  tcpoutqueries,
+  dotoutqueries,
+  unreachables,
+  servFails,
+  nxDomains,
+  noErrors,
+  qcounter,
+  ipv6qcounter,
+  tcpqcounter,
+  unauthorizedUDP, // when this is increased, qcounter isn't
+  unauthorizedTCP, // when this is increased, qcounter isn't
+  sourceDisallowedNotify, // when this is increased, qcounter is also
+  zoneDisallowedNotify, // when this is increased, qcounter is also
+  policyDrops,
+  tcpClientOverflow,
+  clientParseError,
+  serverParseError,
+  tooOldDrops,
+  truncatedDrops,
+  queryPipeFullDrops,
+  unexpectedCount,
+  caseMismatchCount,
+  spoofCount,
+  resourceLimits,
+  overCapacityDrops,
+  ipv6queries,
+  chainResends,
+  nsSetInvalidations,
+  ednsPingMatches,
+  ednsPingMismatches,
+  noPingOutQueries,
+  noEdnsOutQueries,
+  packetCacheHits,
+  noPacketError,
+  ignoredCount,
+  emptyQueriesCount,
+  dnssecQueries,
+  dnssecAuthenticDataQueries,
+  dnssecCheckDisabledQueries,
+  variableResponses,
+  maxMThreadStackUsage,
+  dnssecValidations, // should be the sum of all dnssecResult* stats
+  rebalancedQueries,
+  proxyProtocolInvalidCount,
+  nodLookupsDroppedOversize,
+  dns64prefixanswers,
+  maintenanceUsec,
+  maintenanceCalls,
+  nodCount,
+  udrCount,
+
+  numberOfCounters
+};
+
+// double averages times, weighted according to how many packets they processed
+enum class DoubleWAvgCounter : uint8_t
+{
+  avgLatencyUsec,
+  avgLatencyOursUsec,
+  numberOfCounters
+};
+
+// An RCode histogram
+enum class RCode : uint8_t
+{
+  auth,
+  numberOfCounters
+};
+
+// Recursor Response Stats
+enum class ResponseStats : uint8_t
+{
+  responseStats,
+  numberOfCounters
+};
+
+// A few other histograms
+enum class Histogram : uint8_t
+{
+  answers,
+  auth4Answers,
+  auth6Answers,
+  ourtime,
+  cumulativeAnswers,
+  cumulativeAuth4Answers,
+  cumulativeAuth6Answers,
+
+  numberOfCounters
+};
+
+// DNSSEC validation results
+enum class DNSSECHistogram : uint8_t
+{
+  dnssec,
+  xdnssec,
+
+  numberOfCounters
+};
+
+// Policy hits
+enum class PolicyHistogram : uint8_t
+{
+  policy,
+
+  numberOfCounters
+};
+
+enum class PolicyNameHits : uint8_t
+{
+  policyName,
+
+  numberOfCounters
+};
+
+struct Counters
+{
+  // An array of simple counters
+  std::array<uint64_t, static_cast<size_t>(Counter::numberOfCounters)> uint64Count{};
+
+  struct WeightedAverage
+  {
+    double avg{};
+    uint64_t weight{};
+
+    void add(double value)
+    {
+      avg = value;
+      ++weight;
+    }
+
+    void addToRollingAvg(double value, uint64_t rollsize)
+    {
+      add((1.0 - 1.0 / static_cast<double>(rollsize)) * avg + value / static_cast<double>(rollsize));
+    }
+  };
+  // And an array of weighted averaged values
+  std::array<WeightedAverage, static_cast<size_t>(DoubleWAvgCounter::numberOfCounters)> doubleWAvg{};
+
+  struct RCodeCounters
+  {
+    RCodeCounters& operator+=(const RCodeCounters& rhs)
+    {
+      for (size_t i = 0; i < rcodeCounters.size(); i++) {
+        rcodeCounters.at(i) += rhs.rcodeCounters.at(i);
+      }
+      return *this;
+    }
+    static const size_t numberoOfRCodes = 16;
+    std::array<uint64_t, numberoOfRCodes> rcodeCounters;
+  };
+  // An RCodes histogram
+  RCodeCounters auth{};
+
+  std::array<pdns::Histogram, static_cast<size_t>(Histogram::numberOfCounters)> histograms = {
+    pdns::Histogram{"answers", {1000, 10000, 100000, 1000000}},
+    pdns::Histogram{"auth4answers", {1000, 10000, 100000, 1000000}},
+    pdns::Histogram{"auth6answers", {1000, 10000, 100000, 1000000}},
+    pdns::Histogram{"ourtime", {1000, 2000, 4000, 8000, 16000, 32000}},
+    pdns::Histogram{"cumul-clientanswers-", 10, 19},
+    pdns::Histogram{"cumul-authanswers-", 1000, 13},
+    pdns::Histogram{"cumul-authanswers-", 1000, 13}};
+
+  // Response stats
+  RecResponseStats responseStats{};
+
+  // DNSSEC stats
+  struct DNSSECCounters
+  {
+    DNSSECCounters& operator+=(const DNSSECCounters& rhs)
+    {
+      for (size_t i = 0; i < counts.size(); i++) {
+        counts.at(i) += rhs.counts.at(i);
+      }
+      return *this;
+    }
+    uint64_t& at(vState index)
+    {
+      return counts.at(static_cast<size_t>(index));
+    }
+    std::array<uint64_t, static_cast<size_t>(vState::BogusInvalidDNSKEYProtocol) + 1> counts;
+  };
+  std::array<DNSSECCounters, static_cast<size_t>(DNSSECHistogram::numberOfCounters)> dnssecCounters{};
+
+  // Policy histogram
+  struct PolicyCounters
+  {
+    PolicyCounters& operator+=(const PolicyCounters& rhs)
+    {
+      for (size_t i = 0; i < counts.size(); i++) {
+        counts.at(i) += rhs.counts.at(i);
+      }
+      return *this;
+    }
+    uint64_t& at(DNSFilterEngine::PolicyKind index)
+    {
+      return counts.at(static_cast<size_t>(index));
+    }
+    std::array<uint64_t, static_cast<size_t>(DNSFilterEngine::PolicyKind::Custom) + 1> counts;
+  };
+  PolicyCounters policyCounters{};
+
+  // Policy hits by name
+  struct PolicyNameCounters
+  {
+    PolicyNameCounters& operator+=(const PolicyNameCounters& rhs)
+    {
+      for (const auto& [name, count] : rhs.counts) {
+        counts[name] += count;
+      }
+      return *this;
+    }
+    std::unordered_map<std::string, uint64_t> counts;
+  };
+  PolicyNameCounters policyNameHits;
+
+  Counters()
+  {
+    for (auto& elem : uint64Count) {
+      elem = 0;
+    }
+    // doubleWAvg has a default constructor that initializes
+    for (auto& elem : auth.rcodeCounters) {
+      elem = 0;
+    }
+    // Histogram has a constructor that initializes
+    // RecResponseStats has a default constructor that initializes
+    for (auto& histogram : dnssecCounters) {
+      for (auto& elem : histogram.counts) {
+        elem = 0;
+      }
+    }
+    for (auto& elem : policyCounters.counts) {
+      elem = 0;
+    }
+    // PolicyNameCounters has a default constuctor that initializes
+  }
+
+  // Merge a set of counters into an existing set of counters. For simple counters, that will be additions
+  // for averages, we should take the weights into account. Histograms need to sum all individual counts.
+  Counters& merge(const Counters& data);
+
+  // The following accessors select the right counter type based on the index type
+  uint64_t& at(Counter index)
+  {
+    return uint64Count.at(static_cast<size_t>(index));
+  }
+
+  WeightedAverage& at(DoubleWAvgCounter index)
+  {
+    return doubleWAvg.at(static_cast<size_t>(index));
+  }
+
+  RCodeCounters& at(RCode /*unused*/)
+  {
+    // We only have a single RCode indexed Histogram, so no need to select a specific one
+    return auth;
+  }
+
+  RecResponseStats& at(ResponseStats /*unused*/)
+  {
+    // We only have a single ResponseStats indexed RecResponseStats, so no need to select a specific one
+    return responseStats;
+  }
+
+  pdns::Histogram& at(Histogram index)
+  {
+    return histograms.at(static_cast<size_t>(index));
+  }
+
+  DNSSECCounters& at(DNSSECHistogram index)
+  {
+    return dnssecCounters.at(static_cast<size_t>(index));
+  }
+
+  // We only have a single PolicyHistogram indexed PolicyCounters, so no need to select a specific one
+  PolicyCounters& at(PolicyHistogram)
+  {
+    return policyCounters;
+  }
+
+  // We only have a single policyNameHits indexed PolicyNameCounters, so no need to select a specific one
+  PolicyNameCounters& at(PolicyNameHits /*unused*/)
+  {
+    return policyNameHits;
+  }
+
+  // Mainly for debugging purposes
+  [[nodiscard]] std::string toString() const;
+};
+
+// The application specific types, one for thread local, one for the aggregator
+using TCounters = pdns::TLocalCounters<Counters>;
+using GlobalCounters = pdns::GlobalCounters<Counters>;
+}
index 0ad1cb672109b9aa101ab497f3ea5c8cd41dd91e..0eac47653b534f8eeefd95278f7df86a0a6e6cc9 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,66 +96,70 @@ TCPConnection::TCPConnection(int fd, const ComboAddress& addr) :
 TCPConnection::~TCPConnection()
 {
   try {
-    if (closesocket(d_fd) < 0)
-      g_log << Logger::Error << "Error closing socket for TCPConnection" << endl;
+    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) {
-    g_log << Logger::Error << "Error closing TCPConnection socket: " << e.reason << endl;
+    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
@@ -126,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;
   }
 
@@ -145,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);
   }
 }
 
@@ -172,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) {
@@ -187,13 +230,13 @@ public:
   {
     d_fd = -1;
   }
-  bool handleTCPReadResult(int fd, ssize_t bytes)
+  bool handleTCPReadResult(int /* fd */, ssize_t bytes)
   {
     if (bytes == 0) {
       /* EOF */
       return false;
     }
-    else if (bytes < 0) {
+    if (bytes < 0) {
       if (errno != EAGAIN && errno != EWOULDBLOCK) {
         return false;
       }
@@ -206,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)
+{
+  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;
+
+  if (needECS || (t_pdl && (t_pdl->d_gettag_ffi || t_pdl->d_gettag)) || 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->d_gettag_ffi) {
+            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->d_gettag) {
+            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)
 {
-  shared_ptr<TCPConnection> conn = boost::any_cast<shared_ptr<TCPConnection>>(var);
+  auto conn = boost::any_cast<shared_ptr<TCPConnection>>(var);
 
-  RunningTCPQuestionGuard tcpGuard{fd};
+  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;
     }
 
@@ -224,48 +519,59 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
     ssize_t remaining = isProxyHeaderComplete(conn->data);
     if (remaining == 0) {
       if (g_logCommonErrors) {
-        g_log << Logger::Error << "Unable to consume proxy protocol header in packet from TCP client " << conn->d_remote.toStringWithPort() << endl;
+        SLOG(g_log << Logger::Error << "Unable to consume proxy protocol header in packet from TCP client " << conn->d_remote.toStringWithPort() << endl,
+             g_slogtcpin->info(Logr::Error, "Unable to consume proxy protocol header in packet from TCP client", "remote", Logging::Loggable(conn->d_remote)));
       }
-      ++g_stats.proxyProtocolInvalidCount;
+      ++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) {
         if (g_logCommonErrors) {
-          g_log << Logger::Error << "Unable to parse proxy protocol header in packet from TCP client " << conn->d_remote.toStringWithPort() << endl;
+          SLOG(g_log << Logger::Error << "Unable to parse proxy protocol header in packet from TCP client " << conn->d_remote.toStringWithPort() << endl,
+               g_slogtcpin->info(Logr::Error, "Unable to parse proxy protocol header in packet from TCP client", "remote", Logging::Loggable(conn->d_remote)));
         }
-        ++g_stats.proxyProtocolInvalidCount;
+        ++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) {
-          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;
+          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)));
         }
-        ++g_stats.proxyProtocolInvalidCount;
+        ++t_Counters.at(rec::Counter::proxyProtocolInvalidCount);
         return;
       }
 
       /* Now that we have retrieved the address of the client, as advertised by the proxy
          via the proxy protocol header, check that it is allowed by our ACL */
       /* note that if the proxy header used a 'LOCAL' command, the original source and destination are untouched so everything should be fine */
-      if (t_allowFrom && !t_allowFrom->match(&conn->d_source)) {
+      conn->d_mappedSource = conn->d_source;
+      if (t_proxyMapping) {
+        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) {
-          g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP query from " << conn->d_source.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)));
         }
 
-        ++g_stats.unauthorizedTCP;
+        ++t_Counters.at(rec::Counter::unauthorizedTCP);
         return;
       }
 
@@ -275,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);
@@ -285,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;
     }
   }
@@ -299,9 +606,10 @@ 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) {
-          g_log << Logger::Error << "TCP client " << conn->d_remote.toStringWithPort() << " disconnected after first byte" << endl;
+          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)));
         }
       }
       return;
@@ -311,301 +619,76 @@ 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) {
-          g_log << Logger::Error << "TCP client " << conn->d_remote.toStringWithPort() << " disconnected while reading question body" << endl;
+          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)));
         }
       }
       return;
     }
-    else if (bytes > std::numeric_limits<std::uint16_t>::max()) {
+    if (bytes > std::numeric_limits<std::uint16_t>::max()) {
       if (g_logCommonErrors) {
-        g_log << Logger::Error << "TCP client " << conn->d_remote.toStringWithPort() << " sent an invalid question size while reading question body" << endl;
+        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)));
       }
       return;
     }
     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) {
-        g_stats.clientParseError++;
-        if (g_logCommonErrors) {
-          g_log << Logger::Error << "Unable to parse packet from TCP client " << conn->d_remote.toStringWithPort() << endl;
-        }
-        return;
-      }
-      if (SyncRes::isUnsupported(dc->d_mdp.d_qtype)) {
-        g_stats.ignoredCount++;
+        t_Counters.at(rec::Counter::clientParseError)++;
         if (g_logCommonErrors) {
-          g_log << Logger::Error << "Unsupported qtype " << dc->d_mdp.d_qtype << " from TCP client " << conn->d_remote.toStringWithPort() << endl;
+          SLOG(g_log << Logger::Error << "Unable to parse packet from TCP client " << conn->d_remote.toStringWithPort() << endl,
+               g_slogtcpin->info(Logr::Error, "Unable to parse packet from TCP client", "remte", Logging::Loggable(conn->d_remote)));
         }
         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);
-      dc->setSource(conn->d_source);
+      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);
-      dc->setDestination(conn->d_destination);
+      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;
-      bool needXPF = g_XPFAcl.match(conn->d_remote);
-      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 && luaconfsLocal->protobufExportConfig.logQueries;
-      dc->d_logResponse = t_protobufServers && luaconfsLocal->protobufExportConfig.logResponses;
-
-#ifdef HAVE_FSTRM
-      checkFrameStreamExport(luaconfsLocal);
-#endif
-
-      if (needECS || needXPF || (t_pdl && (t_pdl->d_gettag_ffi || t_pdl->d_gettag)) || dc->d_mdp.d_header.opcode == Opcode::Notify) {
-
-        try {
-          EDNSOptionViewMap ednsOptions;
-          bool xpfFound = false;
-          dc->d_ecsParsed = true;
-          dc->d_ecsFound = false;
-          getQNameAndSubnet(conn->data, &qname, &qtype, &qclass,
-                            dc->d_ecsFound, &dc->d_ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr,
-                            xpfFound, needXPF ? &dc->d_source : nullptr, needXPF ? &dc->d_destination : 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) {
-                g_log << Logger::Warning << "Error parsing a query packet qname='" << qname << "' for tag determination, setting tag=0: " << e.what() << endl;
-              }
-            }
-          }
-        }
-        catch (const std::exception& e) {
-          if (g_logCommonErrors) {
-            g_log << Logger::Warning << "Error parsing a query packet for tag determination, setting tag=0: " << e.what() << endl;
-          }
-        }
-      }
-
-      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 || t_outgoingProtobufServers) {
-        dc->d_requestorId = requestorId;
-        dc->d_deviceId = deviceId;
-        dc->d_deviceName = deviceName;
-        dc->d_uuid = getUniqueID();
-      }
-
-      if (t_protobufServers) {
-        try {
-
-          if (logQuery && !(luaconfsLocal->protobufExportConfig.taggedOnly && dc->d_policyTags.empty())) {
-            protobufLogQuery(luaconfsLocal, dc->d_uuid, dc->d_source, dc->d_destination, 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) {
-            g_log << Logger::Warning << "Error parsing a TCP query packet for edns subnet: " << e.what() << endl;
-          }
-        }
-      }
-
-      if (t_pdl) {
-        bool ipf = t_pdl->ipfilter(dc->d_source, dc->d_destination, *dh, dc->d_eventTrace);
-        if (ipf) {
-          if (!g_quiet) {
-            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_stats.policyDrops++;
-          return;
-        }
-      }
-
-      if (dc->d_mdp.d_header.qr) {
-        g_stats.ignoredCount++;
-        if (g_logCommonErrors) {
-          g_log << Logger::Error << "Ignoring answer from TCP client " << dc->getRemote() << " on server socket!" << endl;
-        }
-        return;
-      }
-      if (dc->d_mdp.d_header.opcode != Opcode::Query && dc->d_mdp.d_header.opcode != Opcode::Notify) {
-        g_stats.ignoredCount++;
-        if (g_logCommonErrors) {
-          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;
-        }
-        sendErrorOverTCP(dc, RCode::NotImp);
-        tcpGuard.keep();
-        return;
-      }
-      else if (dh->qdcount == 0) {
-        g_stats.emptyQueriesCount++;
-        if (g_logCommonErrors) {
-          g_log << Logger::Error << "Ignoring empty (qdcount == 0) query from " << dc->getRemote() << " on server socket!" << endl;
-        }
-        sendErrorOverTCP(dc, RCode::NotImp);
-        tcpGuard.keep();
-        return;
-      }
-      else {
-        // We have read a proper query
-        ++g_stats.qcounter;
-        ++g_stats.tcpqcounter;
-
-        if (dc->d_mdp.d_header.opcode == Opcode::Notify) {
-          if (!t_allowNotifyFrom || !t_allowNotifyFrom->match(dc->d_source)) {
-            if (!g_quiet) {
-              g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP NOTIFY from " << dc->d_source.toString() << ", address not matched by allow-notify-from" << endl;
-            }
-
-            g_stats.sourceDisallowedNotify++;
-            return;
-          }
-
-          if (!isAllowNotifyForZone(qname)) {
-            if (!g_quiet) {
-              g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP NOTIFY from " << dc->d_source.toString() << ", for " << qname.toLogString() << ", zone not matched by allow-notify-for" << endl;
-            }
-
-            g_stats.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_eventTrace.add(RecEventTrace::PCacheCheck, cacheHit, false);
-
-          if (cacheHit) {
-            if (!g_quiet) {
-              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;
-            }
-
-            bool hadError = sendResponseOverTCP(dc, response);
-            finishTCPReply(dc, hadError, false);
-            struct timeval now;
-            Utility::gettimeofday(&now, nullptr);
-            uint64_t spentUsec = uSec(now - start);
-            g_stats.cumulativeAnswers(spentUsec);
-            dc->d_eventTrace.add(RecEventTrace::AnswerSent);
-
-            if (t_protobufServers && 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_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) {
-              g_log << Logger::Info << dc->d_eventTrace.toString() << endl;
-            }
-            tcpGuard.keep();
-            return;
-          } // cache hit
-        } // query opcode
-
-        if (dc->d_mdp.d_header.opcode == Opcode::Notify) {
-          if (!g_quiet) {
-            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;
-          }
-
-          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) {
-      g_stats.overCapacityDrops++;
+    if (g_multiTasker->numProcesses() > g_maxMThreads) {
+      t_Counters.at(rec::Counter::overCapacityDrops)++;
       try {
         closesocket(newsock);
       }
       catch (const PDNSException& e) {
-        g_log << Logger::Error << "Error closing TCP socket after an over capacity drop: " << e.reason << endl;
+        SLOG(g_log << Logger::Error << "Error closing TCP socket after an over capacity drop: " << e.reason << endl,
+             g_slogtcpin->error(Logr::Error, e.reason, "Error closing TCP socket after an over capacity drop", "exception", Logging::Loggable("PDNSException")));
       }
       return;
     }
@@ -615,57 +698,71 @@ void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t&)
     }
 
     bool fromProxyProtocolSource = expectProxyProtocol(addr);
-    if (t_allowFrom && !t_allowFrom->match(&addr) && !fromProxyProtocolSource) {
-      if (!g_quiet)
-        g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP query from " << addr.toString() << ", address neither matched by allow-from nor proxy-protocol-from" << endl;
-
-      g_stats.unauthorizedTCP++;
+    ComboAddress mappedSource = addr;
+    if (!fromProxyProtocolSource && t_proxyMapping) {
+      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 << "[" << 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);
       }
       catch (const PDNSException& e) {
-        g_log << Logger::Error << "Error closing TCP socket after an ACL drop: " << e.reason << endl;
+        SLOG(g_log << Logger::Error << "Error closing TCP socket after an ACL drop: " << e.reason << endl,
+             g_slogtcpin->error(Logr::Error, e.reason, "Error closing TCP socket after an ACL drop", "exception", Logging::Loggable("PDNSException")));
       }
       return;
     }
 
-    if (g_maxTCPPerClient && t_tcpClientCounts->count(addr) && (*t_tcpClientCounts)[addr] >= g_maxTCPPerClient) {
-      g_stats.tcpClientOverflow++;
+    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!
       }
       catch (const PDNSException& e) {
-        g_log << Logger::Error << "Error closing TCP socket after an overflow drop: " << e.reason << endl;
+        SLOG(g_log << Logger::Error << "Error closing TCP socket after an overflow drop: " << e.reason << endl,
+             g_slogtcpin->error(Logr::Error, e.reason, "Error closing TCP socket after an overflow drop", "exception", Logging::Loggable("PDNSException")));
       }
       return;
     }
 
     setNonBlocking(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
+    setTCPNoDelay(newsock);
+    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)
 {
@@ -737,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);
@@ -765,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;
@@ -781,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;
@@ -796,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:
@@ -815,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;
@@ -825,36 +922,41 @@ static void TCPIOHandlerIO(int fd, FDMultiplexer::funcparam_t& var)
   TCPIOHandlerStateChange(pid->lowState, newstate, pid);
 }
 
-void checkFastOpenSysctl(bool active)
+void checkFastOpenSysctl([[maybe_unused]] bool active, [[maybe_unused]] Logr::log_t log)
 {
 #ifdef __linux__
   string line;
   if (readFileIfThere("/proc/sys/net/ipv4/tcp_fastopen", &line)) {
     int flag = std::stoi(line);
     if (active && !(flag & 1)) {
-      g_log << Logger::Error << "tcp-fast-open-connect enabled but net.ipv4.tcp_fastopen does not allow it" << endl;
+      SLOG(g_log << Logger::Error << "tcp-fast-open-connect enabled but net.ipv4.tcp_fastopen does not allow it" << endl,
+           log->info(Logr::Error, "tcp-fast-open-connect enabled but net.ipv4.tcp_fastopen does not allow it"));
     }
     if (!active && !(flag & 2)) {
-      g_log << Logger::Error << "tcp-fast-open enabled but net.ipv4.tcp_fastopen does not allow it" << endl;
+      SLOG(g_log << Logger::Error << "tcp-fast-open enabled but net.ipv4.tcp_fastopen does not allow it" << endl,
+           log->info(Logr::Error, "tcp-fast-open enabled but net.ipv4.tcp_fastopen does not allow it"));
     }
   }
   else {
-    g_log << Logger::Notice << "Cannot determine if kernel settings allow fast-open" << endl;
+    SLOG(g_log << Logger::Notice << "Cannot determine if kernel settings allow fast-open" << endl,
+         log->info(Logr::Notice, "Cannot determine if kernel settings allow fast-open"));
   }
 #else
-  g_log << Logger::Notice << "Cannot determine if kernel settings allow fast-open" << endl;
+  SLOG(g_log << Logger::Notice << "Cannot determine if kernel settings allow fast-open" << endl,
+       log->info(Logr::Notice, "Cannot determine if kernel settings allow fast-open"));
 #endif
 }
 
-void checkTFOconnect()
+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) {
-    g_log << Logger::Error << "tcp-fast-open-connect enabled but returned error: " << e.what() << endl;
+    SLOG(g_log << Logger::Error << "tcp-fast-open-connect enabled but returned error: " << e.what() << endl,
+         log->error(Logr::Error, e.what(), "tcp-fast-open-connect enabled but returned error"));
   }
 }
 
@@ -868,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());
@@ -888,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;
@@ -917,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);
@@ -958,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;
@@ -980,67 +1082,65 @@ LWResult::Result arecvtcp(PacketBuffer& data, const size_t len, shared_ptr<TCPIO
   return LWResult::Result::Success;
 }
 
-void makeTCPServerSockets(deferredAdd_t& deferredAdds, std::set<int>& tcpSockets)
+void makeTCPServerSockets(deferredAdd_t& deferredAdds, std::set<int>& tcpSockets, Logr::log_t log)
 {
-  int fd;
-  vector<string> locals;
-  stringtok(locals, ::arg()["local-address"], " ,");
+  vector<string> localAddresses;
+  stringtok(localAddresses, ::arg()["local-address"], " ,");
 
-  if (locals.empty())
+  if (localAddresses.empty()) {
     throw PDNSException("No local address specified");
+  }
 
-  for (vector<string>::const_iterator i = locals.begin(); i != locals.end(); ++i) {
-    ServiceTuple st;
-    st.port = ::arg().asNum("local-port");
-    parseService(*i, st);
-
-    ComboAddress sin;
-
-    sin.reset();
-    sin.sin4.sin_family = AF_INET;
-    if (!IpToU32(st.host, (uint32_t*)&sin.sin4.sin_addr.s_addr)) {
-      sin.sin6.sin6_family = AF_INET6;
-      if (makeIPv6sockaddr(st.host, &sin.sin6) < 0)
-        throw PDNSException("Unable to resolve local address for TCP server on '" + st.host + "'");
-    }
-
-    fd = socket(sin.sin6.sin6_family, SOCK_STREAM, 0);
-    if (fd < 0)
+#ifdef TCP_DEFER_ACCEPT
+  auto first = true;
+#endif
+  const uint16_t defaultLocalPort = ::arg().asNum("local-port");
+  for (const auto& localAddress : localAddresses) {
+    ComboAddress address{localAddress, defaultLocalPort};
+    const int socketFd = socket(address.sin6.sin6_family, SOCK_STREAM, 0);
+    if (socketFd < 0) {
       throw PDNSException("Making a TCP server socket for resolver: " + stringerror());
+    }
 
-    setCloseOnExec(fd);
+    setCloseOnExec(socketFd);
 
     int tmp = 1;
-    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof tmp) < 0) {
-      g_log << Logger::Error << "Setsockopt failed for TCP listening socket" << endl;
-      exit(1);
+    if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof tmp) < 0) {
+      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);
     }
-    if (sin.sin6.sin6_family == AF_INET6 && setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &tmp, sizeof(tmp)) < 0) {
+    if (address.sin6.sin6_family == AF_INET6 && setsockopt(socketFd, IPPROTO_IPV6, IPV6_V6ONLY, &tmp, sizeof(tmp)) < 0) {
       int err = errno;
-      g_log << Logger::Error << "Failed to set IPv6 socket to IPv6 only, continuing anyhow: " << strerror(err) << endl;
+      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
-    if (setsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &tmp, sizeof tmp) >= 0) {
-      if (i == locals.begin())
-        g_log << Logger::Info << "Enabled TCP data-ready filter for (slight) DoS protection" << endl;
+    if (setsockopt(socketFd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &tmp, sizeof tmp) >= 0) {
+      if (first) {
+        SLOG(g_log << Logger::Info << "Enabled TCP data-ready filter for (slight) DoS protection" << endl,
+             log->info(Logr::Info, "Enabled TCP data-ready filter for (slight) DoS protection"));
+      }
     }
 #endif
 
-    if (::arg().mustDo("non-local-bind"))
-      Utility::setBindAny(AF_INET, fd);
+    if (::arg().mustDo("non-local-bind")) {
+      Utility::setBindAny(AF_INET, socketFd);
+    }
 
     if (g_reusePort) {
 #if defined(SO_REUSEPORT_LB)
       try {
-        SSetsockopt(fd, SOL_SOCKET, SO_REUSEPORT_LB, 1);
+        SSetsockopt(socketFd, SOL_SOCKET, SO_REUSEPORT_LB, 1);
       }
       catch (const std::exception& e) {
         throw PDNSException(std::string("SO_REUSEPORT_LB: ") + e.what());
       }
 #elif defined(SO_REUSEPORT)
       try {
-        SSetsockopt(fd, SOL_SOCKET, SO_REUSEPORT, 1);
+        SSetsockopt(socketFd, SOL_SOCKET, SO_REUSEPORT, 1);
       }
       catch (const std::exception& e) {
         throw PDNSException(std::string("SO_REUSEPORT: ") + e.what());
@@ -1049,39 +1149,44 @@ void makeTCPServerSockets(deferredAdd_t& deferredAdds, std::set<int>& tcpSockets
     }
 
     if (SyncRes::s_tcp_fast_open > 0) {
-      checkFastOpenSysctl(false);
+      checkFastOpenSysctl(false, log);
 #ifdef TCP_FASTOPEN
-      if (setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &SyncRes::s_tcp_fast_open, sizeof SyncRes::s_tcp_fast_open) < 0) {
+      if (setsockopt(socketFd, IPPROTO_TCP, TCP_FASTOPEN, &SyncRes::s_tcp_fast_open, sizeof SyncRes::s_tcp_fast_open) < 0) {
         int err = errno;
-        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
-      g_log << Logger::Warning << "TCP Fast Open configured but not supported for listening socket" << endl;
+      SLOG(g_log << Logger::Warning << "TCP Fast Open configured but not supported for listening socket" << endl,
+           log->info(Logr::Warning, "TCP Fast Open configured but not supported for listening socket"));
 #endif
     }
 
-    sin.sin4.sin_port = htons(st.port);
-    socklen_t socklen = sin.sin4.sin_family == AF_INET ? sizeof(sin.sin4) : sizeof(sin.sin6);
-    if (::bind(fd, (struct sockaddr*)&sin, socklen) < 0)
-      throw PDNSException("Binding TCP server socket for " + st.host + ": " + stringerror());
+    socklen_t socklen = address.sin4.sin_family == AF_INET ? sizeof(address.sin4) : sizeof(address.sin6);
+    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());
+    }
 
-    setNonBlocking(fd);
+    setNonBlocking(socketFd);
     try {
-      setSocketSendBuffer(fd, 65000);
+      setSocketSendBuffer(socketFd, 65000);
     }
     catch (const std::exception& e) {
-      g_log << Logger::Error << e.what() << endl;
+      SLOG(g_log << Logger::Error << e.what() << endl,
+           log->error(Logr::Error, e.what(), "Exception while setting socket send buffer"));
     }
 
-    listen(fd, 128);
-    deferredAdds.emplace_back(fd, handleNewTCPQuestion);
-    tcpSockets.insert(fd);
+    listen(socketFd, 128);
+    deferredAdds.emplace_back(socketFd, handleNewTCPQuestion);
+    tcpSockets.insert(socketFd);
 
     // we don't need to update g_listenSocketsAddresses since it doesn't work for TCP/IP:
     //  - fd is not that which we know here, but returned from accept()
-    if (sin.sin4.sin_family == AF_INET)
-      g_log << Logger::Info << "Listening for TCP queries on " << sin.toString() << ":" << st.port << endl;
-    else
-      g_log << Logger::Info << "Listening for TCP queries on [" << sin.toString() << "]:" << st.port << endl;
+    SLOG(g_log << Logger::Info << "Listening for TCP queries on " << address.toStringWithPort() << endl,
+         log->info(Logr::Info, "Listening for queries", "protocol", Logging::Loggable("TCP"), "address", Logging::Loggable(address)));
+
+#ifdef TCP_DEFER_ACCEPT
+    first = false;
+#endif
   }
 }
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 3918766ac5128173f4e3089d4aeb0411b2ff383a..415933ca4bc701e767a5660a142749fdf2eb7b34 100644 (file)
@@ -40,7 +40,7 @@
 
 struct ZoneData
 {
-  ZoneData(shared_ptr<Logr::Logger>& 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)) {}
@@ -48,21 +48,21 @@ struct ZoneData
   // Potentially the two fields below could be merged into a single map. ATM it is not clear to me
   // if that would make the code easier to read.
   std::map<pair<DNSName, QType>, vector<DNSRecord>> d_all;
-  std::map<pair<DNSName, QType>, vector<shared_ptr<RRSIGRecordContent>>> d_sigs;
+  std::map<pair<DNSName, QType>, vector<shared_ptr<const RRSIGRecordContent>>> d_sigs;
 
   // Maybe use a SuffixMatchTree?
   std::set<DNSName> d_delegations;
 
-  shared_ptr<Logr::Logger>& 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<RRSIGRecordContent>> sigsrr;
-      sigsrr.push_back(rr);
+      vector<shared_ptr<const RRSIGRecordContent>> sigsrr;
+      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");
@@ -215,21 +221,22 @@ static std::vector<std::string> getURL(const RecZoneToCache::Config& config)
 pdns::ZoneMD::Result ZoneData::processLines(const vector<string>& lines, const RecZoneToCache::Config& config, pdns::ZoneMD& zonemd)
 {
   DNSResourceRecord drr;
-  ZoneParserTNG zpt(lines, d_zone);
+  ZoneParserTNG zpt(lines, d_zone, true);
   zpt.setMaxGenerateSteps(1);
   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);
+  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);
-      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);
+      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);
-      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, 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);
+  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");
@@ -397,14 +406,13 @@ void ZoneData::ZoneToCache(const RecZoneToCache::Config& config)
     switch (qtype) {
     case QType::NSEC:
     case QType::NSEC3:
-      break;
     case QType::RRSIG:
       break;
     default: {
-      vector<shared_ptr<RRSIGRecordContent>> sigsrr;
-      auto it = d_sigs.find(key);
-      if (it != d_sigs.end()) {
-        sigsrr = it->second;
+      vector<shared_ptr<const RRSIGRecordContent>> sigsrr;
+      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)
@@ -430,7 +438,7 @@ void RecZoneToCache::maintainStates(const map<DNSName, Config>& configs, map<DNS
     }
   }
   // Reset states for which the config generation changed and create new states for new configs
-  for (auto config : configs) {
+  for (const auto& config : configs) {
     auto state = states.find(config.first);
     if (state != states.end()) {
       if (state->second.d_generation != mygeneration) {
@@ -438,7 +446,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,17 +467,16 @@ 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->info("Unable to load zone into cache, will retry", "exception", Logging::Loggable(e.reason), "refresh", Logging::Loggable(state.d_waittime));
+    log->error(Logr::Error, e.reason, "Unable to load zone into cache, will retry", "exception", Logging::Loggable("PDNSException"), "refresh", Logging::Loggable(state.d_waittime));
   }
   catch (const std::runtime_error& e) {
-    log->info("Unable to load zone into cache, will retry", "exception", Logging::Loggable(e.what()), "refresh", Logging::Loggable(state.d_waittime));
+    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", "exception", Logging::Loggable("unknown"), "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);
deleted file mode 120000 (symlink)
index 7d934221cbccafb1e9650172c570e81560f9aba5..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../rec_channel.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..6abab97a35fceb94b636ad5256d7b28af751bd16
--- /dev/null
@@ -0,0 +1,221 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "rec_channel.hh"
+#include "utility.hh"
+#include <sys/socket.h>
+#include <cerrno>
+#include "misc.hh"
+#include <string.h>
+#include <cstdlib>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <iostream>
+#include <limits.h>
+
+#include "pdnsexception.hh"
+
+#include "namespaces.hh"
+
+std::atomic<bool> RecursorControlChannel::stop = false;
+
+RecursorControlChannel::RecursorControlChannel()
+{
+  d_fd = -1;
+  *d_local.sun_path = 0;
+  d_local.sun_family = 0;
+}
+
+RecursorControlChannel::~RecursorControlChannel()
+{
+  if (d_fd > 0)
+    close(d_fd);
+  if (*d_local.sun_path)
+    unlink(d_local.sun_path);
+}
+
+int RecursorControlChannel::listen(const string& fname)
+{
+  d_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+  setCloseOnExec(d_fd);
+
+  if (d_fd < 0)
+    throw PDNSException("Creating UNIX domain socket: " + stringerror());
+
+  int tmp = 1;
+  if (setsockopt(d_fd, SOL_SOCKET, SO_REUSEADDR, (char*)&tmp, sizeof tmp) < 0)
+    throw PDNSException("Setsockopt failed: " + stringerror());
+
+  int err = unlink(fname.c_str());
+  if (err < 0 && errno != ENOENT)
+    throw PDNSException("Can't remove (previous) controlsocket '" + fname + "': " + stringerror() + " (try --socket-dir)");
+
+  if (makeUNsockaddr(fname, &d_local))
+    throw PDNSException("Unable to bind to controlsocket, path '" + fname + "' is not a valid UNIX socket path.");
+
+  if (bind(d_fd, (sockaddr*)&d_local, sizeof(d_local)) < 0)
+    throw PDNSException("Unable to bind to controlsocket '" + fname + "': " + stringerror());
+  if (::listen(d_fd, 0) == -1) {
+    throw PDNSException("Unable to listen on controlsocket '" + fname + "': " + stringerror());
+  }
+  return d_fd;
+}
+
+void RecursorControlChannel::connect(const string& path, const string& fname)
+{
+  struct sockaddr_un remote;
+
+  d_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+  setCloseOnExec(d_fd);
+
+  if (d_fd < 0)
+    throw PDNSException("Creating UNIX domain socket: " + stringerror());
+
+  try {
+    int tmp = 1;
+    if (setsockopt(d_fd, SOL_SOCKET, SO_REUSEADDR, (char*)&tmp, sizeof tmp) < 0)
+      throw PDNSException("Setsockopt failed: " + stringerror());
+
+    string remotename = path + "/" + fname;
+    if (makeUNsockaddr(remotename, &remote))
+      throw PDNSException("Unable to connect to controlsocket, path '" + remotename + "' is not a valid UNIX socket path.");
+
+    if (::connect(d_fd, (sockaddr*)&remote, sizeof(remote)) < 0) {
+      if (*d_local.sun_path)
+        unlink(d_local.sun_path);
+      throw PDNSException("Unable to connect to remote '" + string(remote.sun_path) + "': " + stringerror());
+    }
+  }
+  catch (...) {
+    close(d_fd);
+    d_fd = -1;
+    d_local.sun_path[0] = 0;
+    throw;
+  }
+}
+
+static void sendfd(int s, int fd)
+{
+  struct msghdr msg;
+  struct cmsghdr* cmsg;
+  union
+  {
+    struct cmsghdr hdr;
+    unsigned char buf[CMSG_SPACE(sizeof(int))];
+  } cmsgbuf;
+  struct iovec io_vector[1];
+  char ch = 'X';
+
+  io_vector[0].iov_base = &ch;
+  io_vector[0].iov_len = 1;
+
+  memset(&msg, 0, sizeof(msg));
+  msg.msg_control = &cmsgbuf.buf;
+  msg.msg_controllen = sizeof(cmsgbuf.buf);
+  msg.msg_iov = io_vector;
+  msg.msg_iovlen = 1;
+
+  cmsg = CMSG_FIRSTHDR(&msg);
+  cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+  cmsg->cmsg_level = SOL_SOCKET;
+  cmsg->cmsg_type = SCM_RIGHTS;
+  *(int*)CMSG_DATA(cmsg) = fd;
+
+  if (sendmsg(s, &msg, 0) == -1) {
+    throw PDNSException("Unable to send fd message over control channel: " + stringerror());
+  }
+}
+
+void RecursorControlChannel::send(int fd, const Answer& msg, unsigned int timeout, int fd_to_pass)
+{
+  int ret = waitForRWData(fd, false, timeout, 0);
+  if (ret == 0) {
+    throw PDNSException("Timeout sending message over control channel");
+  }
+  else if (ret < 0) {
+    throw PDNSException("Error sending message over control channel:" + stringerror());
+  }
+
+  if (::send(fd, &msg.d_ret, sizeof(msg.d_ret), 0) < 0) {
+    throw PDNSException("Unable to send return code over control channel: " + stringerror());
+  }
+  size_t len = msg.d_str.length();
+  if (::send(fd, &len, sizeof(len), 0) < 0) {
+    throw PDNSException("Unable to send length over control channel: " + stringerror());
+  }
+  if (::send(fd, msg.d_str.c_str(), len, 0) != static_cast<ssize_t>(len)) {
+    throw PDNSException("Unable to send message over control channel: " + stringerror());
+  }
+
+  if (fd_to_pass != -1) {
+    sendfd(fd, fd_to_pass);
+  }
+}
+
+static void waitForRead(int fd, unsigned int timeout, time_t start)
+{
+  time_t elapsed = time(nullptr) - 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");
+  }
+}
+
+static size_t getArgMax()
+{
+#if defined(ARG_MAX)
+  return ARG_MAX;
+#endif
+
+#if defined(_SC_ARG_MAX)
+  auto tmp = sysconf(_SC_ARG_MAX);
+  if (tmp != -1) {
+    return tmp;
+  }
+#endif
+  /* _POSIX_ARG_MAX */
+  return 4096;
+}
+
+RecursorControlChannel::Answer RecursorControlChannel::recv(int fd, unsigned int timeout)
+{
+  // timeout covers the operation of all read ops combined
+  const time_t start = time(nullptr);
+
+  waitForRead(fd, timeout, start);
+  int err;
+  if (::recv(fd, &err, sizeof(err), 0) != sizeof(err)) {
+    throw PDNSException("Unable to receive return status over control channel: " + stringerror());
+  }
+
+  waitForRead(fd, timeout, start);
+  size_t len;
+  if (::recv(fd, &len, sizeof(len), 0) != sizeof(len)) {
+    throw PDNSException("Unable to receive length over control channel: " + stringerror());
+  }
+
+  if (len > getArgMax()) {
+    throw PDNSException("Length of control channel message too large");
+  }
+
+  string str;
+  str.reserve(len);
+  while (str.length() < len) {
+    char buffer[1024];
+    waitForRead(fd, timeout, start);
+    size_t toRead = std::min(len - str.length(), sizeof(buffer));
+    ssize_t recvd = ::recv(fd, buffer, toRead, 0);
+    if (recvd <= 0) {
+      // EOF means we have a length error
+      throw PDNSException("Unable to receive message over control channel: " + stringerror());
+    }
+    str.append(buffer, recvd);
+  }
+
+  return {err, std::move(str)};
+}
deleted file mode 120000 (symlink)
index 9afee99fe5e45d954504fc7db8876ef23bb57351..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../rec_channel.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..784539fb2f2a624a34001393b633a8a3ab916a6a
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * 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 <map>
+#include <optional>
+#include <vector>
+#include <inttypes.h>
+#include <sys/un.h>
+#include <signal.h>
+#include <pthread.h>
+#include "iputils.hh"
+#include "dnsname.hh"
+#include "sholder.hh"
+#include <atomic>
+
+extern GlobalStateHolder<SuffixMatchNode> g_dontThrottleNames;
+extern GlobalStateHolder<NetmaskGroup> g_dontThrottleNetmasks;
+
+/** this class is used both to send and answer channel commands to the PowerDNS Recursor */
+class RecursorControlChannel
+{
+public:
+  RecursorControlChannel();
+
+  ~RecursorControlChannel();
+
+  int listen(const std::string& filename);
+  void connect(const std::string& path, const std::string& filename);
+
+  uint64_t getStat(const std::string& name);
+
+  struct Answer
+  {
+    Answer& operator+=(const Answer& rhs)
+    {
+      if (d_ret == 0 && rhs.d_ret != 0) {
+        d_ret = rhs.d_ret;
+      }
+      d_str += rhs.d_str;
+      return *this;
+    }
+    int d_ret{0};
+    std::string d_str;
+  };
+
+  void send(int remote, const Answer&, unsigned int timeout = 5, int fd_to_pass = -1);
+  RecursorControlChannel::Answer recv(int fd, unsigned int timeout = 5);
+
+  int d_fd;
+  static std::atomic<bool> stop;
+
+private:
+  struct sockaddr_un d_local;
+};
+
+class RecursorControlParser
+{
+public:
+  RecursorControlParser() = default;
+  static void nop() {}
+  using func_t = void();
+
+  static RecursorControlChannel::Answer getAnswer(int socket, const std::string& question, func_t** command);
+};
+
+enum class StatComponent
+{
+  API,
+  Carbon,
+  RecControl,
+  SNMP
+};
+
+struct StatsMapEntry
+{
+  std::string d_prometheusName;
+  std::string d_value;
+};
+
+class PrefixDashNumberCompare
+{
+private:
+  static std::pair<std::string, std::string> prefixAndTrailingNum(const std::string& a);
+
+public:
+  bool operator()(const std::string& a, const std::string& b) const;
+};
+
+typedef std::map<std::string, StatsMapEntry, PrefixDashNumberCompare> StatsMap;
+
+StatsMap getAllStatsMap(StatComponent component);
+
+struct CarbonConfig
+{
+  std::string hostname;
+  std::string instance_name;
+  std::string namespace_name;
+  std::vector<std::string> servers;
+};
+
+extern GlobalStateHolder<CarbonConfig> g_carbonConfig;
+
+std::vector<std::pair<DNSName, uint16_t>>* pleaseGetQueryRing();
+std::vector<std::pair<DNSName, uint16_t>>* pleaseGetServfailQueryRing();
+std::vector<std::pair<DNSName, uint16_t>>* pleaseGetBogusQueryRing();
+std::vector<ComboAddress>* pleaseGetRemotes();
+std::vector<ComboAddress>* pleaseGetServfailRemotes();
+std::vector<ComboAddress>* pleaseGetBogusRemotes();
+std::vector<ComboAddress>* pleaseGetLargeAnswerRemotes();
+std::vector<ComboAddress>* pleaseGetTimeouts();
+DNSName getRegisteredName(const DNSName& dom);
+std::atomic<unsigned long>* getDynMetric(const std::string& str, const std::string& prometheusName);
+std::optional<uint64_t> getStatByName(const std::string& name);
+bool isStatDisabled(StatComponent component, const std::string& name);
+void disableStat(StatComponent component, const string& name);
+void disableStats(StatComponent component, const string& stats);
+
+void registerAllStats();
+
+void doExitGeneric(bool nicely);
+void doExit();
+void doExitNicely();
+RecursorControlChannel::Answer doQueueReloadLuaScript(vector<string>::const_iterator begin, vector<string>::const_iterator end);
deleted file mode 120000 (symlink)
index 7bf9dfb9df4973b96047e9cfac02df1f36eea894..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../rec_channel_rec.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..94f7ec453e35430915c1a5020ebbf5e23cee805e
--- /dev/null
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "utility.hh"
+#include "rec_channel.hh"
+
+#include <vector>
+#ifdef MALLOC_TRACE
+#include "malloctrace.hh"
+#endif
+#include "misc.hh"
+#include "recursor_cache.hh"
+#include "syncres.hh"
+#include "negcache.hh"
+#include <boost/format.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include "version.hh"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "logger.hh"
+#include "dnsparser.hh"
+#include "arguments.hh"
+#include <sys/resource.h>
+#include <sys/time.h>
+#include "lock.hh"
+#include "rec-lua-conf.hh"
+
+#include "aggressive_nsec.hh"
+#include "coverage.hh"
+#include "validate-recursor.hh"
+#include "filterpo.hh"
+
+#include "secpoll-recursor.hh"
+#include "pubsuffix.hh"
+#include "namespaces.hh"
+#include "rec-taskqueue.hh"
+#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();
+  if (i == 0) {
+    return {a, ""};
+  }
+  --i;
+  if (!std::isdigit(a[i])) {
+    return {a, ""};
+  }
+  while (i > 0) {
+    if (!std::isdigit(a[i])) {
+      break;
+    }
+    --i;
+  }
+  return {a.substr(0, i + 1), a.substr(i + 1, a.size() - i - 1)};
+}
+
+bool PrefixDashNumberCompare::operator()(const std::string& a, const std::string& b) const
+{
+  auto [aprefix, anum] = prefixAndTrailingNum(a);
+  auto [bprefix, bnum] = prefixAndTrailingNum(b);
+
+  if (aprefix != bprefix || anum.length() == 0 || bnum.length() == 0) {
+    return a < b;
+  }
+  auto aa = std::stoull(anum);
+  auto bb = std::stoull(bnum);
+  return aa < bb;
+}
+
+static map<string, const uint32_t*> d_get32bitpointers;
+static map<string, const pdns::stat_t*> d_getatomics;
+static map<string, std::function<uint64_t()>> d_get64bitmembers;
+static map<string, std::function<StatsMap()>> d_getmultimembers;
+
+struct dynmetrics
+{
+  std::atomic<unsigned long>* d_ptr;
+  std::string d_prometheusName;
+};
+
+static LockGuarded<map<string, dynmetrics>> d_dynmetrics;
+
+static std::map<StatComponent, std::set<std::string>> s_disabledStats;
+
+bool isStatDisabled(StatComponent component, const string& name)
+{
+  return s_disabledStats[component].count(name) != 0;
+}
+
+void disableStat(StatComponent component, const string& name)
+{
+  s_disabledStats[component].insert(name);
+}
+
+void disableStats(StatComponent component, const string& stats)
+{
+  std::vector<std::string> disabledStats;
+  stringtok(disabledStats, stats, ", ");
+  auto& map = s_disabledStats[component];
+  for (const auto& st : disabledStats) {
+    map.insert(st);
+  }
+}
+
+static void addGetStat(const string& name, const uint32_t* place)
+{
+  d_get32bitpointers[name] = place;
+}
+
+static void addGetStat(const string& name, const pdns::stat_t* place)
+{
+  d_getatomics[name] = place;
+}
+
+static void addGetStat(const string& name, std::function<uint64_t()> f)
+{
+  d_get64bitmembers[name] = std::move(f);
+}
+
+static void addGetStat(const string& name, std::function<StatsMap()> f)
+{
+  d_getmultimembers[name] = std::move(f);
+}
+
+static std::string getPrometheusName(const std::string& arg)
+{
+  std::string name = arg;
+  std::replace_if(
+    name.begin(), name.end(), [](char c) { return !isalnum(static_cast<unsigned char>(c)); }, '_');
+  return "pdns_recursor_" + name;
+}
+
+std::atomic<unsigned long>* getDynMetric(const std::string& str, const std::string& prometheusName)
+{
+  auto dm = d_dynmetrics.lock();
+  auto f = dm->find(str);
+  if (f != dm->end()) {
+    return f->second.d_ptr;
+  }
+
+  std::string name(str);
+  if (!prometheusName.empty()) {
+    name = prometheusName;
+  }
+  else {
+    name = getPrometheusName(name);
+  }
+
+  auto ret = dynmetrics{new std::atomic<unsigned long>(), std::move(name)};
+  (*dm)[str] = ret;
+  return ret.d_ptr;
+}
+
+static std::optional<uint64_t> get(const string& name)
+{
+  std::optional<uint64_t> ret;
+
+  if (d_get32bitpointers.count(name))
+    return *d_get32bitpointers.find(name)->second;
+  if (d_getatomics.count(name))
+    return d_getatomics.find(name)->second->load();
+  if (d_get64bitmembers.count(name))
+    return d_get64bitmembers.find(name)->second();
+
+  {
+    auto dm = d_dynmetrics.lock();
+    auto f = rplookup(*dm, name);
+    if (f) {
+      return f->d_ptr->load();
+    }
+  }
+
+  for (const auto& themultimember : d_getmultimembers) {
+    const auto items = themultimember.second();
+    const auto item = items.find(name);
+    if (item != items.end()) {
+      return std::stoull(item->second.d_value);
+    }
+  }
+
+  return ret;
+}
+
+std::optional<uint64_t> getStatByName(const std::string& name)
+{
+  return get(name);
+}
+
+StatsMap getAllStatsMap(StatComponent component)
+{
+  StatsMap ret;
+  const auto& disabledlistMap = s_disabledStats.at(component);
+
+  for (const auto& the32bits : d_get32bitpointers) {
+    if (disabledlistMap.count(the32bits.first) == 0) {
+      ret.emplace(the32bits.first, StatsMapEntry{getPrometheusName(the32bits.first), std::to_string(*the32bits.second)});
+    }
+  }
+  for (const auto& atomic : d_getatomics) {
+    if (disabledlistMap.count(atomic.first) == 0) {
+      ret.emplace(atomic.first, StatsMapEntry{getPrometheusName(atomic.first), std::to_string(atomic.second->load())});
+    }
+  }
+
+  for (const auto& the64bitmembers : d_get64bitmembers) {
+    if (disabledlistMap.count(the64bitmembers.first) == 0) {
+      ret.emplace(the64bitmembers.first, StatsMapEntry{getPrometheusName(the64bitmembers.first), std::to_string(the64bitmembers.second())});
+    }
+  }
+
+  for (const auto& themultimember : d_getmultimembers) {
+    if (disabledlistMap.count(themultimember.first) == 0) {
+      ret.merge(themultimember.second());
+    }
+  }
+
+  {
+    for (const auto& a : *(d_dynmetrics.lock())) {
+      if (disabledlistMap.count(a.first) == 0) {
+        ret.emplace(a.first, StatsMapEntry{a.second.d_prometheusName, std::to_string(*a.second.d_ptr)});
+      }
+    }
+  }
+
+  return ret;
+}
+
+static string getAllStats()
+{
+  auto varmap = getAllStatsMap(StatComponent::RecControl);
+  string ret;
+  for (const auto& tup : varmap) {
+    ret += tup.first + "\t" + tup.second.d_value + "\n";
+  }
+  return ret;
+}
+
+template <typename T>
+static string doGet(T begin, T end)
+{
+  string ret;
+
+  for (T i = begin; i != end; ++i) {
+    std::optional<uint64_t> num = get(*i);
+    if (num)
+      ret += std::to_string(*num) + "\n";
+    else
+      ret += "UNKNOWN\n";
+  }
+  return ret;
+}
+
+template <typename T>
+string static doGetParameter(T begin, T end)
+{
+  string ret;
+  string parm;
+  using boost::replace_all;
+  for (T i = begin; i != end; ++i) {
+    if (::arg().parmIsset(*i)) {
+      parm = ::arg()[*i];
+      replace_all(parm, "\\", "\\\\");
+      replace_all(parm, "\"", "\\\"");
+      replace_all(parm, "\n", "\\n");
+      ret += *i + "=\"" + parm + "\"\n";
+    }
+    else
+      ret += *i + " not known\n";
+  }
+  return ret;
+}
+
+/* Read an (open) fd from the control channel */
+static FDWrapper
+getfd(int s)
+{
+  int fd = -1;
+  struct msghdr msg;
+  struct cmsghdr* cmsg;
+  union
+  {
+    struct cmsghdr hdr;
+    unsigned char buf[CMSG_SPACE(sizeof(int))];
+  } cmsgbuf;
+  struct iovec io_vector[1];
+  char ch;
+
+  io_vector[0].iov_base = &ch;
+  io_vector[0].iov_len = 1;
+
+  memset(&msg, 0, sizeof(msg));
+  msg.msg_control = &cmsgbuf.buf;
+  msg.msg_controllen = sizeof(cmsgbuf.buf);
+  msg.msg_iov = io_vector;
+  msg.msg_iovlen = 1;
+
+  if (recvmsg(s, &msg, 0) == -1) {
+    throw PDNSException("recvmsg");
+  }
+  if ((msg.msg_flags & MSG_TRUNC) || (msg.msg_flags & MSG_CTRUNC)) {
+    throw PDNSException("control message truncated");
+  }
+  for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
+       cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+    if (cmsg->cmsg_len == CMSG_LEN(sizeof(int)) && cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
+      fd = *(int*)CMSG_DATA(cmsg);
+      break;
+    }
+  }
+  return FDWrapper(fd);
+}
+
+static uint64_t dumpAggressiveNSECCache(int fd)
+{
+  if (!g_aggressiveNSECCache) {
+    return 0;
+  }
+
+  int newfd = dup(fd);
+  if (newfd == -1) {
+    return 0;
+  }
+  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
+  if (!fp) {
+    return 0;
+  }
+  fprintf(fp.get(), "; aggressive NSEC cache dump follows\n;\n");
+
+  struct timeval now;
+  Utility::gettimeofday(&now, nullptr);
+  return g_aggressiveNSECCache->dumpToFile(fp, now);
+}
+
+static uint64_t* pleaseDumpEDNSMap(int fd)
+{
+  return new uint64_t(SyncRes::doEDNSDump(fd));
+}
+
+static uint64_t* pleaseDumpNSSpeeds(int fd)
+{
+  return new uint64_t(SyncRes::doDumpNSSpeeds(fd));
+}
+
+static uint64_t* pleaseDumpThrottleMap(int fd)
+{
+  return new uint64_t(SyncRes::doDumpThrottleMap(fd));
+}
+
+static uint64_t* pleaseDumpFailedServers(int fd)
+{
+  return new uint64_t(SyncRes::doDumpFailedServers(fd));
+}
+
+static uint64_t* pleaseDumpSavedParentNSSets(int fd)
+{
+  return new uint64_t(SyncRes::doDumpSavedParentNSSets(fd));
+}
+
+static uint64_t* pleaseDumpNonResolvingNS(int fd)
+{
+  return new uint64_t(SyncRes::doDumpNonResolvingNS(fd));
+}
+
+static uint64_t* pleaseDumpDoTProbeMap(int fd)
+{
+  return new uint64_t(SyncRes::doDumpDoTProbeMap(fd));
+}
+
+// Generic dump to file command
+static RecursorControlChannel::Answer doDumpToFile(int s, uint64_t* (*function)(int s), const string& name, bool threads = true)
+{
+  auto fdw = getfd(s);
+
+  if (fdw < 0) {
+    return {1, name + ": error opening dump file for writing: " + stringerror() + "\n"};
+  }
+
+  uint64_t total = 0;
+  try {
+    if (threads) {
+      int fd = fdw;
+      total = broadcastAccFunction<uint64_t>([function, fd] { return function(fd); });
+    }
+    else {
+      auto ret = function(fdw);
+      total = *ret;
+      delete ret;
+    }
+  }
+  catch (std::exception& e) {
+    return {1, name + ": error dumping data: " + string(e.what()) + "\n"};
+  }
+  catch (PDNSException& e) {
+    return {1, name + ": error dumping data: " + e.reason + "\n"};
+  }
+
+  return {0, name + ": dumped " + std::to_string(total) + " records\n"};
+}
+
+// Does not follow the generic dump to file pattern, has a more complex lambda
+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 {
+    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 (...) {
+  }
+
+  return {0, "dumped " + std::to_string(total) + " records\n"};
+}
+
+// Does not follow the generic dump to file pattern, has an argument
+template <typename T>
+static RecursorControlChannel::Answer doDumpRPZ(int s, T begin, T end)
+{
+  auto fdw = getfd(s);
+
+  if (fdw < 0) {
+    return {1, "Error opening dump file for writing: " + stringerror() + "\n"};
+  }
+
+  T i = begin;
+
+  if (i == end) {
+    return {1, "No zone name specified\n"};
+  }
+  string zoneName = *i;
+
+  auto luaconf = g_luaconfs.getLocal();
+  const auto zone = luaconf->dfe.getZone(zoneName);
+  if (!zone) {
+    return {1, "No RPZ zone named " + zoneName + "\n"};
+  }
+
+  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(fdw, "w"), fclose);
+  if (!fp) {
+    int err = errno;
+    return {1, "converting file descriptor: " + stringerror(err) + "\n"};
+  }
+
+  zone->dump(fp.get());
+
+  return {0, "done\n"};
+}
+
+template <typename T>
+static string doWipeCache(T begin, T end, uint16_t qtype)
+{
+  vector<pair<DNSName, bool>> toWipe;
+  for (T i = begin; i != end; ++i) {
+    DNSName canon;
+    bool subtree = false;
+
+    try {
+      if (boost::ends_with(*i, "$")) {
+        canon = DNSName(i->substr(0, i->size() - 1));
+        subtree = true;
+      }
+      else {
+        canon = DNSName(*i);
+      }
+    }
+    catch (std::exception& e) {
+      return "Error: " + std::string(e.what()) + ", nothing wiped\n";
+    }
+    toWipe.emplace_back(canon, subtree);
+  }
+
+  int count = 0, pcount = 0, countNeg = 0;
+  for (const auto& wipe : toWipe) {
+    try {
+      auto res = wipeCaches(wipe.first, wipe.second, qtype);
+      count += res.record_count;
+      pcount += res.packet_count;
+      countNeg += res.negative_record_count;
+    }
+    catch (const std::exception& e) {
+      g_log << Logger::Warning << ", failed: " << e.what() << endl;
+    }
+  }
+
+  return "wiped " + std::to_string(count) + " records, " + std::to_string(countNeg) + " negative records, " + std::to_string(pcount) + " packets\n";
+}
+
+template <typename T>
+static string doSetCarbonServer(T begin, T end)
+{
+  auto config = g_carbonConfig.getCopy();
+  if (begin == end) {
+    config.servers.clear();
+    g_carbonConfig.setState(std::move(config));
+    return "cleared carbon-server setting\n";
+  }
+
+  string ret;
+  stringtok(config.servers, *begin, ", ");
+  ret = "set carbon-server to '" + *begin + "'\n";
+
+  ++begin;
+  if (begin != end) {
+    config.hostname = *begin;
+    ret += "set carbon-ourname to '" + *begin + "'\n";
+  }
+  else {
+    g_carbonConfig.setState(std::move(config));
+    return ret;
+  }
+
+  ++begin;
+  if (begin != end) {
+    config.namespace_name = *begin;
+    ret += "set carbon-namespace to '" + *begin + "'\n";
+  }
+  else {
+    g_carbonConfig.setState(std::move(config));
+    return ret;
+  }
+
+  ++begin;
+  if (begin != end) {
+    config.instance_name = *begin;
+    ret += "set carbon-instance to '" + *begin + "'\n";
+  }
+
+  g_carbonConfig.setState(std::move(config));
+  return ret;
+}
+
+template <typename T>
+static string doSetDnssecLogBogus(T begin, T end)
+{
+  if (checkDNSSECDisabled())
+    return "DNSSEC is disabled in the configuration, not changing the Bogus logging setting\n";
+
+  if (begin == end)
+    return "No DNSSEC Bogus logging setting specified\n";
+
+  if (pdns_iequals(*begin, "on") || pdns_iequals(*begin, "yes")) {
+    if (!g_dnssecLogBogus) {
+      g_log << Logger::Warning << "Enabling DNSSEC Bogus logging, requested via control channel" << endl;
+      g_dnssecLogBogus = true;
+      return "DNSSEC Bogus logging enabled\n";
+    }
+    return "DNSSEC Bogus logging was already enabled\n";
+  }
+
+  if (pdns_iequals(*begin, "off") || pdns_iequals(*begin, "no")) {
+    if (g_dnssecLogBogus) {
+      g_log << Logger::Warning << "Disabling DNSSEC Bogus logging, requested via control channel" << endl;
+      g_dnssecLogBogus = false;
+      return "DNSSEC Bogus logging disabled\n";
+    }
+    return "DNSSEC Bogus logging was already disabled\n";
+  }
+
+  return "Unknown DNSSEC Bogus setting: '" + *begin + "'\n";
+}
+
+template <typename T>
+static string doAddNTA(T begin, T end)
+{
+  if (checkDNSSECDisabled())
+    return "DNSSEC is disabled in the configuration, not adding a Negative Trust Anchor\n";
+
+  if (begin == end)
+    return "No NTA specified, doing nothing\n";
+
+  DNSName who;
+  try {
+    who = DNSName(*begin);
+  }
+  catch (std::exception& e) {
+    string ret("Can't add Negative Trust Anchor: ");
+    ret += e.what();
+    ret += "\n";
+    return ret;
+  }
+  begin++;
+
+  string why("");
+  while (begin != end) {
+    why += *begin;
+    begin++;
+    if (begin != end)
+      why += " ";
+  }
+  g_log << Logger::Warning << "Adding Negative Trust Anchor for " << who << " with reason '" << why << "', requested via control channel" << endl;
+  g_luaconfs.modify([who, why](LuaConfigItems& lci) {
+    lci.negAnchors[who] = why;
+  });
+  try {
+    wipeCaches(who, true, 0xffff);
+  }
+  catch (std::exception& e) {
+    g_log << Logger::Warning << ", failed: " << e.what() << endl;
+    return "Unable to clear caches while adding Negative Trust Anchor for " + who.toStringRootDot() + ": " + e.what() + "\n";
+  }
+  return "Added Negative Trust Anchor for " + who.toLogString() + " with reason '" + why + "'\n";
+}
+
+template <typename T>
+static string doClearNTA(T begin, T end)
+{
+  if (checkDNSSECDisabled())
+    return "DNSSEC is disabled in the configuration, not removing a Negative Trust Anchor\n";
+
+  if (begin == end)
+    return "No Negative Trust Anchor specified, doing nothing.\n";
+
+  if (begin + 1 == end && *begin == "*") {
+    g_log << Logger::Warning << "Clearing all Negative Trust Anchors, requested via control channel" << endl;
+    g_luaconfs.modify([](LuaConfigItems& lci) {
+      lci.negAnchors.clear();
+    });
+    return "Cleared all Negative Trust Anchors.\n";
+  }
+
+  vector<DNSName> toRemove;
+  DNSName who;
+  while (begin != end) {
+    if (*begin == "*")
+      return "Don't mix all Negative Trust Anchor removal with multiple Negative Trust Anchor removal. Nothing removed\n";
+    try {
+      who = DNSName(*begin);
+    }
+    catch (std::exception& e) {
+      string ret("Error: ");
+      ret += e.what();
+      ret += ". No Negative Anchors removed\n";
+      return ret;
+    }
+    toRemove.push_back(who);
+    begin++;
+  }
+
+  string removed("");
+  bool first(true);
+  try {
+    for (auto const& entry : toRemove) {
+      g_log << Logger::Warning << "Clearing Negative Trust Anchor for " << entry << ", requested via control channel" << endl;
+      g_luaconfs.modify([entry](LuaConfigItems& lci) {
+        lci.negAnchors.erase(entry);
+      });
+      wipeCaches(entry, true, 0xffff);
+      if (!first) {
+        first = false;
+        removed += ",";
+      }
+      removed += " " + entry.toStringRootDot();
+    }
+  }
+  catch (std::exception& e) {
+    g_log << Logger::Warning << ", failed: " << e.what() << endl;
+    return "Unable to clear caches while clearing Negative Trust Anchor for " + who.toStringRootDot() + ": " + e.what() + "\n";
+  }
+
+  return "Removed Negative Trust Anchors for " + removed + "\n";
+}
+
+static string getNTAs()
+{
+  if (checkDNSSECDisabled())
+    return "DNSSEC is disabled in the configuration\n";
+
+  string ret("Configured Negative Trust Anchors:\n");
+  auto luaconf = g_luaconfs.getLocal();
+  for (const auto& negAnchor : luaconf->negAnchors)
+    ret += negAnchor.first.toLogString() + "\t" + negAnchor.second + "\n";
+  return ret;
+}
+
+template <typename T>
+static string doAddTA(T begin, T end)
+{
+  if (checkDNSSECDisabled())
+    return "DNSSEC is disabled in the configuration, not adding a Trust Anchor\n";
+
+  if (begin == end)
+    return "No TA specified, doing nothing\n";
+
+  DNSName who;
+  try {
+    who = DNSName(*begin);
+  }
+  catch (std::exception& e) {
+    string ret("Can't add Trust Anchor: ");
+    ret += e.what();
+    ret += "\n";
+    return ret;
+  }
+  begin++;
+
+  string what("");
+  while (begin != end) {
+    what += *begin + " ";
+    begin++;
+  }
+
+  try {
+    g_log << Logger::Warning << "Adding Trust Anchor for " << who << " with data '" << what << "', requested via control channel";
+    g_luaconfs.modify([who, what](LuaConfigItems& lci) {
+      auto ds = std::dynamic_pointer_cast<DSRecordContent>(DSRecordContent::make(what));
+      lci.dsAnchors[who].insert(*ds);
+    });
+    wipeCaches(who, true, 0xffff);
+    g_log << Logger::Warning << endl;
+    return "Added Trust Anchor for " + who.toStringRootDot() + " with data " + what + "\n";
+  }
+  catch (std::exception& e) {
+    g_log << Logger::Warning << ", failed: " << e.what() << endl;
+    return "Unable to add Trust Anchor for " + who.toStringRootDot() + ": " + e.what() + "\n";
+  }
+}
+
+template <typename T>
+static string doClearTA(T begin, T end)
+{
+  if (checkDNSSECDisabled())
+    return "DNSSEC is disabled in the configuration, not removing a Trust Anchor\n";
+
+  if (begin == end)
+    return "No Trust Anchor to clear\n";
+
+  vector<DNSName> toRemove;
+  DNSName who;
+  while (begin != end) {
+    try {
+      who = DNSName(*begin);
+    }
+    catch (std::exception& e) {
+      string ret("Error: ");
+      ret += e.what();
+      ret += ". No Anchors removed\n";
+      return ret;
+    }
+    if (who.isRoot())
+      return "Refusing to remove root Trust Anchor, no Anchors removed\n";
+    toRemove.push_back(who);
+    begin++;
+  }
+
+  string removed("");
+  bool first(true);
+  try {
+    for (auto const& entry : toRemove) {
+      g_log << Logger::Warning << "Removing Trust Anchor for " << entry << ", requested via control channel" << endl;
+      g_luaconfs.modify([entry](LuaConfigItems& lci) {
+        lci.dsAnchors.erase(entry);
+      });
+      wipeCaches(entry, true, 0xffff);
+      if (!first) {
+        first = false;
+        removed += ",";
+      }
+      removed += " " + entry.toStringRootDot();
+    }
+  }
+  catch (std::exception& e) {
+    g_log << Logger::Warning << ", failed: " << e.what() << endl;
+    return "Unable to clear caches while clearing Trust Anchor for " + who.toStringRootDot() + ": " + e.what() + "\n";
+  }
+
+  return "Removed Trust Anchor(s) for" + removed + "\n";
+}
+
+static string getTAs()
+{
+  if (checkDNSSECDisabled())
+    return "DNSSEC is disabled in the configuration\n";
+
+  string ret("Configured Trust Anchors:\n");
+  auto luaconf = g_luaconfs.getLocal();
+  for (const auto& anchor : luaconf->dsAnchors) {
+    ret += anchor.first.toLogString() + "\n";
+    for (const auto& e : anchor.second) {
+      ret += "\t\t" + e.getZoneRepresentation() + "\n";
+    }
+  }
+
+  return ret;
+}
+
+template <typename T>
+static string setMinimumTTL(T begin, T end)
+{
+  if (end - begin != 1)
+    return "Need to supply new minimum TTL number\n";
+  try {
+    pdns::checked_stoi_into(SyncRes::s_minimumTTL, *begin);
+    return "New minimum TTL: " + std::to_string(SyncRes::s_minimumTTL) + "\n";
+  }
+  catch (const std::exception& e) {
+    return "Error parsing the new minimum TTL number: " + std::string(e.what()) + "\n";
+  }
+}
+
+template <typename T>
+static string setMinimumECSTTL(T begin, T end)
+{
+  if (end - begin != 1)
+    return "Need to supply new ECS minimum TTL number\n";
+  try {
+    pdns::checked_stoi_into(SyncRes::s_minimumECSTTL, *begin);
+    return "New minimum ECS TTL: " + std::to_string(SyncRes::s_minimumECSTTL) + "\n";
+  }
+  catch (const std::exception& e) {
+    return "Error parsing the new ECS minimum TTL number: " + std::string(e.what()) + "\n";
+  }
+}
+
+template <typename T>
+static string setMaxCacheEntries(T begin, T end)
+{
+  if (end - begin != 1)
+    return "Need to supply new cache size\n";
+  try {
+    g_maxCacheEntries = pdns::checked_stoi<uint32_t>(*begin);
+    return "New max cache entries: " + std::to_string(g_maxCacheEntries) + "\n";
+  }
+  catch (const std::exception& e) {
+    return "Error parsing the new cache size: " + std::string(e.what()) + "\n";
+  }
+}
+
+template <typename T>
+static string setMaxPacketCacheEntries(T begin, T end)
+{
+  if (end - begin != 1)
+    return "Need to supply new packet cache size\n";
+  if (::arg().mustDo("disable-packetcache")) {
+    return "Packet cache is disabled\n";
+  }
+  try {
+    g_maxPacketCacheEntries = pdns::checked_stoi<uint32_t>(*begin);
+    g_packetCache->setMaxSize(g_maxPacketCacheEntries);
+    return "New max packetcache entries: " + std::to_string(g_maxPacketCacheEntries) + "\n";
+  }
+  catch (const std::exception& e) {
+    return "Error parsing the new packet cache size: " + std::string(e.what()) + "\n";
+  }
+}
+
+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;
+  getrusage(RUSAGE_SELF, &ru);
+  return (ru.ru_stime.tv_sec * 1000ULL + ru.ru_stime.tv_usec / 1000);
+}
+
+static uint64_t getUserTimeMsec()
+{
+  struct rusage ru;
+  getrusage(RUSAGE_SELF, &ru);
+  return (ru.ru_utime.tv_sec * 1000ULL + ru.ru_utime.tv_usec / 1000);
+}
+
+/* This is a pretty weird set of functions. To get per-thread cpu usage numbers,
+   we have to ask a thread over a pipe. We could do so surgically, so if you want to know about
+   thread 3, we pick pipe 3, but we lack that infrastructure.
+
+   We can however ask "execute this function on all threads and add up the results".
+   This is what the first function does using a custom object ThreadTimes, which if you add
+   to each other keeps filling the first one with CPU usage numbers
+*/
+
+static ThreadTimes* pleaseGetThreadCPUMsec()
+{
+  uint64_t ret = 0;
+#ifdef RUSAGE_THREAD
+  struct rusage ru;
+  getrusage(RUSAGE_THREAD, &ru);
+  ret = (ru.ru_utime.tv_sec * 1000ULL + ru.ru_utime.tv_usec / 1000);
+  ret += (ru.ru_stime.tv_sec * 1000ULL + ru.ru_stime.tv_usec / 1000);
+#endif
+  return new ThreadTimes{ret, vector<uint64_t>()};
+}
+
+/* Next up, when you want msec data for a specific thread, we check
+   if we recently executed pleaseGetThreadCPUMsec. If we didn't we do so
+   now and consult all threads.
+
+   We then answer you from the (re)fresh(ed) ThreadTimes.
+*/
+static uint64_t doGetThreadCPUMsec(int n)
+{
+  static std::mutex s_mut;
+  static time_t last = 0;
+  static ThreadTimes tt;
+
+  std::lock_guard<std::mutex> l(s_mut);
+  if (last != time(nullptr)) {
+    tt = broadcastAccFunction<ThreadTimes>(pleaseGetThreadCPUMsec);
+    last = time(nullptr);
+  }
+
+  return tt.times.at(n);
+}
+
+static ProxyMappingStats_t* pleaseGetProxyMappingStats()
+{
+  auto ret = new ProxyMappingStats_t;
+  if (t_proxyMapping) {
+    for (const auto& [key, entry] : *t_proxyMapping) {
+      ret->emplace(key, ProxyMappingCounts{entry.stats.netmaskMatches, entry.stats.suffixMatches});
+    }
+  }
+  return ret;
+}
+
+static RemoteLoggerStats_t* pleaseGetRemoteLoggerStats()
+{
+  auto ret = make_unique<RemoteLoggerStats_t>();
+
+  if (t_protobufServers.servers) {
+    for (const auto& server : *t_protobufServers.servers) {
+      ret->emplace(server->address(), server->getStats());
+    }
+  }
+  return ret.release();
+}
+
+static string doGetProxyMappingStats()
+{
+  ostringstream ret;
+  ret << "subnet\t\t\tmatches\tsuffixmatches" << endl;
+  auto proxyMappingStats = broadcastAccFunction<ProxyMappingStats_t>(pleaseGetProxyMappingStats);
+  for (const auto& [key, entry] : proxyMappingStats) {
+    ret << key.toString() << '\t' << entry.netmaskMatches << '\t' << entry.suffixMatches << endl;
+  }
+  return ret.str();
+}
+
+static RemoteLoggerStats_t* pleaseGetOutgoingRemoteLoggerStats()
+{
+  auto ret = make_unique<RemoteLoggerStats_t>();
+
+  if (t_outgoingProtobufServers.servers) {
+    for (const auto& server : *t_outgoingProtobufServers.servers) {
+      ret->emplace(server->address(), server->getStats());
+    }
+  }
+  return ret.release();
+}
+
+#ifdef HAVE_FSTRM
+static RemoteLoggerStats_t* pleaseGetFramestreamLoggerStats()
+{
+  auto ret = make_unique<RemoteLoggerStats_t>();
+
+  if (t_frameStreamServersInfo.servers) {
+    for (const auto& server : *t_frameStreamServersInfo.servers) {
+      ret->emplace(server->address(), server->getStats());
+    }
+  }
+  return ret.release();
+}
+
+static RemoteLoggerStats_t* pleaseGetNODFramestreamLoggerStats()
+{
+  auto ret = make_unique<RemoteLoggerStats_t>();
+
+  if (t_nodFrameStreamServersInfo.servers) {
+    for (const auto& server : *t_nodFrameStreamServersInfo.servers) {
+      ret->emplace(server->address(), server->getStats());
+    }
+  }
+  return ret.release();
+}
+#endif
+
+static void remoteLoggerStats(const string& type, const RemoteLoggerStats_t& stats, ostringstream& outpustStream)
+{
+  if (stats.empty()) {
+    return;
+  }
+  for (const auto& [key, entry] : stats) {
+    outpustStream << entry.d_queued << '\t' << entry.d_pipeFull << '\t' << entry.d_tooLarge << '\t' << entry.d_otherError << '\t' << key << '\t' << type << endl;
+  }
+}
+
+static string getRemoteLoggerStats()
+{
+  ostringstream outputStream;
+  outputStream << "Queued\tPipe-\tToo-\tOther-\tAddress\tType" << endl;
+  outputStream << "\tFull\tLarge\terror" << endl;
+  auto stats = broadcastAccFunction<RemoteLoggerStats_t>(pleaseGetRemoteLoggerStats);
+  remoteLoggerStats("protobuf", stats, outputStream);
+  stats = broadcastAccFunction<RemoteLoggerStats_t>(pleaseGetOutgoingRemoteLoggerStats);
+  remoteLoggerStats("outgoingProtobuf", stats, outputStream);
+#ifdef HAVE_FSTRM
+  stats = broadcastAccFunction<RemoteLoggerStats_t>(pleaseGetFramestreamLoggerStats);
+  remoteLoggerStats("dnstapFrameStream", stats, outputStream);
+  stats = broadcastAccFunction<RemoteLoggerStats_t>(pleaseGetNODFramestreamLoggerStats);
+  remoteLoggerStats("dnstapNODFrameStream", stats, outputStream);
+#endif
+  return outputStream.str();
+}
+
+static string* pleaseGetCurrentQueries()
+{
+  ostringstream ostr;
+  struct timeval now;
+  gettimeofday(&now, 0);
+
+  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()->getWaiters()) {
+    const std::shared_ptr<PacketID>& pident = mthread.key;
+    const double spent = g_networkTimeoutMsec - (DiffTime(now, mthread.ttd) * 1000);
+    ostr << (fmt
+             % pident->domain.toLogString() /* ?? */ % DNSRecordContent::NumberToType(pident->type)
+             % pident->remote.toString() % (pident->tcpsock ? 'Y' : 'n')
+             % (pident->fd == -1 ? 'Y' : 'n')
+             % (spent > 0 ? spent : '0'));
+    ++n;
+    if (n >= 100)
+      break;
+  }
+  ostr << " - done\n";
+  return new string(ostr.str());
+}
+
+static string doCurrentQueries()
+{
+  return broadcastAccFunction<string>(pleaseGetCurrentQueries);
+}
+
+static uint64_t getNegCacheSize()
+{
+  return g_negCache->size();
+}
+
+uint64_t* pleaseGetConcurrentQueries()
+{
+  return new uint64_t(getMT() ? getMT()->numProcesses() : 0);
+}
+
+static uint64_t getConcurrentQueries()
+{
+  return broadcastAccFunction<uint64_t>(pleaseGetConcurrentQueries);
+}
+
+static uint64_t doGetCacheSize()
+{
+  return g_recCache->size();
+}
+
+static uint64_t doGetCacheBytes()
+{
+  return g_recCache->bytes();
+}
+
+static uint64_t doGetMallocated()
+{
+  // this turned out to be broken
+  /*  struct mallinfo mi = mallinfo();
+  return mi.uordblks; */
+  return 0;
+}
+
+static StatsMap toStatsMap(const string& name, const pdns::Histogram& histogram)
+{
+  const auto& data = histogram.getCumulativeBuckets();
+  const string pbasename = getPrometheusName(name);
+  StatsMap entries;
+  char buf[32];
+
+  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{std::move(pname), std::to_string(bucket.d_count)});
+  }
+
+  snprintf(buf, sizeof(buf), "%g", histogram.getSum() / 1e6);
+  entries.emplace(name + "sum", StatsMapEntry{pbasename + "seconds_sum", buf});
+  entries.emplace(name + "count", StatsMapEntry{pbasename + "seconds_count", std::to_string(data.back().d_count)});
+
+  return entries;
+}
+
+static StatsMap toStatsMap(const string& name, const pdns::Histogram& histogram4, const pdns::Histogram& histogram6)
+{
+  const string pbasename = getPrometheusName(name);
+  StatsMap entries;
+  char buf[32];
+  std::string pname;
+
+  const auto& data4 = histogram4.getCumulativeBuckets();
+  for (const auto& bucket : data4) {
+    snprintf(buf, sizeof(buf), "%g", bucket.d_boundary / 1e6);
+    pname = pbasename + "seconds_bucket{ipversion=\"v4\",le=\"" + (bucket.d_boundary == std::numeric_limits<uint64_t>::max() ? "+Inf" : buf) + "\"}";
+    entries.emplace(bucket.d_name + "4", StatsMapEntry{pname, std::to_string(bucket.d_count)});
+  }
+  snprintf(buf, sizeof(buf), "%g", histogram4.getSum() / 1e6);
+  entries.emplace(name + "sum4", StatsMapEntry{pbasename + "seconds_sum{ipversion=\"v4\"}", buf});
+  entries.emplace(name + "count4", StatsMapEntry{pbasename + "seconds_count{ipversion=\"v4\"}", std::to_string(data4.back().d_count)});
+
+  const auto& data6 = histogram6.getCumulativeBuckets();
+  for (const auto& bucket : data6) {
+    snprintf(buf, sizeof(buf), "%g", bucket.d_boundary / 1e6);
+    pname = pbasename + "seconds_bucket{ipversion=\"v6\",le=\"" + (bucket.d_boundary == std::numeric_limits<uint64_t>::max() ? "+Inf" : buf) + "\"}";
+    entries.emplace(bucket.d_name + "6", StatsMapEntry{pname, std::to_string(bucket.d_count)});
+  }
+  snprintf(buf, sizeof(buf), "%g", histogram6.getSum() / 1e6);
+  entries.emplace(name + "sum6", StatsMapEntry{pbasename + "seconds_sum{ipversion=\"v6\"}", buf});
+  entries.emplace(name + "count6", StatsMapEntry{pbasename + "seconds_count{ipversion=\"v6\"}", std::to_string(data6.back().d_count)});
+
+  return entries;
+}
+
+static StatsMap toAuthRCodeStatsMap(const string& name)
+{
+  const string pbasename = getPrometheusName(name);
+  StatsMap entries;
+
+  uint8_t n = 0;
+  auto rcodes = g_Counters.sum(rec::RCode::auth).rcodeCounters;
+  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{std::move(pname), std::to_string(entry)});
+    n++;
+  }
+  return entries;
+}
+
+static StatsMap toCPUStatsMap(const string& name)
+{
+  const string pbasename = getPrometheusName(name);
+  StatsMap entries;
+
+  // 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{std::move(pname), std::to_string(tm)});
+  }
+  return entries;
+}
+
+static StatsMap toRPZStatsMap(const string& name, const std::unordered_map<std::string, uint64_t>& map)
+{
+  const string pbasename = getPrometheusName(name);
+  StatsMap entries;
+
+  uint64_t total = 0;
+  for (const auto& entry : map) {
+    const auto& key = entry.first;
+    auto count = entry.second;
+    std::string sname, pname;
+    if (key.empty()) {
+      sname = name + "-filter";
+      pname = pbasename + "{type=\"filter\"}";
+    }
+    else {
+      sname = name + "-rpz-" + key;
+      pname = pbasename + "{type=\"rpz\",policyname=\"" + key + "\"}";
+    }
+    entries.emplace(sname, StatsMapEntry{std::move(pname), std::to_string(count)});
+    total += count;
+  }
+  entries.emplace(name, StatsMapEntry{pbasename, std::to_string(total)});
+  return entries;
+}
+
+static StatsMap toProxyMappingStatsMap(const string& name)
+{
+  const string pbasename = getPrometheusName(name);
+  StatsMap entries;
+
+  auto proxyMappingStats = broadcastAccFunction<ProxyMappingStats_t>(pleaseGetProxyMappingStats);
+  size_t count = 0;
+  for (const auto& [key, entry] : proxyMappingStats) {
+    auto keyname = pbasename + "{netmask=\"" + key.toString() + "\",count=\"";
+    auto sname1 = name + "-n-" + std::to_string(count);
+    auto pname1 = keyname + "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{std::move(pname2), std::to_string(entry.suffixMatches)});
+    count++;
+  }
+  return entries;
+}
+
+static StatsMap toRemoteLoggerStatsMap(const string& name)
+{
+  const auto pbasename = getPrometheusName(name);
+  StatsMap entries;
+
+  std::vector<std::pair<RemoteLoggerStats_t, std::string>> list;
+  auto stats1 = broadcastAccFunction<RemoteLoggerStats_t>(pleaseGetRemoteLoggerStats);
+  list.emplace_back(stats1, "protobuf");
+  auto stats2 = broadcastAccFunction<RemoteLoggerStats_t>(pleaseGetOutgoingRemoteLoggerStats);
+  list.emplace_back(stats2, "outgoingProtobuf");
+#ifdef HAVE_FSTRM
+  auto stats3 = broadcastAccFunction<RemoteLoggerStats_t>(pleaseGetFramestreamLoggerStats);
+  list.emplace_back(stats3, "dnstapFrameStream");
+  auto stats4 = broadcastAccFunction<RemoteLoggerStats_t>(pleaseGetNODFramestreamLoggerStats);
+  list.emplace_back(stats4, "dnstapNODFrameStream");
+#endif
+  uint64_t count = 0;
+  for (const auto& [stats, type] : list) {
+    for (const auto& [key, entry] : stats) {
+      auto keyname = pbasename + "{address=\"" + key + "\",type=\"" + type + "\",count=\"";
+      auto sname1 = name + "-q-" + std::to_string(count);
+      auto pname1 = keyname + "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{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{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{std::move(pname4), std::to_string(entry.d_otherError)});
+      ++count;
+    }
+  }
+  return entries;
+}
+
+static time_t s_startupTime = time(nullptr);
+
+static void registerAllStats1()
+{
+  addGetStat("questions", [] { return g_Counters.sum(rec::Counter::qcounter); });
+  addGetStat("ipv6-questions", [] { return g_Counters.sum(rec::Counter::ipv6qcounter); });
+  addGetStat("tcp-questions", [] { return g_Counters.sum(rec::Counter::tcpqcounter); });
+
+  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(); });
+  addGetStat("cache-bytes", doGetCacheBytes);
+  addGetStat("record-cache-contended", []() { return g_recCache->stats().first; });
+  addGetStat("record-cache-acquired", []() { return g_recCache->stats().second; });
+
+  addGetStat("packetcache-hits", [] { return g_packetCache ? g_packetCache->getHits() : 0; });
+  addGetStat("packetcache-misses", [] { return g_packetCache ? g_packetCache->getMisses() : 0; });
+  addGetStat("packetcache-entries", [] { return g_packetCache ? g_packetCache->size() : 0; });
+  addGetStat("packetcache-bytes", [] { return g_packetCache ? g_packetCache->bytes() : 0; });
+  addGetStat("packetcache-contended", []() { return g_packetCache ? g_packetCache->stats().first : 0; });
+  addGetStat("packetcache-acquired", []() { return g_packetCache ? g_packetCache->stats().second : 0; });
+
+  addGetStat("aggressive-nsec-cache-entries", []() { return g_aggressiveNSECCache ? g_aggressiveNSECCache->getEntriesCount() : 0; });
+  addGetStat("aggressive-nsec-cache-nsec-hits", []() { return g_aggressiveNSECCache ? g_aggressiveNSECCache->getNSECHits() : 0; });
+  addGetStat("aggressive-nsec-cache-nsec3-hits", []() { return g_aggressiveNSECCache ? g_aggressiveNSECCache->getNSEC3Hits() : 0; });
+  addGetStat("aggressive-nsec-cache-nsec-wc-hits", []() { return g_aggressiveNSECCache ? g_aggressiveNSECCache->getNSECWildcardHits() : 0; });
+  addGetStat("aggressive-nsec-cache-nsec3-wc-hits", []() { return g_aggressiveNSECCache ? g_aggressiveNSECCache->getNSEC3WildcardHits() : 0; });
+
+  addGetStat("malloc-bytes", doGetMallocated);
+
+  addGetStat("servfail-answers", [] { return g_Counters.sum(rec::Counter::servFails); });
+  addGetStat("nxdomain-answers", [] { return g_Counters.sum(rec::Counter::nxDomains); });
+  addGetStat("noerror-answers", [] { return g_Counters.sum(rec::Counter::noErrors); });
+
+  addGetStat("unauthorized-udp", [] { return g_Counters.sum(rec::Counter::unauthorizedUDP); });
+  addGetStat("unauthorized-tcp", [] { return g_Counters.sum(rec::Counter::unauthorizedTCP); });
+  addGetStat("source-disallowed-notify", [] { return g_Counters.sum(rec::Counter::sourceDisallowedNotify); });
+  addGetStat("zone-disallowed-notify", [] { return g_Counters.sum(rec::Counter::zoneDisallowedNotify); });
+  addGetStat("tcp-client-overflow", [] { return g_Counters.sum(rec::Counter::tcpClientOverflow); });
+
+  addGetStat("client-parse-errors", [] { return g_Counters.sum(rec::Counter::clientParseError); });
+  addGetStat("server-parse-errors", [] { return g_Counters.sum(rec::Counter::serverParseError); });
+  addGetStat("too-old-drops", [] { return g_Counters.sum(rec::Counter::tooOldDrops); });
+  addGetStat("truncated-drops", [] { return g_Counters.sum(rec::Counter::truncatedDrops); });
+  addGetStat("query-pipe-full-drops", [] { return g_Counters.sum(rec::Counter::queryPipeFullDrops); });
+
+  addGetStat("answers0-1", []() { return g_Counters.sum(rec::Histogram::answers).getCount(0); });
+  addGetStat("answers1-10", []() { return g_Counters.sum(rec::Histogram::answers).getCount(1); });
+  addGetStat("answers10-100", []() { return g_Counters.sum(rec::Histogram::answers).getCount(2); });
+  addGetStat("answers100-1000", []() { return g_Counters.sum(rec::Histogram::answers).getCount(3); });
+  addGetStat("answers-slow", []() { return g_Counters.sum(rec::Histogram::answers).getCount(4); });
+
+  addGetStat("x-ourtime0-1", []() { return g_Counters.sum(rec::Histogram::ourtime).getCount(0); });
+  addGetStat("x-ourtime1-2", []() { return g_Counters.sum(rec::Histogram::ourtime).getCount(1); });
+  addGetStat("x-ourtime2-4", []() { return g_Counters.sum(rec::Histogram::ourtime).getCount(2); });
+  addGetStat("x-ourtime4-8", []() { return g_Counters.sum(rec::Histogram::ourtime).getCount(3); });
+  addGetStat("x-ourtime8-16", []() { return g_Counters.sum(rec::Histogram::ourtime).getCount(4); });
+  addGetStat("x-ourtime16-32", []() { return g_Counters.sum(rec::Histogram::ourtime).getCount(5); });
+  addGetStat("x-ourtime-slow", []() { return g_Counters.sum(rec::Histogram::ourtime).getCount(6); });
+
+  addGetStat("auth4-answers0-1", []() { return g_Counters.sum(rec::Histogram::auth4Answers).getCount(0); });
+  addGetStat("auth4-answers1-10", []() { return g_Counters.sum(rec::Histogram::auth4Answers).getCount(1); });
+  addGetStat("auth4-answers10-100", []() { return g_Counters.sum(rec::Histogram::auth4Answers).getCount(2); });
+  addGetStat("auth4-answers100-1000", []() { return g_Counters.sum(rec::Histogram::auth4Answers).getCount(3); });
+  addGetStat("auth4-answers-slow", []() { return g_Counters.sum(rec::Histogram::auth4Answers).getCount(4); });
+
+  addGetStat("auth6-answers0-1", []() { return g_Counters.sum(rec::Histogram::auth6Answers).getCount(0); });
+  addGetStat("auth6-answers1-10", []() { return g_Counters.sum(rec::Histogram::auth6Answers).getCount(1); });
+  addGetStat("auth6-answers10-100", []() { return g_Counters.sum(rec::Histogram::auth6Answers).getCount(2); });
+  addGetStat("auth6-answers100-1000", []() { return g_Counters.sum(rec::Histogram::auth6Answers).getCount(3); });
+  addGetStat("auth6-answers-slow", []() { return g_Counters.sum(rec::Histogram::auth6Answers).getCount(4); });
+
+  addGetStat("qa-latency", []() { return round(g_Counters.avg(rec::DoubleWAvgCounter::avgLatencyUsec)); });
+  addGetStat("x-our-latency", []() { return round(g_Counters.avg(rec::DoubleWAvgCounter::avgLatencyOursUsec)); });
+  addGetStat("unexpected-packets", [] { return g_Counters.sum(rec::Counter::unexpectedCount); });
+  addGetStat("case-mismatches", [] { return g_Counters.sum(rec::Counter::caseMismatchCount); });
+  addGetStat("spoof-prevents", [] { return g_Counters.sum(rec::Counter::spoofCount); });
+
+  addGetStat("nsset-invalidations", [] { return g_Counters.sum(rec::Counter::nsSetInvalidations); });
+
+  addGetStat("resource-limits", [] { return g_Counters.sum(rec::Counter::resourceLimits); });
+  addGetStat("over-capacity-drops", [] { return g_Counters.sum(rec::Counter::overCapacityDrops); });
+  addGetStat("policy-drops", [] { return g_Counters.sum(rec::Counter::policyDrops); });
+  addGetStat("no-packet-error", [] { return g_Counters.sum(rec::Counter::noPacketError); });
+  addGetStat("ignored-packets", [] { return g_Counters.sum(rec::Counter::ignoredCount); });
+  addGetStat("empty-queries", [] { return g_Counters.sum(rec::Counter::emptyQueriesCount); });
+  addGetStat("max-mthread-stack", [] { return g_Counters.max(rec::Counter::maxMThreadStackUsage); });
+
+  addGetStat("negcache-entries", getNegCacheSize);
+  addGetStat("throttle-entries", SyncRes::getThrottledServersSize);
+
+  addGetStat("nsspeeds-entries", SyncRes::getNSSpeedsSize);
+  addGetStat("failed-host-entries", SyncRes::getFailedServersSize);
+  addGetStat("non-resolving-nameserver-entries", SyncRes::getNonResolvingNSSize);
+
+  addGetStat("concurrent-queries", getConcurrentQueries);
+  addGetStat("security-status", &g_security_status);
+  addGetStat("outgoing-timeouts", [] { return g_Counters.sum(rec::Counter::outgoingtimeouts); });
+  addGetStat("outgoing4-timeouts", [] { return g_Counters.sum(rec::Counter::outgoing4timeouts); });
+  addGetStat("outgoing6-timeouts", [] { return g_Counters.sum(rec::Counter::outgoing6timeouts); });
+  addGetStat("auth-zone-queries", [] { return g_Counters.sum(rec::Counter::authzonequeries); });
+  addGetStat("tcp-outqueries", [] { return g_Counters.sum(rec::Counter::tcpoutqueries); });
+  addGetStat("dot-outqueries", [] { return g_Counters.sum(rec::Counter::dotoutqueries); });
+  addGetStat("all-outqueries", [] { return g_Counters.sum(rec::Counter::outqueries); });
+  addGetStat("ipv6-outqueries", [] { return g_Counters.sum(rec::Counter::ipv6queries); });
+  addGetStat("throttled-outqueries", [] { return g_Counters.sum(rec::Counter::throttledqueries); });
+  addGetStat("dont-outqueries", [] { return g_Counters.sum(rec::Counter::dontqueries); });
+  addGetStat("qname-min-fallback-success", [] { return g_Counters.sum(rec::Counter::qnameminfallbacksuccess); });
+  addGetStat("throttled-out", [] { return g_Counters.sum(rec::Counter::throttledqueries); });
+  addGetStat("unreachables", [] { return g_Counters.sum(rec::Counter::unreachables); });
+  addGetStat("ecs-queries", &SyncRes::s_ecsqueries);
+  addGetStat("ecs-responses", &SyncRes::s_ecsresponses);
+  addGetStat("chain-resends", [] { return g_Counters.sum(rec::Counter::chainResends); });
+  addGetStat("tcp-clients", [] { return TCPConnection::getCurrentConnections(); });
+
+#ifdef __linux__
+  addGetStat("udp-recvbuf-errors", [] { return udpErrorStats("udp-recvbuf-errors"); });
+  addGetStat("udp-sndbuf-errors", [] { return udpErrorStats("udp-sndbuf-errors"); });
+  addGetStat("udp-noport-errors", [] { return udpErrorStats("udp-noport-errors"); });
+  addGetStat("udp-in-errors", [] { return udpErrorStats("udp-in-errors"); });
+  addGetStat("udp-in-csum-errors", [] { return udpErrorStats("udp-in-csum-errors"); });
+  addGetStat("udp6-recvbuf-errors", [] { return udp6ErrorStats("udp6-recvbuf-errors"); });
+  addGetStat("udp6-sndbuf-errors", [] { return udp6ErrorStats("udp6-sndbuf-errors"); });
+  addGetStat("udp6-noport-errors", [] { return udp6ErrorStats("udp6-noport-errors"); });
+  addGetStat("udp6-in-errors", [] { return udp6ErrorStats("udp6-in-errors"); });
+  addGetStat("udp6-in-csum-errors", [] { return udp6ErrorStats("udp6-in-csum-errors"); });
+#endif
+
+  addGetStat("edns-ping-matches", [] { return g_Counters.sum(rec::Counter::ednsPingMatches); });
+  addGetStat("edns-ping-mismatches", [] { return g_Counters.sum(rec::Counter::ednsPingMismatches); });
+  addGetStat("dnssec-queries", [] { return g_Counters.sum(rec::Counter::dnssecQueries); });
+
+  addGetStat("dnssec-authentic-data-queries", [] { return g_Counters.sum(rec::Counter::dnssecAuthenticDataQueries); });
+  addGetStat("dnssec-check-disabled-queries", [] { return g_Counters.sum(rec::Counter::dnssecCheckDisabledQueries); });
+
+  addGetStat("variable-responses", [] { return g_Counters.sum(rec::Counter::variableResponses); });
+
+  addGetStat("noping-outqueries", [] { return g_Counters.sum(rec::Counter::noPingOutQueries); });
+  addGetStat("noedns-outqueries", [] { return g_Counters.sum(rec::Counter::noEdnsOutQueries); });
+
+  addGetStat("uptime", [] { return time(nullptr) - s_startupTime; });
+  addGetStat("real-memory-usage", [] { return getRealMemoryUsage(string()); });
+  addGetStat("special-memory-usage", [] { return getSpecialMemoryUsage(string()); });
+  addGetStat("fd-usage", [] { return getOpenFileDescriptors(string()); });
+
+  //  addGetStat("query-rate", getQueryRate);
+  addGetStat("user-msec", getUserTimeMsec);
+  addGetStat("sys-msec", getSysTimeMsec);
+
+#ifdef __linux__
+  addGetStat("cpu-iowait", [] { return getCPUIOWait(string()); });
+  addGetStat("cpu-steal", [] { return getCPUSteal(string()); });
+#endif
+
+  addGetStat("cpu-msec", []() { return toCPUStatsMap("cpu-msec"); });
+
+#ifdef MALLOC_TRACE
+  addGetStat("memory-allocs", [] { return g_mtracer->getAllocs(string()); });
+  addGetStat("memory-alloc-flux", [] { return g_mtracer->getAllocFlux(string()); });
+  addGetStat("memory-allocated", [] { return g_mtracer->getTotAllocated(string()); });
+#endif
+
+  addGetStat("dnssec-validations", [] { return g_Counters.sum(rec::Counter::dnssecValidations); });
+  addGetStat("dnssec-result-insecure", [] { return g_Counters.sum(rec::DNSSECHistogram::dnssec).at(vState::Insecure); });
+  addGetStat("dnssec-result-secure", [] { return g_Counters.sum(rec::DNSSECHistogram::dnssec).at(vState::Secure); });
+  addGetStat("dnssec-result-bogus", []() {
+    std::set<vState> const bogusStates = {vState::BogusNoValidDNSKEY, vState::BogusInvalidDenial, vState::BogusUnableToGetDSs, vState::BogusUnableToGetDNSKEYs, vState::BogusSelfSignedDS, vState::BogusNoRRSIG, vState::BogusNoValidRRSIG, vState::BogusMissingNegativeIndication, vState::BogusSignatureNotYetValid, vState::BogusSignatureExpired, vState::BogusUnsupportedDNSKEYAlgo, vState::BogusUnsupportedDSDigestType, vState::BogusNoZoneKeyBitSet, vState::BogusRevokedDNSKEY, vState::BogusInvalidDNSKEYProtocol};
+    auto counts = g_Counters.sum(rec::DNSSECHistogram::dnssec);
+    uint64_t total = 0;
+    for (const auto& state : bogusStates) {
+      total += counts.at(state);
+    }
+    return total;
+  });
+
+  addGetStat("dnssec-result-bogus-no-valid-dnskey", [] { return g_Counters.sum(rec::DNSSECHistogram::dnssec).at(vState::BogusNoValidDNSKEY); });
+  addGetStat("dnssec-result-bogus-invalid-denial", [] { return g_Counters.sum(rec::DNSSECHistogram::dnssec).at(vState::BogusInvalidDenial); });
+  addGetStat("dnssec-result-bogus-unable-to-get-dss", [] { return g_Counters.sum(rec::DNSSECHistogram::dnssec).at(vState::BogusUnableToGetDSs); });
+  addGetStat("dnssec-result-bogus-unable-to-get-dnskeys", [] { return g_Counters.sum(rec::DNSSECHistogram::dnssec).at(vState::BogusUnableToGetDNSKEYs); });
+  addGetStat("dnssec-result-bogus-self-signed-ds", [] { return g_Counters.sum(rec::DNSSECHistogram::dnssec).at(vState::BogusSelfSignedDS); });
+  addGetStat("dnssec-result-bogus-no-rrsig", [] { return g_Counters.sum(rec::DNSSECHistogram::dnssec).at(vState::BogusNoRRSIG); });
+  addGetStat("dnssec-result-bogus-no-valid-rrsig", [] { return g_Counters.sum(rec::DNSSECHistogram::dnssec).at(vState::BogusNoValidRRSIG); });
+  addGetStat("dnssec-result-bogus-missing-negative-indication", [] { return g_Counters.sum(rec::DNSSECHistogram::dnssec).at(vState::BogusMissingNegativeIndication); });
+  addGetStat("dnssec-result-bogus-signature-not-yet-valid", [] { return g_Counters.sum(rec::DNSSECHistogram::dnssec).at(vState::BogusSignatureNotYetValid); });
+  addGetStat("dnssec-result-bogus-signature-expired", [] { return g_Counters.sum(rec::DNSSECHistogram::dnssec).at(vState::BogusSignatureExpired); });
+  addGetStat("dnssec-result-bogus-unsupported-dnskey-algo", [] { return g_Counters.sum(rec::DNSSECHistogram::dnssec).at(vState::BogusUnsupportedDNSKEYAlgo); });
+  addGetStat("dnssec-result-bogus-unsupported-ds-digest-type", [] { return g_Counters.sum(rec::DNSSECHistogram::dnssec).at(vState::BogusUnsupportedDSDigestType); });
+  addGetStat("dnssec-result-bogus-no-zone-key-bit-set", [] { return g_Counters.sum(rec::DNSSECHistogram::dnssec).at(vState::BogusNoZoneKeyBitSet); });
+  addGetStat("dnssec-result-bogus-revoked-dnskey", [] { return g_Counters.sum(rec::DNSSECHistogram::dnssec).at(vState::BogusRevokedDNSKEY); });
+  addGetStat("dnssec-result-bogus-invalid-dnskey-protocol", [] { return g_Counters.sum(rec::DNSSECHistogram::dnssec).at(vState::BogusInvalidDNSKEYProtocol); });
+  addGetStat("dnssec-result-indeterminate", [] { return g_Counters.sum(rec::DNSSECHistogram::dnssec).at(vState::Indeterminate); });
+  addGetStat("dnssec-result-nta", [] { return g_Counters.sum(rec::DNSSECHistogram::dnssec).at(vState::NTA); });
+
+  if (::arg()["x-dnssec-names"].length() > 0) {
+    addGetStat("x-dnssec-result-bogus", []() {
+      std::set<vState> const bogusStates = {vState::BogusNoValidDNSKEY, vState::BogusInvalidDenial, vState::BogusUnableToGetDSs, vState::BogusUnableToGetDNSKEYs, vState::BogusSelfSignedDS, vState::BogusNoRRSIG, vState::BogusNoValidRRSIG, vState::BogusMissingNegativeIndication, vState::BogusSignatureNotYetValid, vState::BogusSignatureExpired, vState::BogusUnsupportedDNSKEYAlgo, vState::BogusUnsupportedDSDigestType, vState::BogusNoZoneKeyBitSet, vState::BogusRevokedDNSKEY, vState::BogusInvalidDNSKEYProtocol};
+      auto counts = g_Counters.sum(rec::DNSSECHistogram::xdnssec);
+      uint64_t total = 0;
+      for (const auto& state : bogusStates) {
+        total += counts.at(state);
+      }
+      return total;
+    });
+    addGetStat("x-dnssec-result-bogus-no-valid-dnskey", [] { return g_Counters.sum(rec::DNSSECHistogram::xdnssec).at(vState::BogusNoValidDNSKEY); });
+    addGetStat("x-dnssec-result-bogus-invalid-denial", [] { return g_Counters.sum(rec::DNSSECHistogram::xdnssec).at(vState::BogusInvalidDenial); });
+    addGetStat("x-dnssec-result-bogus-unable-to-get-dss", [] { return g_Counters.sum(rec::DNSSECHistogram::xdnssec).at(vState::BogusUnableToGetDSs); });
+    addGetStat("x-dnssec-result-bogus-unable-to-get-dnskeys", [] { return g_Counters.sum(rec::DNSSECHistogram::xdnssec).at(vState::BogusUnableToGetDNSKEYs); });
+    addGetStat("x-dnssec-result-bogus-self-signed-ds", [] { return g_Counters.sum(rec::DNSSECHistogram::xdnssec).at(vState::BogusSelfSignedDS); });
+    addGetStat("x-dnssec-result-bogus-no-rrsig", [] { return g_Counters.sum(rec::DNSSECHistogram::xdnssec).at(vState::BogusNoRRSIG); });
+    addGetStat("x-dnssec-result-bogus-no-valid-rrsig", [] { return g_Counters.sum(rec::DNSSECHistogram::xdnssec).at(vState::BogusNoValidRRSIG); });
+    addGetStat("x-dnssec-result-bogus-missing-negative-indication", [] { return g_Counters.sum(rec::DNSSECHistogram::xdnssec).at(vState::BogusMissingNegativeIndication); });
+    addGetStat("x-dnssec-result-bogus-signature-not-yet-valid", [] { return g_Counters.sum(rec::DNSSECHistogram::xdnssec).at(vState::BogusSignatureNotYetValid); });
+    addGetStat("x-dnssec-result-bogus-signature-expired", [] { return g_Counters.sum(rec::DNSSECHistogram::xdnssec).at(vState::BogusSignatureExpired); });
+    addGetStat("x-dnssec-result-bogus-unsupported-dnskey-algo", [] { return g_Counters.sum(rec::DNSSECHistogram::xdnssec).at(vState::BogusUnsupportedDNSKEYAlgo); });
+    addGetStat("x-dnssec-result-bogus-unsupported-ds-digest-type", [] { return g_Counters.sum(rec::DNSSECHistogram::xdnssec).at(vState::BogusUnsupportedDSDigestType); });
+    addGetStat("x-dnssec-result-bogus-no-zone-key-bit-set", [] { return g_Counters.sum(rec::DNSSECHistogram::xdnssec).at(vState::BogusNoZoneKeyBitSet); });
+    addGetStat("x-dnssec-result-bogus-revoked-dnskey", [] { return g_Counters.sum(rec::DNSSECHistogram::xdnssec).at(vState::BogusRevokedDNSKEY); });
+    addGetStat("x-dnssec-result-bogus-invalid-dnskey-protocol", [] { return g_Counters.sum(rec::DNSSECHistogram::xdnssec).at(vState::BogusInvalidDNSKEYProtocol); });
+    addGetStat("x-dnssec-result-indeterminate", [] { return g_Counters.sum(rec::DNSSECHistogram::xdnssec).at(vState::Indeterminate); });
+    addGetStat("x-dnssec-result-nta", [] { return g_Counters.sum(rec::DNSSECHistogram::xdnssec).at(vState::NTA); });
+    addGetStat("x-dnssec-result-insecure", [] { return g_Counters.sum(rec::DNSSECHistogram::xdnssec).at(vState::Insecure); });
+    addGetStat("x-dnssec-result-secure", [] { return g_Counters.sum(rec::DNSSECHistogram::xdnssec).at(vState::Secure); });
+  }
+
+  addGetStat("policy-result-noaction", [] { return g_Counters.sum(rec::PolicyHistogram::policy).at(DNSFilterEngine::PolicyKind::NoAction); });
+  addGetStat("policy-result-drop", [] { return g_Counters.sum(rec::PolicyHistogram::policy).at(DNSFilterEngine::PolicyKind::Drop); });
+  addGetStat("policy-result-nxdomain", [] { return g_Counters.sum(rec::PolicyHistogram::policy).at(DNSFilterEngine::PolicyKind::NXDOMAIN); });
+  addGetStat("policy-result-nodata", [] { return g_Counters.sum(rec::PolicyHistogram::policy).at(DNSFilterEngine::PolicyKind::NODATA); });
+  addGetStat("policy-result-truncate", [] { return g_Counters.sum(rec::PolicyHistogram::policy).at(DNSFilterEngine::PolicyKind::Truncate); });
+  addGetStat("policy-result-custom", [] { return g_Counters.sum(rec::PolicyHistogram::policy).at(DNSFilterEngine::PolicyKind::Custom); });
+
+  addGetStat("rebalanced-queries", [] { return g_Counters.sum(rec::Counter::rebalancedQueries); });
+
+  addGetStat("proxy-protocol-invalid", [] { return g_Counters.sum(rec::Counter::proxyProtocolInvalidCount); });
+
+  addGetStat("nod-lookups-dropped-oversize", [] { return g_Counters.sum(rec::Counter::nodLookupsDroppedOversize); });
+
+  addGetStat("taskqueue-pushed", []() { return getTaskPushes(); });
+  addGetStat("taskqueue-expired", []() { return getTaskExpired(); });
+  addGetStat("taskqueue-size", []() { return getTaskSize(); });
+
+  addGetStat("dns64-prefix-answers", [] { return g_Counters.sum(rec::Counter::dns64prefixanswers); });
+
+  addGetStat("almost-expired-pushed", []() { return getAlmostExpiredTasksPushed(); });
+  addGetStat("almost-expired-run", []() { return getAlmostExpiredTasksRun(); });
+  addGetStat("almost-expired-exceptions", []() { return getAlmostExpiredTaskExceptions(); });
+
+  addGetStat("idle-tcpout-connections", getCurrentIdleTCPConnections);
+
+  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++) {
+    const std::string name = "ecs-v4-response-bits-" + std::to_string(idx + 1);
+    addGetStat(name, &(SyncRes::s_ecsResponsesBySubnetSize4.at(idx)));
+  }
+  for (size_t idx = 0; idx < SyncRes::s_ecsResponsesBySubnetSize6.size(); idx++) {
+    const std::string name = "ecs-v6-response-bits-" + std::to_string(idx + 1);
+    addGetStat(name, &(SyncRes::s_ecsResponsesBySubnetSize6.at(idx)));
+  }
+
+  addGetStat("cumul-clientanswers", []() {
+    return toStatsMap(t_Counters.at(rec::Histogram::cumulativeAnswers).getName(), g_Counters.sum(rec::Histogram::cumulativeAnswers));
+  });
+  addGetStat("cumul-authanswers", []() {
+    return toStatsMap(t_Counters.at(rec::Histogram::cumulativeAuth4Answers).getName(), g_Counters.sum(rec::Histogram::cumulativeAuth4Answers), g_Counters.sum(rec::Histogram::cumulativeAuth6Answers));
+  });
+  addGetStat("policy-hits", []() {
+    return toRPZStatsMap("policy-hits", g_Counters.sum(rec::PolicyNameHits::policyName).counts);
+  });
+  addGetStat("proxy-mapping-total", []() {
+    return toProxyMappingStatsMap("proxy-mapping-total");
+  });
+  addGetStat("auth-rcode-answers", []() {
+    return toAuthRCodeStatsMap("auth-rcode-answers");
+  });
+  addGetStat("remote-logger-count", []() {
+    return toRemoteLoggerStatsMap("remote-logger-count");
+  });
+}
+
+void registerAllStats()
+{
+  try {
+    registerAllStats1();
+  }
+  catch (...) {
+    g_log << Logger::Critical << "Could not add stat entries" << endl;
+    exit(1);
+  }
+}
+
+void doExitGeneric(bool nicely)
+{
+  g_log << Logger::Error << "Exiting on user request" << endl;
+  g_rcc.~RecursorControlChannel();
+
+  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);
+  }
+}
+
+void doExit()
+{
+  doExitGeneric(false);
+}
+
+void doExitNicely()
+{
+  doExitGeneric(true);
+}
+
+vector<pair<DNSName, uint16_t>>* pleaseGetQueryRing()
+{
+  typedef pair<DNSName, uint16_t> query_t;
+  vector<query_t>* ret = new vector<query_t>();
+  if (!t_queryring)
+    return ret;
+  ret->reserve(t_queryring->size());
+
+  for (const query_t& q : *t_queryring) {
+    ret->push_back(q);
+  }
+  return ret;
+}
+vector<pair<DNSName, uint16_t>>* pleaseGetServfailQueryRing()
+{
+  typedef pair<DNSName, uint16_t> query_t;
+  vector<query_t>* ret = new vector<query_t>();
+  if (!t_servfailqueryring)
+    return ret;
+  ret->reserve(t_servfailqueryring->size());
+  for (const query_t& q : *t_servfailqueryring) {
+    ret->push_back(q);
+  }
+  return ret;
+}
+vector<pair<DNSName, uint16_t>>* pleaseGetBogusQueryRing()
+{
+  typedef pair<DNSName, uint16_t> query_t;
+  vector<query_t>* ret = new vector<query_t>();
+  if (!t_bogusqueryring)
+    return ret;
+  ret->reserve(t_bogusqueryring->size());
+  for (const query_t& q : *t_bogusqueryring) {
+    ret->push_back(q);
+  }
+  return ret;
+}
+
+typedef std::function<vector<ComboAddress>*()> pleaseremotefunc_t;
+typedef std::function<vector<pair<DNSName, uint16_t>>*()> pleasequeryfunc_t;
+
+vector<ComboAddress>* pleaseGetRemotes()
+{
+  vector<ComboAddress>* ret = new vector<ComboAddress>();
+  if (!t_remotes)
+    return ret;
+
+  ret->reserve(t_remotes->size());
+  for (const ComboAddress& ca : *t_remotes) {
+    ret->push_back(ca);
+  }
+  return ret;
+}
+
+vector<ComboAddress>* pleaseGetServfailRemotes()
+{
+  vector<ComboAddress>* ret = new vector<ComboAddress>();
+  if (!t_servfailremotes)
+    return ret;
+  ret->reserve(t_servfailremotes->size());
+  for (const ComboAddress& ca : *t_servfailremotes) {
+    ret->push_back(ca);
+  }
+  return ret;
+}
+
+vector<ComboAddress>* pleaseGetBogusRemotes()
+{
+  vector<ComboAddress>* ret = new vector<ComboAddress>();
+  if (!t_bogusremotes)
+    return ret;
+  ret->reserve(t_bogusremotes->size());
+  for (const ComboAddress& ca : *t_bogusremotes) {
+    ret->push_back(ca);
+  }
+  return ret;
+}
+
+vector<ComboAddress>* pleaseGetLargeAnswerRemotes()
+{
+  vector<ComboAddress>* ret = new vector<ComboAddress>();
+  if (!t_largeanswerremotes)
+    return ret;
+  ret->reserve(t_largeanswerremotes->size());
+  for (const ComboAddress& ca : *t_largeanswerremotes) {
+    ret->push_back(ca);
+  }
+  return ret;
+}
+
+vector<ComboAddress>* pleaseGetTimeouts()
+{
+  vector<ComboAddress>* ret = new vector<ComboAddress>();
+  if (!t_timeouts)
+    return ret;
+  ret->reserve(t_timeouts->size());
+  for (const ComboAddress& ca : *t_timeouts) {
+    ret->push_back(ca);
+  }
+  return ret;
+}
+
+static string doGenericTopRemotes(pleaseremotefunc_t func)
+{
+  typedef map<ComboAddress, int, ComboAddress::addressOnlyLessThan> counts_t;
+  counts_t counts;
+
+  vector<ComboAddress> remotes = broadcastAccFunction<vector<ComboAddress>>(func);
+
+  unsigned int total = 0;
+  for (const ComboAddress& ca : remotes) {
+    total++;
+    counts[ca]++;
+  }
+
+  typedef std::multimap<int, ComboAddress> rcounts_t;
+  rcounts_t rcounts;
+
+  for (auto&& c : counts)
+    rcounts.emplace(-c.second, c.first);
+
+  ostringstream ret;
+  ret << "Over last " << total << " entries:\n";
+  boost::format fmt("%.02f%%\t%s\n");
+  int limit = 0, accounted = 0;
+  if (total) {
+    for (rcounts_t::const_iterator i = rcounts.begin(); i != rcounts.end() && limit < 20; ++i, ++limit) {
+      ret << fmt % (-100.0 * i->first / total) % i->second.toString();
+      accounted += -i->first;
+    }
+    ret << '\n'
+        << fmt % (100.0 * (total - accounted) / total) % "rest";
+  }
+  return ret.str();
+}
+
+// XXX DNSName Pain - this function should benefit from native DNSName methods
+DNSName getRegisteredName(const DNSName& dom)
+{
+  auto parts = dom.getRawLabels();
+  if (parts.size() <= 2)
+    return dom;
+  reverse(parts.begin(), parts.end());
+  for (string& str : parts) {
+    str = toLower(str);
+  };
+
+  // uk co migweb
+  string last;
+  while (!parts.empty()) {
+    if (parts.size() == 1 || binary_search(g_pubs.begin(), g_pubs.end(), parts)) {
+
+      string ret = last;
+      if (!ret.empty())
+        ret += ".";
+
+      for (auto p = parts.crbegin(); p != parts.crend(); ++p) {
+        ret += (*p) + ".";
+      }
+      return DNSName(ret);
+    }
+
+    last = parts[parts.size() - 1];
+    parts.resize(parts.size() - 1);
+  }
+  return DNSName("??");
+}
+
+static DNSName nopFilter(const DNSName& name)
+{
+  return name;
+}
+
+static string doGenericTopQueries(pleasequeryfunc_t func, std::function<DNSName(const DNSName&)> filter = nopFilter)
+{
+  typedef pair<DNSName, uint16_t> query_t;
+  typedef map<query_t, int> counts_t;
+  counts_t counts;
+  vector<query_t> queries = broadcastAccFunction<vector<query_t>>(func);
+
+  unsigned int total = 0;
+  for (const query_t& q : queries) {
+    total++;
+    counts[pair(filter(q.first), q.second)]++;
+  }
+
+  typedef std::multimap<int, query_t> rcounts_t;
+  rcounts_t rcounts;
+
+  for (auto&& c : counts)
+    rcounts.emplace(-c.second, c.first);
+
+  ostringstream ret;
+  ret << "Over last " << total << " entries:\n";
+  boost::format fmt("%.02f%%\t%s\n");
+  int limit = 0, accounted = 0;
+  if (total) {
+    for (rcounts_t::const_iterator i = rcounts.begin(); i != rcounts.end() && limit < 20; ++i, ++limit) {
+      ret << fmt % (-100.0 * i->first / total) % (i->second.first.toLogString() + "|" + DNSRecordContent::NumberToType(i->second.second));
+      accounted += -i->first;
+    }
+    ret << '\n'
+        << fmt % (100.0 * (total - accounted) / total) % "rest";
+  }
+
+  return ret.str();
+}
+
+static string* nopFunction()
+{
+  return new string("pong " + RecThreadInfo::self().getName() + '\n');
+}
+
+static string getDontThrottleNames()
+{
+  auto dtn = g_dontThrottleNames.getLocal();
+  return dtn->toString() + "\n";
+}
+
+static string getDontThrottleNetmasks()
+{
+  auto dtn = g_dontThrottleNetmasks.getLocal();
+  return dtn->toString() + "\n";
+}
+
+template <typename T>
+static string addDontThrottleNames(T begin, T end)
+{
+  if (begin == end) {
+    return "No names specified, keeping existing list\n";
+  }
+  vector<DNSName> toAdd;
+  while (begin != end) {
+    try {
+      auto d = DNSName(*begin);
+      toAdd.push_back(d);
+    }
+    catch (const std::exception& e) {
+      return "Problem parsing '" + *begin + "': " + e.what() + ", nothing added\n";
+    }
+    begin++;
+  }
+
+  string ret = "Added";
+  auto dnt = g_dontThrottleNames.getCopy();
+  bool first = true;
+  for (auto const& d : toAdd) {
+    if (!first) {
+      ret += ",";
+    }
+    first = false;
+    ret += " " + d.toLogString();
+    dnt.add(d);
+  }
+
+  g_dontThrottleNames.setState(std::move(dnt));
+
+  ret += " to the list of nameservers that may not be throttled";
+  g_log << Logger::Info << ret << ", requested via control channel" << endl;
+  return ret + "\n";
+}
+
+template <typename T>
+static string addDontThrottleNetmasks(T begin, T end)
+{
+  if (begin == end) {
+    return "No netmasks specified, keeping existing list\n";
+  }
+  vector<Netmask> toAdd;
+  while (begin != end) {
+    try {
+      auto n = Netmask(*begin);
+      toAdd.push_back(n);
+    }
+    catch (const std::exception& e) {
+      return "Problem parsing '" + *begin + "': " + e.what() + ", nothing added\n";
+    }
+    catch (const PDNSException& e) {
+      return "Problem parsing '" + *begin + "': " + e.reason + ", nothing added\n";
+    }
+    begin++;
+  }
+
+  string ret = "Added";
+  auto dnt = g_dontThrottleNetmasks.getCopy();
+  bool first = true;
+  for (auto const& t : toAdd) {
+    if (!first) {
+      ret += ",";
+    }
+    first = false;
+    ret += " " + t.toString();
+    dnt.addMask(t);
+  }
+
+  g_dontThrottleNetmasks.setState(std::move(dnt));
+
+  ret += " to the list of nameserver netmasks that may not be throttled";
+  g_log << Logger::Info << ret << ", requested via control channel" << endl;
+  return ret + "\n";
+}
+
+template <typename T>
+static string clearDontThrottleNames(T begin, T end)
+{
+  if (begin == end)
+    return "No names specified, doing nothing.\n";
+
+  if (begin + 1 == end && *begin == "*") {
+    SuffixMatchNode smn;
+    g_dontThrottleNames.setState(std::move(smn));
+    string ret = "Cleared list of nameserver names that may not be throttled";
+    g_log << Logger::Warning << ret << ", requested via control channel" << endl;
+    return ret + "\n";
+  }
+
+  vector<DNSName> toRemove;
+  while (begin != end) {
+    try {
+      if (*begin == "*") {
+        return "Please don't mix '*' with other names, nothing removed\n";
+      }
+      toRemove.push_back(DNSName(*begin));
+    }
+    catch (const std::exception& e) {
+      return "Problem parsing '" + *begin + "': " + e.what() + ", nothing removed\n";
+    }
+    begin++;
+  }
+
+  string ret = "Removed";
+  bool first = true;
+  auto dnt = g_dontThrottleNames.getCopy();
+  for (const auto& name : toRemove) {
+    if (!first) {
+      ret += ",";
+    }
+    first = false;
+    ret += " " + name.toLogString();
+    dnt.remove(name);
+  }
+
+  g_dontThrottleNames.setState(std::move(dnt));
+
+  ret += " from the list of nameservers that may not be throttled";
+  g_log << Logger::Info << ret << ", requested via control channel" << endl;
+  return ret + "\n";
+}
+
+template <typename T>
+static string clearDontThrottleNetmasks(T begin, T end)
+{
+  if (begin == end)
+    return "No netmasks specified, doing nothing.\n";
+
+  if (begin + 1 == end && *begin == "*") {
+    auto nmg = g_dontThrottleNetmasks.getCopy();
+    nmg.clear();
+    g_dontThrottleNetmasks.setState(std::move(nmg));
+
+    string ret = "Cleared list of nameserver addresses that may not be throttled";
+    g_log << Logger::Warning << ret << ", requested via control channel" << endl;
+    return ret + "\n";
+  }
+
+  std::vector<Netmask> toRemove;
+  while (begin != end) {
+    try {
+      if (*begin == "*") {
+        return "Please don't mix '*' with other netmasks, nothing removed\n";
+      }
+      auto n = Netmask(*begin);
+      toRemove.push_back(n);
+    }
+    catch (const std::exception& e) {
+      return "Problem parsing '" + *begin + "': " + e.what() + ", nothing added\n";
+    }
+    catch (const PDNSException& e) {
+      return "Problem parsing '" + *begin + "': " + e.reason + ", nothing added\n";
+    }
+    begin++;
+  }
+
+  string ret = "Removed";
+  bool first = true;
+  auto dnt = g_dontThrottleNetmasks.getCopy();
+  for (const auto& mask : toRemove) {
+    if (!first) {
+      ret += ",";
+    }
+    first = false;
+    ret += " " + mask.toString();
+    dnt.deleteMask(mask);
+  }
+
+  g_dontThrottleNetmasks.setState(std::move(dnt));
+
+  ret += " from the list of nameservers that may not be throttled";
+  g_log << Logger::Info << ret << ", requested via control channel" << endl;
+  return ret + "\n";
+}
+
+template <typename T>
+static string setEventTracing(T begin, T end)
+{
+  if (begin == end) {
+    return "No event trace enabled value specified\n";
+  }
+  try {
+    pdns::checked_stoi_into(SyncRes::s_event_trace_enabled, *begin);
+    return "New event trace enabled value: " + std::to_string(SyncRes::s_event_trace_enabled) + "\n";
+  }
+  catch (const std::exception& e) {
+    return "Error parsing the new event trace enabled value: " + std::string(e.what()) + "\n";
+  }
+}
+
+static void* pleaseSupplantProxyMapping(const ProxyMapping& pm)
+{
+  if (pm.empty()) {
+    t_proxyMapping = nullptr;
+  }
+  else {
+    // Copy the existing stats values, for the new config items also present in the old
+    auto newmapping = make_unique<ProxyMapping>();
+    for (const auto& [nm, entry] : pm) {
+      auto& newentry = newmapping->insert(nm);
+      newentry.second = entry;
+      if (t_proxyMapping) {
+        if (const auto* existing = t_proxyMapping->lookup(nm); existing != nullptr) {
+          newentry.second.stats = existing->second.stats;
+        }
+      }
+    }
+    t_proxyMapping = std::move(newmapping);
+  }
+  return nullptr;
+}
+
+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()) {
+    return {1, "invalid command\n"};
+  }
+
+  string cmd = toLower(words[0]);
+  auto begin = words.begin() + 1;
+  auto end = words.end();
+
+  // should probably have a smart dispatcher here, like auth has
+  if (cmd == "help") {
+    return help();
+  }
+  if (cmd == "get-all") {
+    return {0, getAllStats()};
+  }
+  if (cmd == "get") {
+    return {0, doGet(begin, end)};
+  }
+  if (cmd == "get-parameter") {
+    return {0, doGetParameter(begin, end)};
+  }
+  if (cmd == "quit") {
+    *command = &doExit;
+    return {0, "bye\n"};
+  }
+  if (cmd == "version") {
+    return {0, getPDNSVersion() + "\n"};
+  }
+  if (cmd == "quit-nicely") {
+    *command = &doExitNicely;
+    return {0, "bye nicely\n"};
+  }
+  if (cmd == "dump-cache") {
+    return doDumpCache(socket, begin, end);
+  }
+  if (cmd == "dump-dot-probe-map") {
+    return doDumpToFile(socket, pleaseDumpDoTProbeMap, cmd, false);
+  }
+  if (cmd == "dump-ednsstatus" || cmd == "dump-edns") {
+    return doDumpToFile(socket, pleaseDumpEDNSMap, cmd, false);
+  }
+  if (cmd == "dump-nsspeeds") {
+    return doDumpToFile(socket, pleaseDumpNSSpeeds, cmd, false);
+  }
+  if (cmd == "dump-failedservers") {
+    return doDumpToFile(socket, pleaseDumpFailedServers, cmd, false);
+  }
+  if (cmd == "dump-saved-parent-ns-sets") {
+    return doDumpToFile(socket, pleaseDumpSavedParentNSSets, cmd, false);
+  }
+  if (cmd == "dump-rpz") {
+    return doDumpRPZ(socket, begin, end);
+  }
+  if (cmd == "dump-throttlemap") {
+    return doDumpToFile(socket, pleaseDumpThrottleMap, cmd, false);
+  }
+  if (cmd == "dump-non-resolving") {
+    return doDumpToFile(socket, pleaseDumpNonResolvingNS, cmd, false);
+  }
+  if (cmd == "wipe-cache" || cmd == "flushname") {
+    return {0, doWipeCache(begin, end, 0xffff)};
+  }
+  if (cmd == "wipe-cache-typed") {
+    if (begin == end) {
+      return {1, "Need a qtype\n"};
+    }
+    uint16_t qtype = QType::chartocode(begin->c_str());
+    if (qtype == 0) {
+      return {1, "Unknown qtype " + *begin + "\n"};
+    }
+    ++begin;
+    return {0, doWipeCache(begin, end, qtype)};
+  }
+  if (cmd == "reload-lua-script") {
+    return doQueueReloadLuaScript(begin, end);
+  }
+  if (cmd == "reload-lua-config") {
+    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(socket), begin, end)};
+  }
+  if (cmd == "unload-lua-script") {
+    vector<string> empty;
+    empty.emplace_back();
+    return doQueueReloadLuaScript(empty.begin(), empty.end());
+  }
+  if (cmd == "reload-acls") {
+    return reloadACLs();
+  }
+  if (cmd == "top-remotes") {
+    return {0, doGenericTopRemotes(pleaseGetRemotes)};
+  }
+  if (cmd == "top-queries") {
+    return {0, doGenericTopQueries(pleaseGetQueryRing)};
+  }
+  if (cmd == "top-pub-queries") {
+    return {0, doGenericTopQueries(pleaseGetQueryRing, getRegisteredName)};
+  }
+  if (cmd == "top-servfail-queries") {
+    return {0, doGenericTopQueries(pleaseGetServfailQueryRing)};
+  }
+  if (cmd == "top-pub-servfail-queries") {
+    return {0, doGenericTopQueries(pleaseGetServfailQueryRing, getRegisteredName)};
+  }
+  if (cmd == "top-bogus-queries") {
+    return {0, doGenericTopQueries(pleaseGetBogusQueryRing)};
+  }
+  if (cmd == "top-pub-bogus-queries") {
+    return {0, doGenericTopQueries(pleaseGetBogusQueryRing, getRegisteredName)};
+  }
+  if (cmd == "top-servfail-remotes") {
+    return {0, doGenericTopRemotes(pleaseGetServfailRemotes)};
+  }
+  if (cmd == "top-bogus-remotes") {
+    return {0, doGenericTopRemotes(pleaseGetBogusRemotes)};
+  }
+  if (cmd == "top-largeanswer-remotes") {
+    return {0, doGenericTopRemotes(pleaseGetLargeAnswerRemotes)};
+  }
+  if (cmd == "top-timeouts") {
+    return {0, doGenericTopRemotes(pleaseGetTimeouts)};
+  }
+  if (cmd == "current-queries") {
+    return {0, doCurrentQueries()};
+  }
+  if (cmd == "ping") {
+    return {0, broadcastAccFunction<string>(nopFunction)};
+  }
+  if (cmd == "reload-zones") {
+    if (!::arg()["chroot"].empty()) {
+      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(g_yamlSettings)};
+  }
+  if (cmd == "set-ecs-minimum-ttl") {
+    return {0, setMinimumECSTTL(begin, end)};
+  }
+  if (cmd == "set-max-cache-entries") {
+    return {0, setMaxCacheEntries(begin, end)};
+  }
+  if (cmd == "set-max-packetcache-entries") {
+    return {0, setMaxPacketCacheEntries(begin, end)};
+  }
+  if (cmd == "set-minimum-ttl") {
+    return {0, setMinimumTTL(begin, end)};
+  }
+  if (cmd == "get-qtypelist") {
+    return {0, g_Counters.sum(rec::ResponseStats::responseStats).getQTypeReport()};
+  }
+  if (cmd == "add-nta") {
+    return {0, doAddNTA(begin, end)};
+  }
+  if (cmd == "clear-nta") {
+    return {0, doClearNTA(begin, end)};
+  }
+  if (cmd == "get-ntas") {
+    return {0, getNTAs()};
+  }
+  if (cmd == "add-ta") {
+    return {0, doAddTA(begin, end)};
+  }
+  if (cmd == "clear-ta") {
+    return {0, doClearTA(begin, end)};
+  }
+  if (cmd == "get-tas") {
+    return {0, getTAs()};
+  }
+  if (cmd == "set-dnssec-log-bogus") {
+    return {0, doSetDnssecLogBogus(begin, end)};
+  }
+  if (cmd == "get-dont-throttle-names") {
+    return {0, getDontThrottleNames()};
+  }
+  if (cmd == "get-dont-throttle-netmasks") {
+    return {0, getDontThrottleNetmasks()};
+  }
+  if (cmd == "add-dont-throttle-names") {
+    return {0, addDontThrottleNames(begin, end)};
+  }
+  if (cmd == "add-dont-throttle-netmasks") {
+    return {0, addDontThrottleNetmasks(begin, end)};
+  }
+  if (cmd == "clear-dont-throttle-names") {
+    return {0, clearDontThrottleNames(begin, end)};
+  }
+  if (cmd == "clear-dont-throttle-netmasks") {
+    return {0, clearDontThrottleNetmasks(begin, end)};
+  }
+  if (cmd == "set-event-trace-enabled") {
+    return {0, setEventTracing(begin, end)};
+  }
+  if (cmd == "get-proxymapping-stats") {
+    return {0, doGetProxyMappingStats()};
+  }
+  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"};
+}
deleted file mode 120000 (symlink)
index c61dc4d70ca2aa8f826fb90f194112f877c642af..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../rec_control.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..4ac9de949a03d08aab853dd860d95904cfa514cb
--- /dev/null
@@ -0,0 +1,361 @@
+/*
+ * 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 <iostream>
+#include <fcntl.h>
+
+#include "pdnsexception.hh"
+#include "arguments.hh"
+#include "credentials.hh"
+#include "namespaces.hh"
+#include "rec_channel.hh"
+#include "settings/cxxsettings.hh"
+
+ArgvMap& arg()
+{
+  static ArgvMap arg;
+  return arg;
+}
+
+static void initArguments(int argc, char** argv)
+{
+  arg().set("config-dir", "Location of configuration directory (recursor.conf)") = SYSCONFDIR;
+
+  arg().set("socket-dir", string("Where the controlsocket will live, ") + LOCALSTATEDIR + "/pdns-recursor when unset and not chrooted") = "";
+  arg().set("chroot", "switch to chroot jail") = "";
+  arg().set("process", "When controlling multiple recursors, the target process number") = "";
+  arg().set("timeout", "Number of seconds to wait for the recursor to respond") = "5";
+  arg().set("config-name", "Name of this virtual configuration - will rename the binary image") = "";
+  arg().setCmd("help", "Provide this helpful message");
+  arg().setCmd("version", "Show the version of this program");
+
+  arg().laxParse(argc, argv);
+  if (arg().mustDo("version")) {
+    cout << "rec_control version " << VERSION << endl;
+    exit(0);
+  }
+  if (arg().mustDo("help") || arg().getCommands().empty()) {
+    cout << "syntax: rec_control [options] command, options as below: " << endl
+         << endl;
+    cout << arg().helpstring(arg()["help"]) << endl;
+    cout << "In addition, 'rec_control help' can be used to retrieve a list\nof available commands from PowerDNS" << endl;
+    exit(arg().mustDo("help") ? 0 : 99);
+  }
+
+  string configname = ::arg()["config-dir"] + "/recursor";
+  if (!::arg()["config-name"].empty()) {
+    configname = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"];
+  }
+
+  cleanSlashes(configname);
+
+  const string yamlconfigname = configname + ".yml";
+  string msg;
+  pdns::rust::settings::rec::Recursorsettings settings;
+
+  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()) {
+      ::arg().set("socket-dir") = std::string(LOCALSTATEDIR) + "/pdns-recursor";
+    }
+    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;
+  const set<string> fileCommands = {
+    "dump-cache",
+    "dump-edns",
+    "dump-ednsstatus",
+    "dump-nsspeeds",
+    "dump-failedservers",
+    "dump-rpz",
+    "dump-throttlemap",
+    "dump-non-resolving",
+    "dump-saved-parent-ns-sets",
+    "dump-dot-probe-map",
+    "trace-regex",
+  };
+  try {
+    initArguments(argc, argv);
+    string sockname = "pdns_recursor";
+
+    if (arg()["config-name"] != "")
+      sockname += "-" + arg()["config-name"];
+
+    if (!arg()["process"].empty())
+      sockname += "." + arg()["process"];
+
+    sockname.append(".controlsocket");
+
+    const vector<string>& commands = arg().getCommands();
+
+    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 {
+          pdns::checked_stoi_into(workFactor, commands.at(1));
+        }
+        catch (const std::exception& e) {
+          cerr << "Unable to parse the supplied work factor: " << e.what() << endl;
+          return EXIT_FAILURE;
+        }
+      }
+
+      auto password = CredentialsHolder::readFromTerminal();
+
+      try {
+        cout << hashPassword(password.getString(), workFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize) << endl;
+        return EXIT_SUCCESS;
+      }
+      catch (const std::exception& e) {
+        cerr << "Error while hashing the supplied password: " << e.what() << endl;
+        return EXIT_FAILURE;
+      }
+    }
+
+    string command;
+    int fd = -1;
+    unsigned int i = 0;
+    while (i < commands.size()) {
+      if (i > 0) {
+        command += " ";
+      }
+      command += commands[i];
+
+      // special case: trace-regex with no arguments is clear regex
+      auto traceregexClear = command == "trace-regex" && commands.size() == 1;
+
+      if (fileCommands.count(commands[i]) > 0 && !traceregexClear) {
+        if (i + 1 < commands.size()) {
+          // dump-rpz is different, it also has a zonename as argument
+          // trace-regex is different, it also has a regexp as argument
+          if (commands[i] == "dump-rpz" || commands[i] == "trace-regex") {
+            if (i + 2 < commands.size()) {
+              ++i;
+              command += " ";
+              command += commands[i]; // add rpzname/regex and continue with filename
+            }
+            else {
+              throw PDNSException("Command needs two arguments");
+            }
+          }
+          ++i;
+          if (commands[i] == "-") {
+            fd = STDOUT_FILENO;
+          }
+          else {
+            fd = open(commands[i].c_str(), O_CREAT | O_EXCL | O_WRONLY, 0660);
+          }
+          if (fd == -1) {
+            int err = errno;
+            throw PDNSException("Error opening dump file for writing: " + stringerror(err));
+          }
+        }
+        else {
+          throw PDNSException("Command needs a file argument");
+        }
+      }
+      ++i;
+    }
+
+    auto timeout = arg().asNum("timeout");
+    RecursorControlChannel rccS;
+    rccS.connect(arg()["socket-dir"], sockname);
+    rccS.send(rccS.d_fd, {0, std::move(command)}, timeout, fd);
+
+    auto receive = rccS.recv(rccS.d_fd, timeout);
+    if (receive.d_ret != 0) {
+      cerr << receive.d_str;
+    }
+    else {
+      cout << receive.d_str;
+    }
+    return receive.d_ret;
+  }
+  catch (PDNSException& ae) {
+    cerr << "Fatal: " << ae.reason << "\n";
+    return 1;
+  }
+}
deleted file mode 120000 (symlink)
index e869ba2331d27f343a6679fa9c2c0da7b2caacbf..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../recpacketcache.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..c372ee186221887dde67fff030d8e17b277da1e6
--- /dev/null
@@ -0,0 +1,302 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <iostream>
+#include <cinttypes>
+
+#include "recpacketcache.hh"
+#include "cachecleaner.hh"
+#include "dns.hh"
+#include "namespaces.hh"
+#include "rec-taskqueue.hh"
+
+unsigned int RecursorPacketCache::s_refresh_ttlperc{0};
+
+void RecursorPacketCache::setShardSizes(size_t shardSize)
+{
+  for (auto& shard : d_maps) {
+    auto lock = shard.lock();
+    lock->d_shardSize = shardSize;
+  }
+}
+
+uint64_t RecursorPacketCache::size() const
+{
+  uint64_t count = 0;
+  for (const auto& map : d_maps) {
+    count += map.getEntriesCount();
+  }
+  return count;
+}
+
+uint64_t RecursorPacketCache::bytes()
+{
+  uint64_t sum = 0;
+  for (auto& shard : d_maps) {
+    auto lock = shard.lock();
+    for (const auto& entry : lock->d_map) {
+      sum += sizeof(entry) + entry.d_packet.length() + 4;
+    }
+  }
+  return sum;
+}
+
+uint64_t RecursorPacketCache::getHits()
+{
+  uint64_t sum = 0;
+  for (auto& shard : d_maps) {
+    auto lock = shard.lock();
+    sum += lock->d_hits;
+  }
+  return sum;
+}
+
+uint64_t RecursorPacketCache::getMisses()
+{
+  uint64_t sum = 0;
+  for (auto& shard : d_maps) {
+    auto lock = shard.lock();
+    sum += lock->d_misses;
+  }
+  return sum;
+}
+
+pair<uint64_t, uint64_t> RecursorPacketCache::stats()
+{
+  uint64_t contended = 0;
+  uint64_t acquired = 0;
+  for (auto& shard : d_maps) {
+    auto content = shard.lock();
+    contended += content->d_contended_count;
+    acquired += content->d_acquired_count;
+  }
+  return {contended, acquired};
+}
+
+uint64_t RecursorPacketCache::doWipePacketCache(const DNSName& name, uint16_t qtype, bool subtree)
+{
+  uint64_t count = 0;
+  for (auto& map : d_maps) {
+    auto shard = map.lock();
+    auto& idx = shard->d_map.get<NameTag>();
+    for (auto iter = idx.lower_bound(name); iter != idx.end();) {
+      if (subtree) {
+        if (!iter->d_name.isPartOf(name)) { // this is case insensitive
+          break;
+        }
+      }
+      else {
+        if (iter->d_name != name) {
+          break;
+        }
+      }
+      if (qtype == 0xffff || iter->d_type == qtype) {
+        iter = idx.erase(iter);
+        map.decEntriesCount();
+        count++;
+      }
+      else {
+        ++iter;
+      }
+    }
+  }
+  return count;
+}
+
+bool RecursorPacketCache::qrMatch(const packetCache_t::index<HashTag>::type::iterator& iter, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass)
+{
+  // this ignores checking on the EDNS subnet flags!
+  if (qname != iter->d_name || iter->d_type != qtype || iter->d_class != qclass) {
+    return false;
+  }
+
+  static const std::unordered_set<uint16_t> optionsToSkip{EDNSOptionCode::COOKIE, EDNSOptionCode::ECS};
+  return queryMatches(iter->d_query, queryPacket, qname, optionsToSkip);
+}
+
+bool RecursorPacketCache::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)
+{
+  for (auto iter = range.first; iter != range.second; ++iter) {
+    // the possibility is VERY real that we get hits that are not right - birthday paradox
+    if (!qrMatch(iter, queryPacket, qname, qtype, qclass)) {
+      continue;
+    }
+
+    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 && 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;
+      responsePacket->replace(0, 2, queryPacket.c_str(), 2);
+      *valState = iter->d_vstate;
+
+      const size_t wirelength = qname.wirelength();
+      if (responsePacket->size() > (sizeof(dnsheader) + wirelength)) {
+        responsePacket->replace(sizeof(dnsheader), wirelength, queryPacket, sizeof(dnsheader), wirelength);
+      }
+
+      shard.d_hits++;
+      moveCacheItemToBack<SequencedTag>(shard.d_map, iter);
+
+      if (pbdata != nullptr) {
+        if (iter->d_pbdata) {
+          *pbdata = iter->d_pbdata;
+        }
+        else {
+          *pbdata = boost::none;
+        }
+      }
+
+      return true;
+    }
+    // We used to move the item to the front of "the to be deleted" sequence,
+    // but we very likely will update the entry very soon, so leave it
+    shard.d_misses++;
+    break;
+  }
+
+  return false;
+}
+
+static const std::unordered_set<uint16_t> s_skipOptions = {EDNSOptionCode::ECS, EDNSOptionCode::COOKIE};
+
+bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now,
+                                            std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, OptPBData* pbdata, bool tcp)
+{
+  *qhash = canHashPacket(queryPacket, s_skipOptions);
+  auto& map = getMap(tag, *qhash, tcp);
+  auto shard = map.lock();
+  const auto& idx = shard->d_map.get<HashTag>();
+  auto range = idx.equal_range(std::tie(tag, *qhash, tcp));
+
+  if (range.first == range.second) {
+    shard->d_misses++;
+    return false;
+  }
+
+  return checkResponseMatches(*shard, range, queryPacket, qname, qtype, qclass, now, responsePacket, age, valState, pbdata);
+}
+
+bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string& queryPacket, DNSName& qname, uint16_t* qtype, uint16_t* qclass, time_t now,
+                                            std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, OptPBData* pbdata, bool tcp)
+{
+  *qhash = canHashPacket(queryPacket, s_skipOptions);
+  auto& map = getMap(tag, *qhash, tcp);
+  auto shard = map.lock();
+  const auto& idx = shard->d_map.get<HashTag>();
+  auto range = idx.equal_range(std::tie(tag, *qhash, tcp));
+
+  if (range.first == range.second) {
+    shard->d_misses++;
+    return false;
+  }
+
+  qname = DNSName(queryPacket.c_str(), static_cast<int>(queryPacket.length()), sizeof(dnsheader), false, qtype, qclass);
+
+  return checkResponseMatches(*shard, range, queryPacket, qname, *qtype, *qclass, now, responsePacket, age, valState, pbdata);
+}
+
+void RecursorPacketCache::insertResponsePacket(unsigned int tag, uint32_t qhash, std::string&& query, const DNSName& qname, uint16_t qtype, uint16_t qclass, std::string&& responsePacket, time_t now, uint32_t ttl, const vState& valState, OptPBData&& pbdata, bool tcp)
+{
+  auto& map = getMap(tag, qhash, tcp);
+  auto shard = map.lock();
+  auto& idx = shard->d_map.get<HashTag>();
+  auto range = idx.equal_range(std::tie(tag, qhash, tcp));
+  auto iter = range.first;
+
+  for (; iter != range.second; ++iter) {
+    if (iter->d_type != qtype || iter->d_class != qclass || iter->d_name != qname) {
+      continue;
+    }
+
+    moveCacheItemToBack<SequencedTag>(shard->d_map, iter);
+    iter->d_packet = std::move(responsePacket);
+    iter->d_query = std::move(query);
+    iter->d_ttd = now + ttl;
+    iter->d_creation = now;
+    iter->d_vstate = valState;
+    iter->d_submitted = false;
+    if (pbdata) {
+      iter->d_pbdata = std::move(*pbdata);
+    }
+
+    return;
+  }
+
+  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.incEntriesCount();
+
+  if (shard->d_map.size() > shard->d_shardSize) {
+    auto& seq_idx = shard->d_map.get<SequencedTag>();
+    seq_idx.erase(seq_idx.begin());
+    map.decEntriesCount();
+  }
+  assert(map.getEntriesCount() == shard->d_map.size()); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay): clib implementation
+}
+
+void RecursorPacketCache::doPruneTo(time_t now, size_t maxSize)
+{
+  size_t cacheSize = size();
+  pruneMutexCollectionsVector<SequencedTag>(now, d_maps, maxSize, cacheSize);
+}
+
+uint64_t RecursorPacketCache::doDump(int file)
+{
+  int fdupped = dup(file);
+  if (fdupped == -1) {
+    return 0;
+  }
+  auto filePtr = std::unique_ptr<FILE, decltype(&fclose)>(fdopen(fdupped, "w"), fclose);
+  if (!filePtr) {
+    close(fdupped);
+    return 0;
+  }
+
+  uint64_t count = 0;
+  time_t now = time(nullptr);
+
+  size_t shardNum = 0;
+  size_t min = std::numeric_limits<size_t>::max();
+  size_t max = 0;
+  uint64_t maxSize = 0;
+
+  for (auto& shard : d_maps) {
+    auto lock = shard.lock();
+    const auto& sidx = lock->d_map.get<SequencedTag>();
+    const auto shardSize = lock->d_map.size();
+    fprintf(filePtr.get(), "; packetcache shard %zu; size %zu/%zu\n", shardNum, shardSize, lock->d_shardSize);
+    min = std::min(min, shardSize);
+    max = std::max(max, shardSize);
+    maxSize += lock->d_shardSize;
+    shardNum++;
+    for (const auto& entry : sidx) {
+      count++;
+      try {
+        fprintf(filePtr.get(), "%s %" PRId64 " %s  ; tag %d %s\n", entry.d_name.toString().c_str(), static_cast<int64_t>(entry.d_ttd - now), DNSRecordContent::NumberToType(entry.d_type).c_str(), entry.d_tag, entry.d_tcp ? "tcp" : "udp");
+      }
+      catch (...) {
+        fprintf(filePtr.get(), "; error printing '%s'\n", entry.d_name.empty() ? "EMPTY" : entry.d_name.toString().c_str());
+      }
+    }
+  }
+  fprintf(filePtr.get(), "; packetcache size: %" PRIu64 "/%" PRIu64 " shards: %zu min/max shard size: %zu/%zu\n", size(), maxSize, d_maps.size(), min, max);
+  return count;
+}
deleted file mode 120000 (symlink)
index 8ee8b3be6086a13e0f235df9d9ab752879986827..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../recpacketcache.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..17b618563eba72a334bd4c1d94c33f036d589732
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * 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 <cinttypes>
+#include "dns.hh"
+#include "namespaces.hh"
+#include <iostream>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+#include <boost/multi_index/key_extractors.hpp>
+#include <boost/optional.hpp>
+
+#include "packetcache.hh"
+#include "validate.hh"
+#include "lock.hh"
+#include "stat_t.hh"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+using namespace ::boost::multi_index;
+
+class RecursorPacketCache : public PacketCache
+{
+public:
+  static unsigned int s_refresh_ttlperc;
+
+  struct PBData
+  {
+    std::string d_message;
+    std::string d_response;
+    bool d_tagged;
+  };
+  using OptPBData = boost::optional<PBData>;
+
+  RecursorPacketCache(size_t maxsize, size_t shards = 1024) :
+    d_maps(shards)
+  {
+    setMaxSize(maxsize);
+  }
+
+  bool getResponsePacket(unsigned int tag, const std::string& queryPacket, time_t now,
+                         std::string* responsePacket, uint32_t* age, uint32_t* qhash)
+  {
+    DNSName qname;
+    uint16_t qtype{0};
+    uint16_t qclass{0};
+    vState valState{vState::Indeterminate};
+    return getResponsePacket(tag, queryPacket, qname, &qtype, &qclass, now, responsePacket, age, &valState, qhash, nullptr, false);
+  }
+
+  bool getResponsePacket(unsigned int tag, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now,
+                         std::string* responsePacket, uint32_t* age, uint32_t* qhash)
+  {
+    vState valState{vState::Indeterminate};
+    return getResponsePacket(tag, queryPacket, qname, qtype, qclass, now, responsePacket, age, &valState, qhash, nullptr, false);
+  }
+
+  bool getResponsePacket(unsigned int tag, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now, std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, OptPBData* pbdata, bool tcp);
+  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(time_t now, size_t maxSize);
+  uint64_t doDump(int file);
+  uint64_t doWipePacketCache(const DNSName& name, uint16_t qtype = 0xffff, bool subtree = false);
+
+  void setMaxSize(size_t size)
+  {
+    if (size < d_maps.size()) {
+      size = d_maps.size();
+    }
+    setShardSizes(size / d_maps.size());
+  }
+
+  [[nodiscard]] uint64_t size() const;
+  [[nodiscard]] uint64_t bytes();
+  [[nodiscard]] uint64_t getHits();
+  [[nodiscard]] uint64_t getMisses();
+  [[nodiscard]] pair<uint64_t, uint64_t> stats();
+
+private:
+  struct Entry
+  {
+    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(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)
+    {
+    }
+
+    DNSName d_name;
+    mutable std::string d_packet;
+    mutable std::string d_query;
+    mutable OptPBData d_pbdata;
+    mutable time_t d_ttd;
+    mutable time_t d_creation; // so we can 'age' our packets
+    uint32_t d_qhash;
+    uint32_t d_tag;
+    uint16_t d_type;
+    uint16_t d_class;
+    mutable vState d_vstate;
+    mutable bool d_submitted{false}; // whether this entry has been queued for refetch
+    bool d_tcp; // whether this entry was created from a TCP query
+    inline bool operator<(const struct Entry& rhs) const;
+
+    bool isStale(time_t now) const
+    {
+      return d_ttd < now;
+    }
+
+    uint32_t getOrigTTL() const
+    {
+      return d_ttd - d_creation;
+    }
+  };
+
+  struct HashTag
+  {
+  };
+  struct NameTag
+  {
+  };
+  struct SequencedTag
+  {
+  };
+  using packetCache_t = multi_index_container<Entry,
+                                              indexed_by<hashed_non_unique<tag<HashTag>,
+                                                                           composite_key<Entry,
+                                                                                         member<Entry, uint32_t, &Entry::d_tag>,
+                                                                                         member<Entry, uint32_t, &Entry::d_qhash>,
+                                                                                         member<Entry, bool, &Entry::d_tcp>>>,
+                                                         sequenced<tag<SequencedTag>>,
+                                                         ordered_non_unique<tag<NameTag>, member<Entry, DNSName, &Entry::d_name>, CanonDNSNameCompare>>>;
+
+  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;
+      size_t d_shardSize{0};
+      uint64_t d_hits{0};
+      uint64_t d_misses{0};
+      uint64_t d_contended_count{0};
+      uint64_t d_acquired_count{0};
+      void invalidate() {}
+      void preRemoval(const Entry& /* entry */) {}
+    };
+
+    LockGuardedTryHolder<MapCombo::LockedContent> lock()
+    {
+      auto locked = d_content.try_lock();
+      if (!locked.owns_lock()) {
+        locked.lock();
+        ++locked->d_contended_count;
+      }
+      ++locked->d_acquired_count;
+      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;
+
+  static size_t combine(unsigned int tag, uint32_t hash, bool tcp)
+  {
+    size_t ret = 0;
+    boost::hash_combine(ret, tag);
+    boost::hash_combine(ret, hash);
+    boost::hash_combine(ret, tcp);
+    return ret;
+  }
+
+  MapCombo& getMap(unsigned int tag, uint32_t hash, bool tcp)
+  {
+    return d_maps.at(combine(tag, hash, tcp) % d_maps.size());
+  }
+
+  [[nodiscard]] const MapCombo& getMap(unsigned int tag, uint32_t hash, bool tcp) const
+  {
+    return d_maps.at(combine(tag, hash, tcp) % d_maps.size());
+  }
+
+  static bool qrMatch(const packetCache_t::index<HashTag>::type::iterator& iter, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass);
+  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);
+};
deleted file mode 120000 (symlink)
index 00b6d25b91e5a59aa9bfac23cce54df1aec35051..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../recursor_cache.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..fd40434011916e582ac6a125c35c292b922f9717
--- /dev/null
@@ -0,0 +1,837 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <cinttypes>
+
+#include "recursor_cache.hh"
+#include "misc.hh"
+#include <iostream>
+#include "dnsrecords.hh"
+#include "arguments.hh"
+#include "syncres.hh"
+#include "namespaces.hh"
+#include "cachecleaner.hh"
+#include "rec-taskqueue.hh"
+
+/*
+ * SERVE-STALE: the general approach
+ *
+ * The general switch to enable serve-stale is s_maxServedStaleExtensions. If this value is zero, no
+ * serve-stale is done. If it is positive, it determines how many times the serve-stale status of a
+ * record can be extended.
+ *
+ * Each record in the cache has a field d_servedStale. If this value is zero, no special handling is
+ * done. If it is positive, the record is being served stale. The value determines how many times
+ * the serve-stale status was extended. Each time an extension happens, the value is incremented and
+ * a task to see if the record resolves will be pushed. When the served-stale status is extended,
+ * the TTD of a record is also changed so the record will be considered not-expired by the get()
+ * function. The TTD will be s_serveStaleExtensionPeriod in the future, unless the original TTL was
+ * smaller than that. If d_servedStale reaches s_maxServedStaleExtensions the serve-stale status
+ * will no longer be extended and the record will be considered really expired.
+ *
+ * With s_serveStaleExtensionPeriod of 30 seconds, setting s_maxServedStaleExtensions to 1440 will
+ * cause a record to be served stale a maximum of 30s * 1440 = 12 hours. If the original TTL is
+ * smaller than 30, this period will be shorter. If there was a long time between serve-stale
+ * extensions, the value of d_servedStale will be incremented by more than one to account for the
+ * longer period.
+ *
+ * If serve-stale is enabled, the resolving process first will try to resolve a record in the
+ * ordinary way, with the difference that a timeout will not lead to an ImmediateServFailException
+ * being passed to the caller, but the resolving will be tried again with a flag to allow marking
+ * records as served-stale. If the second time around a timeout happens, an
+ * ImmediateServFailException *will* be passed to the caller.
+ *
+ * When serving stale, records are only wiped from the cache if they are older than
+ * s_maxServedStaleExtensions * s_serveStaleExtensionPeriod. See isStale(). This is to have a good
+ * chance of records being available for marking stale if a name server has an issue.
+ *
+ * The tasks to see if nameservers are reachable again do a resolve in refresh mode, considering
+ * served-stale records as expired. When a record resolves again, the d_servedStale field will be
+ * reset.
+ */
+
+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)
+{
+}
+
+size_t MemRecursorCache::size() const
+{
+  size_t count = 0;
+  for (const auto& shard : d_maps) {
+    count += shard.getEntriesCount();
+  }
+  return count;
+}
+
+pair<uint64_t, uint64_t> MemRecursorCache::stats()
+{
+  uint64_t contended = 0;
+  uint64_t acquired = 0;
+  for (auto& shard : d_maps) {
+    auto lockedShard = shard.lock();
+    contended += lockedShard->d_contended_count;
+    acquired += lockedShard->d_acquired_count;
+  }
+  return {contended, acquired};
+}
+
+size_t MemRecursorCache::ecsIndexSize()
+{
+  // XXX!
+  size_t count = 0;
+  for (auto& shard : d_maps) {
+    auto lockedShard = shard.lock();
+    count += lockedShard->d_ecsIndex.size();
+  }
+  return count;
+}
+
+// this function is too slow to poll!
+size_t MemRecursorCache::bytes()
+{
+  size_t ret = 0;
+  for (auto& shard : d_maps) {
+    auto lockedShard = shard.lock();
+    for (const auto& entry : lockedShard->d_map) {
+      ret += sizeof(struct CacheEntry);
+      ret += entry.d_qname.toString().length();
+      for (const auto& record : entry.d_records) {
+        ret += sizeof(record); // XXX WRONG we don't know the stored size!
+      }
+    }
+  }
+  return ret;
+}
+
+static void updateDNSSECValidationStateFromCache(boost::optional<vState>& state, const vState stateUpdate)
+{
+  // if there was no state it's easy */
+  if (state == boost::none) {
+    state = stateUpdate;
+    return;
+  }
+
+  if (stateUpdate == vState::TA) {
+    state = vState::Secure;
+  }
+  else if (stateUpdate == vState::NTA) {
+    state = vState::Insecure;
+  }
+  else if (vStateIsBogus(stateUpdate) || stateUpdate == vState::Indeterminate) {
+    state = stateUpdate;
+  }
+  else if (stateUpdate == vState::Insecure || 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(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 (!entry->d_netmask.empty() || entry->d_rtag) {
+    ptrAssign(variable, true);
+  }
+
+  if (res != nullptr) {
+    res->reserve(res->size() + entry->d_records.size());
+
+    for (const auto& record : entry->d_records) {
+      DNSRecord result;
+      result.d_name = qname;
+      result.d_type = entry->d_qtype;
+      result.d_class = QClass::IN;
+      result.setContent(record);
+      // coverity[store_truncates_time_t]
+      result.d_ttl = static_cast<uint32_t>(entry->d_ttd);
+      result.d_place = DNSResourceRecord::ANSWER;
+      res->push_back(std::move(result));
+    }
+  }
+
+  if (signatures != nullptr) {
+    signatures->insert(signatures->end(), entry->d_signatures.begin(), entry->d_signatures.end());
+  }
+
+  if (authorityRecs != nullptr) {
+    authorityRecs->insert(authorityRecs->end(), entry->d_authorityRecs.begin(), entry->d_authorityRecs.end());
+  }
+
+  updateDNSSECValidationStateFromCache(state, entry->d_state);
+
+  if (wasAuth != nullptr) {
+    *wasAuth = *wasAuth && entry->d_auth;
+  }
+  ptrAssign(fromAuthZone, entry->d_authZone);
+  ptrAssign(fromAuthIP, entry->d_from);
+
+  moveCacheItemToBack<SequencedTag>(content.d_map, entry);
+
+  return ttd;
+}
+
+static void pushRefreshTask(const DNSName& qname, QType qtype, time_t deadline, const Netmask& netmask)
+{
+  if (qtype == QType::ADDR) {
+    pushAlmostExpiredTask(qname, QType::A, deadline, netmask);
+    pushAlmostExpiredTask(qname, QType::AAAA, deadline, netmask);
+  }
+  else {
+    pushAlmostExpiredTask(qname, qtype, deadline, netmask);
+  }
+}
+
+void MemRecursorCache::updateStaleEntry(time_t now, MemRecursorCache::OrderedTagIterator_t& entry)
+{
+  // We need to take care a infrequently access stale item cannot be extended past
+  // s_maxServedStaleExtension * s_serveStaleExtensionPeriod
+  // We look how old the entry is, and increase d_servedStale accordingly, taking care not to overflow
+  const time_t howlong = std::max(static_cast<time_t>(1), now - entry->d_ttd);
+  const uint32_t extension = std::max(1U, std::min(entry->d_orig_ttl, s_serveStaleExtensionPeriod));
+  entry->d_servedStale = std::min(entry->d_servedStale + 1 + howlong / extension, static_cast<time_t>(s_maxServedStaleExtensions));
+  entry->d_ttd = now + extension;
+
+  pushRefreshTask(entry->d_qname, entry->d_qtype, entry->d_ttd, entry->d_netmask);
+}
+
+// If we are serving this record stale (or *should*) and the ttd has
+// passed increase ttd to the future and remember that we did. Also
+// push a refresh task.
+void MemRecursorCache::handleServeStaleBookkeeping(time_t now, bool serveStale, MemRecursorCache::OrderedTagIterator_t& entry)
+{
+  if ((serveStale || entry->d_servedStale > 0) && entry->d_ttd <= now && entry->d_servedStale < s_maxServedStaleExtensions) {
+    updateStaleEntry(now, entry);
+  }
+}
+
+MemRecursorCache::cache_t::const_iterator MemRecursorCache::getEntryUsingECSIndex(MapCombo::LockedContent& map, time_t now, const DNSName& qname, const QType qtype, bool requireAuth, const ComboAddress& who, bool serveStale)
+{
+  // MUTEX SHOULD BE ACQUIRED (as indicated by the reference to the content which is protected by a lock)
+  auto ecsIndexKey = std::tie(qname, qtype);
+  auto ecsIndex = map.d_ecsIndex.find(ecsIndexKey);
+  if (ecsIndex != map.d_ecsIndex.end() && !ecsIndex->isEmpty()) {
+    /* we have netmask-specific entries, let's see if we match one */
+    while (true) {
+      const Netmask best = ecsIndex->lookupBestMatch(who);
+      if (best.empty()) {
+        /* we have nothing more specific for you */
+        break;
+      }
+      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 */
+        ecsIndex->removeNetmask(best);
+        if (ecsIndex->isEmpty()) {
+          map.d_ecsIndex.erase(ecsIndex);
+          break;
+        }
+        continue;
+      }
+      handleServeStaleBookkeeping(now, serveStale, entry);
+
+      if (entry->d_ttd > now) {
+        if (!requireAuth || entry->d_auth) {
+          return entry;
+        }
+        /* we need auth data and the best match is not authoritative */
+        return map.d_map.end();
+      }
+      /* 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::tuple(qname, qtype, boost::none, Netmask());
+  auto entry = map.d_map.find(key);
+  if (entry != map.d_map.end()) {
+    handleServeStaleBookkeeping(now, serveStale, entry);
+    if (entry->d_ttd > now) {
+      if (!requireAuth || entry->d_auth) {
+        return entry;
+      }
+    }
+    else {
+      moveCacheItemToFront<SequencedTag>(map.d_map, entry);
+    }
+  }
+
+  /* nothing for you, sorry */
+  return map.d_map.end();
+}
+
+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) {
+    map.d_cachedqname = qname;
+    map.d_cachedrtag = rtag;
+    const auto& idx = map.d_map.get<NameAndRTagOnlyHashedTag>();
+    map.d_cachecache = idx.equal_range(std::tie(qname, rtag));
+    map.d_cachecachevalid = true;
+  }
+  return map.d_cachecache;
+}
+
+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) {
+    return false;
+  }
+
+  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;
+}
+
+// Fake a cache miss if more than refreshTTLPerc of the original TTL has passed
+time_t MemRecursorCache::fakeTTD(MemRecursorCache::OrderedTagIterator_t& entry, const DNSName& qname, QType qtype, time_t ret, time_t now, uint32_t origTTL, bool refresh)
+{
+  time_t ttl = ret - now;
+  // If we are checking an entry being served stale in refresh mode,
+  // we always consider it stale so a real refresh attempt will be
+  // kicked by SyncRes
+  if (refresh && entry->d_servedStale > 0) {
+    return -1;
+  }
+  if (ttl > 0 && SyncRes::s_refresh_ttlperc > 0) {
+    const uint32_t deadline = origTTL * SyncRes::s_refresh_ttlperc / 100;
+    // coverity[store_truncates_time_t]
+    const bool almostExpired = static_cast<uint32_t>(ttl) <= deadline;
+    if (almostExpired && qname != g_rootdnsname) {
+      if (refresh) {
+        return -1;
+      }
+      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 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) != 0;
+  bool refresh = (flags & Refresh) != 0;
+  bool serveStale = (flags & ServeStale) != 0;
+
+  boost::optional<vState> cachedState{boost::none};
+  uint32_t origTTL = 0;
+
+  if (res != nullptr) {
+    res->clear();
+  }
+
+  // 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();
+
+  /* If we don't have any netmask-specific entries at all, let's just skip this
+     to be able to use the nice d_cachecache hack. */
+  if (qtype != QType::ANY && !lockedShard->d_ecsIndex.empty() && !routingTag) {
+    if (qtype == QType::ADDR) {
+      time_t ret = -1;
+
+      auto entryA = getEntryUsingECSIndex(*lockedShard, now, qname, QType::A, requireAuth, who, serveStale);
+      if (entryA != lockedShard->d_map.end()) {
+        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(now, *lockedShard, entryAAAA, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
+        if (ret > 0) {
+          ret = std::min(ret, ttdAAAA);
+        }
+        else {
+          ret = ttdAAAA;
+        }
+      }
+
+      if (cachedState && ret > 0) {
+        ptrAssign(state, *cachedState);
+      }
+
+      return ret > 0 ? (ret - now) : ret;
+    }
+    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 fakeTTD(entry, qname, qtype, ret, now, origTTL, refresh);
+    }
+    return -1;
+  }
+
+  if (routingTag) {
+    auto entries = getEntries(*lockedShard, qname, qtype, routingTag);
+    bool found = false;
+    time_t ttd{};
+
+    if (entries.first != entries.second) {
+      OrderedTagIterator_t firstIndexIterator;
+      for (auto i = entries.first; i != entries.second; ++i) {
+        firstIndexIterator = lockedShard->d_map.project<OrderedTag>(i);
+
+        // When serving stale, we consider expired records
+        if (!i->isEntryUsable(now, serveStale)) {
+          moveCacheItemToFront<SequencedTag>(lockedShard->d_map, firstIndexIterator);
+          continue;
+        }
+
+        if (!entryMatches(firstIndexIterator, qtype, requireAuth, who)) {
+          continue;
+        }
+        found = true;
+
+        handleServeStaleBookkeeping(now, serveStale, firstIndexIterator);
+
+        ttd = handleHit(now, *lockedShard, firstIndexIterator, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
+
+        if (qtype != QType::ANY && qtype != QType::ADDR) { // normally if we have a hit, we are done
+          break;
+        }
+      }
+      if (found) {
+        if (cachedState && ttd > now) {
+          ptrAssign(state, *cachedState);
+        }
+        return fakeTTD(firstIndexIterator, qname, qtype, ttd, now, origTTL, refresh);
+      }
+      return -1;
+    }
+  }
+  // Try (again) without tag
+  auto entries = getEntries(*lockedShard, qname, qtype, boost::none);
+
+  if (entries.first != entries.second) {
+    OrderedTagIterator_t firstIndexIterator;
+    bool found = false;
+    time_t ttd{};
+
+    for (auto i = entries.first; i != entries.second; ++i) {
+      firstIndexIterator = lockedShard->d_map.project<OrderedTag>(i);
+
+      // When serving stale, we consider expired records
+      if (!i->isEntryUsable(now, serveStale)) {
+        moveCacheItemToFront<SequencedTag>(lockedShard->d_map, firstIndexIterator);
+        continue;
+      }
+
+      if (!entryMatches(firstIndexIterator, qtype, requireAuth, who)) {
+        continue;
+      }
+      found = true;
+
+      handleServeStaleBookkeeping(now, serveStale, firstIndexIterator);
+
+      ttd = handleHit(now, *lockedShard, firstIndexIterator, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
+
+      if (qtype != QType::ANY && qtype != QType::ADDR) { // normally if we have a hit, we are done
+        break;
+      }
+    }
+    if (found) {
+      if (cachedState && ttd > now) {
+        ptrAssign(state, *cachedState);
+      }
+      return fakeTTD(firstIndexIterator, qname, qtype, ttd, now, origTTL, refresh);
+    }
+  }
+  return -1;
+}
+
+bool MemRecursorCache::CacheEntry::shouldReplace(time_t now, bool auth, vState state, bool refresh)
+{
+  if (!auth && d_auth) { // unauth data came in, we have some auth data, but is it fresh?
+    // an auth entry that is going to expire while we are resolving can hurt, as it prevents infra
+    // records (which might be unauth) to be updated. So apply a safety margin.
+    const time_t margin = 5;
+    if (d_ttd - margin > now) { // we still have valid data, ignore unauth data
+      return false;
+    }
+    d_auth = false; // new data won't be auth
+  }
+
+  if (auth) {
+    /* we don't want to keep a non-auth entry while we have an auth one */
+    if (vStateIsBogus(state) && (!vStateIsBogus(d_state) && d_state != vState::Indeterminate) && d_ttd > now) {
+      /* the new entry is Bogus, the existing one is not and is still valid, let's keep the existing one */
+      return false;
+    }
+    // Always allow upgrade unauth data to auth
+    if (!d_auth) {
+      return true;
+    }
+  }
+
+  if (SyncRes::s_locked_ttlperc > 0) {
+    // Override locking if existing data is stale or new data is Secure or refreshing
+    if (d_ttd <= now || state == vState::Secure || refresh) {
+      return true;
+    }
+    const uint32_t percentage = 100 - SyncRes::s_locked_ttlperc;
+    const time_t ttl = d_ttd - now;
+    const uint32_t lockline = d_orig_ttl * percentage / 100;
+    // We know ttl is > 0 as d_ttd > now
+    // coverity[store_truncates_time_t]
+    const bool locked = static_cast<uint32_t>(ttl) > lockline;
+    if (locked) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+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();
+
+  lockedShard->d_cachecachevalid = false;
+  if (ednsmask) {
+    ednsmask = ednsmask->getNormalized();
+  }
+
+  // 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::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.incEntriesCount();
+    isNew = true;
+  }
+
+  /* if we are inserting a new entry or updating an expired one (in which case the
+     ECS index might have been removed but the entry still exists because it has not
+     been garbage collected yet) we might need to update the ECS index.
+     Otherwise it should already be indexed and we don't need to update it.
+  */
+  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::tuple(qname, qtype.getCode());
+      auto ecsIndex = lockedShard->d_ecsIndex.find(ecsIndexKey);
+      if (ecsIndex == lockedShard->d_ecsIndex.end()) {
+        ecsIndex = lockedShard->d_ecsIndex.insert(ECSIndexEntry(qname, qtype.getCode())).first;
+      }
+      ecsIndex->addMask(*ednsmask);
+    }
+  }
+
+  time_t maxTTD = std::numeric_limits<time_t>::max();
+  CacheEntry cacheEntry = *stored; // this is a COPY
+  cacheEntry.d_qtype = qtype.getCode();
+
+  if (!isNew && !cacheEntry.shouldReplace(now, auth, state, refresh)) {
+    return;
+  }
+
+  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 (cacheEntry.d_auth && auth && qtype == QType::NS && !isNew && !qname.isRoot()) {
+    maxTTD = cacheEntry.d_ttd;
+  }
+
+  if (auth) {
+    cacheEntry.d_auth = true;
+  }
+
+  cacheEntry.d_signatures = signatures;
+  cacheEntry.d_authorityRecs = authorityRecs;
+  cacheEntry.d_records.clear();
+  cacheEntry.d_records.reserve(content.size());
+  cacheEntry.d_authZone = authZone;
+  if (from) {
+    cacheEntry.d_from = *from;
+  }
+  else {
+    cacheEntry.d_from = ComboAddress();
+  }
+
+  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. */
+    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);
+  }
+  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)
+{
+  size_t count = 0;
+
+  if (!sub) {
+    auto& shard = getMap(name);
+    auto lockedShard = shard.lock();
+    lockedShard->d_cachecachevalid = false;
+    auto& idx = lockedShard->d_map.get<OrderedTag>();
+    auto range = idx.equal_range(name);
+    auto iter = range.first;
+    while (iter != range.second) {
+      if (iter->d_qtype == qtype || qtype == 0xffff) {
+        iter = idx.erase(iter);
+        count++;
+        shard.decEntriesCount();
+      }
+      else {
+        ++iter;
+      }
+    }
+
+    if (qtype == 0xffff) {
+      auto& ecsIdx = lockedShard->d_ecsIndex.get<OrderedTag>();
+      auto ecsIndexRange = ecsIdx.equal_range(name);
+      ecsIdx.erase(ecsIndexRange.first, ecsIndexRange.second);
+    }
+    else {
+      auto& ecsIdx = lockedShard->d_ecsIndex.get<HashedTag>();
+      auto ecsIndexRange = ecsIdx.equal_range(std::tie(name, qtype));
+      ecsIdx.erase(ecsIndexRange.first, ecsIndexRange.second);
+    }
+  }
+  else {
+    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)) {
+          break;
+        }
+        if (i->d_qtype == qtype || qtype == 0xffff) {
+          count++;
+          i = idx.erase(i);
+          content.decEntriesCount();
+        }
+        else {
+          ++i;
+        }
+      }
+      auto& ecsIdx = map->d_ecsIndex.get<OrderedTag>();
+      for (auto i = ecsIdx.lower_bound(name); i != ecsIdx.end();) {
+        if (!i->d_qname.isPartOf(name)) {
+          break;
+        }
+        if (i->d_qtype == qtype || qtype == 0xffff) {
+          i = ecsIdx.erase(i);
+        }
+        else {
+          ++i;
+        }
+      }
+    }
+  }
+  return count;
+}
+
+// Name should be doLimitTime or so
+bool MemRecursorCache::doAgeCache(time_t now, const DNSName& name, const QType qtype, uint32_t newTTL)
+{
+  auto& shard = getMap(name);
+  auto lockedShard = shard.lock();
+  cache_t::iterator iter = lockedShard->d_map.find(std::tie(name, qtype));
+  if (iter == lockedShard->d_map.end()) {
+    return false;
+  }
+
+  CacheEntry cacheEntry = *iter;
+  if (cacheEntry.d_ttd < now) {
+    return false; // would be dead anyhow
+  }
+
+  // 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 (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 qtype, const ComboAddress& who, const OptTag& routingTag, bool requireAuth, vState newState, boost::optional<time_t> capTTD)
+{
+  if (qtype == QType::ANY) {
+    throw std::runtime_error("Trying to update the DNSSEC validation status of all (via ANY) records for " + qname.toLogString());
+  }
+  if (qtype == QType::ADDR) {
+    throw std::runtime_error("Trying to update the DNSSEC validation status of several (via ADDR) records for " + qname.toLogString());
+  }
+
+  auto& content = getMap(qname);
+  auto map = content.lock();
+
+  bool updated = false;
+  if (!map->d_ecsIndex.empty() && !routingTag) {
+    auto entry = getEntryUsingECSIndex(*map, now, qname, qtype, requireAuth, who, false); // XXX serveStale?
+    if (entry == map->d_map.end()) {
+      return false;
+    }
+
+    entry->d_state = newState;
+    if (capTTD) {
+      entry->d_ttd = std::min(entry->d_ttd, *capTTD);
+    }
+    return true;
+  }
+
+  auto entries = getEntries(*map, qname, qtype, routingTag);
+
+  for (auto i = entries.first; i != entries.second; ++i) {
+    auto firstIndexIterator = map->d_map.project<OrderedTag>(i);
+
+    if (!entryMatches(firstIndexIterator, qtype, requireAuth, who)) {
+      continue;
+    }
+
+    i->d_state = newState;
+    if (capTTD) {
+      i->d_ttd = std::min(i->d_ttd, *capTTD);
+    }
+    updated = true;
+
+    break;
+  }
+
+  return updated;
+}
+
+uint64_t MemRecursorCache::doDump(int fileDesc, size_t maxCacheEntries)
+{
+  int newfd = dup(fileDesc);
+  if (newfd == -1) {
+    return 0;
+  }
+  auto filePtr = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
+  if (!filePtr) { // dup probably failed
+    close(newfd);
+    return 0;
+  }
+
+  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();
+  size_t max = 0;
+  for (auto& shard : d_maps) {
+    auto lockedShard = shard.lock();
+    const auto shardSize = lockedShard->d_map.size();
+    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& recordSet : sidx) {
+      for (const auto& record : recordSet.d_records) {
+        count++;
+        try {
+          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(filePtr.get(), "; error printing '%s'\n", recordSet.d_qname.empty() ? "EMPTY" : recordSet.d_qname.toString().c_str());
+        }
+      }
+      for (const auto& sig : recordSet.d_signatures) {
+        count++;
+        try {
+          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(filePtr.get(), "; error printing '%s'\n", recordSet.d_qname.empty() ? "EMPTY" : recordSet.d_qname.toString().c_str());
+        }
+      }
+    }
+  }
+  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(time_t now, size_t keep)
+{
+  size_t cacheSize = size();
+  pruneMutexCollectionsVector<SequencedTag>(now, d_maps, keep, cacheSize);
+}
+
+namespace boost
+{
+size_t hash_value(const MemRecursorCache::OptTag& rtag)
+{
+  return rtag ? hash_value(rtag.get()) : 0xcafebaaf;
+}
+}
deleted file mode 120000 (symlink)
index af2c45fb88077de7e32e178cfb7ae9f154a43fa1..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../recursor_cache.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..360e97958c681869b38a97b0f02ed3720ddab3d9
--- /dev/null
@@ -0,0 +1,344 @@
+/*
+ * 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 <set>
+#include "dns.hh"
+#include "qtype.hh"
+#include "misc.hh"
+#include "dnsname.hh"
+#include <iostream>
+#include "dnsrecords.hh"
+#include <boost/utility.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/key_extractors.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+#include <boost/version.hpp>
+#include "iputils.hh"
+#include "lock.hh"
+#include "stat_t.hh"
+#include "validate.hh"
+#undef max
+
+#include "namespaces.hh"
+using namespace ::boost::multi_index;
+
+class MemRecursorCache : public boost::noncopyable //  : public RecursorCache
+{
+public:
+  MemRecursorCache(size_t mapsCount = 1024);
+
+  // The number of times a stale cache entry is extended
+  static uint16_t s_maxServedStaleExtensions;
+  // The time a stale cache entry is extended
+  static constexpr uint32_t s_serveStaleExtensionPeriod = 30;
+
+  [[nodiscard]] size_t size() const;
+  [[nodiscard]] size_t bytes();
+  [[nodiscard]] pair<uint64_t, uint64_t> stats();
+  [[nodiscard]] size_t ecsIndexSize();
+
+  using OptTag = boost::optional<std::string>;
+
+  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;
+
+  [[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, 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(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 qtype, const ComboAddress& who, const OptTag& routingTag, bool requireAuth, vState newState, boost::optional<time_t> capTTD);
+
+  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_qtype(std::get<1>(key)), d_auth(auth)
+    {
+    }
+
+    using records_t = vector<std::shared_ptr<const DNSRecordContent>>;
+
+    bool isStale(time_t now) const
+    {
+      // We like to keep things in cache when we (potentially) should serve stale
+      if (s_maxServedStaleExtensions > 0) {
+        return d_ttd + static_cast<time_t>(s_maxServedStaleExtensions) * std::min(s_serveStaleExtensionPeriod, d_orig_ttl) < now;
+      }
+      return d_ttd < now;
+    }
+
+    bool isEntryUsable(time_t now, bool serveStale) const
+    {
+      // When serving stale, we consider expired records
+      return d_ttd > now || serveStale || d_servedStale != 0;
+    }
+
+    bool shouldReplace(time_t now, bool auth, vState state, bool refresh);
+
+    records_t d_records;
+    std::vector<std::shared_ptr<const RRSIGRecordContent>> d_signatures;
+    std::vector<std::shared_ptr<DNSRecord>> d_authorityRecs;
+    DNSName d_qname;
+    DNSName d_authZone;
+    ComboAddress d_from;
+    Netmask d_netmask;
+    OptTag d_rtag;
+    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{false}; // whether this entry has been queued for refetch
+  };
+
+  /* The ECS Index (d_ecsIndex) keeps track of whether there is any ECS-specific
+     entry for a given (qname,qtype) entry in the cache (d_map), and if so
+     provides a NetmaskTree of those ECS entries.
+     This allows figuring out quickly if we should look for an entry
+     specific to the requestor IP, and if so which entry is the most
+     specific one.
+     Keeping the entries in the regular cache is currently necessary
+     because of the way we manage expired entries (moving them to the
+     front of the expunge queue to be deleted at a regular interval).
+  */
+  class ECSIndexEntry
+  {
+  public:
+    ECSIndexEntry(DNSName qname, QType qtype) :
+      d_qname(std::move(qname)), d_qtype(qtype)
+    {
+    }
+
+    [[nodiscard]] Netmask lookupBestMatch(const ComboAddress& addr) const
+    {
+      const auto* best = d_nmt.lookup(addr);
+      if (best != nullptr) {
+        return best->first;
+      }
+
+      return {};
+    }
+
+    void addMask(const Netmask& netmask) const
+    {
+      d_nmt.insert(netmask).second = true;
+    }
+
+    void removeNetmask(const Netmask& netmask) const
+    {
+      d_nmt.erase(netmask);
+    }
+
+    [[nodiscard]] bool isEmpty() const
+    {
+      return d_nmt.empty();
+    }
+
+    mutable NetmaskTree<bool> d_nmt;
+    DNSName d_qname;
+    QType d_qtype;
+  };
+
+  struct HashedTag
+  {
+  };
+  struct SequencedTag
+  {
+  };
+  struct NameAndRTagOnlyHashedTag
+  {
+  };
+  struct OrderedTag
+  {
+  };
+
+  using cache_t = multi_index_container<
+    CacheEntry,
+    indexed_by<
+      ordered_unique<tag<OrderedTag>,
+                     composite_key<
+                       CacheEntry,
+                       member<CacheEntry, DNSName, &CacheEntry::d_qname>,
+                       member<CacheEntry, QType, &CacheEntry::d_qtype>,
+                       member<CacheEntry, OptTag, &CacheEntry::d_rtag>,
+                       member<CacheEntry, Netmask, &CacheEntry::d_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>>>>>;
+
+  using OrderedTagIterator_t = MemRecursorCache::cache_t::index<MemRecursorCache::OrderedTag>::type::iterator;
+  using NameAndRTagOnlyHashedTagIterator_t = MemRecursorCache::cache_t::index<MemRecursorCache::NameAndRTagOnlyHashedTag>::type::iterator;
+
+  using ecsIndex_t = multi_index_container<
+    ECSIndexEntry,
+    indexed_by<
+      hashed_unique<tag<HashedTag>,
+                    composite_key<
+                      ECSIndexEntry,
+                      member<ECSIndexEntry, DNSName, &ECSIndexEntry::d_qname>,
+                      member<ECSIndexEntry, QType, &ECSIndexEntry::d_qtype>>>,
+      ordered_unique<tag<OrderedTag>,
+                     composite_key<
+                       ECSIndexEntry,
+                       member<ECSIndexEntry, DNSName, &ECSIndexEntry::d_qname>,
+                       member<ECSIndexEntry, QType, &ECSIndexEntry::d_qtype>>,
+                     composite_key_compare<CanonDNSNameCompare, std::less<>>>>>;
+
+  using Entries = std::pair<NameAndRTagOnlyHashedTagIterator_t, NameAndRTagOnlyHashedTagIterator_t>;
+
+  struct 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;
+      ecsIndex_t d_ecsIndex;
+      DNSName d_cachedqname;
+      OptTag d_cachedrtag;
+      Entries d_cachecache;
+      uint64_t d_contended_count{0};
+      uint64_t d_acquired_count{0};
+      bool d_cachecachevalid{false};
+
+      void invalidate()
+      {
+        d_cachecachevalid = false;
+      }
+
+      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()
+    {
+      auto locked = d_content.try_lock();
+      if (!locked.owns_lock()) {
+        locked.lock();
+        ++locked->d_contended_count;
+      }
+      ++locked->d_acquired_count;
+      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;
+  MapCombo& getMap(const DNSName& qname)
+  {
+    return d_maps.at(qname.hash() % d_maps.size());
+  }
+
+  static time_t fakeTTD(OrderedTagIterator_t& entry, const DNSName& qname, QType qtype, time_t ret, time_t now, uint32_t origTTL, bool refresh);
+
+  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);
+
+  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
+{
+size_t hash_value(const MemRecursorCache::OptTag& rtag);
+}
diff --git a/pdns/recursordist/reczones-helpers.cc b/pdns/recursordist/reczones-helpers.cc
new file mode 100644 (file)
index 0000000..2326054
--- /dev/null
@@ -0,0 +1,317 @@
+/*
+ * 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 "arguments.hh"
+#include "syncres.hh"
+#include "reczones-helpers.hh"
+#include "root-addresses.hh"
+#include "zoneparser-tng.hh"
+
+static void putIntoCache(time_t now, QType qtype, vState state, const ComboAddress& from, const set<DNSName>& seen, const std::multimap<DNSName, DNSRecord>& allRecords)
+{
+  for (const auto& name : seen) {
+    auto records = allRecords.equal_range(name);
+    vector<DNSRecord> aset;
+    for (auto elem = records.first; elem != records.second; ++elem) {
+      aset.emplace_back(elem->second);
+    }
+    // Put non-default root hints into cache as authoritative.  As argued below in
+    // putDefaultHintsIntoCache, this is actually wrong, but people might depend on it by having
+    // root-hints that refer to servers that aren't actually capable or willing to serve root data.
+    g_recCache->replace(now, name, qtype, aset, {}, {}, true, g_rootdnsname, boost::none, boost::none, state, from);
+  }
+}
+
+static void parseHintFile(time_t now, const std::string& hintfile, set<DNSName>& seenA, set<DNSName>& seenAAAA, set<DNSName>& seenNS, std::multimap<DNSName, DNSRecord>& aRecords, std::multimap<DNSName, DNSRecord>& aaaaRecords, vector<DNSRecord>& nsvec)
+{
+  ZoneParserTNG zpt(hintfile);
+  zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
+  zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
+  DNSResourceRecord rrecord;
+
+  while (zpt.get(rrecord)) {
+    rrecord.ttl += now;
+    switch (rrecord.qtype) {
+    case QType::A:
+      seenA.insert(rrecord.qname);
+      aRecords.emplace(rrecord.qname, DNSRecord(rrecord));
+      break;
+    case QType::AAAA:
+      seenAAAA.insert(rrecord.qname);
+      aaaaRecords.emplace(rrecord.qname, DNSRecord(rrecord));
+      break;
+    case QType::NS:
+      seenNS.emplace(rrecord.content);
+      rrecord.content = toLower(rrecord.content);
+      nsvec.emplace_back(rrecord);
+      break;
+    }
+  }
+}
+
+static bool determineReachable(const set<DNSName>& names, const set<DNSName>& nameservers)
+{
+  bool reachable = false;
+  for (auto const& record : names) {
+    if (nameservers.count(record) != 0) {
+      reachable = true;
+      break;
+    }
+  }
+  return reachable;
+}
+
+bool readHintsIntoCache(time_t now, const std::string& hintfile, std::vector<DNSRecord>& nsvec)
+{
+  const ComboAddress from("255.255.255.255");
+  set<DNSName> seenNS;
+  set<DNSName> seenA;
+  set<DNSName> seenAAAA;
+
+  std::multimap<DNSName, DNSRecord> aRecords;
+  std::multimap<DNSName, DNSRecord> aaaaRecords;
+
+  parseHintFile(now, hintfile, seenA, seenAAAA, seenNS, aRecords, aaaaRecords, nsvec);
+
+  putIntoCache(now, QType::A, vState::Insecure, from, seenA, aRecords);
+  putIntoCache(now, QType::AAAA, vState::Insecure, from, seenAAAA, aaaaRecords);
+
+  bool reachableA = determineReachable(seenA, seenNS);
+  bool reachableAAAA = determineReachable(seenAAAA, seenNS);
+
+  auto log = g_slog->withName("config");
+  if (SyncRes::s_doIPv4 && !SyncRes::s_doIPv6 && !reachableA) {
+    SLOG(g_log << Logger::Error << "Running IPv4 only but no IPv4 root hints" << endl,
+         log->info(Logr::Error, "Running IPv4 only but no IPv4 root hints"));
+    return false;
+  }
+  if (!SyncRes::s_doIPv4 && SyncRes::s_doIPv6 && !reachableAAAA) {
+    SLOG(g_log << Logger::Error << "Running IPv6 only but no IPv6 root hints" << endl,
+         log->info(Logr::Error, "Running IPv6 only but no IPv6 root hints"));
+    return false;
+  }
+  if (SyncRes::s_doIPv4 && SyncRes::s_doIPv6 && !reachableA && !reachableAAAA) {
+    SLOG(g_log << Logger::Error << "No valid root hints" << endl,
+         log->info(Logr::Error, "No valid root hints"));
+    return false;
+  }
+  return true;
+}
+
+void putDefaultHintsIntoCache(time_t now, std::vector<DNSRecord>& nsvec)
+{
+  const ComboAddress from("255.255.255.255");
+
+  DNSRecord arr;
+  DNSRecord aaaarr;
+  DNSRecord nsrr;
+
+  nsrr.d_name = g_rootdnsname;
+  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.";
+
+  static_assert(rootIps4.size() == rootIps6.size());
+
+  for (size_t letter = 0; letter < rootIps4.size(); ++letter) {
+    templ.at(0) = static_cast<char>(letter + 'a');
+    aaaarr.d_name = arr.d_name = DNSName(templ);
+    nsrr.setContent(std::make_shared<NSRecordContent>(DNSName(templ)));
+    nsvec.push_back(nsrr);
+
+    if (!rootIps4.at(letter).empty()) {
+      arr.setContent(std::make_shared<ARecordContent>(ComboAddress(rootIps4.at(letter))));
+      /*
+       * Originally the hint records were inserted with the auth flag set, with the consequence that
+       * data from AUTHORITY and ADDITIONAL sections (as seen in a . NS response) were not used. This
+       * (together with the long ttl) caused outdated hint to be kept in cache. So insert as non-auth,
+       * and the extra sections in the . NS refreshing cause the cached records to be updated with
+       * up-to-date information received from a real root server.
+       *
+       * Note that if a user query is done for one of the root-server.net names, it will be inserted
+       * into the cache with the auth bit set. Further NS refreshes will not update that entry. If all
+       * root names are queried at the same time by a user, all root-server.net names will be marked
+       * auth and will expire at the same time. A re-prime is then triggered, as before, when the
+       * records were inserted with the auth bit set and the TTD comes.
+       */
+      g_recCache->replace(now, DNSName(templ), QType::A, {arr}, {}, {}, false, g_rootdnsname, boost::none, boost::none, vState::Insecure, from);
+    }
+    if (!rootIps6.at(letter).empty()) {
+      aaaarr.setContent(std::make_shared<AAAARecordContent>(ComboAddress(rootIps6.at(letter))));
+      g_recCache->replace(now, DNSName(templ), QType::AAAA, {aaaarr}, {}, {}, false, g_rootdnsname, boost::none, boost::none, vState::Insecure, from);
+    }
+  }
+}
+
+template <typename T>
+static SyncRes::AuthDomain makeSOAAndNSNodes(DNSRecord& dr, T content)
+{
+  dr.d_class = 1;
+  dr.d_place = DNSResourceRecord::ANSWER;
+  dr.d_ttl = 86400;
+  dr.d_type = QType::SOA;
+  dr.setContent(DNSRecordContent::make(QType::SOA, 1, "localhost. root 1 604800 86400 2419200 604800"));
+
+  SyncRes::AuthDomain ad;
+  ad.d_rdForward = false;
+  ad.d_records.insert(dr);
+
+  dr.d_type = QType::NS;
+  dr.setContent(std::make_shared<NSRecordContent>(content));
+  ad.d_records.insert(dr);
+
+  return ad;
+}
+
+static void addToDomainMap(SyncRes::domainmap_t& newMap,
+                           SyncRes::AuthDomain ad,
+                           const DNSName& name,
+                           Logr::log_t log,
+                           const bool partial = false,
+                           const bool reverse = false)
+{
+  if (newMap.count(name) != 0) {
+    SLOG(g_log << Logger::Warning << "Will not overwrite zone '" << name << "' already loaded" << endl,
+         log->info(Logr::Warning, "Will not overwrite already loaded zone", "zone",
+                   Logging::Loggable(name)));
+  }
+  else {
+    if (!partial) {
+      const auto direction = reverse ? std::string{"reverse"} : std::string{"forward"};
+      SLOG(g_log << Logger::Warning << "Inserting " << direction << " zone '" << name << "' based on hosts file" << endl,
+           log->info(Logr::Notice, "Inserting " + direction + " zone based on hosts file", "zone", Logging::Loggable(name)));
+    }
+    ad.d_name = name;
+    newMap[ad.d_name] = ad;
+  }
+}
+
+static void makeNameToIPZone(SyncRes::domainmap_t& newMap,
+                             const DNSName& hostname,
+                             const ComboAddress& address)
+{
+  DNSRecord dr;
+  dr.d_name = hostname;
+
+  auto entry = newMap.find(hostname);
+  if (entry == newMap.end()) {
+    auto ad = makeSOAAndNSNodes(dr, "localhost.");
+    ad.d_name = dr.d_name;
+    entry = newMap.insert({dr.d_name, ad}).first;
+  }
+
+  auto recType = address.isIPv6() ? QType::AAAA : QType::A;
+  dr.d_type = recType;
+  dr.d_ttl = 86400;
+  dr.setContent(DNSRecordContent::make(recType, QClass::IN, address.toStringNoInterface()));
+  entry->second.d_records.insert(dr);
+}
+
+static void makeIPToNamesZone(SyncRes::domainmap_t& newMap,
+                              const ComboAddress& address,
+                              const std::string& canonicalHostname,
+                              Logr::log_t log)
+{
+  DNSRecord dr;
+  dr.d_name = DNSName(address.toStringReversed());
+  dr.d_name.appendRawLabel(address.isIPv4() ? "in-addr" : "ip6");
+  dr.d_name.appendRawLabel("arpa");
+
+  SyncRes::AuthDomain ad = makeSOAAndNSNodes(dr, DNSName("localhost."));
+
+  // Add a PTR entry for the primary name for reverse lookups.
+  dr.d_type = QType::PTR;
+  dr.setContent(DNSRecordContent::make(QType::PTR, 1, DNSName(canonicalHostname).toString()));
+  ad.d_records.insert(dr);
+
+  addToDomainMap(newMap, std::move(ad), dr.d_name, log, false, true);
+}
+
+void makePartialIPZone(SyncRes::domainmap_t& newMap,
+                       std::initializer_list<const char*> labels,
+                       Logr::log_t log)
+{
+  DNSRecord dr;
+  for (auto label = std::rbegin(labels); label != std::rend(labels); ++label) {
+    dr.d_name.appendRawLabel(*label);
+  }
+  dr.d_name.appendRawLabel("in-addr");
+  dr.d_name.appendRawLabel("arpa");
+
+  SyncRes::AuthDomain ad = makeSOAAndNSNodes(dr, DNSName("localhost."));
+
+  addToDomainMap(newMap, std::move(ad), dr.d_name, log, true, true);
+}
+
+void addForwardAndReverseLookupEntries(SyncRes::domainmap_t& newMap,
+                                       const std::string& searchSuffix,
+                                       const std::vector<std::string>& parts,
+                                       Logr::log_t log)
+{
+  const ComboAddress address{parts[0]};
+
+  // Go over the hostname and aliases (parts[1], parts[2], etc...) and add entries
+  // for forward lookups.
+  for (auto name = parts.cbegin() + 1; name != parts.cend(); ++name) {
+    if (searchSuffix.empty() || name->find('.') != string::npos) {
+      makeNameToIPZone(newMap, DNSName(*name), address);
+    }
+    else {
+      DNSName canonical = toCanonic(DNSName(searchSuffix), *name);
+      if (canonical != DNSName(*name)) {
+        makeNameToIPZone(newMap, canonical, address);
+      }
+    }
+  }
+
+  // Add entries for the primary name for reverse lookups.
+  if (searchSuffix.empty() || parts[1].find('.') != string::npos) {
+    makeIPToNamesZone(newMap, address, parts[1], log);
+  }
+  else {
+    DNSName canonical = toCanonic(DNSName(searchSuffix), parts[1]);
+    makeIPToNamesZone(newMap, address, canonical.toString(), log);
+  }
+}
+
+bool parseEtcHostsLine(std::vector<std::string>& parts, std::string& line)
+{
+  const string::size_type pos = line.find('#');
+  if (pos != string::npos) {
+    line.resize(pos);
+  }
+  boost::trim(line);
+  if (line.empty()) {
+    return false;
+  }
+  parts.clear();
+  stringtok(parts, line, "\t\r\n ");
+  return parts.size() >= 2;
+}
diff --git a/pdns/recursordist/reczones-helpers.hh b/pdns/recursordist/reczones-helpers.hh
new file mode 100644 (file)
index 0000000..dafeee4
--- /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.
+ */
+
+#pragma once
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string>
+#include <vector>
+#include <memory>
+#include "syncres.hh"
+#include "logger.hh"
+
+bool readHintsIntoCache(time_t now, const std::string& hintfile, std::vector<DNSRecord>& nsvec);
+void putDefaultHintsIntoCache(time_t now, std::vector<DNSRecord>& nsvec);
+
+void makeIPToNamesZone(const std::shared_ptr<SyncRes::domainmap_t>& newMap,
+                       const vector<string>& parts,
+                       Logr::log_t log);
+
+//! A return value `false` means that the line cannot be parsed (e.g. unsupported IPv6).
+bool parseEtcHostsLine(std::vector<std::string>& parts, std::string& line);
+
+void makePartialIPZone(SyncRes::domainmap_t& newMap,
+                       std::initializer_list<const char*> labels,
+                       Logr::log_t log);
+
+void addForwardAndReverseLookupEntries(SyncRes::domainmap_t& newMap,
+                                       const std::string& searchSuffix,
+                                       const std::vector<std::string>& parts,
+                                       Logr::log_t log);
deleted file mode 120000 (symlink)
index 23c67b4d32eac91763a22c698c0f67491cc92a0f..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../reczones.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..fa763cd9ad704821ba47889c532a50be000b8d8b
--- /dev/null
@@ -0,0 +1,550 @@
+/*
+ * 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/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;
+
+bool primeHints(time_t now)
+{
+  const string hintfile = ::arg()["hint-file"];
+  vector<DNSRecord> nsvec;
+  bool ret = true;
+
+  if (hintfile == "no" || hintfile == "no-refresh") {
+    auto log = g_slog->withName("config");
+    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;
+  }
+
+  if (hintfile.empty()) {
+    putDefaultHintsIntoCache(now, nsvec);
+  }
+  else {
+    ret = readHintsIntoCache(now, hintfile, nsvec);
+  }
+
+  g_recCache->doWipeCache(g_rootdnsname, false, QType::NS);
+  g_recCache->replace(now, g_rootdnsname, QType::NS, nsvec, {}, {}, false, g_rootdnsname, boost::none, boost::none, vState::Insecure, ComboAddress("255.255.255.255")); // and stuff in the cache
+  return ret;
+}
+
+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);
+  authDomain.d_servers.clear();
+
+  vector<string> addresses;
+  for (auto& server : servers) {
+    ComboAddress addr = parseIPAndPort(server, 53);
+    authDomain.d_servers.push_back(addr);
+    if (verbose) {
+      addresses.push_back(addr.toStringWithPort());
+    }
+  }
+  if (verbose) {
+    if (!g_slogStructured) {
+      g_log << Logger::Info << "Redirecting queries for zone '" << zone << "' ";
+      if (authDomain.d_rdForward) {
+        g_log << "with recursion ";
+      }
+      g_log << "to: ";
+      bool first = true;
+      for (const auto& address : addresses) {
+        if (!first) {
+          g_log << ", ";
+        }
+        else {
+          first = false;
+        }
+        g_log << address;
+      }
+      g_log << endl;
+    }
+    else {
+      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(std::move(newmap));
+  return nullptr;
+}
+
+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"));
+
+    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");
+    ::arg().preParse(g_argc, g_argv, "auth-zones");
+    ::arg().preParse(g_argc, g_argv, "allow-notify-for");
+    ::arg().preParse(g_argc, g_argv, "allow-notify-for-file");
+    ::arg().preParse(g_argc, g_argv, "export-etc-hosts");
+    ::arg().preParse(g_argc, g_argv, "serve-rfc1918");
+
+    auto [newDomainMap, newNotifySet] = parseZoneConfiguration(yaml);
+
+    // purge both original and new names
+    std::set<DNSName> oldAndNewDomains;
+    for (const auto& entry : *newDomainMap) {
+      oldAndNewDomains.insert(entry.first);
+    }
+
+    if (original) {
+      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([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& entry : oldAndNewDomains) {
+      wipeCaches(entry, true, 0xffff);
+    }
+    return "ok\n";
+  }
+  catch (const std::exception& e) {
+    SLOG(g_log << Logger::Error << "Encountered error reloading zones, keeping original data: " << e.what() << endl,
+         log->error(Logr::Error, e.what(), "Encountered error reloading zones, keeping original data"));
+  }
+  catch (const PDNSException& ae) {
+    SLOG(g_log << Logger::Error << "Encountered error reloading zones, keeping original data: " << ae.reason << endl,
+         log->error(Logr::Error, ae.reason, "Encountered error reloading zones, keeping original data"));
+  }
+  catch (...) {
+    SLOG(g_log << Logger::Error << "Encountered unknown error reloading zones, keeping original data" << endl,
+         log->error(Logr::Error, "Exception", "Encountered error reloading zones, keeping original data"));
+  }
+  return "reloading failed, see log\n";
+}
+
+static void readAuthZoneData(SyncRes::AuthDomain& authDomain, const pair<string, string>& headers, Logr::log_t log)
+{
+  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 + "'");
+    }
+
+    authDomain.d_records.insert(dnsrecord);
+  }
+}
+
+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);
+
+      if (option == 0) {
+        authDomain.d_rdForward = false;
+        readAuthZoneData(authDomain, headers, log);
+      }
+      else {
+        authDomain.d_rdForward = (option == 2);
+        convertServersForAD(headers.first, headers.second, authDomain, ";", log);
+      }
+
+      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();
+
+  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 = std::unique_ptr<FILE, int (*)(FILE*)>(fopen(filename.c_str(), "r"), fclose);
+    if (!filePtr) {
+      int err = errno;
+      throw PDNSException("Error opening forward-zones-file '" + filename + "': " + stringerror(err));
+    }
+
+    string line;
+    int linenum = 0;
+    while (linenum++, stringfgets(filePtr.get(), line)) {
+      SyncRes::AuthDomain authDomain;
+      boost::trim(line);
+      if (line[0] == '#') { // Comment line, skip to the next line
+        continue;
+      }
+      string domain;
+      string instructions;
+      std::tie(domain, instructions) = splitField(line, '=');
+      instructions = splitField(instructions, '#').first; // Remove EOL comments
+      boost::trim(domain);
+      boost::trim(instructions);
+      if (domain.empty()) {
+        if (instructions.empty()) { // empty line
+          continue;
+        }
+        throw PDNSException("Error parsing line " + std::to_string(linenum) + " of " + filename);
+      }
+
+      bool allowNotifyFor = false;
+
+      for (; !domain.empty(); domain.erase(0, 1)) {
+        switch (domain[0]) {
+        case '+':
+          authDomain.d_rdForward = true;
+          continue;
+        case '^':
+          allowNotifyFor = true;
+          continue;
+        }
+        break;
+      }
+
+      if (domain.empty()) {
+        throw PDNSException("Error parsing line " + std::to_string(linenum) + " of " + filename);
+      }
+
+      try {
+        convertServersForAD(domain, instructions, authDomain, ",; ", log, false);
+      }
+      catch (...) {
+        throw PDNSException("Conversion error parsing line " + std::to_string(linenum) + " of " + filename);
+      }
+
+      authDomain.d_name = DNSName(domain);
+      (*newMap)[authDomain.d_name] = authDomain;
+      if (allowNotifyFor) {
+        newSet->insert(authDomain.d_name);
+      }
+    }
+  }
+  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)));
+}
+
+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;
+    }
+
+    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)));
+    }
+  }
+}
+
+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);
+
+  for (int count = 16; count < 32; count++) {
+    makePartialIPZone(*newMap, {"172", std::to_string(count).c_str()}, log);
+  }
+}
+
+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));
+  }
+}
+
+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 = std::unique_ptr<FILE, int (*)(FILE*)>(fopen(filename.c_str(), "r"), fclose);
+    if (!filePtr) {
+      throw PDNSException("Error opening allow-notify-for-file '" + filename + "': " + stringerror());
+    }
+
+    string line;
+    while (stringfgets(filePtr.get(), line)) {
+      boost::trim(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 '" << 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};
+}
deleted file mode 120000 (symlink)
index ad6002f391fe7eed2f8400d5de2b7a5a8dc6f302..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../resolve-context.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..c4b38c3d566627456a62b30bf861e01218ae524c
--- /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.
+ */
+
+#pragma once
+
+#include "config.h"
+
+#include <boost/uuid/uuid.hpp>
+#include <boost/optional.hpp>
+#include <functional>
+
+#include "dnsname.hh"
+
+struct 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&) = delete;
+  ResolveContext& operator=(const ResolveContext&) = delete;
+  ResolveContext(ResolveContext&&) = delete;
+  ResolveContext& operator=(ResolveContext&&) = delete;
+
+  boost::optional<const boost::uuids::uuid&> d_initialRequestId;
+  DNSName d_nsName;
+#ifdef HAVE_FSTRM
+  boost::optional<const DNSName&> d_auth;
+#endif
+};
diff --git a/pdns/recursordist/responsestats.cc b/pdns/recursordist/responsestats.cc
deleted file mode 120000 (symlink)
index 24fbd43..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../responsestats.cc
\ No newline at end of file
diff --git a/pdns/recursordist/responsestats.hh b/pdns/recursordist/responsestats.hh
deleted file mode 120000 (symlink)
index c5449e5..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../responsestats.hh
\ No newline at end of file
deleted file mode 120000 (symlink)
index 69985aebf8c4a49867f2e0a97960e4e8cd1a3963..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../root-addresses.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..f95f0ea7f959407b65b347bf1b28b663a1b6f3eb
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <array>
+#include <string>
+
+const std::array<const std::string, 13> rootIps4 = {
+  "198.41.0.4", // a.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.
+  "192.5.5.241", // f.root-servers.net.
+  "192.112.36.4", // g.root-servers.net.
+  "198.97.190.53", // h.root-servers.net.
+  "192.36.148.17", // i.root-servers.net.
+  "192.58.128.30", // j.root-servers.net.
+  "193.0.14.129", // k.root-servers.net.
+  "199.7.83.42", // l.root-servers.net.
+  "202.12.27.33" // m.root-servers.net.
+};
+
+const std::array<const std::string, 13> rootIps6 = {
+  "2001:503:ba3e::2:30", // a.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.
+  "2001:500:2f::f", // f.root-servers.net.
+  "2001:500:12::d0d", // g.root-servers.net.
+  "2001:500:1::53", // h.root-servers.net.
+  "2001:7fe::53", // i.root-servers.net.
+  "2001:503:c27::2:30", // j.root-servers.net.
+  "2001:7fd::1", // k.root-servers.net.
+  "2001:500:9f::42", // l.root-servers.net.
+  "2001:dc3::35" // m.root-servers.net.
+};
deleted file mode 120000 (symlink)
index 163320cafc6845b5c6dbe7692317b0da8b6405b6..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../rpzloader.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..33c9ae27a5ec7bb4debc97c90f47afb83bf1cb43
--- /dev/null
@@ -0,0 +1,720 @@
+/*
+ * 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"
+#include "ixfr.hh"
+#include "syncres.hh"
+#include "axfr-retriever.hh"
+#include "lock.hh"
+#include "logger.hh"
+#include "logging.hh"
+#include "rec-lua-conf.hh"
+#include "rpzloader.hh"
+#include "zoneparser-tng.hh"
+#include "threadname.hh"
+#include "query-local-address.hh"
+
+Netmask makeNetmaskFromRPZ(const DNSName& name)
+{
+  auto parts = name.getRawLabels();
+  /*
+   * why 2?, the minimally valid IPv6 address that can be encoded in an RPZ is
+   * $NETMASK.zz (::/$NETMASK)
+   * Terrible right?
+   */
+  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))
+        isV6 = true;
+
+    if (pdns_iequals(part, "zz")) {
+      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)
+    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[parts.size() - 1] == "") {
+    v6 += ":";
+  }
+  for (uint8_t i = parts.size() - 1; i > 0; i--) {
+    v6 += parts[i];
+    if (i > 1 || (i == 1 && parts[i] == "")) {
+      v6 += ":";
+    }
+  }
+  v6 += "/" + parts[0];
+
+  return Netmask(v6);
+}
+
+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 const DNSName drop("rpz-drop."), truncate("rpz-tcp-only."), noaction("rpz-passthru.");
+  static const DNSName rpzClientIP("rpz-client-ip"), rpzIP("rpz-ip"),
+    rpzNSDname("rpz-nsdname"), rpzNSIP("rpz-nsip.");
+  static const std::string rpzPrefix("rpz-");
+
+  DNSFilterEngine::Policy pol;
+  bool defpolApplied = false;
+
+  if (dr.d_class != QClass::IN) {
+    return;
+  }
+
+  if (dr.d_type == QType::CNAME) {
+    auto crc = getRR<CNAMERecordContent>(dr);
+    if (!crc) {
+      return;
+    }
+    auto crcTarget = crc->getTarget();
+    if (defpol) {
+      pol = *defpol;
+      defpolApplied = true;
+    }
+    else if (crcTarget.isRoot()) {
+      // cerr<<"Wants NXDOMAIN for "<<dr.d_name<<": ";
+      pol.d_kind = DNSFilterEngine::PolicyKind::NXDOMAIN;
+    }
+    else if (crcTarget == g_wildcarddnsname) {
+      // cerr<<"Wants NODATA for "<<dr.d_name<<": ";
+      pol.d_kind = DNSFilterEngine::PolicyKind::NODATA;
+    }
+    else if (crcTarget == drop) {
+      // cerr<<"Wants DROP for "<<dr.d_name<<": ";
+      pol.d_kind = DNSFilterEngine::PolicyKind::Drop;
+    }
+    else if (crcTarget == truncate) {
+      // cerr<<"Wants TRUNCATE for "<<dr.d_name<<": ";
+      pol.d_kind = DNSFilterEngine::PolicyKind::Truncate;
+    }
+    else if (crcTarget == noaction) {
+      // cerr<<"Wants NOACTION for "<<dr.d_name<<": ";
+      pol.d_kind = DNSFilterEngine::PolicyKind::NoAction;
+    }
+    /* "The special RPZ encodings which are not to be taken as Local Data are
+       CNAMEs with targets that are:
+       +  "."  (NXDOMAIN action),
+       +  "*." (NODATA action),
+       +  a top level domain starting with "rpz-",
+       +  a child of a top level domain starting with "rpz-".
+    */
+    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)));
+      return;
+    }
+    else {
+      pol.d_kind = DNSFilterEngine::PolicyKind::Custom;
+      pol.d_custom.emplace_back(dr.getContent());
+      // cerr<<"Wants custom "<<crcTarget<<" for "<<dr.d_name<<": ";
+    }
+  }
+  else {
+    if (defpol && defpolOverrideLocal) {
+      pol = *defpol;
+      defpolApplied = true;
+    }
+    else {
+      pol.d_kind = DNSFilterEngine::PolicyKind::Custom;
+      pol.d_custom.emplace_back(dr.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));
+  }
+  else {
+    pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, static_cast<uint32_t>(pol.d_ttl)));
+  }
+
+  // now to DO something with that
+
+  if (dr.d_name.isPartOf(rpzNSDname)) {
+    DNSName filt = dr.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)) {
+    // 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));
+  }
+  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);
+    }
+    else {
+      zone->rmQNameTrigger(dr.d_name, std::move(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)
+{
+
+  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)));
+  }
+
+  ComboAddress local(localAddress);
+  if (local == ComboAddress())
+    local = pdns::getQueryLocalAddress(primary.sin4.sin_family, 0);
+
+  AXFRRetriever axfr(primary, zoneName, tt, &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;
+  // coverity[store_truncates_time_t]
+  while (axfr.getChunk(nop, &chunk, (axfrStart + axfrTimeout - axfrNow))) {
+    for (auto& dr : chunk) {
+      if (dr.d_type == QType::NS || dr.d_type == QType::TSIG) {
+        continue;
+      }
+
+      dr.d_name.makeUsRelative(zoneName);
+      if (dr.d_type == QType::SOA) {
+        sr = getRR<SOARecordContent>(dr);
+        zone->setSOA(dr);
+        continue;
+      }
+
+      RPZRecordToPolicy(dr, 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)) {
+      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);
+    }
+  }
+  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;
+}
+
+static LockGuarded<std::unordered_map<std::string, shared_ptr<rpzStats>>> s_rpzStats;
+
+shared_ptr<rpzStats> getRPZZoneStats(const std::string& zone)
+{
+  auto stats = s_rpzStats.lock();
+  auto it = stats->find(zone);
+  if (it == stats->end()) {
+    auto stat = std::make_shared<rpzStats>();
+    (*stats)[zone] = stat;
+    return stat;
+  }
+  return it->second;
+}
+
+static void incRPZFailedTransfers(const std::string& zone)
+{
+  auto stats = getRPZZoneStats(zone);
+  if (stats != nullptr)
+    stats->d_failedTransfers++;
+}
+
+static void setRPZZoneNewState(const std::string& zone, uint32_t serial, uint64_t numberOfRecords, bool fromFile, bool wasAXFR)
+{
+  auto stats = getRPZZoneStats(zone);
+  if (stats == nullptr) {
+    return;
+  }
+  if (!fromFile) {
+    stats->d_successfulTransfers++;
+    if (wasAXFR) {
+      stats->d_fullTransfers++;
+    }
+  }
+  stats->d_lastUpdate = time(nullptr);
+  stats->d_serial = serial;
+  stats->d_numberOfRecords = numberOfRecords;
+}
+
+// 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)
+{
+  shared_ptr<const SOARecordContent> sr = 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())
+        drr.content = ".";
+      DNSRecord dr(drr);
+      if (dr.d_type == QType::SOA) {
+        sr = getRR<SOARecordContent>(dr);
+        domain = dr.d_name;
+        zone->setDomain(domain);
+        soaRecord = std::move(dr);
+      }
+      else if (dr.d_type == QType::NS) {
+        continue;
+      }
+      else {
+        dr.d_name = dr.d_name.makeRelative(domain);
+        RPZRecordToPolicy(dr, zone, true, defpol, defpolOverrideLocal, maxTTL, log);
+      }
+    }
+    catch (const PDNSException& pe) {
+      throw PDNSException("Issue parsing '" + drr.qname.toLogString() + "' '" + drr.content + "' at " + zpt.getLineOfFile() + ": " + pe.reason);
+    }
+  }
+
+  if (sr != nullptr) {
+    zone->setRefresh(sr->d_st.refresh);
+    zone->setSOA(std::move(soaRecord));
+    setRPZZoneNewState(zone->getName(), sr->d_st.serial, zone->size(), true, false);
+  }
+  return sr;
+}
+
+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) {
+    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) {
+    int err = errno;
+    close(fd);
+    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());
+  }
+  catch (const std::exception& e) {
+    SLOG(g_log << Logger::Warning << "Error while dumping the content of the RPZ zone " << zoneName << ": " << e.what() << endl,
+         logger->error(Logr::Error, e.what(), "Error while dumping the content of the RPZ"));
+    return false;
+  }
+
+  if (fflush(fp.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) {
+    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) {
+    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;
+  }
+
+  if (rename(temp.c_str(), dumpZoneFileName.c_str()) != 0) {
+    SLOG(g_log << Logger::Warning << "Error while moving the content of the RPZ zone " << zoneName << " to the dump file: " << stringerror() << endl,
+         logger->error(Logr::Error, errno, "Error while moving the content of the RPZ", "destination_file", Logging::Loggable(dumpZoneFileName)));
+    return false;
+  }
+
+  return true;
+}
+
+// A struct that holds the condition var and related stuff to allow notifies to be sent to the tread owning
+// the struct.
+struct RPZWaiter
+{
+  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 : params.primaries) {
+      try {
+        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 = params.zoneIdx, &newZone](LuaConfigItems& lci) {
+          lci.dfe.setZone(zoneIdx, newZone);
+        });
+
+        if (!params.dumpZoneFileName.empty()) {
+          dumpZoneToDisk(logger, zoneName, newZone, params.dumpZoneFileName);
+        }
+
+        /* no need to try another primary */
+        break;
+      }
+      catch (const std::exception& e) {
+        SLOG(g_log << Logger::Warning << "Unable to load RPZ zone '" << zoneName << "' from '" << primary << "': '" << e.what() << "'. (Will try again in " << refresh << " seconds...)" << endl,
+             logger->error(Logr::Warning, e.what(), "Unable to load RPZ zone, will retry", "from", Logging::Loggable(primary), "exception", Logging::Loggable("std::exception"), "refresh", Logging::Loggable(refresh)));
+        incRPZFailedTransfers(polName);
+      }
+      catch (const PDNSException& e) {
+        SLOG(g_log << Logger::Warning << "Unable to load RPZ zone '" << zoneName << "' from '" << primary << "': '" << e.reason << "'. (Will try again in " << refresh << " seconds...)" << endl,
+             logger->error(Logr::Warning, e.reason, "Unable to load RPZ zone, will retry", "from", Logging::Loggable(primary), "exception", Logging::Loggable("PDNSException"), "refresh", Logging::Loggable(refresh)));
+        incRPZFailedTransfers(polName);
+      }
+    }
+    // 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;
+  }
+}
+
+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 {
+    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 false;
+  }
+
+  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(params.localAddress);
+    if (local == ComboAddress()) {
+      local = pdns::getQueryLocalAddress(primary.sin4.sin_family, 0);
+    }
+
+    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;
+    }
+  }
+
+  if (deltas.empty()) {
+    return true;
+  }
+
+  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;
+      }
+      for (const auto& resourceRecord : remove) { // should always contain the SOA
+        if (resourceRecord.d_type == QType::NS) {
+          continue;
+        }
+        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 {
+            if (!oldsr) {
+              throw std::runtime_error("Unable to extract serial from SOA record while processing the removal part of an update");
+            }
+            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);
+        }
+      }
+
+      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);
+        }
+      }
+    }
+
+    /* only update sr now that all changes have been converted */
+    if (currentSR) {
+      params.soaRecordContent = std::move(currentSR);
+    }
+    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);
+    }
+    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;
+  }
+}
deleted file mode 120000 (symlink)
index e4631b4e0ec2ee5adef5298d0852ab9990e955ec..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../rpzloader.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..2933d9a1d0faa525f38407e549e2d80f2b652f76
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * 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 "filterpo.hh"
+#include <string>
+#include "dnsrecords.hh"
+
+extern bool g_logRPZChanges;
+
+// 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, 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
+{
+  std::atomic<uint64_t> d_failedTransfers;
+  std::atomic<uint64_t> d_successfulTransfers;
+  std::atomic<uint64_t> d_fullTransfers;
+  std::atomic<uint64_t> d_numberOfRecords;
+  std::atomic<time_t> d_lastUpdate;
+  std::atomic<uint32_t> d_serial;
+};
+
+Netmask makeNetmaskFromRPZ(const DNSName& name);
+shared_ptr<rpzStats> getRPZZoneStats(const std::string& zone);
deleted file mode 120000 (symlink)
index bccc7ec592cd0148d77d9e62835050eb06103510..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../secpoll-recursor.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..b54ae7dc5c71ca74d40a80f979160196d268a47c
--- /dev/null
@@ -0,0 +1,109 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "secpoll-recursor.hh"
+#include "syncres.hh"
+#include "logger.hh"
+#include "arguments.hh"
+#include "version.hh"
+#include "validate-recursor.hh"
+#include "secpoll.hh"
+
+#include <cstdint>
+#ifndef PACKAGEVERSION
+#define PACKAGEVERSION getPDNSVersion()
+#endif
+
+uint32_t g_security_status;
+string g_security_message;
+
+void doSecPoll(time_t* last_secpoll, Logr::log_t log)
+{
+  if (::arg()["security-poll-suffix"].empty()) {
+    return;
+  }
+
+  string pkgv(PACKAGEVERSION);
+  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 resolver(now);
+  if (g_dnssecmode != DNSSECMode::Off) {
+    resolver.setDoDNSSEC(true);
+    resolver.setDNSSECValidationRequested(true);
+  }
+  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() != '.') {
+    qstring += '.';
+  }
+
+  boost::replace_all(qstring, "+", "_");
+  boost::replace_all(qstring, "~", "_");
+
+  vState state = vState::Indeterminate;
+  DNSName query(qstring);
+  int res = resolver.beginResolve(query, QType(QType::TXT), 1, ret);
+
+  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
+      g_security_status = 0;
+    }
+    return;
+  }
+
+  if (res == RCode::NXDomain && !isReleaseVersion(pkgv)) {
+    SLOG(g_log << Logger::Warning << "Not validating response for security status update, this is a non-release version" << endl,
+         vlog->info(Logr::Warning, "Not validating response for security status update, this is a non-release version"));
+    return;
+  }
+
+  string security_message;
+  int security_status = static_cast<int>(g_security_status);
+
+  try {
+    processSecPoll(res, ret, security_status, security_message);
+  }
+  catch (const PDNSException& pe) {
+    g_security_status = security_status;
+    SLOG(g_log << Logger::Warning << "Failed to retrieve security status update for '" << pkgv << "' on '" << query << "': " << pe.reason << endl,
+         vlog->error(Logr::Warning, pe.reason, "Failed to retrieve security status update"));
+    return;
+  }
+
+  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) {
+    SLOG(g_log << Logger::Warning << "Polled security status of version " << pkgv << ", no known issues reported: " << g_security_message << endl,
+         rlog->info(Logr::Notice, "Polled security status of version, no known issues reported"));
+  }
+  if (security_status == 2) {
+    SLOG(g_log << Logger::Error << "PowerDNS Security Update Recommended: " << g_security_message << endl,
+         rlog->info(Logr::Error, "PowerDNS Security Update Recommended"));
+  }
+  if (security_status == 3) {
+    SLOG(g_log << Logger::Error << "PowerDNS Security Update Mandatory: " << g_security_message << endl,
+         rlog->info(Logr::Error, "PowerDNS Security Update Mandatory"));
+  }
+
+  g_security_status = security_status;
+}
deleted file mode 120000 (symlink)
index 505a00ed1762c648ee9ab072779c86a7e90f7c49..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../secpoll-recursor.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..8fc6f4074d1d3f62b5073668bf9720f85b44d4eb
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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 "namespaces.hh"
+#include "logr.hh"
+#include <stdint.h>
+
+void doSecPoll(time_t*, Logr::log_t);
+extern uint32_t g_security_status;
+extern std::string g_security_message;
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..8a9e331
--- /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 = std::unique_ptr<FILE, decltype(&fclose)>(fopen(file.c_str(), "r"), fclose);
+  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..5028b4c
--- /dev/null
@@ -0,0 +1,3109 @@
+# 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 stdout',
+        'doc' : '''
+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 :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.String,
+        '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'
+    },
+    {
+        '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 stdout 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.String,
+        'default' : '',
+        'help' : 'A Proxy Protocol header is only allowed 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'
+    },
+    {
+        '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.
+ ''',
+    'versionadded': '4.8.0'
+    },
+    {
+        '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'
+    },
+]
deleted file mode 120000 (symlink)
index fceca228d2288e4fc37477840c38efc82ee34b90..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../syncres.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..48c4acc2b3cdef18d98ed176ff8dfb3a62c34206
--- /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.
+ */
+#ifdef HAVE_CONFIG_H
+#include <utility>
+
+#include "config.h"
+#endif
+
+#include "arguments.hh"
+#include "aggressive_nsec.hh"
+#include "cachecleaner.hh"
+#include "dns_random.hh"
+#include "dnsparser.hh"
+#include "dnsrecords.hh"
+#include "ednssubnet.hh"
+#include "logger.hh"
+#include "lua-recursor4.hh"
+#include "rec-lua-conf.hh"
+#include "syncres.hh"
+#include "dnsseckeeper.hh"
+#include "validate-recursor.hh"
+#include "rec-taskqueue.hh"
+
+rec::GlobalCounters g_Counters;
+thread_local rec::TCounters t_Counters(g_Counters);
+
+template <class T>
+class fails_t : public boost::noncopyable
+{
+public:
+  using counter_t = uint64_t;
+  struct value_t
+  {
+    value_t(T arg) :
+      key(std::move(arg)) {}
+    T key;
+    mutable counter_t value{0};
+    time_t last{0};
+  };
+
+  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>>>>;
+
+  [[nodiscard]] cont_t getMapCopy() const
+  {
+    return d_cont;
+  }
+
+  [[nodiscard]] counter_t value(const T& arg) const
+  {
+    auto iter = d_cont.find(arg);
+
+    if (iter == d_cont.end()) {
+      return 0;
+    }
+    return iter->value;
+  }
+
+  counter_t incr(const T& key, const struct timeval& now)
+  {
+    auto iter = d_cont.insert(key).first;
+
+    if (iter->value < std::numeric_limits<counter_t>::max()) {
+      iter->value++;
+    }
+    auto& ind = d_cont.template get<T>();
+    time_t nowSecs = now.tv_sec;
+    ind.modify(iter, [nowSecs](value_t& val) { val.last = nowSecs; });
+    return iter->value;
+  }
+
+  void clear(const T& arg)
+  {
+    d_cont.erase(arg);
+  }
+
+  void clear()
+  {
+    d_cont.clear();
+  }
+
+  [[nodiscard]] size_t size() const
+  {
+    return d_cont.size();
+  }
+
+  void prune(time_t cutoff)
+  {
+    auto& ind = d_cont.template get<time_t>();
+    ind.erase(ind.begin(), ind.upper_bound(cutoff));
+  }
+
+private:
+  cont_t d_cont;
+};
+
+/** Class that implements a decaying EWMA.
+    This class keeps an exponentially weighted moving average which, additionally, decays over time.
+    The decaying is only done on get.
+*/
+
+//! This represents a number of decaying Ewmas, used to store performance per nameserver-name.
+/** Modelled to work mostly like the underlying DecayingEwma */
+class DecayingEwmaCollection
+{
+private:
+  struct DecayingEwma
+  {
+  public:
+    void submit(int arg, const struct timeval& last, const struct timeval& now)
+    {
+      d_last = arg;
+      auto val = static_cast<float>(arg);
+      if (d_val == 0) {
+        d_val = val;
+      }
+      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;
+      }
+    }
+
+    float get(float factor)
+    {
+      return d_val *= factor;
+    }
+
+    [[nodiscard]] float peek() const
+    {
+      return d_val;
+    }
+
+    [[nodiscard]] int last() const
+    {
+      return d_last;
+    }
+
+    float d_val{0};
+    int d_last{0};
+  };
+
+public:
+  DecayingEwmaCollection(DNSName name, const struct timeval val = {0, 0}) :
+    d_name(std::move(name)), d_lastget(val)
+  {
+  }
+
+  void submit(const ComboAddress& remote, int usecs, const struct timeval& now) const
+  {
+    d_collection[remote].submit(usecs, d_lastget, now);
+  }
+
+  float getFactor(const struct timeval& now) const
+  {
+    float diff = makeFloat(d_lastget - now);
+    return expf(diff / 60.0F); // is 1.0 or less
+  }
+
+  bool stale(time_t limit) const
+  {
+    return limit > d_lastget.tv_sec;
+  }
+
+  void purge(const std::map<ComboAddress, float>& keep) const
+  {
+    for (auto iter = d_collection.begin(); iter != d_collection.end();) {
+      if (keep.find(iter->first) != keep.end()) {
+        ++iter;
+      }
+      else {
+        iter = d_collection.erase(iter);
+      }
+    }
+  }
+
+  // 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;
+  DNSName d_name;
+  struct timeval d_lastget;
+};
+
+class nsspeeds_t : public multi_index_container<DecayingEwmaCollection,
+                                                indexed_by<
+                                                  hashed_unique<tag<DNSName>, member<DecayingEwmaCollection, const DNSName, &DecayingEwmaCollection::d_name>>,
+                                                  ordered_non_unique<tag<timeval>, member<DecayingEwmaCollection, timeval, &DecayingEwmaCollection::d_lastget>>>>
+{
+public:
+  const auto& find_or_enter(const DNSName& name, const struct timeval& now)
+  {
+    const auto iter = insert(DecayingEwmaCollection{name, now}).first;
+    return *iter;
+  }
+
+  const auto& find_or_enter(const DNSName& name)
+  {
+    const auto iter = insert(DecayingEwmaCollection{name}).first;
+    return *iter;
+  }
+
+  float fastest(const DNSName& name, const struct timeval& now)
+  {
+    auto& ind = get<DNSName>();
+    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 (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 = iter->getFactor(now);
+    for (auto& entry : iter->d_collection) {
+      if (float tmp = entry.second.get(factor); tmp < ret) {
+        ret = tmp;
+      }
+    }
+    ind.modify(iter, [&](DecayingEwmaCollection& dec) { dec.d_lastget = now; });
+    return ret;
+  }
+};
+
+static LockGuarded<nsspeeds_t> s_nsSpeeds;
+
+template <class Thing>
+class Throttle : public boost::noncopyable
+{
+public:
+  struct entry_t
+  {
+    entry_t(const Thing& thing_, time_t ttd_, unsigned int count_) :
+      thing(thing_), ttd(ttd_), count(count_)
+    {
+    }
+    Thing thing;
+    time_t ttd;
+    mutable unsigned int count;
+  };
+  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& arg)
+  {
+    auto iter = d_cont.find(arg);
+    if (iter == d_cont.end()) {
+      return false;
+    }
+    if (now > iter->ttd || iter->count == 0) {
+      d_cont.erase(iter);
+      return false;
+    }
+    iter->count--;
+
+    return true; // still listed, still blocked
+  }
+
+  void throttle(time_t now, const Thing& arg, time_t ttl, unsigned int count)
+  {
+    auto iter = d_cont.find(arg);
+    time_t ttd = now + ttl;
+    if (iter == d_cont.end()) {
+      d_cont.emplace(arg, ttd, 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(iter, [ttd, count](entry_t& entry) { entry.ttd = ttd; entry.count = count; });
+    }
+  }
+
+  [[nodiscard]] size_t size() const
+  {
+    return d_cont.size();
+  }
+
+  [[nodiscard]] cont_t getThrottleMap() const
+  {
+    return d_cont;
+  }
+
+  void clear()
+  {
+    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>();
+    ind.erase(ind.begin(), ind.upper_bound(now));
+  }
+
+private:
+  cont_t d_cont;
+};
+
+static LockGuarded<Throttle<std::tuple<ComboAddress, DNSName, QType>>> s_throttle;
+
+struct SavedParentEntry
+{
+  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;
+  map<DNSName, vector<ComboAddress>> d_nsAddresses;
+  time_t d_ttd;
+  mutable uint64_t d_count{0};
+};
+
+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>>>>;
+
+class SavedParentNSSet : public SavedParentNSSetBase
+{
+public:
+  void prune(time_t now)
+  {
+    auto& ind = get<time_t>();
+    ind.erase(ind.begin(), ind.upper_bound(now));
+  }
+  void inc(const DNSName& name)
+  {
+    auto iter = find(name);
+    if (iter != end()) {
+      ++(*iter).d_count;
+    }
+  }
+  [[nodiscard]] SavedParentNSSet getMapCopy() const
+  {
+    return *this;
+  }
+};
+
+static LockGuarded<SavedParentNSSet> s_savedParentNSSet;
+
+thread_local SyncRes::ThreadLocalStorage SyncRes::t_sstorage;
+thread_local std::unique_ptr<addrringbuf_t> t_timeouts;
+
+std::unique_ptr<NetmaskGroup> SyncRes::s_dontQuery{nullptr};
+NetmaskGroup SyncRes::s_ednslocalsubnets;
+NetmaskGroup SyncRes::s_ednsremotesubnets;
+SuffixMatchNode SyncRes::s_ednsdomains;
+EDNSSubnetOpts SyncRes::s_ecsScopeZero;
+string SyncRes::s_serverID;
+SyncRes::LogMode SyncRes::s_lm;
+const std::unordered_set<QType> SyncRes::s_redirectionQTypes = {QType::CNAME, QType::DNAME};
+static LockGuarded<fails_t<ComboAddress>> s_fails;
+static LockGuarded<fails_t<DNSName>> s_nonresolving;
+
+struct DoTStatus
+{
+  DoTStatus(const ComboAddress& address, DNSName auth, time_t ttd) :
+    d_address(address), d_auth(std::move(auth)), d_ttd(ttd)
+  {
+  }
+  enum Status : uint8_t
+  {
+    Unknown,
+    Busy,
+    Bad,
+    Good
+  };
+  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> names{"Unknown", "Busy", "Bad", "Good"};
+    auto val = static_cast<unsigned int>(d_status);
+    return val >= names.size() ? "?" : names.at(val);
+  }
+};
+
+struct DoTMap
+{
+  multi_index_container<DoTStatus,
+                        indexed_by<
+                          ordered_unique<tag<ComboAddress>, member<DoTStatus, const ComboAddress, &DoTStatus::d_address>>,
+                          ordered_non_unique<tag<time_t>, member<DoTStatus, time_t, &DoTStatus::d_ttd>>>>
+    d_map;
+  uint64_t d_numBusy{0};
+
+  void prune(time_t cutoff)
+  {
+    auto& ind = d_map.template get<time_t>();
+    ind.erase(ind.begin(), ind.upper_bound(cutoff));
+  }
+};
+
+static LockGuarded<DoTMap> s_dotMap;
+
+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;
+unsigned int SyncRes::s_maxbogusttl;
+unsigned int SyncRes::s_maxcachettl;
+unsigned int SyncRes::s_maxqperq;
+unsigned int SyncRes::s_maxnsperresolve;
+unsigned int SyncRes::s_maxnsaddressqperq;
+unsigned int SyncRes::s_maxtotusec;
+unsigned int SyncRes::s_maxdepth;
+unsigned int SyncRes::s_minimumTTL;
+unsigned int SyncRes::s_minimumECSTTL;
+unsigned int SyncRes::s_packetcachettl;
+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;
+std::map<uint8_t, pdns::stat_t> SyncRes::s_ecsResponsesBySubnetSize6;
+
+uint8_t SyncRes::s_ecsipv4limit;
+uint8_t SyncRes::s_ecsipv6limit;
+uint8_t SyncRes::s_ecsipv4cachelimit;
+uint8_t SyncRes::s_ecsipv6cachelimit;
+bool SyncRes::s_ecsipv4nevercache;
+bool SyncRes::s_ecsipv6nevercache;
+
+bool SyncRes::s_doIPv4;
+bool SyncRes::s_doIPv6;
+bool SyncRes::s_rootNXTrust;
+bool SyncRes::s_noEDNS;
+bool SyncRes::s_qnameminimization;
+SyncRes::HardenNXD SyncRes::s_hardenNXD;
+unsigned int SyncRes::s_refresh_ttlperc;
+unsigned int SyncRes::s_locked_ttlperc;
+int SyncRes::s_tcp_fast_open;
+bool SyncRes::s_tcp_fast_open_connect;
+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;   \
+  }                                  \
+  else if (d_lm == Store) {          \
+    addTraceTS(d_fixednow, d_trace); \
+    d_trace << x;                    \
+  }
+
+OptLog SyncRes::LogObject(const string& prefix)
+{
+  OptLog ret;
+  if (d_lm == Log) {
+    ret = {prefix, d_fixednow, g_log};
+  }
+  else if (d_lm == Store) {
+    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 value)
+{
+  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 {buf.data(), static_cast<size_t>(ret)};
+}
+
+static inline void accountAuthLatency(uint64_t usec, int family)
+{
+  if (family == AF_INET) {
+    t_Counters.at(rec::Histogram::auth4Answers)(usec);
+    t_Counters.at(rec::Histogram::cumulativeAuth4Answers)(usec);
+  }
+  else {
+    t_Counters.at(rec::Histogram::auth6Answers)(usec);
+    t_Counters.at(rec::Histogram::cumulativeAuth6Answers)(usec);
+  }
+}
+
+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);
+
+void SyncRes::resolveAdditionals(const DNSName& qname, QType qtype, AdditionalMode mode, std::vector<DNSRecord>& additionals, unsigned int depth, bool& additionalsNotInCache)
+{
+  vector<DNSRecord> addRecords;
+
+  Context context;
+  switch (mode) {
+  case AdditionalMode::ResolveImmediately: {
+    set<GetBestNSAnswer> beenthere;
+    int res = doResolve(qname, qtype, addRecords, depth, beenthere, context);
+    if (res != 0) {
+      return;
+    }
+    // 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)) {
+      return;
+    }
+    if (shouldValidate() && context.state != vState::Secure && context.state != vState::Insecure) {
+      return;
+    }
+    for (auto& rec : addRecords) {
+      if (rec.d_place == DNSResourceRecord::ANSWER) {
+        additionals.push_back(std::move(rec));
+      }
+    }
+    break;
+  }
+  case AdditionalMode::CacheOnly:
+  case AdditionalMode::CacheOnlyRequireAuth: {
+    // Peek into cache
+    MemRecursorCache::Flags flags = mode == AdditionalMode::CacheOnlyRequireAuth ? MemRecursorCache::RequireAuth : MemRecursorCache::None;
+    if (g_recCache->get(d_now.tv_sec, qname, qtype, flags, &addRecords, d_cacheRemote, d_routingTag, nullptr, nullptr, nullptr, &context.state) <= 0) {
+      return;
+    }
+    // See the comment for the ResolveImmediately case
+    if (vStateIsBogus(context.state)) {
+      return;
+    }
+    if (shouldValidate() && context.state != vState::Secure && context.state != vState::Insecure) {
+      return;
+    }
+    for (auto& rec : addRecords) {
+      if (rec.d_place == DNSResourceRecord::ANSWER) {
+        rec.d_ttl -= d_now.tv_sec;
+        additionals.push_back(std::move(rec));
+      }
+    }
+    break;
+  }
+  case AdditionalMode::ResolveDeferred: {
+    const bool oldCacheOnly = setCacheOnly(true);
+    set<GetBestNSAnswer> beenthere;
+    int res = doResolve(qname, qtype, addRecords, depth, beenthere, context);
+    setCacheOnly(oldCacheOnly);
+    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)) {
+        return;
+      }
+      if (shouldValidate() && context.state != vState::Secure && context.state != vState::Insecure) {
+        return;
+      }
+      bool found = false;
+      for (auto& rec : addRecords) {
+        if (rec.d_place == DNSResourceRecord::ANSWER) {
+          found = true;
+          additionals.push_back(std::move(rec));
+        }
+      }
+      if (found) {
+        return;
+      }
+    }
+    // Not found in cache, check negcache and push task if also not in negcache
+    if (pushResolveIfNotInNegCache(qname, qtype, d_now)) {
+      additionalsNotInCache = true;
+    }
+    break;
+  }
+  case AdditionalMode::Ignore:
+    break;
+  }
+}
+
+// The main (recursive) function to add additionals
+// qtype: the original query type to expand
+// start: records to start from
+// This function uses to state sets to avoid infinite recursion and allow depulication
+// depth is the main recursion depth
+// additionaldepth is the depth for addAdditionals itself
+void SyncRes::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 additionaldepth, bool& additionalsNotInCache)
+{
+  if (additionaldepth >= 5 || start.empty()) {
+    return;
+  }
+
+  auto luaLocal = g_luaconfs.getLocal();
+  const auto iter = luaLocal->allowAdditionalQTypes.find(qtype);
+  if (iter == luaLocal->allowAdditionalQTypes.end()) {
+    return;
+  }
+  std::unordered_set<DNSName> addnames;
+  for (const auto& rec : start) {
+    if (rec.d_place == DNSResourceRecord::ANSWER) {
+      // currently, this function only knows about names, we could also take the target types that are dependent on
+      // record contents into account
+      // e.g. for NAPTR records, go only for SRV for flag value "s", or A/AAAA for flag value "a"
+      allowAdditionalEntry(addnames, rec);
+    }
+  }
+
+  // We maintain two sets for deduplication:
+  // - uniqueCalls makes sure we never resolve a qname/qtype twice
+  // - 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 = 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;
+      if (inserted) {
+        resolveAdditionals(addname, targettype, mode, records, depth, additionalsNotInCache);
+      }
+      if (!records.empty()) {
+        for (auto record = records.begin(); record != records.end();) {
+          QType covered = QType::ENT;
+          if (record->d_type == QType::RRSIG) {
+            if (auto rsig = getRR<RRSIGRecordContent>(*record); rsig != nullptr) {
+              covered = rsig->d_type;
+            }
+          }
+          if (uniqueResults.count(std::tuple(record->d_name, QType(record->d_type), covered)) > 0) {
+            // A bit expensive for vectors, but they are small
+            record = records.erase(record);
+          }
+          else {
+            ++record;
+          }
+        }
+        for (const auto& record : records) {
+          additionals.push_back(record);
+          QType covered = QType::ENT;
+          if (record.d_type == QType::RRSIG) {
+            if (auto rsig = getRR<RRSIGRecordContent>(record); rsig != nullptr) {
+              covered = rsig->d_type;
+            }
+          }
+          uniqueResults.emplace(record.d_name, record.d_type, covered);
+        }
+        addAdditionals(targettype, records, additionals, uniqueCalls, uniqueResults, depth, additionaldepth + 1, additionalsNotInCache);
+      }
+    }
+  }
+}
+
+// The entry point for other code
+bool SyncRes::addAdditionals(QType qtype, vector<DNSRecord>& ret, unsigned int depth)
+{
+  // The additional records of interest
+  std::vector<DNSRecord> additionals;
+
+  // We only call resolve for a specific name/type combo once
+  std::set<std::pair<DNSName, QType>> uniqueCalls;
+
+  // Collect multiple name/qtype from a single resolve but do not add a new set from new resolve calls
+  // For RRSIGs, the type covered is stored in the second Qtype
+  std::set<std::tuple<DNSName, QType, QType>> uniqueResults;
+
+  bool additionalsNotInCache = false;
+  addAdditionals(qtype, ret, additionals, uniqueCalls, uniqueResults, depth, 0, additionalsNotInCache);
+
+  for (auto& rec : additionals) {
+    rec.d_place = DNSResourceRecord::ADDITIONAL;
+    ret.push_back(std::move(rec));
+  }
+  return additionalsNotInCache;
+}
+
+/** everything begins here - this is the entry point just after receiving a packet */
+int SyncRes::beginResolve(const DNSName& qname, const QType qtype, QClass qclass, vector<DNSRecord>& ret, unsigned int depth)
+{
+  d_eventTrace.add(RecEventTrace::SyncRes);
+  t_Counters.at(rec::Counter::syncresqueries)++;
+  d_wasVariable = false;
+  d_wasOutOfBand = false;
+  d_cutStates.clear();
+
+  if (doSpecialNamesResolve(qname, qtype, qclass, ret)) {
+    d_queryValidationState = vState::Insecure; // this could fool our stats into thinking a validation took place
+    return 0; // so do check before updating counters (we do now)
+  }
+
+  if (isUnsupported(qtype)) {
+    return -1;
+  }
+
+  if (qclass == QClass::ANY) {
+    qclass = QClass::IN;
+  }
+  else if (qclass != QClass::IN) {
+    return -1;
+  }
+
+  if (qtype == QType::DS) {
+    d_externalDSQuery = qname;
+  }
+  else {
+    d_externalDSQuery.clear();
+  }
+
+  set<GetBestNSAnswer> beenthere;
+  Context context;
+  int res = doResolve(qname, qtype, ret, depth, beenthere, context);
+  d_queryValidationState = context.state;
+  d_extendedError = context.extendedError;
+
+  if (shouldValidate()) {
+    if (d_queryValidationState != vState::Indeterminate) {
+      t_Counters.at(rec::Counter::dnssecValidations)++;
+    }
+    auto xdnssec = g_xdnssec.getLocal();
+    if (xdnssec->check(qname)) {
+      increaseXDNSSECStateCounter(d_queryValidationState);
+    }
+    else {
+      increaseDNSSECStateCounter(d_queryValidationState);
+    }
+  }
+
+  // Avoid calling addAdditionals() if we know we won't find anything
+  auto luaLocal = g_luaconfs.getLocal();
+  if (res == 0 && qclass == QClass::IN && luaLocal->allowAdditionalQTypes.find(qtype) != luaLocal->allowAdditionalQTypes.end()) {
+    bool additionalsNotInCache = addAdditionals(qtype, ret, depth);
+    if (additionalsNotInCache) {
+      d_wasVariable = true;
+    }
+  }
+  d_eventTrace.add(RecEventTrace::SyncRes, res, false);
+  return res;
+}
+
+/*! Handles all special, built-in names
+ * Fills ret with an answer and returns true if it handled the query.
+ *
+ * Handles the following queries (and their ANY variants):
+ *
+ * - localhost. IN A
+ * - localhost. IN AAAA
+ * - 1.0.0.127.in-addr.arpa. IN PTR
+ * - 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa. IN PTR
+ * - version.bind. CH TXT
+ * - version.pdns. CH TXT
+ * - id.server. CH TXT
+ * - trustanchor.server CH TXT
+ * - negativetrustanchor.server CH TXT
+ */
+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.");
+  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) {
+      answers.emplace_back(QType::PTR, "localhost.");
+    }
+  }
+
+  if (qname.isPartOf(localhost) && qclass == QClass::IN) {
+    handled = true;
+    if (qtype == QType::A || qtype == QType::ANY) {
+      answers.emplace_back(QType::A, "127.0.0.1");
+    }
+    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) {
+        answers.emplace_back(QType::TXT, "\"" + ::arg()["version-string"] + "\"");
+      }
+      else if (s_serverID != "disabled") {
+        answers.emplace_back(QType::TXT, "\"" + s_serverID + "\"");
+      }
+    }
+  }
+
+  if (qname == trustanchorserver && qclass == QClass::CHAOS && ::arg().mustDo("allow-trust-anchor-query")) {
+    handled = true;
+    if (qtype == QType::TXT || qtype == QType::ANY) {
+      auto luaLocal = g_luaconfs.getLocal();
+      for (auto const& dsAnchor : luaLocal->dsAnchors) {
+        ostringstream ans;
+        ans << "\"";
+        ans << dsAnchor.first.toString(); // Explicit toString to have a trailing dot
+        for (auto const& dsRecord : dsAnchor.second) {
+          ans << " ";
+          ans << dsRecord.d_tag;
+        }
+        ans << "\"";
+        answers.emplace_back(QType::TXT, ans.str());
+      }
+    }
+  }
+
+  if (qname == negativetrustanchorserver && qclass == QClass::CHAOS && ::arg().mustDo("allow-trust-anchor-query")) {
+    handled = true;
+    if (qtype == QType::TXT || qtype == QType::ANY) {
+      auto luaLocal = g_luaconfs.getLocal();
+      for (auto const& negAnchor : luaLocal->negAnchors) {
+        ostringstream ans;
+        ans << "\"";
+        ans << negAnchor.first.toString(); // Explicit toString to have a trailing dot
+        if (negAnchor.second.length() != 0) {
+          ans << " " << negAnchor.second;
+        }
+        ans << "\"";
+        answers.emplace_back(QType::TXT, ans.str());
+      }
+    }
+  }
+
+  if (handled && !answers.empty()) {
+    ret.clear();
+    d_wasOutOfBand = true;
+
+    DNSRecord dnsRecord;
+    dnsRecord.d_name = qname;
+    dnsRecord.d_place = DNSResourceRecord::ANSWER;
+    dnsRecord.d_class = qclass;
+    dnsRecord.d_ttl = 86400;
+    for (const auto& ans : answers) {
+      dnsRecord.d_type = ans.first;
+      dnsRecord.setContent(DNSRecordContent::make(ans.first, qclass, ans.second));
+      ret.push_back(dnsRecord);
+    }
+  }
+
+  return handled;
+}
+
+//! 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::tuple(getName(), QType::SOA));
+  if (ziter != d_records.end()) {
+    DNSRecord dnsRecord = *ziter;
+    dnsRecord.d_place = DNSResourceRecord::AUTHORITY;
+    records.push_back(dnsRecord);
+  }
+}
+
+bool SyncRes::AuthDomain::operator==(const AuthDomain& rhs) const
+{
+  return d_records == rhs.d_records
+    && d_servers == rhs.d_servers
+    && d_name == rhs.d_name
+    && d_rdForward == rhs.d_rdForward;
+}
+
+[[nodiscard]] std::string SyncRes::AuthDomain::print(const std::string& indent,
+                                                     const std::string& indentLevel) const
+{
+  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) {
+    outputsStream << indent << indentLevel << "Record `" << record.d_name << "` {" << std::endl;
+    outputsStream << record.print(recordContentIndentation);
+    outputsStream << indent << indentLevel << "}" << std::endl;
+  }
+  outputsStream << indent << "}" << std::endl;
+  outputsStream << indent << "Servers {" << std::endl;
+  for (const auto& server : d_servers) {
+    outputsStream << indent << indentLevel << server.toString() << std::endl;
+  }
+  outputsStream << indent << "}" << std::endl;
+  return outputsStream.str();
+}
+
+int SyncRes::AuthDomain::getRecords(const DNSName& qname, const QType qtype, std::vector<DNSRecord>& records) const
+{
+  int result = RCode::NoError;
+  records.clear();
+
+  // partial lookup
+  std::pair<records_t::const_iterator, records_t::const_iterator> range = d_records.equal_range(std::tie(qname));
+
+  SyncRes::AuthDomain::records_t::const_iterator ziter;
+  bool somedata = false;
+
+  for (ziter = range.first; ziter != range.second; ++ziter) {
+    somedata = true;
+
+    if (qtype == QType::ANY || ziter->d_type == qtype || ziter->d_type == QType::CNAME) {
+      // let rest of nameserver do the legwork on this one
+      records.push_back(*ziter);
+    }
+    else if (ziter->d_type == QType::NS && ziter->d_name.countLabels() > getName().countLabels()) {
+      // we hit a delegation point!
+      DNSRecord dnsRecord = *ziter;
+      dnsRecord.d_place = DNSResourceRecord::AUTHORITY;
+      records.push_back(dnsRecord);
+    }
+  }
+
+  if (!records.empty()) {
+    /* We have found an exact match, we're done */
+    return result;
+  }
+
+  if (somedata) {
+    /* We have records for that name, but not of the wanted qtype */
+    addSOA(records);
+
+    return result;
+  }
+
+  DNSName wcarddomain(qname);
+  while (wcarddomain != getName() && wcarddomain.chopOff()) {
+    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 dnsRecord = *ziter;
+      // if we hit a CNAME, just answer that - rest of recursor will do the needful & follow
+      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);
+      }
+    }
+
+    if (records.empty()) {
+      addSOA(records);
+    }
+
+    return result;
+  }
+
+  /* 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::tuple(nsdomain, QType::NS));
+    if (range.first == range.second) {
+      continue;
+    }
+    for (ziter = range.first; ziter != range.second; ++ziter) {
+      DNSRecord dnsRecord = *ziter;
+      dnsRecord.d_place = DNSResourceRecord::AUTHORITY;
+      records.push_back(dnsRecord);
+    }
+  }
+
+  if (records.empty()) {
+    addSOA(records);
+    result = RCode::NXDomain;
+  }
+
+  return result;
+}
+
+bool SyncRes::doOOBResolve(const AuthDomain& domain, const DNSName& qname, const QType qtype, vector<DNSRecord>& ret, int& res)
+{
+  d_authzonequeries++;
+  t_Counters.at(rec::Counter::authzonequeries)++;
+
+  res = domain.getRecords(qname, qtype, ret);
+  return true;
+}
+
+bool SyncRes::doOOBResolve(const DNSName& qname, const QType qtype, vector<DNSRecord>& ret, unsigned int /* depth */, const string& prefix, int& res)
+{
+  DNSName authdomain(qname);
+  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;
+  }
+
+  LOG(prefix << qname << ": Auth storage has data, zone='" << authdomain << "'" << endl);
+  return doOOBResolve(iter->second, qname, qtype, ret, res);
+}
+
+bool SyncRes::isRecursiveForwardOrAuth(const DNSName& qname)
+{
+  DNSName authname(qname);
+  const auto iter = getBestAuthZone(&authname);
+  return iter != t_sstorage.domainmap->end() && (iter->second.isAuth() || iter->second.shouldRecurse());
+}
+
+bool SyncRes::isForwardOrAuth(const DNSName& qname)
+{
+  DNSName authname(qname);
+  const auto iter = getBestAuthZone(&authname);
+  return iter != t_sstorage.domainmap->end();
+}
+
+const char* isoDateTimeMillis(const struct timeval& tval, timebuf_t& buf)
+{
+  const std::string s_timestampFormat = "%Y-%m-%dT%T";
+  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.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 (buf.size() > len + 4) {
+    snprintf(&buf.at(len), buf.size() - len, ".%03ld", static_cast<long>(tval.tv_usec) / 1000);
+  }
+  return buf.data();
+}
+
+static const char* timestamp(time_t arg, timebuf_t& buf)
+{
+  const std::string s_timestampFormat = "%Y-%m-%dT%T";
+  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.data(), buf.size(), "%lld", static_cast<long long>(arg));
+    if (ret < 0 || static_cast<size_t>(ret) >= buf.size()) {
+      buf[0] = '\0';
+    }
+  }
+  return buf.data();
+}
+
+struct ednsstatus_t : public multi_index_container<SyncRes::EDNSStatus,
+                                                   indexed_by<
+                                                     ordered_unique<tag<ComboAddress>, member<SyncRes::EDNSStatus, ComboAddress, &SyncRes::EDNSStatus::address>>,
+                                                     ordered_non_unique<tag<time_t>, member<SyncRes::EDNSStatus, time_t, &SyncRes::EDNSStatus::ttd>>>>
+{
+  // Get a copy
+  [[nodiscard]] ednsstatus_t getMap() const
+  {
+    return *this;
+  }
+
+  static void setMode(index<ComboAddress>::type& ind, iterator iter, SyncRes::EDNSStatus::EDNSMode mode, time_t theTime)
+  {
+    if (iter->mode != mode || iter->ttd == 0) {
+      ind.modify(iter, [=](SyncRes::EDNSStatus& status) { status.mode = mode; status.ttd = theTime + Expire; });
+    }
+  }
+
+  void prune(time_t now)
+  {
+    auto& ind = get<time_t>();
+    ind.erase(ind.begin(), ind.upper_bound(now));
+  }
+
+  static const time_t Expire = 7200;
+};
+
+static LockGuarded<ednsstatus_t> s_ednsstatus;
+
+SyncRes::EDNSStatus::EDNSMode SyncRes::getEDNSStatus(const ComboAddress& server)
+{
+  auto lock = s_ednsstatus.lock();
+  const auto& iter = lock->find(server);
+  if (iter == lock->end()) {
+    return EDNSStatus::EDNSOK;
+  }
+  return iter->mode;
+}
+
+uint64_t SyncRes::getEDNSStatusesSize()
+{
+  return s_ednsstatus.lock()->size();
+}
+
+void SyncRes::clearEDNSStatuses()
+{
+  s_ednsstatus.lock()->clear();
+}
+
+void SyncRes::pruneEDNSStatuses(time_t cutoff)
+{
+  s_ednsstatus.lock()->prune(cutoff);
+}
+
+uint64_t SyncRes::doEDNSDump(int fileDesc)
+{
+  int newfd = dup(fileDesc);
+  if (newfd == -1) {
+    return 0;
+  }
+  auto filePtr = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
+  if (!filePtr) {
+    close(newfd);
+    return 0;
+  }
+  uint64_t count = 0;
+
+  fprintf(filePtr.get(), "; edns dump follows\n; ip\tstatus\tttd\n");
+  const auto copy = s_ednsstatus.lock()->getMap();
+  for (const auto& eds : copy) {
+    count++;
+    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;
+}
+
+void SyncRes::pruneNSSpeeds(time_t limit)
+{
+  auto lock = s_nsSpeeds.lock();
+  auto& ind = lock->get<timeval>();
+  ind.erase(ind.begin(), ind.upper_bound(timeval{limit, 0}));
+}
+
+uint64_t SyncRes::getNSSpeedsSize()
+{
+  return s_nsSpeeds.lock()->size();
+}
+
+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(address, usec, now);
+}
+
+void SyncRes::clearNSSpeeds()
+{
+  s_nsSpeeds.lock()->clear();
+}
+
+float SyncRes::getNSSpeed(const DNSName& server, const ComboAddress& address)
+{
+  auto lock = s_nsSpeeds.lock();
+  return lock->find_or_enter(server).d_collection[address].peek();
+}
+
+uint64_t SyncRes::doDumpNSSpeeds(int fileDesc)
+{
+  int newfd = dup(fileDesc);
+  if (newfd == -1) {
+    return 0;
+  }
+  auto filePtr = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
+  if (!filePtr) {
+    close(newfd);
+    return 0;
+  }
+
+  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& iter : *s_nsSpeeds.lock()) {
+    count++;
+
+    // an <empty> can appear hear in case of authoritative (hosted) zones
+    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& 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(filePtr.get(), "\n");
+  }
+  return count;
+}
+
+uint64_t SyncRes::getThrottledServersSize()
+{
+  return s_throttle.lock()->size();
+}
+
+void SyncRes::pruneThrottledServers(time_t now)
+{
+  s_throttle.lock()->prune(now);
+}
+
+void SyncRes::clearThrottle()
+{
+  s_throttle.lock()->clear();
+}
+
+bool SyncRes::isThrottled(time_t now, const ComboAddress& server, const DNSName& target, QType qtype)
+{
+  return s_throttle.lock()->shouldThrottle(now, std::tuple(server, target, qtype));
+}
+
+bool SyncRes::isThrottled(time_t now, const ComboAddress& server)
+{
+  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::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::tuple(server, name, qtype), duration, tries);
+}
+
+uint64_t SyncRes::doDumpThrottleMap(int fileDesc)
+{
+  int newfd = dup(fileDesc);
+  if (newfd == -1) {
+    return 0;
+  }
+  auto filePtr = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
+  if (!filePtr) {
+    close(newfd);
+    return 0;
+  }
+  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& iter : throttleMap) {
+    count++;
+    timebuf_t tmp;
+    // remote IP, dns name, qtype, count, ttd
+    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;
+}
+
+uint64_t SyncRes::getFailedServersSize()
+{
+  return s_fails.lock()->size();
+}
+
+void SyncRes::clearFailedServers()
+{
+  s_fails.lock()->clear();
+}
+
+void SyncRes::pruneFailedServers(time_t cutoff)
+{
+  s_fails.lock()->prune(cutoff);
+}
+
+unsigned long SyncRes::getServerFailsCount(const ComboAddress& server)
+{
+  return s_fails.lock()->value(server);
+}
+
+uint64_t SyncRes::doDumpFailedServers(int fileDesc)
+{
+  int newfd = dup(fileDesc);
+  if (newfd == -1) {
+    return 0;
+  }
+  auto filePtr = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
+  if (!filePtr) {
+    close(newfd);
+    return 0;
+  }
+  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& iter : s_fails.lock()->getMapCopy()) {
+    count++;
+    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;
+}
+
+uint64_t SyncRes::getNonResolvingNSSize()
+{
+  return s_nonresolving.lock()->size();
+}
+
+void SyncRes::clearNonResolvingNS()
+{
+  s_nonresolving.lock()->clear();
+}
+
+void SyncRes::pruneNonResolving(time_t cutoff)
+{
+  s_nonresolving.lock()->prune(cutoff);
+}
+
+uint64_t SyncRes::doDumpNonResolvingNS(int fileDesc)
+{
+  int newfd = dup(fileDesc);
+  if (newfd == -1) {
+    return 0;
+  }
+  auto filePtr = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
+  if (!filePtr) {
+    close(newfd);
+    return 0;
+  }
+  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& iter : s_nonresolving.lock()->getMapCopy()) {
+    count++;
+    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;
+}
+
+void SyncRes::clearSaveParentsNSSets()
+{
+  s_savedParentNSSet.lock()->clear();
+}
+
+size_t SyncRes::getSaveParentsNSSetsSize()
+{
+  return s_savedParentNSSet.lock()->size();
+}
+
+void SyncRes::pruneSaveParentsNSSets(time_t now)
+{
+  s_savedParentNSSet.lock()->prune(now);
+}
+
+uint64_t SyncRes::doDumpSavedParentNSSets(int fileDesc)
+{
+  int newfd = dup(fileDesc);
+  if (newfd == -1) {
+    return 0;
+  }
+  auto filePtr = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
+  if (!filePtr) {
+    close(newfd);
+    return 0;
+  }
+  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& iter : s_savedParentNSSet.lock()->getMapCopy()) {
+    if (iter.d_count == 0) {
+      continue;
+    }
+    count++;
+    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;
+}
+
+void SyncRes::pruneDoTProbeMap(time_t cutoff)
+{
+  auto lock = s_dotMap.lock();
+  auto& ind = lock->d_map.get<time_t>();
+
+  for (auto i = ind.begin(); i != ind.end();) {
+    if (i->d_ttd >= cutoff) {
+      // We're done as we loop ordered by d_ttd
+      break;
+    }
+    if (i->d_status == DoTStatus::Status::Busy) {
+      lock->d_numBusy--;
+    }
+    i = ind.erase(i);
+  }
+}
+
+uint64_t SyncRes::doDumpDoTProbeMap(int fileDesc)
+{
+  int newfd = dup(fileDesc);
+  if (newfd == -1) {
+    return 0;
+  }
+  auto filePtr = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
+  if (!filePtr) {
+    close(newfd);
+    return 0;
+  }
+  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
+  DoTMap copy;
+  {
+    copy = *s_dotMap.lock();
+  }
+  fprintf(filePtr.get(), "; %" PRIu64 " Busy entries\n", copy.d_numBusy);
+  for (const auto& iter : copy.d_map) {
+    count++;
+    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;
+}
+
+/* so here is the story. First we complete the full resolution process for a domain name. And only THEN do we decide
+   to also do DNSSEC validation, which leads to new queries. To make this simple, we *always* ask for DNSSEC records
+   so that if there are RRSIGs for a name, we'll have them.
+
+   However, some hosts simply can't answer questions which ask for DNSSEC. This can manifest itself as:
+   * No answer
+   * FormErr
+   * Nonsense answer
+
+   The cause of "No answer" may be fragmentation, and it is tempting to probe if smaller answers would get through.
+   Another cause of "No answer" may simply be a network condition.
+   Nonsense answers are a clearer indication this host won't be able to do DNSSEC evah.
+
+   Previous implementations have suffered from turning off DNSSEC questions for an authoritative server based on timeouts.
+   A clever idea is to only turn off DNSSEC if we know a domain isn't signed anyhow. The problem with that really
+   clever idea however is that at this point in PowerDNS, we may simply not know that yet. All the DNSSEC thinking happens
+   elsewhere. It may not have happened yet.
+
+   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& 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
+     The levels are:
+
+     1) EDNSOK: Honors EDNS0, absent from table
+     2) EDNSIGNORANT: Ignores EDNS0, gives replies without EDNS0
+     3) NOEDNS: Generates FORMERR on EDNS queries
+
+     Everybody starts out assumed to be EDNSOK.
+     If EDNSOK, send out EDNS0
+        If you FORMERR us, go to NOEDNS,
+        If no EDNS in response, go to EDNSIGNORANT
+     If EDNSIGNORANT, keep on including EDNS0, see what happens
+        Same behaviour as EDNSOK
+     If NOEDNS, send bare queries
+  */
+
+  // Read current status, defaulting to OK
+  SyncRes::EDNSStatus::EDNSMode mode = EDNSStatus::EDNSOK;
+  {
+    auto lock = s_ednsstatus.lock();
+    auto ednsstatus = lock->find(address); // does this include port? YES
+    if (ednsstatus != lock->end()) {
+      if (ednsstatus->ttd != 0 && ednsstatus->ttd < d_now.tv_sec) {
+        lock->erase(ednsstatus);
+      }
+      else {
+        mode = ednsstatus->mode;
+      }
+    }
+  }
+
+  int EDNSLevel = 0;
+  auto luaconfsLocal = g_luaconfs.getLocal();
+  ResolveContext ctx(d_initialRequestId, nsName);
+#ifdef HAVE_FSTRM
+  ctx.d_auth = auth;
+#endif
+
+  LWResult::Result ret{};
+
+  for (int tries = 0; tries < 2; ++tries) {
+
+    if (mode == EDNSStatus::NOEDNS) {
+      t_Counters.at(rec::Counter::noEdnsOutQueries)++;
+      EDNSLevel = 0; // level != mode
+    }
+    else if (ednsMANDATORY || mode != EDNSStatus::NOEDNS) {
+      EDNSLevel = 1;
+    }
+
+    DNSName sendQname(domain);
+    if (g_lowercaseOutgoing) {
+      sendQname.makeUsLowerCase();
+    }
+
+    if (d_asyncResolve) {
+      ret = d_asyncResolve(address, sendQname, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, ctx, res, chained);
+    }
+    else {
+      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) {
+      break; // transport error, nothing to learn here
+    }
+
+    if (ret == LWResult::Result::Timeout) { // timeout, not doing anything with it now
+      break;
+    }
+
+    if (EDNSLevel == 1) {
+      // We sent out with EDNS
+      // ret is LWResult::Result::Success
+      // ednsstatus in table might be pruned or changed by another request/thread, so do a new lookup/insert if needed
+      auto lock = s_ednsstatus.lock(); // all three branches below need a lock
+
+      // Determine new mode
+      if (res->d_validpacket && !res->d_haveEDNS && res->d_rcode == RCode::FormErr) {
+        mode = EDNSStatus::NOEDNS;
+        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;
+      }
+      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(address);
+      }
+    }
+
+    break;
+  }
+  return ret;
+}
+
+/* The parameters from rfc9156. */
+/* 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 qmIteration)
+{
+  unsigned int step{};
+
+  if (qmIteration < SyncRes::s_minimize_one_label) {
+    step = 1;
+  }
+  else if (qmIteration < SyncRes::s_max_minimize_count) {
+    step = std::max(1U, (qnamelen - labels) / (SyncRes::s_max_minimize_count - qmIteration));
+  }
+  else {
+    step = qnamelen - labels;
+  }
+  unsigned int targetlen = std::min(labels + step, qnamelen);
+  return targetlen;
+}
+
+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();
+
+  /* Apply qname (including CNAME chain) filtering policies */
+  if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
+    if (luaconfsLocal->dfe.getQueryPolicy(qname, d_discardedPolicies, d_appliedPolicy)) {
+      mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+      bool done = false;
+      int rcode = RCode::NoError;
+      handlePolicyHit(prefix, qname, qtype, ret, done, rcode, depth);
+      if (done) {
+        return rcode;
+      }
+    }
+  }
+
+  initZoneCutsFromTA(qname, prefix);
+
+  // In the auth or recursive forward case, it does not make sense to do qname-minimization
+  if (!getQNameMinimization() || isRecursiveForwardOrAuth(qname)) {
+    return doResolveNoQNameMinimization(qname, qtype, ret, depth, beenthere, context);
+  }
+
+  // The qname minimization algorithm is a simplified version of the one in RFC 7816 (bis).
+  // It could be simplified because the cache maintenance (both positive and negative)
+  // is already done by doResolveNoQNameMinimization().
+  //
+  // Sketch of algorithm:
+  // Check cache
+  //  If result found: done
+  //  Otherwise determine closes ancestor from cache data
+  //    Repeat querying A, adding more labels of the original qname
+  //    If we get a delegation continue at ancestor determination
+  //    Until we have the full name.
+  //
+  // The algorithm starts with adding a single label per iteration, and
+  // moves to three labels per iteration after three iterations.
+
+  DNSName child;
+  prefix.append(string("QM "));
+
+  LOG(prefix << qname << ": doResolve" << endl);
+
+  // Look in cache only
+  vector<DNSRecord> retq;
+  bool old = setCacheOnly(true);
+  bool fromCache = false;
+  // For cache peeking, we tell doResolveNoQNameMinimization not to consider the (non-recursive) forward case.
+  // Otherwise all queries in a forward domain will be forwarded, while we want to consult the cache.
+  int res = doResolveNoQNameMinimization(qname, qtype, retq, depth, beenthere, context, &fromCache, nullptr);
+  setCacheOnly(old);
+  if (fromCache) {
+    LOG(prefix << qname << ": Step0 Found in cache" << endl);
+    if (d_appliedPolicy.d_type != DNSFilterEngine::PolicyType::None && (d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NXDOMAIN || d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NODATA)) {
+      ret.clear();
+    }
+    ret.insert(ret.end(), retq.begin(), retq.end());
+
+    return res;
+  }
+  LOG(prefix << qname << ": Step0 Not cached" << endl);
+
+  const unsigned int qnamelen = qname.countLabels();
+
+  DNSName fwdomain(qname);
+  const bool forwarded = getBestAuthZone(&fwdomain) != t_sstorage.domainmap->end();
+  if (forwarded) {
+    LOG(prefix << qname << ": Step0 qname is in a forwarded domain " << fwdomain << endl);
+  }
+
+  for (unsigned int i = 0; i <= qnamelen; i++) {
+
+    // Step 1
+    vector<DNSRecord> bestns;
+    DNSName nsdomain(qname);
+    if (qtype == QType::DS) {
+      nsdomain.chopOff();
+    }
+    // the two retries allow getBestNSFromCache&co to reprime the root
+    // hints, in case they ever go missing
+    for (int tries = 0; tries < 2 && bestns.empty(); ++tries) {
+      bool flawedNSSet = false;
+      set<GetBestNSAnswer> beenthereIgnored;
+      getBestNSFromCache(nsdomain, qtype, bestns, &flawedNSSet, depth, prefix, beenthereIgnored, boost::make_optional(forwarded, fwdomain));
+      if (forwarded) {
+        break;
+      }
+    }
+
+    if (bestns.empty()) {
+      if (!forwarded) {
+        // Something terrible is wrong
+        LOG(prefix << qname << ": Step1 No ancestor found return ServFail" << endl);
+        return RCode::ServFail;
+      }
+      child = fwdomain;
+    }
+    else {
+      LOG(prefix << qname << ": Step1 Ancestor from cache is " << bestns[0].d_name << endl);
+      if (forwarded) {
+        child = bestns[0].d_name.isPartOf(fwdomain) ? bestns[0].d_name : fwdomain;
+        LOG(prefix << qname << ": Step1 Final Ancestor (using forwarding info) is " << child << endl);
+      }
+      else {
+        child = bestns[0].d_name;
+      }
+    }
+    for (; i <= qnamelen; i++) {
+      // Step 2
+      unsigned int labels = child.countLabels();
+      unsigned int targetlen = qmStepLen(labels, qnamelen, i);
+
+      while (labels < targetlen) {
+        child.prependRawLabel(qname.getRawLabel(qnamelen - labels - 1));
+        labels++;
+      }
+      // rfc9156 section-2.3, append labels if they start with an underscore
+      while (labels < qnamelen) {
+        auto prependLabel = qname.getRawLabel(qnamelen - labels - 1);
+        if (prependLabel.at(0) != '_') {
+          break;
+        }
+        child.prependRawLabel(prependLabel);
+        labels++;
+      }
+
+      LOG(prefix << qname << ": Step2 New child " << child << endl);
+
+      // Step 3 resolve
+      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: " << resToString(res) << "/" << ret.size() << endl);
+        return res;
+      }
+
+      // 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) {
+          qmLoopDetected = true;
+          break;
+        }
+      }
+      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
+      LOG(prefix << qname << ": Step4 Resolve A for child " << child << endl);
+      bool oldFollowCNAME = d_followCNAME;
+      d_followCNAME = false;
+      retq.resize(0);
+      StopAtDelegation stopAtDelegation = Stop;
+      res = doResolveNoQNameMinimization(child, QType::A, retq, depth, beenthere, context, nullptr, &stopAtDelegation);
+      d_followCNAME = oldFollowCNAME;
+      LOG(prefix << qname << ": Step4 Resolve " << child << "|A result is " << RCode::to_s(res) << "/" << retq.size() << "/" << stopAtDelegation << endl);
+      if (stopAtDelegation == Stopped) {
+        LOG(prefix << qname << ": Delegation seen, continue at step 1" << endl);
+        break;
+      }
+
+      if (res != RCode::NoError) {
+        // Case 5: unexpected answer
+        LOG(prefix << qname << ": Step5: other rcode, last effort final resolve" << endl);
+        setQNameMinimization(false);
+        setQMFallbackMode(true);
+
+        auto oldEDE = context.extendedError;
+        res = doResolveNoQNameMinimization(qname, qtype, ret, depth + 1, beenthere, context);
+
+        if (res == RCode::NoError) {
+          t_Counters.at(rec::Counter::qnameminfallbacksuccess)++;
+        }
+        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 = std::move(oldEDE);
+          }
+        }
+
+        LOG(prefix << qname << ": Step5 End resolve: " << resToString(res) << "/" << ret.size() << endl);
+        return res;
+      }
+    }
+  }
+
+  // Should not be reached
+  LOG(prefix << qname << ": Max iterations reached, return ServFail" << endl);
+  return RCode::ServFail;
+}
+
+unsigned int SyncRes::getAdjustedRecursionBound() const
+{
+  auto bound = s_maxdepth; // 40 is default value of s_maxdepth
+  if (getQMFallbackMode()) {
+    // We might have hit a depth level check, but we still want to allow some recursion levels in the fallback
+    // no-qname-minimization case. This has the effect that a qname minimization fallback case might reach 150% of
+    // maxdepth, taking care to not repeatedly increase the bound.
+    bound += s_maxdepth / 2;
+  }
+  return bound;
+}
+
+/*! This function will check the cache and go out to the internet if the answer is not in cache
+ *
+ * \param qname The name we need an answer for
+ * \param qtype
+ * \param ret The vector of DNSRecords we need to fill with the answers
+ * \param depth The recursion depth we are in
+ * \param beenthere
+ * \param fromCache tells the caller the result came from the cache, may be nullptr
+ * \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) // 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();
+    // 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(std::move(msg));
+    }
+  }
+
+  int res = 0;
+
+  const int iterations = !d_refresh && MemRecursorCache::s_maxServedStaleExtensions > 0 ? 2 : 1;
+  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()) {
+      DNSName authname(qname);
+      const auto iter = getBestAuthZone(&authname);
+
+      if (d_cacheonly) {
+        if (iter != t_sstorage.domainmap->end()) {
+          if (iter->second.isAuth()) {
+            LOG(prefix << qname << ": Cache only lookup for '" << qname << "|" << qtype << "', in auth zone" << endl);
+            ret.clear();
+            d_wasOutOfBand = doOOBResolve(qname, qtype, ret, depth, prefix, res);
+            if (fromCache != nullptr) {
+              *fromCache = d_wasOutOfBand;
+            }
+            return res;
+          }
+        }
+      }
+
+      bool wasForwardedOrAuthZone = false;
+      bool wasAuthZone = false;
+      bool wasForwardRecurse = false;
+
+      if (iter != t_sstorage.domainmap->end()) {
+        wasForwardedOrAuthZone = true;
+
+        if (iter->second.isAuth()) {
+          wasAuthZone = true;
+        }
+        else if (iter->second.shouldRecurse()) {
+          wasForwardRecurse = true;
+        }
+      }
+
+      /* 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, 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.
+        // It means that we will sometimes go to the next steps when we are in fact done, but that's fine since
+        // we will get the records from the cache, resulting in a small overhead.
+        // This might be a real problem if we had a RPZ hit, though, because we do not want the processing to continue, since
+        // RPZ rules will not be evaluated anymore (we already matched).
+        const bool stoppedByPolicyHit = d_appliedPolicy.wasHit();
+
+        if (fromCache != nullptr && (!d_cacheonly || stoppedByPolicyHit)) {
+          *fromCache = true;
+        }
+        /* Apply Post filtering policies */
+
+        if (d_wantsRPZ && !stoppedByPolicyHit) {
+          auto luaLocal = g_luaconfs.getLocal();
+          if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
+            mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+            bool done = false;
+            handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
+            if (done && fromCache != nullptr) {
+              *fromCache = true;
+            }
+          }
+        }
+        return res;
+      }
+
+      if (doCacheCheck(qname, authname, wasForwardedOrAuthZone, wasAuthZone, wasForwardRecurse, qtype, ret, depth, prefix, res, context)) {
+        // we done
+        d_wasOutOfBand = wasAuthZone;
+        if (fromCache != nullptr) {
+          *fromCache = true;
+        }
+
+        if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
+          auto luaLocal = g_luaconfs.getLocal();
+          if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
+            mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+            bool done = false;
+            handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
+          }
+        }
+
+        return res;
+      }
+
+      /* if 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, 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.
+        // It means that we will sometimes go to the next steps when we are in fact done, but that's fine since
+        // we will get the records from the cache, resulting in a small overhead.
+        // This might be a real problem if we had a RPZ hit, though, because we do not want the processing to continue, since
+        // RPZ rules will not be evaluated anymore (we already matched).
+        const bool stoppedByPolicyHit = d_appliedPolicy.wasHit();
+
+        if (fromCache != nullptr && (!d_cacheonly || stoppedByPolicyHit)) {
+          *fromCache = true;
+        }
+        /* Apply Post filtering policies */
+
+        if (d_wantsRPZ && !stoppedByPolicyHit) {
+          auto luaLocal = g_luaconfs.getLocal();
+          if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
+            mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+            bool done = false;
+            handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
+            if (done && fromCache != nullptr) {
+              *fromCache = true;
+            }
+          }
+        }
+
+        return res;
+      }
+    }
+
+    if (d_cacheonly) {
+      return 0;
+    }
+
+    // When trying to serve-stale, we also only look at the cache. Don't look at d_serveStale, it
+    // might be changed by recursive calls (this should be fixed in a better way!).
+    if (loop == 1) {
+      return res;
+    }
+
+    LOG(prefix << qname << ": No cache hit for '" << qname << "|" << qtype << "', trying to find an appropriate NS record" << endl);
+
+    DNSName subdomain(qname);
+    if (qtype == QType::DS) {
+      subdomain.chopOff();
+    }
+
+    NsSet nsset;
+    bool flawedNSSet = false;
+
+    // the two retries allow getBestNSNamesFromCache&co to reprime the root
+    // hints, in case they ever go missing
+    for (int tries = 0; tries < 2 && nsset.empty(); ++tries) {
+      subdomain = getBestNSNamesFromCache(subdomain, qtype, nsset, &flawedNSSet, depth, prefix, beenthere); //  pass beenthere to both occasions
+    }
+
+    res = doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, prefix, beenthere, context, stopAtDelegation, nullptr);
+
+    if (res == -1 && s_save_parent_ns_set) {
+      // It did not work out, lets check if we have a saved parent NS set
+      map<DNSName, vector<ComboAddress>> fallBack;
+      {
+        auto lock = s_savedParentNSSet.lock();
+        auto domainData = lock->find(subdomain);
+        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& nsAddress : domainData->d_nsAddresses) {
+            nsset.emplace(nsAddress.first, pair(std::vector<ComboAddress>(), false));
+            fallBack.emplace(nsAddress.first, nsAddress.second);
+          }
+        }
+      }
+      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) {
+          // It did work out
+          s_savedParentNSSet.lock()->inc(subdomain);
+        }
+      }
+    }
+    /* Apply Post filtering policies */
+    if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
+      auto luaLocal = g_luaconfs.getLocal();
+      if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
+        mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+        bool done = false;
+        handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
+      }
+    }
+
+    if (res == 0) {
+      return 0;
+    }
+
+    LOG(prefix << qname << ": Failed (res=" << res << ")" << endl);
+    if (res >= 0) {
+      break;
+    }
+  }
+  return res < 0 ? RCode::ServFail : res;
+}
+
+#if 0
+// for testing purposes
+static bool ipv6First(const ComboAddress& a, const ComboAddress& b)
+{
+  return !(a.sin4.sin_family < a.sin4.sin_family);
+}
+#endif
+
+struct speedOrderCA
+{
+  speedOrderCA(std::map<ComboAddress, float>& speeds) :
+    d_speeds(speeds) {}
+  bool operator()(const ComboAddress& lhs, const ComboAddress& rhs) const
+  {
+    return d_speeds[lhs] < d_speeds[rhs];
+  }
+  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)
+{
+  typedef vector<DNSRecord> res_t;
+  typedef vector<ComboAddress> ret_t;
+  ret_t ret;
+
+  bool oldCacheOnly = setCacheOnly(cacheOnly);
+  bool oldRequireAuthData = d_requireAuthData;
+  bool oldValidationRequested = d_DNSSECValidationRequested;
+  bool oldFollowCNAME = d_followCNAME;
+  bool seenV6 = false;
+  const unsigned int startqueries = d_outqueries;
+  d_requireAuthData = false;
+  d_DNSSECValidationRequested = false;
+  d_followCNAME = false;
+
+  MemRecursorCache::Flags flags = MemRecursorCache::None;
+  if (d_serveStale) {
+    flags |= MemRecursorCache::ServeStale;
+  }
+  try {
+    // 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) {
+      collectAddresses<ARecordContent>(cset, ret);
+    }
+    if (s_doIPv6 && g_recCache->get(d_now.tv_sec, qname, QType::AAAA, flags, &cset, d_cacheRemote, d_routingTag) > 0) {
+      if (collectAddresses<AAAARecordContent>(cset, ret)) {
+        seenV6 = true;
+      }
+    }
+    if (ret.empty()) {
+      // Neither A nor AAAA in the cache...
+      Context newContext1;
+      cset.clear();
+      // Go out to get A's
+      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 (doResolveNoQNameMinimization(qname, QType::AAAA, cset, depth + 1, beenthere, newContext2) == 0) { // this consults cache, OR goes out
+            if (collectAddresses<AAAARecordContent>(cset, ret)) {
+              seenV6 = true;
+            }
+          }
+        }
+        else {
+          // 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) {
+            if (collectAddresses<AAAARecordContent>(cset, ret)) {
+              seenV6 = true;
+            }
+          }
+        }
+      }
+    }
+    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
+      pushResolveIfNotInNegCache(qname, QType::AAAA, d_now);
+    }
+  }
+  catch (const PolicyHitException&) {
+    // We ignore a policy hit while trying to retrieve the addresses
+    // of a NS and keep processing the current query
+  }
+
+  if (ret.empty() && d_outqueries > startqueries) {
+    // We did 1 or more outgoing queries to resolve this NS name but returned empty handed
+    addressQueriesForNS++;
+  }
+  d_requireAuthData = oldRequireAuthData;
+  d_DNSSECValidationRequested = oldValidationRequested;
+  setCacheOnly(oldCacheOnly);
+  d_followCNAME = oldFollowCNAME;
+
+  if (s_max_busy_dot_probes > 0 && s_dot_to_port_853) {
+    for (auto& add : ret) {
+      if (shouldDoDoT(add, d_now.tv_sec)) {
+        add.setPort(853);
+      }
+    }
+  }
+  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) // NOLINT(readability-function-cognitive-complexity)
+{
+  DNSName subdomain(qname);
+  bestns.clear();
+  bool brokeloop = false;
+  MemRecursorCache::Flags flags = MemRecursorCache::None;
+  if (d_serveStale) {
+    flags |= MemRecursorCache::ServeStale;
+  }
+  do {
+    if (cutOffDomain && (subdomain == *cutOffDomain || !subdomain.isPartOf(*cutOffDomain))) {
+      break;
+    }
+    brokeloop = false;
+    LOG(prefix << qname << ": Checking if we have NS in cache for '" << subdomain << "'" << endl);
+    vector<DNSRecord> nsVector;
+    *flawedNSSet = false;
+
+    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(nsVector.cbegin(), nsVector.cend(), std::back_inserter(selected), s_maxnsperresolve, pdns::dns_random_engine());
+        nsVector = std::move(selected);
+      }
+      bestns.reserve(nsVector.size());
+
+      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) {
+            nsqt = QType::A;
+          }
+          else if (!s_doIPv4 && s_doIPv6) {
+            nsqt = QType::AAAA;
+          }
+
+          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()) {
+              LOG(", in cache, ttl=" << (unsigned int)(((time_t)aset.begin()->d_ttl - d_now.tv_sec)) << endl);
+            }
+            else {
+              LOG(", not in cache / did not look at cache" << endl);
+            }
+          }
+          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);
+          }
+        }
+      }
+
+      if (!bestns.empty()) {
+        GetBestNSAnswer answer;
+        answer.qname = qname;
+        answer.qtype = qtype.getCode();
+        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 " << insertionPair.first->qname << ")! Trying less specific NS" << endl);
+          ;
+          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 {
+          LOG(prefix << qname << ": We have NS in cache for '" << subdomain << "' (flawedNSSet=" << *flawedNSSet << ")" << endl);
+          return;
+        }
+      }
+    }
+    LOG(prefix << qname << ": No valid/useful NS in cache for '" << subdomain << "'" << endl);
+
+    if (subdomain.isRoot() && !brokeloop) {
+      // We lost the root NS records
+      primeHints();
+      LOG(prefix << qname << ": Reprimed the root" << endl);
+      /* let's prevent an infinite loop */
+      if (!d_updatingRootNS) {
+        auto log = g_slog->withName("housekeeping");
+        getRootNS(d_now, d_asyncResolve, depth, log);
+      }
+    }
+  } while (subdomain.chopOff());
+}
+
+SyncRes::domainmap_t::const_iterator SyncRes::getBestAuthZone(DNSName* qname)
+{
+  if (t_sstorage.domainmap->empty()) {
+    return t_sstorage.domainmap->end();
+  }
+
+  SyncRes::domainmap_t::const_iterator ret;
+  do {
+    ret = t_sstorage.domainmap->find(*qname);
+    if (ret != t_sstorage.domainmap->end()) {
+      break;
+    }
+  } while (qname->chopOff());
+  return ret;
+}
+
+/** doesn't actually do the work, leaves that to getBestNSFromCache */
+DNSName SyncRes::getBestNSNamesFromCache(const DNSName& qname, const QType qtype, NsSet& nsset, bool* flawedNSSet, unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere)
+{
+  DNSName authOrForwDomain(qname);
+
+  auto iter = getBestAuthZone(&authOrForwDomain);
+  // We have an auth, forwarder of forwarder-recurse
+  if (iter != t_sstorage.domainmap->end()) {
+    if (iter->second.isAuth()) {
+      // this gets picked up in doResolveAt, the empty DNSName, combined with the
+      // empty vector means 'we are auth for this zone'
+      nsset.insert({DNSName(), {{}, false}});
+      return authOrForwDomain;
+    }
+    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;
+    }
+  }
+
+  // We might have a (non-recursive) forwarder, but maybe the cache already contains
+  // a better NS
+  vector<DNSRecord> bestns;
+  DNSName nsFromCacheDomain(g_rootdnsname);
+  getBestNSFromCache(qname, qtype, bestns, flawedNSSet, depth, prefix, beenthere);
+
+  // Pick up the auth domain
+  for (const auto& nsRecord : bestns) {
+    const auto nsContent = getRR<NSRecordContent>(nsRecord);
+    if (nsContent) {
+      nsFromCacheDomain = nsRecord.d_name;
+      break;
+    }
+  }
+
+  if (iter != t_sstorage.domainmap->end()) {
+    if (doLog()) {
+      LOG(prefix << qname << " authOrForwDomain: " << authOrForwDomain << " nsFromCacheDomain: " << nsFromCacheDomain << " isPartof: " << authOrForwDomain.isPartOf(nsFromCacheDomain) << endl);
+    }
+
+    // If the forwarder is better or equal to what's found in the cache, use forwarder. Note that name.isPartOf(name).
+    // So queries that get NS for authOrForwDomain itself go to the forwarder
+    if (authOrForwDomain.isPartOf(nsFromCacheDomain)) {
+      if (doLog()) {
+        LOG(prefix << qname << ": Using forwarder as NS" << endl);
+      }
+      nsset.insert({DNSName(), {iter->second.d_servers, false}});
+      return authOrForwDomain;
+    }
+    if (doLog()) {
+      LOG(prefix << qname << ": Using NS from cache" << endl);
+    }
+  }
+  for (const auto& bestn : bestns) {
+    // The actual resolver code will not even look at the ComboAddress or bool
+    const auto nsContent = getRR<NSRecordContent>(bestn);
+    if (nsContent) {
+      nsset.insert({nsContent->getNS(), {{}, false}});
+    }
+  }
+  return nsFromCacheDomain;
+}
+
+void SyncRes::updateValidationStatusInCache(const DNSName& qname, const QType qtype, bool aaFlag, vState newState) const
+{
+  if (qtype == QType::ANY || qtype == QType::ADDR) {
+    // not doing that
+    return;
+  }
+
+  if (vStateIsBogus(newState)) {
+    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, qtype, d_cacheRemote, d_routingTag, aaFlag, newState, boost::none);
+  }
+}
+
+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, numCNames};
+      }
+    }
+  }
+  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 checkForDups) // NOLINT(readability-function-cognitive-complexity)
+{
+  vector<DNSRecord> cset;
+  vector<std::shared_ptr<const RRSIGRecordContent>> signatures;
+  vector<std::shared_ptr<DNSRecord>> authorityRecs;
+  bool wasAuth = false;
+  uint32_t capTTL = std::numeric_limits<uint32_t>::max();
+  DNSName foundName;
+  DNSName authZone;
+  QType foundQT = QType::ENT;
+
+  /* we don't require auth data for forward-recurse lookups */
+  MemRecursorCache::Flags flags = MemRecursorCache::None;
+  if (!wasForwardRecurse && d_requireAuthData) {
+    flags |= MemRecursorCache::RequireAuth;
+  }
+  if (d_refresh) {
+    flags |= MemRecursorCache::Refresh;
+  }
+  if (d_serveStale) {
+    flags |= MemRecursorCache::ServeStale;
+  }
+  if (g_recCache->get(d_now.tv_sec, qname, QType::CNAME, flags, &cset, d_cacheRemote, d_routingTag, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &context.state, &wasAuth, &authZone, &d_fromAuthIP) > 0) {
+    foundName = qname;
+    foundQT = QType::CNAME;
+  }
+
+  if (foundName.empty() && qname != g_rootdnsname) {
+    // look for a DNAME cache hit
+    auto labels = qname.getRawLabels();
+    DNSName dnameName(g_rootdnsname);
+
+    do {
+      dnameName.prependRawLabel(labels.back());
+      labels.pop_back();
+      if (dnameName == qname && qtype != QType::DNAME) { // The client does not want a DNAME, but we've reached the QNAME already. So there is no match
+        break;
+      }
+      if (g_recCache->get(d_now.tv_sec, dnameName, QType::DNAME, flags, &cset, d_cacheRemote, d_routingTag, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &context.state, &wasAuth, &authZone, &d_fromAuthIP) > 0) {
+        foundName = dnameName;
+        foundQT = QType::DNAME;
+        break;
+      }
+    } while (!labels.empty());
+  }
+
+  if (foundName.empty()) {
+    return false;
+  }
+
+  if (qtype == QType::DS && authZone == qname) {
+    /* CNAME at APEX of the child zone, we can't use that to prove that
+       there is no DS */
+    LOG(prefix << qname << ": Found a " << foundQT.toString() << " cache hit of '" << qname << "' from " << authZone << ", but such a record at the apex of the child zone does not prove that there is no DS in the parent zone" << endl);
+    return false;
+  }
+
+  for (auto const& record : cset) {
+    if (record.d_class != QClass::IN) {
+      continue;
+    }
+
+    if (record.d_ttl > (unsigned int)d_now.tv_sec) {
+
+      if (!wasAuthZone && shouldValidate() && (wasAuth || wasForwardRecurse) && context.state == vState::Indeterminate && d_requireAuthData) {
+        /* This means we couldn't figure out the state when this entry was cached */
+
+        vState recordState = getValidationStatus(foundName, !signatures.empty(), qtype == QType::DS, depth, prefix);
+        if (recordState == vState::Secure) {
+          LOG(prefix << qname << ": Got vState::Indeterminate state from the " << foundQT.toString() << " cache, validating.." << endl);
+          context.state = SyncRes::validateRecordsWithSigs(depth, prefix, qname, qtype, foundName, foundQT, cset, signatures);
+          if (context.state != vState::Indeterminate) {
+            LOG(prefix << qname << ": Got vState::Indeterminate state from the " << foundQT.toString() << " cache, new validation result is " << context.state << endl);
+            if (vStateIsBogus(context.state)) {
+              capTTL = s_maxbogusttl;
+            }
+            updateValidationStatusInCache(foundName, foundQT, wasAuth, context.state);
+          }
+        }
+      }
+
+      LOG(prefix << qname << ": Found cache " << foundQT.toString() << " hit for '" << foundName << "|" << foundQT.toString() << "' to '" << record.getContent()->getZoneRepresentation() << "', validation state is " << context.state << endl);
+
+      DNSRecord dnsRecord = record;
+      auto alreadyPresent = false;
+
+      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);
+        }
+      }
+
+      DNSName newTarget;
+      if (foundQT == QType::DNAME) {
+        if (qtype == QType::DNAME && qname == foundName) { // client wanted the DNAME, no need to synthesize a CNAME
+          res = RCode::NoError;
+          return true;
+        }
+        // Synthesize a CNAME
+        auto dnameRR = getRR<DNAMERecordContent>(record);
+        if (dnameRR == nullptr) {
+          throw ImmediateServFailException("Unable to get record content for " + foundName.toLogString() + "|DNAME cache entry");
+        }
+        const auto& dnameSuffix = dnameRR->getTarget();
+        DNSName targetPrefix = qname.makeRelative(foundName);
+        try {
+          dnsRecord.d_type = QType::CNAME;
+          dnsRecord.d_name = targetPrefix + foundName;
+          newTarget = targetPrefix + dnameSuffix;
+          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)
+          // But this is consistent with processRecords
+          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 " << dnsRecord.d_name << "|CNAME " << newTarget << endl);
+      }
+
+      if (qtype == QType::CNAME) { // perhaps they really wanted a CNAME!
+        res = RCode::NoError;
+        return true;
+      }
+
+      if (qtype == QType::DS || qtype == QType::DNSKEY) {
+        res = RCode::NoError;
+        return true;
+      }
+
+      // We have a DNAME _or_ CNAME cache hit and the client wants something else than those two.
+      // Let's find the answer!
+      if (foundQT == QType::CNAME) {
+        const auto cnameContent = getRR<CNAMERecordContent>(record);
+        if (cnameContent == nullptr) {
+          throw ImmediateServFailException("Unable to get record content for " + foundName.toLogString() + "|CNAME cache entry");
+        }
+        newTarget = cnameContent->getTarget();
+      }
+
+      if (qname == newTarget) {
+        string msg = "Got a CNAME referral (from cache) to self";
+        LOG(prefix << qname << ": " << msg << endl);
+        throw ImmediateServFailException(std::move(msg));
+      }
+
+      if (newTarget.isPartOf(qname)) {
+        // a.b.c. CNAME x.a.b.c will go to great depths with QM on
+        string msg = "Got a CNAME referral (from cache) to child, disabling QM";
+        LOG(prefix << qname << ": " << msg << endl);
+        setQNameMinimization(false);
+      }
+
+      if (!d_followCNAME) {
+        res = RCode::NoError;
+        return true;
+      }
+
+      // 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(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;
+      Context cnameContext;
+      // Be aware that going out on the network might be disabled (cache-only), for example because we are in QM Step0,
+      // so you can't trust that a real lookup will have been made.
+      res = doResolve(newTarget, qtype, ret, depth + 1, beenthere, cnameContext);
+      LOG(prefix << qname << ": Updating validation state for response to " << qname << " from " << context.state << " with the state from the DNAME/CNAME quest: " << cnameContext.state << endl);
+      updateValidationState(qname, context.state, cnameContext.state, prefix);
+
+      return true;
+    }
+  }
+  throw ImmediateServFailException("Could not determine whether or not there was a CNAME or DNAME in cache for '" + qname.toLogString() + "'");
+}
+
+namespace
+{
+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
+{
+  DNSName name;
+  QType type;
+  DNSResourceRecord::Place place;
+  bool operator<(const CacheKey& rhs) const
+  {
+    return std::tie(type, place, name) < std::tie(rhs.type, rhs.place, rhs.name);
+  }
+};
+using tcache_t = map<CacheKey, CacheEntry>;
+}
+
+static void reapRecordsFromNegCacheEntryForValidation(tcache_t& tcache, const vector<DNSRecord>& records)
+{
+  for (const auto& rec : records) {
+    if (rec.d_type == QType::RRSIG) {
+      auto rrsig = getRR<RRSIGRecordContent>(rec);
+      if (rrsig) {
+        tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signatures.push_back(rrsig);
+      }
+    }
+    else {
+      tcache[{rec.d_name, rec.d_type, rec.d_place}].records.push_back(rec);
+    }
+  }
+}
+
+static bool negativeCacheEntryHasSOA(const NegCache::NegCacheEntry& negEntry)
+{
+  return !negEntry.authoritySOA.records.empty();
+}
+
+static void reapRecordsForValidation(std::map<QType, CacheEntry>& entries, const vector<DNSRecord>& records)
+{
+  for (const auto& rec : records) {
+    entries[rec.d_type].records.push_back(rec);
+  }
+}
+
+static void reapSignaturesForValidation(std::map<QType, CacheEntry>& entries, const vector<std::shared_ptr<const RRSIGRecordContent>>& signatures)
+{
+  for (const auto& sig : signatures) {
+    entries[sig->d_type].signatures.push_back(sig);
+  }
+}
+
+/*!
+ * Convenience function to push the records from records into ret with a new TTL
+ *
+ * \param records DNSRecords that need to go into ret
+ * \param ttl     The new TTL for these records
+ * \param ret     The vector of DNSRecords that should contain the records with the modified TTL
+ */
+static void addTTLModifiedRecords(vector<DNSRecord>& records, const uint32_t ttl, vector<DNSRecord>& ret)
+{
+  for (auto& rec : records) {
+    rec.d_ttl = ttl;
+    ret.push_back(std::move(rec));
+  }
+}
+
+void SyncRes::computeNegCacheValidationStatus(const NegCache::NegCacheEntry& negEntry, const DNSName& qname, const QType qtype, const int res, vState& state, unsigned int depth, const string& prefix)
+{
+  tcache_t tcache;
+  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
+    if (entry.second.records.empty()) {
+      continue;
+    }
+
+    const DNSName& owner = entry.first.name;
+
+    vState recordState = getValidationStatus(owner, !entry.second.signatures.empty(), qtype == QType::DS, depth, prefix);
+    if (state == vState::Indeterminate) {
+      state = recordState;
+    }
+
+    if (recordState == vState::Secure) {
+      recordState = SyncRes::validateRecordsWithSigs(depth, prefix, qname, qtype, owner, QType(entry.first.type), entry.second.records, entry.second.signatures);
+    }
+
+    if (recordState != vState::Indeterminate && recordState != state) {
+      updateValidationState(qname, state, recordState, prefix);
+      if (state != vState::Secure) {
+        break;
+      }
+    }
+  }
+
+  if (state == vState::Secure) {
+    vState neValidationState = negEntry.d_validationState;
+    dState expectedState = res == RCode::NXDomain ? dState::NXDOMAIN : dState::NXQTYPE;
+    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 */
+    boost::optional<time_t> capTTD = boost::none;
+    if (vStateIsBogus(state)) {
+      capTTD = d_now.tv_sec + s_maxbogusttl;
+    }
+    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) // NOLINT(readability-function-cognitive-complexity)
+{
+  bool giveNegative = false;
+
+  // sqname and sqtype are used contain 'higher' names if we have them (e.g. powerdns.com|SOA when we find a negative entry for doesnotexist.powerdns.com|A)
+  DNSName sqname(qname);
+  QType sqt(qtype);
+  uint32_t sttl = 0;
+  //  cout<<"Lookup for '"<<qname<<"|"<<qtype.toString()<<"' -> "<<getLastLabel(qname)<<endl;
+  vState cachedState{};
+  NegCache::NegCacheEntry negEntry;
+
+  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 = 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, 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 || 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 && negEntry.d_qtype.getCode() != 0 && !d_externalDSQuery.empty() && qname == d_externalDSQuery && !negativeCacheEntryHasSOA(negEntry)) {
+        giveNegative = false;
+      }
+      else {
+        res = RCode::NXDomain;
+        sttl = negEntry.d_ttd - d_now.tv_sec;
+        giveNegative = true;
+        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 '" << 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"};
+          }
+        }
+      }
+    }
+  }
+  else if (s_hardenNXD != HardenNXD::No && !qname.isRoot() && !wasForwardedOrAuthZone) {
+    auto labels = qname.getRawLabels();
+    DNSName negCacheName(g_rootdnsname);
+    negCacheName.prependRawLabel(labels.back());
+    labels.pop_back();
+    while (!labels.empty()) {
+      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(negEntry.d_validationState)) || negEntry.d_validationState == vState::Secure) {
+          res = RCode::NXDomain;
+          sttl = negEntry.d_ttd - d_now.tv_sec;
+          giveNegative = true;
+          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)"};
+          }
+          break;
+        }
+      }
+      negCacheName.prependRawLabel(labels.back());
+      labels.pop_back();
+    }
+  }
+
+  if (giveNegative) {
+
+    context.state = cachedState;
+
+    if (!wasAuthZone && shouldValidate() && context.state == vState::Indeterminate) {
+      LOG(prefix << qname << ": Got vState::Indeterminate state for records retrieved from the negative cache, validating.." << endl);
+      computeNegCacheValidationStatus(negEntry, qname, qtype, res, context.state, depth, prefix);
+
+      if (context.state != cachedState && vStateIsBogus(context.state)) {
+        sttl = std::min(sttl, s_maxbogusttl);
+      }
+    }
+
+    // Transplant SOA to the returned packet
+    addTTLModifiedRecords(negEntry.authoritySOA.records, sttl, ret);
+    if (d_doDNSSEC) {
+      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);
+    return true;
+  }
+
+  vector<DNSRecord> cset;
+  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{};
+  MemRecursorCache::Flags flags = MemRecursorCache::None;
+  if (!wasForwardRecurse && d_requireAuthData) {
+    flags |= MemRecursorCache::RequireAuth;
+  }
+  if (d_serveStale) {
+    flags |= MemRecursorCache::ServeStale;
+  }
+  if (d_refresh) {
+    flags |= MemRecursorCache::Refresh;
+  }
+  if (g_recCache->get(d_now.tv_sec, sqname, sqt, flags, &cset, d_cacheRemote, d_routingTag, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &cachedState, &wasCachedAuth, nullptr, &d_fromAuthIP) > 0) {
+
+    LOG(prefix << sqname << ": Found cache hit for " << sqt.toString() << ": ");
+
+    if (!wasAuthZone && shouldValidate() && (wasCachedAuth || wasForwardRecurse) && cachedState == vState::Indeterminate && d_requireAuthData) {
+
+      /* This means we couldn't figure out the state when this entry was cached */
+      vState recordState = getValidationStatus(qname, !signatures.empty(), qtype == QType::DS, depth, prefix);
+
+      if (recordState == vState::Secure) {
+        LOG(prefix << sqname << ": Got vState::Indeterminate state from the cache, validating.." << endl);
+        if (sqt == QType::DNSKEY && sqname == getSigner(signatures)) {
+          cachedState = validateDNSKeys(sqname, cset, signatures, depth, prefix);
+        }
+        else {
+          if (sqt == QType::ANY) {
+            std::map<QType, CacheEntry> types;
+            reapRecordsForValidation(types, cset);
+            reapSignaturesForValidation(types, signatures);
+
+            for (const auto& type : types) {
+              vState cachedRecordState{};
+              if (type.first == QType::DNSKEY && sqname == getSigner(type.second.signatures)) {
+                cachedRecordState = validateDNSKeys(sqname, type.second.records, type.second.signatures, depth, prefix);
+              }
+              else {
+                cachedRecordState = SyncRes::validateRecordsWithSigs(depth, prefix, qname, qtype, sqname, type.first, type.second.records, type.second.signatures);
+              }
+              updateDNSSECValidationState(cachedState, cachedRecordState);
+            }
+          }
+          else {
+            cachedState = SyncRes::validateRecordsWithSigs(depth, prefix, qname, qtype, sqname, sqt, cset, signatures);
+          }
+        }
+      }
+      else {
+        cachedState = recordState;
+      }
+
+      if (cachedState != vState::Indeterminate) {
+        LOG(prefix << qname << ": Got vState::Indeterminate state from the cache, validation result is " << cachedState << endl);
+        if (vStateIsBogus(cachedState)) {
+          capTTL = s_maxbogusttl;
+        }
+        if (sqt != QType::ANY && sqt != QType::ADDR) {
+          updateValidationStatusInCache(sqname, sqt, wasCachedAuth, cachedState);
+        }
+      }
+    }
+
+    for (auto j = cset.cbegin(); j != cset.cend(); ++j) {
+
+      LOG(j->getContent()->getZoneRepresentation());
+
+      if (j->d_class != QClass::IN) {
+        continue;
+      }
+
+      if (j->d_ttl > (unsigned int)d_now.tv_sec) {
+        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 {
+        LOG("[expired] ");
+        expired = true;
+      }
+    }
+
+    ret.reserve(ret.size() + signatures.size() + authorityRecs.size());
+
+    for (const auto& signature : signatures) {
+      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 dnsRecord(*rec);
+      dnsRecord.d_ttl = ttl;
+      ret.push_back(dnsRecord);
+    }
+
+    LOG(endl);
+    if (found && !expired) {
+      if (!giveNegative) {
+        res = 0;
+      }
+      LOG(prefix << qname << ": Updating validation state with cache content for " << qname << " to " << cachedState << endl);
+      context.state = cachedState;
+      return true;
+    }
+    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, 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)"};
+      }
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool SyncRes::moreSpecificThan(const DNSName& lhs, const DNSName& rhs)
+{
+  return (lhs.isPartOf(rhs) && lhs.countLabels() > rhs.countLabels());
+}
+
+struct speedOrder
+{
+  bool operator()(const std::pair<DNSName, float>& lhs, const std::pair<DNSName, float>& rhs) const
+  {
+    return lhs.second < rhs.second;
+  }
+};
+
+std::vector<std::pair<DNSName, float>> SyncRes::shuffleInSpeedOrder(const DNSName& qname, NsSet& tnameservers, const string& prefix)
+{
+  std::vector<std::pair<DNSName, float>> rnameservers;
+  rnameservers.reserve(tnameservers.size());
+  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
+      return rnameservers;
+    }
+  }
+
+  shuffle(rnameservers.begin(), rnameservers.end(), pdns::dns_random_engine());
+  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) == 0) {
+          LOG(endl
+              << prefix << "             ");
+        }
+      }
+      LOG(i->first.toLogString() << "(" << fmtfloat(i->second / 1000.0) << "ms)");
+    }
+    LOG(endl);
+  }
+  return rnameservers;
+}
+
+vector<ComboAddress> SyncRes::shuffleForwardSpeed(const DNSName& qname, const vector<ComboAddress>& rnameservers, const string& prefix, const bool wasRd)
+{
+  vector<ComboAddress> nameservers = rnameservers;
+  map<ComboAddress, float> speeds;
+
+  for (const auto& val : nameservers) {
+    DNSName nsName = DNSName(val.toStringWithPort());
+    float speed = s_nsSpeeds.lock()->fastest(nsName, d_now);
+    speeds[val] = speed;
+  }
+  shuffle(nameservers.begin(), nameservers.end(), pdns::dns_random_engine());
+  speedOrderCA speedCompare(speeds);
+  stable_sort(nameservers.begin(), nameservers.end(), speedCompare);
+
+  if (doLog()) {
+    LOG(prefix << qname << ": Nameservers: ");
+    for (auto i = nameservers.cbegin(); i != nameservers.cend(); ++i) {
+      if (i != nameservers.cbegin()) {
+        LOG(", ");
+        if (((i - nameservers.cbegin()) % 3) == 0) {
+          LOG(endl
+              << prefix << "             ");
+        }
+      }
+      LOG((wasRd ? string("+") : string("-")) << i->toStringWithPort() << "(" << fmtfloat(speeds[*i] / 1000.0) << "ms)");
+    }
+    LOG(endl);
+  }
+  return nameservers;
+}
+
+static uint32_t getRRSIGTTL(const time_t now, const std::shared_ptr<const RRSIGRecordContent>& rrsig)
+{
+  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;
+}
+
+static const set<QType> nsecTypes = {QType::NSEC, QType::NSEC3};
+
+/* Fills the authoritySOA and DNSSECRecords fields from ne with those found in the records
+ *
+ * \param records The records to parse for the authority SOA and NSEC(3) records
+ * \param ne      The NegCacheEntry to be filled out (will not be cleared, only appended to
+ */
+static void harvestNXRecords(const vector<DNSRecord>& records, NegCache::NegCacheEntry& negEntry, const time_t now, uint32_t* lowestTTL)
+{
+  for (const auto& rec : records) {
+    if (rec.d_place != DNSResourceRecord::AUTHORITY) {
+      // RFC 4035 section 3.1.3. indicates that NSEC records MUST be placed in
+      // the AUTHORITY section. Section 3.1.1 indicates that that RRSIGs for
+      // records MUST be in the same section as the records they cover.
+      // Hence, we ignore all records outside of the AUTHORITY section.
+      continue;
+    }
+
+    if (rec.d_type == QType::RRSIG) {
+      auto rrsig = getRR<RRSIGRecordContent>(rec);
+      if (rrsig) {
+        if (rrsig->d_type == QType::SOA) {
+          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) != 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));
+          }
+        }
+      }
+      continue;
+    }
+    if (rec.d_type == QType::SOA) {
+      negEntry.authoritySOA.records.push_back(rec);
+      if (lowestTTL != nullptr) {
+        *lowestTTL = min(*lowestTTL, rec.d_ttl);
+      }
+      continue;
+    }
+    if (nsecTypes.count(rec.d_type) != 0) {
+      negEntry.DNSSECRecords.records.push_back(rec);
+      if (lowestTTL != nullptr) {
+        *lowestTTL = min(*lowestTTL, rec.d_ttl);
+      }
+      continue;
+    }
+  }
+}
+
+static cspmap_t harvestCSPFromNE(const NegCache::NegCacheEntry& negEntry)
+{
+  cspmap_t cspmap;
+  for (const auto& rec : negEntry.DNSSECRecords.signatures) {
+    if (rec.d_type == QType::RRSIG) {
+      auto rrc = getRR<RRSIGRecordContent>(rec);
+      if (rrc) {
+        cspmap[{rec.d_name, rrc->d_type}].signatures.push_back(rrc);
+      }
+    }
+  }
+  for (const auto& rec : negEntry.DNSSECRecords.records) {
+    cspmap[{rec.d_name, rec.d_type}].records.insert(rec.getContent());
+  }
+  return cspmap;
+}
+
+// TODO remove after processRecords is fixed!
+// Adds the RRSIG for the SOA and the NSEC(3) + RRSIGs to ret
+static void addNXNSECS(vector<DNSRecord>& ret, const vector<DNSRecord>& records)
+{
+  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)
+{
+  if (qtype == QType::CNAME) {
+    return true;
+  }
+
+  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()) {
+          /* we have a CNAME whose target matches the entry we are about to
+             generate, so it will complete the current records, not replace
+             them
+          */
+          return false;
+        }
+      }
+    }
+  }
+
+  return true;
+}
+
+static void removeConflictingRecord(std::vector<DNSRecord>& records, const DNSName& name, const QType dtype)
+{
+  for (auto it = records.begin(); it != records.end();) {
+    bool remove = false;
+
+    if (it->d_class == QClass::IN && (it->d_type == QType::CNAME || dtype == QType::CNAME || it->d_type == dtype) && it->d_name == name) {
+      remove = true;
+    }
+    else if (it->d_class == QClass::IN && it->d_type == QType::RRSIG && it->d_name == name) {
+      if (auto rrc = getRR<RRSIGRecordContent>(*it)) {
+        if (rrc->d_type == QType::CNAME || rrc->d_type == dtype) {
+          /* also remove any RRSIG that could conflict */
+          remove = true;
+        }
+      }
+    }
+
+    if (remove) {
+      it = records.erase(it);
+    }
+    else {
+      ++it;
+    }
+  }
+}
+
+void SyncRes::handlePolicyHit(const std::string& prefix, const DNSName& qname, const QType qtype, std::vector<DNSRecord>& ret, bool& done, int& rcode, unsigned int depth)
+{
+  if (d_pdl && d_pdl->policyHitEventFilter(d_requestor, qname, qtype, d_queryReceivedOverTCP, d_appliedPolicy, d_policyTags, d_discardedPolicies)) {
+    /* reset to no match */
+    d_appliedPolicy = DNSFilterEngine::Policy();
+    return;
+  }
+
+  /* don't account truncate actions for TCP queries, since they are not applied */
+  if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::Truncate || !d_queryReceivedOverTCP) {
+    ++t_Counters.at(rec::PolicyHistogram::policy).at(d_appliedPolicy.d_kind);
+    ++t_Counters.at(rec::PolicyNameHits::policyName).counts[d_appliedPolicy.getName()];
+  }
+
+  if (d_appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) {
+    LOG(prefix << qname << "|" << qtype << ':' << d_appliedPolicy.getLogString() << endl);
+  }
+
+  switch (d_appliedPolicy.d_kind) {
+
+  case DNSFilterEngine::PolicyKind::NoAction:
+    return;
+
+  case DNSFilterEngine::PolicyKind::Drop:
+    ++t_Counters.at(rec::Counter::policyDrops);
+    throw ImmediateQueryDropException();
+
+  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;
+
+  case DNSFilterEngine::PolicyKind::Truncate:
+    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;
+
+  case DNSFilterEngine::PolicyKind::Custom: {
+    if (rpzHitShouldReplaceContent(qname, qtype, ret)) {
+      ret.clear();
+    }
+
+    rcode = RCode::NoError;
+    done = true;
+    auto spoofed = d_appliedPolicy.getCustomRecords(qname, qtype.getCode());
+    for (auto& dnsRecord : spoofed) {
+      removeConflictingRecord(ret, dnsRecord.d_name, dnsRecord.d_type);
+    }
+
+    for (auto& dnsRecord : spoofed) {
+      ret.push_back(dnsRecord);
+
+      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);
+  }
+  }
+}
+
+bool SyncRes::nameserversBlockedByRPZ(const DNSFilterEngine& dfe, const NsSet& nameservers)
+{
+  /* we skip RPZ processing if:
+     - it was disabled (d_wantsRPZ is false) ;
+     - we already got a RPZ hit (d_appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) since
+     the only way we can get back here is that it was a 'pass-thru' (NoAction) meaning that we should not
+     process any further RPZ rules. Except that we need to process rules of higher priority..
+  */
+  if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
+    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 " << 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 : 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 " << nameserver.first << " IP address " << address.toString() << " was blocked by RPZ policy '" << d_appliedPolicy.getName() << "'" << endl);
+            return true;
+          }
+        }
+      }
+    }
+  }
+  return false;
+}
+
+bool SyncRes::nameserverIPBlockedByRPZ(const DNSFilterEngine& dfe, const ComboAddress& remoteIP)
+{
+  /* we skip RPZ processing if:
+     - it was disabled (d_wantsRPZ is false) ;
+     - we already got a RPZ hit (d_appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) since
+     the only way we can get back here is that it was a 'pass-thru' (NoAction) meaning that we should not
+     process any further RPZ rules. Except that we need to process rules of higher priority..
+  */
+  if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
+    bool match = dfe.getProcessingPolicy(remoteIP, d_discardedPolicies, d_appliedPolicy);
+    if (match) {
+      mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+      if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) {
+        LOG(" (blocked by RPZ policy '" + d_appliedPolicy.getName() + "')");
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+vector<ComboAddress> SyncRes::retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, std::vector<std::pair<DNSName, float>>::const_iterator& tns, const unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<std::pair<DNSName, float>>& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& /* flawedNSSet */, bool cacheOnly, unsigned int& nretrieveAddressesForNS)
+{
+  vector<ComboAddress> result;
+
+  size_t nonresolvingfails = 0;
+  if (!tns->first.empty()) {
+    if (s_nonresolvingnsmaxfails > 0) {
+      nonresolvingfails = s_nonresolving.lock()->value(tns->first);
+      if (nonresolvingfails >= s_nonresolvingnsmaxfails) {
+        LOG(prefix << qname << ": NS " << tns->first << " in non-resolving map, skipping" << endl);
+        return result;
+      }
+    }
+
+    LOG(prefix << qname << ": Trying to resolve NS '" << tns->first << "' (" << 1 + tns - rnameservers.begin() << "/" << (unsigned int)rnameservers.size() << ")" << endl);
+    const unsigned int oldOutQueries = d_outqueries;
+    try {
+      result = getAddrs(tns->first, depth, prefix, beenthere, cacheOnly, nretrieveAddressesForNS);
+    }
+    // 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)) {
+          s_nonresolving.lock()->incr(tns->first, d_now);
+        }
+      }
+      throw ex;
+    }
+    if (s_nonresolvingnsmaxfails > 0 && d_outqueries > oldOutQueries) {
+      if (result.empty()) {
+        auto dontThrottleNames = g_dontThrottleNames.getLocal();
+        if (!dontThrottleNames->check(tns->first)) {
+          s_nonresolving.lock()->incr(tns->first, d_now);
+        }
+      }
+      else if (nonresolvingfails > 0) {
+        // Succeeding resolve, clear memory of recent failures
+        s_nonresolving.lock()->clear(tns->first);
+      }
+    }
+    pierceDontQuery = false;
+  }
+  else {
+    LOG(prefix << qname << ": Domain has hardcoded nameserver");
+
+    if (nameservers[tns->first].first.size() > 1) {
+      LOG("s");
+    }
+    LOG(endl);
+
+    sendRDQuery = nameservers[tns->first].second;
+    result = shuffleForwardSpeed(qname, nameservers[tns->first].first, prefix, sendRDQuery);
+    pierceDontQuery = true;
+  }
+  return result;
+}
+
+void SyncRes::checkMaxQperQ(const DNSName& qname) const
+{
+  if (d_outqueries + d_throttledqueries > s_maxqperq) {
+    throw ImmediateServFailException("more than " + std::to_string(s_maxqperq) + " (max-qperq) queries sent or throttled while resolving " + qname.toLogString());
+  }
+}
+
+bool SyncRes::throttledOrBlocked(const std::string& prefix, const ComboAddress& remoteIP, const DNSName& qname, const QType qtype, bool pierceDontQuery)
+{
+  if (isThrottled(d_now.tv_sec, remoteIP)) {
+    LOG(prefix << qname << ": Server throttled " << endl);
+    t_Counters.at(rec::Counter::throttledqueries)++;
+    d_throttledqueries++;
+    return true;
+  }
+  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;
+  }
+  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 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;
+    }
+    // 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()
+{
+  return g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate;
+}
+
+uint32_t SyncRes::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
+{
+  uint32_t lowestTTD = std::numeric_limits<uint32_t>::max();
+  for (const auto& record : records) {
+    lowestTTD = min(lowestTTD, record.d_ttl);
+  }
+
+  /* even if it was not requested for that request (Process, and neither AD nor DO set),
+     it might be requested at a later time so we need to be careful with the TTL. */
+  if (validationEnabled() && !signatures.empty()) {
+    /* if we are validating, we don't want to cache records after their signatures expire. */
+    /* records TTL are now TTD, let's add 'now' to the signatures lowest TTL */
+    lowestTTD = min(lowestTTD, static_cast<uint32_t>(signaturesTTL + d_now.tv_sec));
+
+    for (const auto& sig : signatures) {
+      if (isRRSIGNotExpired(d_now.tv_sec, *sig)) {
+        // we don't decrement d_sigexpire by 'now' because we actually want a TTD, not a TTL */
+        lowestTTD = min(lowestTTD, static_cast<uint32_t>(sig->d_sigexpire));
+      }
+    }
+  }
+
+  for (const auto& entry : authorityRecs) {
+    /* be careful, this is still a TTL here */
+    lowestTTD = min(lowestTTD, static_cast<uint32_t>(entry->d_ttl + d_now.tv_sec));
+
+    if (entry->d_type == QType::RRSIG && validationEnabled()) {
+      auto rrsig = getRR<RRSIGRecordContent>(*entry);
+      if (rrsig) {
+        if (isRRSIGNotExpired(d_now.tv_sec, *rrsig)) {
+          // we don't decrement d_sigexpire by 'now' because we actually want a TTD, not a TTL */
+          lowestTTD = min(lowestTTD, static_cast<uint32_t>(rrsig->d_sigexpire));
+        }
+      }
+    }
+  }
+
+  return lowestTTD;
+}
+
+void SyncRes::updateValidationState(const DNSName& qname, vState& state, const vState stateUpdate, const string& prefix)
+{
+  LOG(prefix << qname << ": Validation state was " << state << ", state update is " << stateUpdate);
+  updateDNSSECValidationState(state, stateUpdate);
+  LOG(", validation state is now " << state << endl);
+}
+
+vState SyncRes::getTA(const DNSName& zone, dsmap_t& dsMap, const string& prefix)
+{
+  auto luaLocal = g_luaconfs.getLocal();
+
+  if (luaLocal->dsAnchors.empty()) {
+    LOG(prefix << zone << ": No trust anchors configured, everything is Insecure" << endl);
+    /* We have no TA, everything is insecure */
+    return vState::Insecure;
+  }
+
+  std::string reason;
+  if (haveNegativeTrustAnchor(luaLocal->negAnchors, zone, reason)) {
+    LOG(prefix << zone << ": Got NTA" << endl);
+    return vState::NTA;
+  }
+
+  if (getTrustAnchor(luaLocal->dsAnchors, zone, dsMap)) {
+    if (!zone.isRoot()) {
+      LOG(prefix << zone << ": Got TA" << endl);
+    }
+    return vState::TA;
+  }
+
+  if (zone.isRoot()) {
+    /* No TA for the root */
+    return vState::Insecure;
+  }
+
+  return vState::Indeterminate;
+}
+
+size_t SyncRes::countSupportedDS(const dsmap_t& dsmap, const string& prefix)
+{
+  size_t count = 0;
+
+  for (const auto& dsRecordContent : dsmap) {
+    if (isSupportedDS(dsRecordContent, LogObject(prefix))) {
+      count++;
+    }
+  }
+
+  return count;
+}
+
+void SyncRes::initZoneCutsFromTA(const DNSName& from, const string& prefix)
+{
+  DNSName zone(from);
+  do {
+    dsmap_t dsMap;
+    vState result = getTA(zone, dsMap, prefix);
+    if (result != vState::Indeterminate) {
+      if (result == vState::TA) {
+        if (countSupportedDS(dsMap, prefix) == 0) {
+          dsMap.clear();
+          result = vState::Insecure;
+        }
+        else {
+          result = vState::Secure;
+        }
+      }
+      else if (result == vState::NTA) {
+        result = vState::Insecure;
+      }
+
+      d_cutStates[zone] = result;
+    }
+  } while (zone.chopOff());
+}
+
+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, dsMap, prefix);
+
+  if (result != vState::Indeterminate || onlyTA) {
+    if (foundCut != nullptr) {
+      *foundCut = (result != vState::Indeterminate);
+    }
+
+    if (result == vState::TA) {
+      if (countSupportedDS(dsMap, prefix) == 0) {
+        dsMap.clear();
+        result = vState::Insecure;
+      }
+      else {
+        result = vState::Secure;
+      }
+    }
+    else if (result == vState::NTA) {
+      result = vState::Insecure;
+    }
+
+    return result;
+  }
+
+  std::set<GetBestNSAnswer> beenthere;
+  std::vector<DNSRecord> dsrecords;
+
+  Context context;
+
+  const bool oldCacheOnly = setCacheOnly(false);
+  const bool oldQM = setQNameMinimization(!getQMFallbackMode());
+  int rcode = doResolve(zone, QType::DS, dsrecords, depth + 1, beenthere, context);
+  setCacheOnly(oldCacheOnly);
+  setQNameMinimization(oldQM);
+
+  if (rcode == RCode::ServFail) {
+    throw ImmediateServFailException("Server Failure while retrieving DS records for " + zone.toLogString());
+  }
+
+  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;
+        }
+        dsMap.insert(*dscontent);
+      }
+    }
+    else if (record.d_type == QType::CNAME && record.d_name == zone) {
+      gotCNAME = true;
+    }
+  }
+
+  /* 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 (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 */
+
+        if (foundCut != nullptr) {
+          *foundCut = false;
+        }
+        return context.state;
+      }
+
+      d_cutStates[zone] = context.state == vState::Secure ? vState::Insecure : context.state;
+      /* delegation with no DS, might be Secure -> Insecure */
+      if (foundCut != nullptr) {
+        *foundCut = true;
+      }
+
+      /* 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;
+    }
+  }
+
+  return context.state;
+}
+
+vState SyncRes::getValidationStatus(const DNSName& name, bool wouldBeValid, bool typeIsDS, unsigned int depth, const string& prefix)
+{
+  vState result = vState::Indeterminate;
+
+  if (!shouldValidate()) {
+    return result;
+  }
+
+  DNSName subdomain(name);
+  if (typeIsDS) {
+    subdomain.chopOff();
+  }
+
+  {
+    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& 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;
+      }
+      break;
+    }
+  }
+
+  /* by now we have the best match, it's likely Secure (otherwise we would not be there)
+     but we don't know if we missed a cut (or several).
+     We could see if we have DS (or denial of) in cache but let's not worry for now,
+     we will if we don't have a signature, or if the signer doesn't match what we expect */
+  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 dsName(best);
+    std::vector<string> labelsToAdd = subdomain.makeRelative(dsName).getRawLabels();
+
+    while (!labelsToAdd.empty()) {
+
+      dsName.prependRawLabel(labelsToAdd.back());
+      labelsToAdd.pop_back();
+      LOG(prefix << name << ": - Looking for a DS at " << dsName << endl);
+
+      bool foundCut = false;
+      dsmap_t results;
+      vState dsState = getDSRecords(dsName, results, false, depth, prefix, false, &foundCut);
+
+      if (foundCut) {
+        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;
+        }
+      }
+    }
+
+    /* we did not miss a cut, good luck */
+    return result;
+  }
+
+#if 0
+  /* we don't need this, we actually do the right thing later */
+  DNSName signer = getSigner(signatures);
+
+  if (!signer.empty() && name.isPartOf(signer)) {
+    if (signer == best) {
+      return result;
+    }
+    /* the zone cut is not the one we expected,
+       this is fine because we will retrieve the needed DNSKEYs and DSs
+       later, and even go Insecure if we missed a cut to Insecure (no DS)
+       and the signatures do not validate (we should not go Bogus in that
+       case) */
+  }
+  /* something is not right, but let's not worry about that for now.. */
+#endif
+
+  return result;
+}
+
+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 dsMap;
+  if (signatures.empty()) {
+    LOG(prefix << zone << ": We have " << std::to_string(dnskeys.size()) << " DNSKEYs but no signature, going Bogus!" << endl);
+    return vState::BogusNoRRSIG;
+  }
+
+  DNSName signer = getSigner(signatures);
+
+  if (!signer.empty() && zone.isPartOf(signer)) {
+    vState state = getDSRecords(signer, dsMap, false, depth, prefix);
+
+    if (state != vState::Secure) {
+      return state;
+    }
+  }
+  else {
+    LOG(prefix << zone << ": We have " << std::to_string(dnskeys.size()) << " DNSKEYs but the zone (" << zone << ") is not part of the signer (" << signer << "), check that we did not miss a zone cut" << 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 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;
+    }
+    return zState;
+  }
+
+  skeyset_t tentativeKeys;
+  sortedRecords_t toSign;
+
+  for (const auto& dnskey : dnskeys) {
+    if (dnskey.d_type == QType::DNSKEY) {
+      auto content = getRR<DNSKEYRecordContent>(dnskey);
+      if (content) {
+        tentativeKeys.insert(content);
+        toSign.insert(content);
+      }
+    }
+  }
+
+  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, 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);
+
+  /* if we found at least one valid RRSIG covering the set,
+     all tentative keys are validated keys. Otherwise it means
+     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 " << 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 " << static_cast<const char*>(__func__) << "(" << zone << ")" << endl);
+      return state;
+    }
+    return zState;
+  }
+
+  return state;
+}
+
+vState SyncRes::getDNSKeys(const DNSName& signer, skeyset_t& keys, bool& servFailOccurred, unsigned int depth, const string& prefix)
+{
+  std::vector<DNSRecord> records;
+  std::set<GetBestNSAnswer> beenthere;
+  LOG(prefix << signer << ": Retrieving DNSKEYs" << endl);
+
+  Context context;
+
+  const bool oldCacheOnly = setCacheOnly(false);
+  int rcode = doResolve(signer, QType::DNSKEY, records, depth + 1, beenthere, context);
+  setCacheOnly(oldCacheOnly);
+
+  if (rcode == RCode::ServFail) {
+    servFailOccurred = true;
+    return vState::BogusUnableToGetDNSKEYs;
+  }
+
+  if (rcode == RCode::NoError) {
+    if (context.state == vState::Secure) {
+      for (const auto& key : records) {
+        if (key.d_type == QType::DNSKEY) {
+          auto content = getRR<DNSKEYRecordContent>(key);
+          if (content) {
+            keys.insert(content);
+          }
+        }
+      }
+    }
+    LOG(prefix << signer << ": Retrieved " << keys.size() << " DNSKeys, state is " << context.state << endl);
+    return context.state;
+  }
+
+  if (context.state == vState::Insecure) {
+    return context.state;
+  }
+
+  LOG(prefix << signer << ": Returning Bogus state from " << static_cast<const char*>(__func__) << "(" << signer << ")" << endl);
+  return vState::BogusUnableToGetDNSKEYs;
+}
+
+vState SyncRes::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)
+{
+  skeyset_t keys;
+  if (signatures.empty()) {
+    LOG(prefix << qname << ": Bogus!" << endl);
+    return vState::BogusNoRRSIG;
+  }
+
+  const DNSName signer = getSigner(signatures);
+  bool dsFailed = false;
+  if (!signer.empty() && name.isPartOf(signer)) {
+    vState state = vState::Secure;
+
+    if ((qtype == QType::DNSKEY || qtype == QType::DS) && signer == qname) {
+      /* we are already retrieving those keys, sorry */
+      if (type == QType::DS && signer == name && !signer.isRoot()) {
+        /* Unless we are getting the DS of the root zone, we should never see a
+           DS (or a denial of a DS) signed by the DS itself, since we should be
+           requesting it from the parent zone. Something is very wrong */
+        LOG(prefix << qname << ": The DS for " << qname << " is signed by itself" << endl);
+        state = vState::BogusSelfSignedDS;
+        dsFailed = true;
+      }
+      else if (qtype == QType::DS && signer == qname && !signer.isRoot()) {
+        if (type == QType::SOA || type == QType::NSEC || type == QType::NSEC3) {
+          /* if we are trying to validate the DS or more likely NSEC(3)s proving that it does not exist, we have a problem.
+             In that case let's go Bogus (we will check later if we missed a cut)
+          */
+          state = vState::BogusSelfSignedDS;
+          dsFailed = true;
+        }
+        else if (type == QType::CNAME) {
+          state = vState::BogusUnableToGetDSs;
+          dsFailed = true;
+        }
+      }
+      else if (qtype == QType::DNSKEY && signer == qname) {
+        /* that actually does happen when a server returns NS records in authority
+           along with the DNSKEY, leading us to trying to validate the RRSIGs for
+           the NS with the DNSKEY that we are about to process. */
+        if ((name == signer && type == QType::NSEC) || type == QType::NSEC3) {
+          /* if we are trying to validate the DNSKEY (should not happen here),
+             or more likely NSEC(3)s proving that it does not exist, we have a problem.
+             In that case let's see if the DS does exist, and if it does let's go Bogus
+          */
+          dsmap_t results;
+          vState dsState = getDSRecords(signer, results, false, depth, prefix, true);
+          if (vStateIsBogus(dsState) || dsState == vState::Insecure) {
+            state = dsState;
+            if (vStateIsBogus(dsState)) {
+              dsFailed = true;
+            }
+          }
+          else {
+            LOG(prefix << qname << ": Unable to get the DS for " << signer << endl);
+            state = vState::BogusUnableToGetDNSKEYs;
+            dsFailed = true;
+          }
+        }
+        else {
+          /* return immediately since looking at the cuts is not going to change the
+             fact that we are looking at a signature done with the key we are trying to
+             obtain */
+          LOG(prefix << qname << ": We are looking at a signature done with the key we are trying to obtain " << signer << endl);
+          return vState::Indeterminate;
+        }
+      }
+    }
+    bool servFailOccurred = false;
+    if (state == vState::Secure) {
+      state = getDNSKeys(signer, keys, servFailOccurred, depth, prefix);
+    }
+
+    if (state != vState::Secure) {
+      if (!vStateIsBogus(state)) {
+        return state;
+      }
+      /* try again to get the missed cuts, harder this time */
+      LOG(prefix << signer << ": Checking whether we missed a zone cut for " << signer << " before returning a Bogus state for " << name << "|" << type.toString() << endl);
+      auto zState = getValidationStatus(signer, false, dsFailed, depth, prefix);
+      if (zState == vState::Secure) {
+        if (state == vState::BogusUnableToGetDNSKEYs && servFailOccurred) {
+          throw ImmediateServFailException("Server Failure while retrieving DNSKEY records for " + signer.toLogString());
+        }
+        /* too bad */
+        LOG(prefix << signer << ": We are still in a Secure zone, returning " << vStateToString(state) << endl);
+        return state;
+      }
+      return zState;
+    }
+  }
+
+  sortedRecords_t recordcontents;
+  for (const auto& record : records) {
+    recordcontents.insert(record.getContent());
+  }
+
+  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), 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;
+  }
+
+  LOG(prefix << vStateToString(state) << "!" << endl);
+  /* try again to get the missed cuts, harder this time */
+  auto zState = getValidationStatus(name, false, type == QType::DS, depth, prefix);
+  LOG(prefix << name << ": Checking whether we missed a zone cut before returning a Bogus state" << endl);
+  if (zState == vState::Secure) {
+    /* too bad */
+    LOG(prefix << name << ": We are still in a Secure zone, returning " << vStateToString(state) << endl);
+    return state;
+  }
+  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.
+   This is unfortunately needed to deal with very crappy so-called DNS servers */
+void SyncRes::fixupAnswer(const std::string& prefix, LWResult& lwr, const DNSName& qname, const QType qtype, const DNSName& auth, bool wasForwarded, bool rdQuery)
+{
+  const bool wasForwardRecurse = wasForwarded && rdQuery;
+
+  if (wasForwardRecurse || lwr.d_aabit) {
+    /* easy */
+    return;
+  }
+
+  for (const auto& rec : lwr.d_records) {
+
+    if (rec.d_type == QType::OPT) {
+      continue;
+    }
+
+    if (rec.d_class != QClass::IN) {
+      continue;
+    }
+
+    if (rec.d_type == QType::ANY) {
+      continue;
+    }
+
+    if (rec.d_place == DNSResourceRecord::ANSWER && (rec.d_type == qtype || rec.d_type == QType::CNAME || qtype == QType::ANY) && rec.d_name == qname && rec.d_name.isPartOf(auth)) {
+      /* This is clearly an answer to the question we were asking, from an authoritative server that is allowed to send it.
+         We are going to assume this server is broken and does not know it should set the AA bit, even though it is DNS 101 */
+      LOG(prefix << qname << ": Received a record for " << rec.d_name << "|" << DNSRecordContent::NumberToType(rec.d_type) << " in the answer section from " << auth << ", without the AA bit set. Assuming this server is clueless and setting the AA bit." << endl);
+      lwr.d_aabit = true;
+      return;
+    }
+
+    if (rec.d_place != DNSResourceRecord::ANSWER) {
+      /* we have scanned all the records in the answer section, if any, we are done */
+      return;
+    }
+  }
+}
+
+static void allowAdditionalEntry(std::unordered_set<DNSName>& allowedAdditionals, const DNSRecord& rec)
+{
+  switch (rec.d_type) {
+  case QType::MX:
+    if (auto mxContent = getRR<MXRecordContent>(rec)) {
+      allowedAdditionals.insert(mxContent->d_mxname);
+    }
+    break;
+  case QType::NS:
+    if (auto nsContent = getRR<NSRecordContent>(rec)) {
+      allowedAdditionals.insert(nsContent->getNS());
+    }
+    break;
+  case QType::SRV:
+    if (auto srvContent = getRR<SRVRecordContent>(rec)) {
+      allowedAdditionals.insert(srvContent->d_target);
+    }
+    break;
+  case QType::SVCB: /* fall-through */
+  case QType::HTTPS:
+    if (auto svcbContent = getRR<SVCBBaseRecordContent>(rec)) {
+      if (svcbContent->getPriority() > 0) {
+        DNSName target = svcbContent->getTarget();
+        if (target.isRoot()) {
+          target = rec.d_name;
+        }
+        allowedAdditionals.insert(target);
+      }
+      else {
+        // FIXME: Alias mode not implemented yet
+      }
+    }
+    break;
+  case QType::NAPTR:
+    if (auto naptrContent = getRR<NAPTRRecordContent>(rec)) {
+      auto flags = naptrContent->getFlags();
+      toLowerInPlace(flags);
+      if (flags.find('a') != string::npos || flags.find('s') != string::npos) {
+        allowedAdditionals.insert(naptrContent->getReplacement());
+      }
+    }
+    break;
+  default:
+    break;
+  }
+}
+
+void SyncRes::sanitizeRecords(const std::string& prefix, LWResult& lwr, const DNSName& qname, const QType qtype, const DNSName& auth, bool wasForwarded, bool rdQuery)
+{
+  const bool wasForwardRecurse = wasForwarded && rdQuery;
+  /* list of names for which we will allow A and AAAA records in the additional section
+     to remain */
+  std::unordered_set<DNSName> allowedAdditionals = {qname};
+  bool haveAnswers = false;
+  bool isNXDomain = false;
+  bool isNXQType = false;
+
+  for (auto rec = lwr.d_records.begin(); rec != lwr.d_records.end();) {
+
+    if (rec->d_type == QType::OPT) {
+      ++rec;
+      continue;
+    }
+
+    if (rec->d_class != QClass::IN) {
+      LOG(prefix << qname << ": Removing non internet-classed data received from " << auth << endl);
+      rec = lwr.d_records.erase(rec);
+      continue;
+    }
+
+    if (rec->d_type == QType::ANY) {
+      LOG(prefix << qname << ": Removing 'ANY'-typed data received from " << auth << endl);
+      rec = lwr.d_records.erase(rec);
+      continue;
+    }
+
+    if (!rec->d_name.isPartOf(auth)) {
+      LOG(prefix << qname << ": Removing record '" << rec->d_name << "|" << DNSRecordContent::NumberToType(rec->d_type) << "|" << rec->getContent()->getZoneRepresentation() << "' in the " << (int)rec->d_place << " section received from " << auth << endl);
+      rec = lwr.d_records.erase(rec);
+      continue;
+    }
+
+    /* dealing with the records in answer */
+    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) {
+        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;
+      }
+    }
+
+    if (rec->d_type == QType::DNAME && (rec->d_place != DNSResourceRecord::ANSWER || !qname.isPartOf(rec->d_name))) {
+      LOG(prefix << qname << ": Removing invalid DNAME record '" << rec->d_name << "|" << DNSRecordContent::NumberToType(rec->d_type) << "|" << rec->getContent()->getZoneRepresentation() << "' in the " << (int)rec->d_place << " section received from " << auth << endl);
+      rec = lwr.d_records.erase(rec);
+      continue;
+    }
+
+    if (rec->d_place == DNSResourceRecord::ANSWER && (qtype != QType::ANY && rec->d_type != qtype.getCode() && s_redirectionQTypes.count(rec->d_type) == 0 && rec->d_type != QType::SOA && rec->d_type != QType::RRSIG)) {
+      LOG(prefix << qname << ": Removing irrelevant record '" << rec->d_name << "|" << DNSRecordContent::NumberToType(rec->d_type) << "|" << rec->getContent()->getZoneRepresentation() << "' in the ANSWER section received from " << auth << endl);
+      rec = lwr.d_records.erase(rec);
+      continue;
+    }
+
+    if (rec->d_place == DNSResourceRecord::ANSWER && !haveAnswers) {
+      haveAnswers = true;
+    }
+
+    if (rec->d_place == DNSResourceRecord::ANSWER) {
+      allowAdditionalEntry(allowedAdditionals, *rec);
+    }
+
+    /* dealing with the records in authority */
+    if (rec->d_place == DNSResourceRecord::AUTHORITY && rec->d_type != QType::NS && rec->d_type != QType::DS && rec->d_type != QType::SOA && rec->d_type != QType::RRSIG && rec->d_type != QType::NSEC && rec->d_type != QType::NSEC3) {
+      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);
+      rec = lwr.d_records.erase(rec);
+      continue;
+    }
+
+    if (rec->d_place == DNSResourceRecord::AUTHORITY && rec->d_type == QType::SOA) {
+      if (!qname.isPartOf(rec->d_name)) {
+        LOG(prefix << qname << ": Removing irrelevant SOA record '" << rec->d_name << "|" << rec->getContent()->getZoneRepresentation() << "' in the AUTHORITY section received from " << auth << endl);
+        rec = lwr.d_records.erase(rec);
+        continue;
+      }
+
+      if (!(lwr.d_aabit || wasForwardRecurse)) {
+        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;
+      }
+
+      if (!haveAnswers) {
+        if (lwr.d_rcode == RCode::NXDomain) {
+          isNXDomain = true;
+        }
+        else if (lwr.d_rcode == RCode::NoError) {
+          isNXQType = true;
+        }
+      }
+    }
+
+    if (rec->d_place == DNSResourceRecord::AUTHORITY && rec->d_type == QType::NS && (isNXDomain || isNXQType)) {
+      /*
+       * We don't want to pick up NS records in AUTHORITY and their ADDITIONAL sections of NXDomain answers
+       * because they are somewhat easy to insert into a large, fragmented UDP response
+       * for an off-path attacker by injecting spoofed UDP fragments. So do not add these to allowedAdditionals.
+       */
+      LOG(prefix << qname << ": Removing NS record '" << rec->d_name << "|" << DNSRecordContent::NumberToType(rec->d_type) << "|" << rec->getContent()->getZoneRepresentation() << "' in the " << (int)rec->d_place << " section of a " << (isNXDomain ? "NXD" : "NXQTYPE") << " response received from " << auth << endl);
+      rec = lwr.d_records.erase(rec);
+      continue;
+    }
+
+    if (rec->d_place == DNSResourceRecord::AUTHORITY && rec->d_type == QType::NS && !d_updatingRootNS && rec->d_name == g_rootdnsname) {
+      /*
+       * We don't want to pick up root NS records in AUTHORITY and their associated ADDITIONAL sections of random queries.
+       * So don't add them to allowedAdditionals.
+       */
+      LOG(prefix << qname << ": Removing NS record '" << rec->d_name << "|" << DNSRecordContent::NumberToType(rec->d_type) << "|" << rec->getContent()->getZoneRepresentation() << "' in the " << (int)rec->d_place << " section of a response received from " << auth << endl);
+      rec = lwr.d_records.erase(rec);
+      continue;
+    }
+
+    if (rec->d_place == DNSResourceRecord::AUTHORITY && rec->d_type == QType::NS) {
+      allowAdditionalEntry(allowedAdditionals, *rec);
+    }
+
+    /* dealing with the records in additional */
+    if (rec->d_place == DNSResourceRecord::ADDITIONAL && rec->d_type != QType::A && rec->d_type != QType::AAAA && rec->d_type != QType::RRSIG) {
+      LOG(prefix << qname << ": Removing irrelevant record '" << rec->d_name << "|" << DNSRecordContent::NumberToType(rec->d_type) << "|" << rec->getContent()->getZoneRepresentation() << "' in the ADDITIONAL section received from " << auth << endl);
+      rec = lwr.d_records.erase(rec);
+      continue;
+    }
+
+    if (rec->d_place == DNSResourceRecord::ADDITIONAL && allowedAdditionals.count(rec->d_name) == 0) {
+      LOG(prefix << qname << ": Removing irrelevant additional record '" << rec->d_name << "|" << DNSRecordContent::NumberToType(rec->d_type) << "|" << rec->getContent()->getZoneRepresentation() << "' in the ADDITIONAL section received from " << auth << endl);
+      rec = lwr.d_records.erase(rec);
+      continue;
+    }
+
+    ++rec;
+  }
+}
+
+void SyncRes::rememberParentSetIfNeeded(const DNSName& domain, const vector<DNSRecord>& newRecords, unsigned int depth, const string& prefix)
+{
+  vector<DNSRecord> existing;
+  bool wasAuth = false;
+  auto ttl = g_recCache->get(d_now.tv_sec, domain, QType::NS, MemRecursorCache::None, &existing, d_cacheRemote, d_routingTag, nullptr, nullptr, nullptr, nullptr, &wasAuth);
+
+  if (ttl <= 0 || wasAuth) {
+    return;
+  }
+  {
+    auto lock = s_savedParentNSSet.lock();
+    if (lock->find(domain) != lock->end()) {
+      // no relevant data, or we already stored the parent data
+      return;
+    }
+  }
+
+  set<DNSName> authSet;
+  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& 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;
+      break;
+    }
+  }
+
+  if (shouldSave) {
+    map<DNSName, vector<ComboAddress>> entries;
+    for (const auto& dnsRecord : existing) {
+      auto content = getRR<NSRecordContent>(dnsRecord);
+      const DNSName& name = content->getNS();
+      set<GetBestNSAnswer> beenthereIgnored;
+      unsigned int nretrieveAddressesForNSIgnored{};
+      auto addresses = getAddrs(name, depth, prefix, beenthereIgnored, true, nretrieveAddressesForNSIgnored);
+      entries.emplace(name, addresses);
+    }
+    s_savedParentNSSet.lock()->emplace(domain, std::move(entries), d_now.tv_sec + ttl);
+  }
+}
+
+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;
+
+  fixupAnswer(prefix, lwr, qname, qtype, auth, wasForwarded, rdQuery);
+  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;
+
+  for (auto& rec : lwr.d_records) {
+    if (rec.d_type == QType::OPT || rec.d_class != QClass::IN) {
+      continue;
+    }
+
+    rec.d_ttl = min(s_maxcachettl, rec.d_ttl);
+
+    if (!isCNAMEAnswer && rec.d_place == DNSResourceRecord::ANSWER && rec.d_type == QType::CNAME && (!(qtype == QType::CNAME)) && rec.d_name == qname && !isDNAMEAnswer) {
+      isCNAMEAnswer = true;
+    }
+    if (!isDNAMEAnswer && rec.d_place == DNSResourceRecord::ANSWER && rec.d_type == QType::DNAME && qtype != QType::DNAME && qname.isPartOf(rec.d_name)) {
+      isDNAMEAnswer = true;
+      isCNAMEAnswer = false;
+    }
+
+    if (rec.d_type == QType::SOA && rec.d_place == DNSResourceRecord::AUTHORITY && qname.isPartOf(rec.d_name)) {
+      seenAuth = rec.d_name;
+    }
+
+    if (rec.d_type == QType::RRSIG) {
+      auto rrsig = getRR<RRSIGRecordContent>(rec);
+      if (rrsig) {
+        /* As illustrated in rfc4035's Appendix B.6, the RRSIG label
+           count can be lower than the name's label count if it was
+           synthesized from the wildcard. Note that the difference might
+           be > 1. */
+        if (rec.d_name == qname && isWildcardExpanded(labelCount, *rrsig)) {
+          gatherWildcardProof = true;
+          if (!isWildcardExpandedOntoItself(rec.d_name, labelCount, *rrsig)) {
+            /* if we have a wildcard expanded onto itself, we don't need to prove
+               that the exact name doesn't exist because it actually does.
+               We still want to gather the corresponding NSEC/NSEC3 records
+               to pass them to our client in case it wants to validate by itself.
+            */
+            LOG(prefix << qname << ": RRSIG indicates the name was synthesized from a wildcard, we need a wildcard proof" << endl);
+            needWildcardProof = true;
+          }
+          else {
+            LOG(prefix << qname << ": RRSIG indicates the name was synthesized from a wildcard expanded onto itself, we need to gather wildcard proof" << endl);
+          }
+          wildcardLabelsCount = rrsig->d_labels;
+        }
+
+        // cerr<<"Got an RRSIG for "<<DNSRecordContent::NumberToType(rrsig->d_type)<<" with name '"<<rec.d_name<<"' and place "<<rec.d_place<<endl;
+        tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signatures.push_back(rrsig);
+        tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signaturesTTL = std::min(tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signaturesTTL, rec.d_ttl);
+      }
+    }
+  }
+
+  /* if we have a positive answer synthesized from a wildcard,
+     we need to store the corresponding NSEC/NSEC3 records proving
+     that the exact name did not exist in the negative cache */
+  if (gatherWildcardProof) {
+    for (const auto& rec : lwr.d_records) {
+      if (rec.d_type == QType::OPT || rec.d_class != QClass::IN) {
+        continue;
+      }
+
+      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) != 0) {
+          authorityRecs.push_back(std::make_shared<DNSRecord>(rec));
+        }
+      }
+    }
+  }
+
+  // reap all answers from this packet that are acceptable
+  for (auto& rec : lwr.d_records) {
+    if (rec.d_type == QType::OPT) {
+      LOG(prefix << qname << ": OPT answer '" << rec.d_name << "' from '" << auth << "' nameservers" << endl);
+      continue;
+    }
+
+    LOG(prefix << qname << ": Accept answer '" << rec.d_name << "|" << DNSRecordContent::NumberToType(rec.d_type) << "|" << rec.getContent()->getZoneRepresentation() << "' from '" << auth << "' nameservers? ttl=" << rec.d_ttl << ", place=" << (int)rec.d_place << " ");
+
+    // We called sanitizeRecords before, so all ANY, non-IN and non-aa/non-forwardrecurse answer records are already removed
+
+    if (rec.d_name.isPartOf(auth)) {
+      if (rec.d_type == QType::RRSIG) {
+        LOG("RRSIG - separate" << endl);
+      }
+      else if (rec.d_type == QType::DS && rec.d_name == auth) {
+        LOG("NO - DS provided by child zone" << endl);
+      }
+      else {
+        bool haveLogged = false;
+        if (isDNAMEAnswer && rec.d_type == QType::CNAME) {
+          LOG("NO - we already have a DNAME answer for this domain" << endl);
+          continue;
+        }
+        if (!t_sstorage.domainmap->empty()) {
+          // Check if we are authoritative for a zone in this answer
+          DNSName tmp_qname(rec.d_name);
+          // We may be auth for domain example.com, but the DS record needs to come from the parent (.com) nameserver
+          if (rec.d_type == QType::DS) {
+            tmp_qname.chopOff();
+          }
+          auto auth_domain_iter = getBestAuthZone(&tmp_qname);
+          if (auth_domain_iter != t_sstorage.domainmap->end() && auth.countLabels() <= auth_domain_iter->first.countLabels()) {
+            if (auth_domain_iter->first != auth) {
+              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("received from a server we forward to.");
+            }
+            haveLogged = true;
+            LOG(endl);
+          }
+        }
+        if (!haveLogged) {
+          LOG("YES!" << endl);
+        }
+
+        rec.d_ttl = min(s_maxcachettl, rec.d_ttl);
+
+        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
+      LOG("NO!" << endl);
+  }
+
+  // supplant
+  for (auto& entry : tcache) {
+    if ((entry.second.records.size() + entry.second.signatures.size() + authorityRecs.size()) > 1) { // need to group the ttl to be the minimum of the RRSET (RFC 2181, 5.2)
+      uint32_t lowestTTD = computeLowestTTD(entry.second.records, entry.second.signatures, entry.second.signaturesTTL, authorityRecs);
+
+      for (auto& record : entry.second.records) {
+        record.d_ttl = lowestTTD; // boom
+      }
+    }
+  }
+
+  for (auto tCacheEntry = tcache.begin(); tCacheEntry != tcache.end(); ++tCacheEntry) {
+
+    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
+       because keeping records in the additional section is allowed even
+       if the corresponding RRSIGs are not included, without setting the TC
+       bit, as stated in rfc4035's section 3.1.1.  Including RRSIG RRs in a Response:
+       "When placing a signed RRset in the Additional section, the name
+       server MUST also place its RRSIG RRs in the Additional section.
+       If space does not permit inclusion of both the RRset and its
+       associated RRSIG RRs, the name server MAY retain the RRset while
+       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 && 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 = 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 && (tCacheEntry->first.type == QType::DS || tCacheEntry->first.type == QType::NSEC || tCacheEntry->first.type == QType::NSEC3) && tCacheEntry->first.place == DNSResourceRecord::AUTHORITY) {
+      expectSignature = true;
+    }
+
+    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
+        contains only authoritative data.  However when the name sought is an
+        alias (see section 10.1.1) only the record describing that alias is
+        necessarily authoritative.  Clients should assume that other records
+        may have come from the server's cache.  Where authoritative answers
+        are required, the client should query again, using the canonical name
+        associated with the alias.
+      */
+      isAA = false;
+      expectSignature = false;
+    }
+    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) && 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
+         of non-authoritative NS in the cache, they might be able to keep a "ghost" zone alive forever,
+         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 " << tCacheEntry->first.name << "|" << DNSRecordContent::NumberToType(tCacheEntry->first.type) << endl);
+      continue;
+    }
+
+    /*
+     * RFC 6672 section 5.3.1
+     *  In any response, a signed DNAME RR indicates a non-terminal
+     *  redirection of the query.  There might or might not be a server-
+     *  synthesized CNAME in the answer section; if there is, the CNAME will
+     *  never be signed.  For a DNSSEC validator, verification of the DNAME
+     *  RR and then that the CNAME was properly synthesized is sufficient
+     *  proof.
+     *
+     * We do the synthesis check in processRecords, here we make sure we
+     * don't validate the CNAME.
+     */
+    if (isDNAMEAnswer && tCacheEntry->first.type == QType::CNAME) {
+      expectSignature = false;
+    }
+
+    vState recordState = vState::Indeterminate;
+
+    if (expectSignature && shouldValidate()) {
+      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 (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(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 {
+        recordState = initialState;
+        LOG(prefix << qname << ": Skipping validation because the current state is " << recordState << endl);
+      }
+
+      LOG(prefix << qname << ": Validation result is " << recordState << ", current state is " << state << endl);
+      if (state != recordState) {
+        updateValidationState(qname, state, recordState, prefix);
+      }
+    }
+
+    if (vStateIsBogus(recordState)) {
+      /* this is a TTD by now, be careful */
+      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:
+       - we don't allow direct NSEC3 queries
+       - denial of existence proofs in wildcard expanded positive responses are stored in authorityRecs
+       - denial of existence proofs for negative responses are stored in the negative cache
+       We also don't want to cache non-authoritative data except for:
+       - records coming from non forward-recurse servers (those will never be AA)
+       - DS (special case)
+       - NS, A and AAAA (used for infra queries)
+    */
+    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 (tCacheEntry->first.place == DNSResourceRecord::ANSWER && ednsmask) {
+        const bool isv4 = ednsmask->isIPv4();
+        if ((isv4 && s_ecsipv4nevercache) || (!isv4 && s_ecsipv6nevercache)) {
+          doCache = false;
+        }
+        // If ednsmask is relevant, we do not want to cache if the scope prefix length is large and TTL is small
+        if (doCache && s_ecscachelimitttl > 0) {
+          bool manyMaskBits = (isv4 && ednsmask->getBits() > s_ecsipv4cachelimit) || (!isv4 && ednsmask->getBits() > s_ecsipv6cachelimit);
+
+          if (manyMaskBits) {
+            uint32_t minttl = UINT32_MAX;
+            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) {
+              // Case: many bits and ttlIsSmall
+              doCache = false;
+            }
+          }
+        }
+      }
+
+      d_fromAuthIP = remoteIP;
+
+      if (doCache) {
+        // Check if we are going to replace a non-auth (parent) NS recordset
+        if (isAA && tCacheEntry->first.type == QType::NS && s_save_parent_ns_set) {
+          rememberParentSetIfNeeded(tCacheEntry->first.name, tCacheEntry->second.records, depth, prefix);
+        }
+        g_recCache->replace(d_now.tv_sec, tCacheEntry->first.name, tCacheEntry->first.type, tCacheEntry->second.records, tCacheEntry->second.signatures, authorityRecs, 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(tCacheEntry->first.name, tCacheEntry->first.type);
+        }
+
+        if (g_aggressiveNSECCache && needWildcardProof && recordState == vState::Secure && tCacheEntry->first.place == DNSResourceRecord::ANSWER && tCacheEntry->first.name == qname && !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 = tCacheEntry->second.signatures.at(0);
+
+          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(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(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() && !tCacheEntry->second.signatures.empty()) {
+      seenAuth = getSigner(tCacheEntry->second.signatures);
+    }
+
+    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, tCacheEntry->first.name, tCacheEntry->second.records.at(0), tCacheEntry->second.signatures, tCacheEntry->first.type == QType::NSEC3);
+    }
+
+    if (tCacheEntry->first.place == DNSResourceRecord::ANSWER && ednsmask) {
+      d_wasVariable = true;
+    }
+  }
+
+  return RCode::NoError;
+}
+
+void SyncRes::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)
+{
+  if (denialState == expectedState) {
+    neValidationState = vState::Secure;
+  }
+  else {
+    if (denialState == dState::OPTOUT) {
+      LOG(prefix << qname << ": OPT-out denial found for " << neName << endl);
+      /* rfc5155 states:
+         "The AD bit, as defined by [RFC4035], MUST NOT be set when returning a
+         response containing a closest (provable) encloser proof in which the
+         NSEC3 RR that covers the "next closer" name has the Opt-Out bit set.
+
+         This rule is based on what this closest encloser proof actually
+         proves: names that would be covered by the Opt-Out NSEC3 RR may or
+         may not exist as insecure delegations.  As such, not all the data in
+         responses containing such closest encloser proofs will have been
+         cryptographically verified, so the AD bit cannot be set."
+
+         At best the Opt-Out NSEC3 RR proves that there is no signed DS (so no
+         secure delegation).
+      */
+      neValidationState = vState::Insecure;
+    }
+    else if (denialState == dState::INSECURE) {
+      LOG(prefix << qname << ": Insecure denial found for " << neName << ", returning Insecure" << endl);
+      neValidationState = vState::Insecure;
+    }
+    else {
+      LOG(prefix << qname << ": Invalid denial found for " << neName << ", res=" << denialState << ", expectedState=" << expectedState << ", checking whether we have missed a zone cut before returning a Bogus state" << endl);
+      /* try again to get the missed cuts, harder this time */
+      auto zState = getValidationStatus(neName, false, isDS, depth, prefix);
+      if (zState != vState::Secure) {
+        neValidationState = zState;
+      }
+      else {
+        LOG(prefix << qname << ": Still in a secure zone with an invalid denial for " << neName << ", returning " << vStateToString(vState::BogusInvalidDenial) << endl);
+        neValidationState = vState::BogusInvalidDenial;
+      }
+    }
+  }
+  updateValidationState(qname, state, neValidationState, prefix);
+}
+
+dState SyncRes::getDenialValidationState(const NegCache::NegCacheEntry& negEntry, const dState expectedState, bool referralToUnsigned, const string& 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) // // NOLINT(readability-function-cognitive-complexity)
+{
+  bool done = false;
+  DNSName dnameTarget;
+  DNSName dnameOwner;
+  uint32_t dnameTTL = 0;
+  bool referralOnDS = false;
+
+  for (auto& rec : lwr.d_records) {
+    if (rec.d_type == QType::OPT || rec.d_class != QClass::IN) {
+      continue;
+    }
+
+    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) {
+        continue;
+      }
+    }
+    const bool negCacheIndication = rec.d_place == DNSResourceRecord::AUTHORITY && rec.d_type == QType::SOA && lwr.d_rcode == RCode::NXDomain && qname.isPartOf(rec.d_name) && rec.d_name.isPartOf(auth);
+
+    bool putInNegCache = true;
+    if (negCacheIndication && qtype == QType::DS && isForwardOrAuth(qname)) {
+      // #10189, a NXDOMAIN to a DS query for a forwarded or auth domain should not NXDOMAIN the whole domain
+      putInNegCache = false;
+    }
+
+    if (negCacheIndication) {
+      LOG(prefix << qname << ": Got negative caching indication for name '" << qname << "' (accept=" << rec.d_name.isPartOf(auth) << "), newtarget='" << newtarget << "'" << endl);
+
+      rec.d_ttl = min(rec.d_ttl, s_maxnegttl);
+      // only add a SOA if we're not going anywhere after this
+      if (newtarget.empty()) {
+        ret.push_back(rec);
+      }
+
+      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 */
+      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)) {
+        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, !negEntry.authoritySOA.signatures.empty() || !negEntry.DNSSECRecords.signatures.empty(), false, depth, prefix);
+        if (recordState == vState::Secure) {
+          dState denialState = getDenialValidationState(negEntry, dState::NXDOMAIN, false, prefix);
+          updateDenialValidationState(qname, negEntry.d_validationState, negEntry.d_name, state, denialState, dState::NXDOMAIN, false, depth, prefix);
+        }
+        else {
+          negEntry.d_validationState = recordState;
+          updateValidationState(qname, state, negEntry.d_validationState, prefix);
+        }
+      }
+
+      if (vStateIsBogus(negEntry.d_validationState)) {
+        lowestTTL = min(lowestTTL, s_maxbogusttl);
+      }
+
+      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(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 && negEntry.d_auth.isRoot() && auth.isRoot() && lwr.d_aabit) {
+          negEntry.d_name = negEntry.d_name.getLastLabel();
+          g_negCache->add(negEntry);
+        }
+      }
+
+      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
+             s_redirectionQTypes.count(qtype.getCode()) == 0) { // But not in response to a CNAME or DNAME query
+      if (rec.d_type == QType::CNAME && rec.d_name == qname) {
+        if (!dnameOwner.empty()) { // We synthesize ourselves
+          continue;
+        }
+        ret.push_back(rec);
+        if (auto content = getRR<CNAMERecordContent>(rec)) {
+          newtarget = DNSName(content->getTarget());
+        }
+      }
+      else if (rec.d_type == QType::DNAME && qname.isPartOf(rec.d_name)) { // DNAME
+        ret.push_back(rec);
+        if (auto content = getRR<DNAMERecordContent>(rec)) {
+          dnameOwner = rec.d_name;
+          dnameTarget = content->getTarget();
+          dnameTTL = rec.d_ttl;
+          if (!newtarget.empty()) { // We had a CNAME before, remove it from ret so we don't cache it
+            ret.erase(std::remove_if(
+                        ret.begin(),
+                        ret.end(),
+                        [&qname](DNSRecord& dnsrecord) {
+                          return (dnsrecord.d_place == DNSResourceRecord::ANSWER && dnsrecord.d_type == QType::CNAME && dnsrecord.d_name == qname);
+                        }),
+                      ret.end());
+          }
+          try {
+            newtarget = qname.makeRelative(dnameOwner) + dnameTarget;
+          }
+          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)
+            // But there is no way to set the RCODE from this function
+            throw ImmediateServFailException("Unable to perform DNAME substitution(DNAME owner: '" + dnameOwner.toLogString() + "', DNAME target: '" + dnameTarget.toLogString() + "', substituted name: '" + qname.makeRelative(dnameOwner).toLogString() + "." + dnameTarget.toLogString() + "' : " + e.what());
+          }
+        }
+      }
+    }
+    /* if we have a positive answer synthesized from a wildcard, we need to
+       return the corresponding NSEC/NSEC3 records from the AUTHORITY section
+       proving that the exact name did not exist.
+       Except if this is a NODATA answer because then we will gather the NXNSEC records later */
+    else if (gatherWildcardProof && !negindic && (rec.d_type == QType::RRSIG || rec.d_type == QType::NSEC || rec.d_type == QType::NSEC3) && rec.d_place == DNSResourceRecord::AUTHORITY) {
+      ret.push_back(rec); // enjoy your DNSSEC
+    }
+    // for ANY answers we *must* have an authoritative answer, unless we are forwarding recursively
+    else if (rec.d_place == DNSResourceRecord::ANSWER && rec.d_name == qname && (rec.d_type == qtype.getCode() || ((lwr.d_aabit || sendRDQuery) && qtype == QType::ANY))) {
+      LOG(prefix << qname << ": Answer is in: resolved to '" << rec.getContent()->getZoneRepresentation() << "|" << DNSRecordContent::NumberToType(rec.d_type) << "'" << endl);
+
+      done = true;
+      rcode = RCode::NoError;
+
+      if (needWildcardProof) {
+        /* positive answer synthesized from a wildcard */
+        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, negEntry, d_now.tv_sec, &lowestTTL);
+
+        if (vStateIsBogus(state)) {
+          negEntry.d_validationState = state;
+        }
+        else {
+          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(negEntry);
+            dState res = getDenial(csp, qname, negEntry.d_qtype.getCode(), false, false, d_validationContext, LogObject(prefix), false, wildcardLabelsCount);
+            if (res != dState::NXDOMAIN) {
+              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. */
+                tmpState = vState::Insecure;
+                LOG(prefix << qname << ": Unable to validate denial in wildcard expanded positive response found for " << qname << ", returning Insecure, res=" << res << endl);
+              }
+              else {
+                LOG(prefix << qname << ": Invalid denial in wildcard expanded positive response found for " << qname << ", returning Bogus, res=" << res << endl);
+                rec.d_ttl = std::min(rec.d_ttl, s_maxbogusttl);
+              }
+
+              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, tmpState);
+            }
+          }
+        }
+      }
+
+      ret.push_back(rec);
+    }
+    else if ((rec.d_type == QType::RRSIG || rec.d_type == QType::NSEC || rec.d_type == QType::NSEC3) && rec.d_place == DNSResourceRecord::ANSWER) {
+      if (rec.d_type != QType::RRSIG || rec.d_name == qname) {
+        ret.push_back(rec); // enjoy your DNSSEC
+      }
+      else if (rec.d_type == QType::RRSIG && qname.isPartOf(rec.d_name)) {
+        auto rrsig = getRR<RRSIGRecordContent>(rec);
+        if (rrsig != nullptr && rrsig->d_type == QType::DNAME) {
+          ret.push_back(rec);
+        }
+      }
+    }
+    else if (rec.d_place == DNSResourceRecord::AUTHORITY && rec.d_type == QType::NS && qname.isPartOf(rec.d_name)) {
+      if (moreSpecificThan(rec.d_name, auth)) {
+        newauth = rec.d_name;
+        LOG(prefix << qname << ": Got NS record '" << rec.d_name << "' -> '" << rec.getContent()->getZoneRepresentation() << "'" << endl);
+
+        /* check if we have a referral from the parent zone to a child zone for a DS query, which is not right */
+        if (qtype == QType::DS && (newauth.isPartOf(qname) || qname == newauth)) {
+          /* just got a referral from the parent zone when asking for a DS, looks like this server did not get the DNSSEC memo.. */
+          referralOnDS = true;
+        }
+        else {
+          realreferral = true;
+          if (auto content = getRR<NSRecordContent>(rec)) {
+            nsset.insert(content->getNS());
+          }
+        }
+      }
+      else {
+        LOG(prefix << qname << ": Got upwards/level NS record '" << rec.d_name << "' -> '" << rec.getContent()->getZoneRepresentation() << "', had '" << auth << "'" << endl);
+        if (auto content = getRR<NSRecordContent>(rec)) {
+          nsset.insert(content->getNS());
+        }
+      }
+    }
+    else if (rec.d_place == DNSResourceRecord::AUTHORITY && rec.d_type == QType::DS && qname.isPartOf(rec.d_name)) {
+      LOG(prefix << qname << ": Got DS record '" << rec.d_name << "' -> '" << rec.getContent()->getZoneRepresentation() << "'" << endl);
+    }
+    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 negEntry;
+      uint32_t lowestTTL = rec.d_ttl;
+      harvestNXRecords(lwr.d_records, negEntry, d_now.tv_sec, &lowestTTL);
+
+      if (!vStateIsBogus(state)) {
+        auto recordState = getValidationStatus(newauth, !negEntry.authoritySOA.signatures.empty() || !negEntry.DNSSECRecords.signatures.empty(), true, depth, prefix);
+
+        if (recordState == vState::Secure) {
+          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(negEntry, dState::NXQTYPE, true, prefix);
+
+          if (denialState == dState::NXQTYPE || denialState == dState::OPTOUT || denialState == dState::INSECURE) {
+            negEntry.d_ttd = lowestTTL + d_now.tv_sec;
+            negEntry.d_orig_ttl = lowestTTL;
+            negEntry.d_validationState = vState::Secure;
+            if (denialState == dState::OPTOUT) {
+              negEntry.d_validationState = vState::Insecure;
+            }
+            LOG(prefix << qname << ": Got negative indication of DS record for '" << newauth << "'" << endl);
+
+            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
+               query. */
+            if (qtype == QType::DS && qname == newauth && (d_externalDSQuery.empty() || qname != d_externalDSQuery)) {
+              /* we are actually done! */
+              negindic = true;
+              negIndicHasSignatures = !negEntry.authoritySOA.signatures.empty() || !negEntry.DNSSECRecords.signatures.empty();
+              nsset.clear();
+            }
+          }
+        }
+      }
+    }
+    else if (!done && rec.d_place == DNSResourceRecord::AUTHORITY && rec.d_type == QType::SOA && lwr.d_rcode == RCode::NoError && qname.isPartOf(rec.d_name)) {
+      LOG(prefix << qname << ": Got negative caching indication for '" << qname << "|" << qtype << "'" << endl);
+
+      if (!newtarget.empty()) {
+        LOG(prefix << qname << ": Hang on! Got a redirect to '" << newtarget << "' already" << endl);
+      }
+      else {
+        rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
+
+        NegCache::NegCacheEntry negEntry;
+        negEntry.d_auth = rec.d_name;
+        uint32_t lowestTTL = rec.d_ttl;
+        negEntry.d_name = qname;
+        negEntry.d_qtype = qtype;
+        harvestNXRecords(lwr.d_records, negEntry, d_now.tv_sec, &lowestTTL);
+
+        if (vStateIsBogus(state)) {
+          negEntry.d_validationState = state;
+        }
+        else {
+          auto recordState = getValidationStatus(qname, !negEntry.authoritySOA.signatures.empty() || !negEntry.DNSSECRecords.signatures.empty(), qtype == QType::DS, depth, prefix);
+          if (recordState == vState::Secure) {
+            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 {
+            negEntry.d_validationState = recordState;
+            updateValidationState(qname, state, negEntry.d_validationState, prefix);
+          }
+        }
+
+        if (vStateIsBogus(negEntry.d_validationState)) {
+          lowestTTL = min(lowestTTL, s_maxbogusttl);
+          rec.d_ttl = min(rec.d_ttl, s_maxbogusttl);
+        }
+        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 = !negEntry.authoritySOA.signatures.empty() || !negEntry.DNSSECRecords.signatures.empty();
+      }
+    }
+  }
+
+  if (!dnameTarget.empty()) {
+    // Synthesize a CNAME
+    auto cnamerec = DNSRecord();
+    cnamerec.d_name = qname;
+    cnamerec.d_type = QType::CNAME;
+    cnamerec.d_ttl = dnameTTL;
+    cnamerec.setContent(std::make_shared<CNAMERecordContent>(CNAMERecordContent(newtarget)));
+    ret.push_back(std::move(cnamerec));
+  }
+
+  /* If we have seen a proper denial, let's forget that we also had a referral for a DS query.
+     Otherwise we need to deal with it. */
+  if (referralOnDS && !negindic) {
+    LOG(prefix << qname << ": Got a referral to the child zone for a DS query without a negative indication (missing SOA in authority), treating that as a NODATA" << endl);
+    if (!vStateIsBogus(state)) {
+      auto recordState = getValidationStatus(qname, false, true, depth, prefix);
+      if (recordState == vState::Secure) {
+        /* we are in a secure zone, got a referral to the child zone on a DS query, no denial, that's wrong */
+        LOG(prefix << qname << ": NODATA without a negative indication (missing SOA in authority) in a DNSSEC secure zone, going Bogus" << endl);
+        updateValidationState(qname, state, vState::BogusMissingNegativeIndication, prefix);
+      }
+    }
+    negindic = true;
+    negIndicHasSignatures = false;
+  }
+
+  return done;
+}
+
+static void submitTryDotTask(ComboAddress address, const DNSName& auth, const DNSName& nsname, time_t now)
+{
+  if (address.getPort() == 853) {
+    return;
+  }
+  address.setPort(853);
+  auto lock = s_dotMap.lock();
+  if (lock->d_numBusy >= SyncRes::s_max_busy_dot_probes) {
+    return;
+  }
+  auto iter = lock->d_map.emplace(DoTStatus{address, auth, now + dotFailWait}).first;
+  if (iter->d_status == DoTStatus::Busy) {
+    return;
+  }
+  if (iter->d_ttd > now) {
+    if (iter->d_status == DoTStatus::Bad) {
+      return;
+    }
+    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 (iter->d_status == DoTStatus::Unknown && iter->d_count == 0) {
+      return;
+    }
+  }
+  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) {
+    iter->d_status = DoTStatus::Busy;
+    ++lock->d_numBusy;
+  }
+}
+
+static bool shouldDoDoT(ComboAddress address, time_t now)
+{
+  address.setPort(853);
+  auto lock = s_dotMap.lock();
+  auto iter = lock->d_map.find(address);
+  if (iter == lock->d_map.end()) {
+    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 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;
+    }
+  }
+}
+
+bool SyncRes::tryDoT(const DNSName& qname, const QType qtype, const DNSName& nsName, ComboAddress address, time_t now)
+{
+  auto log = g_slog->withName("taskq")->withValues("method", Logging::Loggable("tryDoT"), "name", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype).toString()), "ip", Logging::Loggable(address));
+
+  auto logHelper1 = [&log](const string& ename) {
+    log->info(Logr::Debug, "Failed to probe DoT records, got an exception", "exception", Logging::Loggable(ename));
+  };
+  auto logHelper2 = [&log](const string& msg, const string& ename) {
+    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> netmask;
+  address.setPort(853);
+  // We use the fact that qname equals auth
+  bool isOK = false;
+  try {
+    boost::optional<EDNSExtendedError> extendedError;
+    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");
+  }
+  catch (const ImmediateServFailException& e) {
+    logHelper2(e.reason, "ImmediateServFailException");
+  }
+  catch (const PolicyHitException& e) {
+    logHelper1("PolicyHitException");
+  }
+  catch (const std::exception& e) {
+    logHelper2(e.what(), "std::exception");
+  }
+  catch (...) {
+    logHelper1("other");
+  }
+  updateDoTStatus(address, isOK ? DoTStatus::Good : DoTStatus::Bad, now + (isOK ? dotSuccessWait : dotFailWait), true);
+  return isOK;
+}
+
+void SyncRes::ednsStats(boost::optional<Netmask>& ednsmask, const DNSName& qname, const string& prefix)
+{
+  if (!ednsmask) {
+    return;
+  }
+  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);
+    }
+  }
+}
+
+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 " << address.toStringWithPort() << endl);
+      t_Counters.at(rec::Counter::dotoutqueries)++;
+      d_dotoutqueries++;
+    }
+    else {
+      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) // NOLINT(readability-function-cognitive-complexity)
+{
+  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})) {
+    LOG(prefix << qname << ": Query handled by Lua" << endl);
+  }
+  else {
+    ednsmask = getEDNSSubnetMask(qname, remoteIP);
+    if (ednsmask) {
+      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!
+    ednsStats(ednsmask, qname, prefix);
+  }
+
+  /* preoutquery killed the query by setting dq.rcode to -3 */
+  if (preOutQueryRet == -3) {
+    throw ImmediateServFailException("Query killed by policy");
+  }
+
+  d_totUsec += lwr.d_usec;
+
+  if (resolveret == LWResult::Result::Spoofed) {
+    spoofed = true;
+    return false;
+  }
+
+  accountAuthLatency(lwr.d_usec, remoteIP.sin4.sin_family);
+  ++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);
+  }
+
+  if (resolveret != LWResult::Result::Success) {
+    /* Error while resolving */
+    if (resolveret == LWResult::Result::Timeout) {
+      /* Time out */
+
+      LOG(prefix << qname << ": Timeout resolving after " << lwr.d_usec / 1000.0 << " ms " << (doTCP ? "over TCP" : "") << endl);
+      d_timeouts++;
+      t_Counters.at(rec::Counter::outgoingtimeouts)++;
+
+      if (remoteIP.sin4.sin_family == AF_INET) {
+        t_Counters.at(rec::Counter::outgoing4timeouts)++;
+      }
+      else {
+        t_Counters.at(rec::Counter::outgoing6timeouts)++;
+      }
+
+      if (t_timeouts) {
+        t_timeouts->push_back(remoteIP);
+      }
+    }
+    else if (resolveret == LWResult::Result::OSLimitError) {
+      /* OS resource limit reached */
+      LOG(prefix << qname << ": Hit a local resource limit resolving" << (doTCP ? " over TCP" : "") << ", probable error: " << stringerror() << endl);
+      t_Counters.at(rec::Counter::resourceLimits)++;
+    }
+    else {
+      /* LWResult::Result::PermanentError */
+      t_Counters.at(rec::Counter::unreachables)++;
+      d_unreachables++;
+      // XXX questionable use of errno
+      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) {
+      s_nsSpeeds.lock()->find_or_enter(nsName.empty() ? DNSName(remoteIP.toStringWithPort()) : nsName, d_now).submit(remoteIP, 1000000, d_now); // 1 sec
+
+      // 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);
+      }
+      else if (resolveret == LWResult::Result::PermanentError) {
+        // unreachable, 1 minute or 100 queries
+        doThrottle(d_now.tv_sec, remoteIP, qname, qtype, 60, 100);
+      }
+      else {
+        // timeout, 10 seconds or 5 queries
+        doThrottle(d_now.tv_sec, remoteIP, qname, qtype, 10, 5);
+      }
+    }
+
+    return 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) {
+
+      // let's make sure we prefer a different server for some time, if there is one available
+      s_nsSpeeds.lock()->find_or_enter(nsName.empty() ? DNSName(remoteIP.toStringWithPort()) : nsName, d_now).submit(remoteIP, 1000000, d_now); // 1 sec
+
+      if (doTCP) {
+        // we can be more heavy-handed over TCP
+        doThrottle(d_now.tv_sec, remoteIP, qname, qtype, 60, 10);
+      }
+      else {
+        doThrottle(d_now.tv_sec, remoteIP, qname, qtype, 10, 2);
+      }
+    }
+    return false;
+  }
+  /* 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;
+  }
+
+  /* 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;
+
+    if (doTCP) {
+      LOG(prefix << qname << ": Truncated bit set, over TCP?" << endl);
+      if (!dontThrottle) {
+        /* let's treat that as a ServFail answer from this server */
+        doThrottle(d_now.tv_sec, remoteIP, qname, qtype, 60, 3);
+      }
+      return false;
+    }
+    LOG(prefix << qname << ": Truncated bit set, over UDP" << endl);
+
+    return true;
+  }
+
+  return true;
+}
+
+void SyncRes::handleNewTarget(const std::string& prefix, const DNSName& qname, const DNSName& newtarget, const QType qtype, std::vector<DNSRecord>& ret, int& rcode, unsigned int depth, const std::vector<DNSRecord>& recordsFromAnswer, vState& state)
+{
+  if (newtarget == qname) {
+    LOG(prefix << qname << ": Status=got a CNAME referral to self, returning SERVFAIL" << endl);
+    ret.clear();
+    rcode = RCode::ServFail;
+    return;
+  }
+  if (newtarget.isPartOf(qname)) {
+    // a.b.c. CNAME x.a.b.c will go to great depths with QM on
+    LOG(prefix << qname << ": Status=got a CNAME referral to child, disabling QM" << endl);
+    setQNameMinimization(false);
+  }
+
+  if (!d_followCNAME) {
+    rcode = RCode::NoError;
+    return;
+  }
+
+  // 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);
+
+    if (d_doDNSSEC) {
+      addNXNSECS(ret, recordsFromAnswer);
+    }
+
+    rcode = RCode::NoError;
+    return;
+  }
+
+  LOG(prefix << qname << ": Status=got a CNAME referral, starting over with " << newtarget << endl);
+
+  set<GetBestNSAnswer> beenthere;
+  Context cnameContext;
+  rcode = doResolve(newtarget, qtype, ret, depth + 1, beenthere, cnameContext);
+  LOG(prefix << qname << ": Updating validation state for response to " << qname << " from " << state << " with the state from the CNAME quest: " << cnameContext.state << endl);
+  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)
+{
+  if (s_minimumTTL != 0) {
+    for (auto& rec : lwr.d_records) {
+      rec.d_ttl = max(rec.d_ttl, s_minimumTTL);
+    }
+  }
+
+  /* if the answer is ECS-specific, a minimum TTL is set for this kind of answers
+     and it's higher than the global minimum TTL */
+  if (ednsmask && s_minimumECSTTL > 0 && (s_minimumTTL == 0 || s_minimumECSTTL > s_minimumTTL)) {
+    for (auto& rec : lwr.d_records) {
+      if (rec.d_place == DNSResourceRecord::ANSWER) {
+        rec.d_ttl = max(rec.d_ttl, s_minimumECSTTL);
+      }
+    }
+  }
+
+  bool needWildcardProof = false;
+  bool gatherWildcardProof = false;
+  unsigned int wildcardLabelsCount = 0;
+  *rcode = updateCacheFromRecords(depth, prefix, lwr, qname, qtype, auth, wasForwarded, ednsmask, state, needWildcardProof, gatherWildcardProof, wildcardLabelsCount, sendRDQuery, remoteIP);
+  if (*rcode != RCode::NoError) {
+    return true;
+  }
+
+  LOG(prefix << qname << ": Determining status after receiving this packet" << endl);
+
+  set<DNSName> nsset;
+  bool realreferral = false;
+  bool negindic = false;
+  bool negIndicHasSignatures = false;
+  DNSName newauth;
+  DNSName newtarget;
+
+  bool done = processRecords(prefix, qname, qtype, auth, lwr, sendRDQuery, ret, nsset, newtarget, newauth, realreferral, negindic, state, needWildcardProof, gatherWildcardProof, wildcardLabelsCount, *rcode, negIndicHasSignatures, depth);
+
+  if (done) {
+    LOG(prefix << qname << ": Status=got results, this level of recursion done" << endl);
+    LOG(prefix << qname << ": Validation status is " << state << endl);
+    return true;
+  }
+
+  if (!newtarget.empty()) {
+    handleNewTarget(prefix, qname, newtarget, qtype.getCode(), ret, *rcode, depth, lwr.d_records, state);
+    return true;
+  }
+
+  if (lwr.d_rcode == RCode::NXDomain) {
+    LOG(prefix << qname << ": Status=NXDOMAIN, we are done " << (negindic ? "(have negative SOA)" : "") << endl);
+
+    auto tempState = getValidationStatus(qname, negIndicHasSignatures, qtype == QType::DS, depth, prefix);
+    if (tempState == vState::Secure && (lwr.d_aabit || sendRDQuery) && !negindic) {
+      LOG(prefix << qname << ": NXDOMAIN without a negative indication (missing SOA in authority) in a DNSSEC secure zone, going Bogus" << endl);
+      updateValidationState(qname, state, vState::BogusMissingNegativeIndication, prefix);
+    }
+    else {
+      /* we might not have validated any record, because we did get a NXDOMAIN without any SOA
+         from an insecure zone, for example */
+      updateValidationState(qname, state, tempState, prefix);
+    }
+
+    if (d_doDNSSEC) {
+      addNXNSECS(ret, lwr.d_records);
+    }
+
+    *rcode = RCode::NXDomain;
+    return true;
+  }
+
+  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);
+    if (tempState == vState::Secure && (lwr.d_aabit || sendRDQuery) && !negindic) {
+      LOG(prefix << qname << ": NODATA without a negative indication (missing SOA in authority) in a DNSSEC secure zone, going Bogus" << endl);
+      updateValidationState(qname, state, vState::BogusMissingNegativeIndication, prefix);
+    }
+    else {
+      /* we might not have validated any record, because we did get a NODATA without any SOA
+         from an insecure zone, for example */
+      updateValidationState(qname, state, tempState, prefix);
+    }
+
+    if (d_doDNSSEC) {
+      addNXNSECS(ret, lwr.d_records);
+    }
+
+    *rcode = RCode::NoError;
+    return true;
+  }
+
+  if (realreferral) {
+    LOG(prefix << qname << ": Status=did not resolve, got " << (unsigned int)nsset.size() << " NS, ");
+
+    nameservers.clear();
+    for (auto const& nameserver : nsset) {
+      if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
+        bool match = dfe.getProcessingPolicy(nameserver, 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
+            if (d_pdl && d_pdl->policyHitEventFilter(d_requestor, qname, qtype, d_queryReceivedOverTCP, d_appliedPolicy, d_policyTags, d_discardedPolicies)) {
+              /* reset to no match */
+              d_appliedPolicy = DNSFilterEngine::Policy();
+            }
+            else {
+              LOG("however " << nameserver << " was blocked by RPZ policy '" << d_appliedPolicy.getName() << "'" << endl);
+              throw PolicyHitException();
+            }
+          }
+        }
+      }
+      nameservers.insert({nameserver, {{}, false}});
+    }
+    LOG("looping to them" << endl);
+    *gotNewServers = true;
+    auth = std::move(newauth);
+
+    return false;
+  }
+
+  return false;
+}
+
+bool SyncRes::doDoTtoAuth(const DNSName& nameServer)
+{
+  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,
+                         map<DNSName, vector<ComboAddress>>* fallBack)
+{
+  auto luaconfsLocal = g_luaconfs.getLocal();
+
+  LOG(prefix << qname << ": Cache consultations done, have " << (unsigned int)nameservers.size() << " NS to contact");
+
+  if (nameserversBlockedByRPZ(luaconfsLocal->dfe, nameservers)) {
+    /* 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(endl);
+
+  unsigned int addressQueriesForNS = 0;
+  for (;;) { // we may get more specific nameservers
+    auto rnameservers = shuffleInSpeedOrder(qname, nameservers, prefix);
+
+    // We allow s_maxnsaddressqperq (default 10) queries with empty responses when resolving NS names.
+    // If a zone publishes many (more than s_maxnsaddressqperq) NS records, we allow less.
+    // This is to "punish" zones that publish many non-resolving NS names.
+    // We always allow 5 NS name resolving attempts with empty results.
+    unsigned int nsLimit = s_maxnsaddressqperq;
+    if (rnameservers.size() > nsLimit) {
+      int newLimit = static_cast<int>(nsLimit - (rnameservers.size() - nsLimit));
+      nsLimit = std::max(5, newLimit);
+    }
+
+    for (auto tns = rnameservers.cbegin();; ++tns) {
+      if (addressQueriesForNS >= nsLimit) {
+        throw ImmediateServFailException(std::to_string(nsLimit) + " (adjusted max-ns-address-qperq) or more queries with empty results for NS addresses sent resolving " + qname.toLogString());
+      }
+      if (tns == rnameservers.cend()) {
+        LOG(prefix << qname << ": Failed to resolve via any of the " << (unsigned int)rnameservers.size() << " offered NS at level '" << auth << "'" << endl);
+        if (s_addExtendedResolutionDNSErrors) {
+          context.extendedError = EDNSExtendedError{static_cast<uint16_t>(EDNSExtendedError::code::NoReachableAuthority), "delegation " + auth.toLogString()};
+        }
+        if (!auth.isRoot() && flawedNSSet) {
+          LOG(prefix << qname << ": Ageing nameservers for level '" << auth << "', next query might succeed" << endl);
+          if (g_recCache->doAgeCache(d_now.tv_sec, auth, QType::NS, 10)) {
+            t_Counters.at(rec::Counter::nsSetInvalidations)++;
+          }
+        }
+        return -1;
+      }
+
+      bool cacheOnly = false;
+      // this line needs to identify the 'self-resolving' behaviour
+      if (qname == tns->first && (qtype.getCode() == QType::A || qtype.getCode() == QType::AAAA)) {
+        /* we might have a glue entry in cache so let's try this NS
+           but only if we have enough in the cache to know how to reach it */
+        LOG(prefix << qname << ": Using NS to resolve itself, but only using what we have in cache (" << (1 + tns - rnameservers.cbegin()) << "/" << rnameservers.size() << ")" << endl);
+        cacheOnly = true;
+      }
+
+      typedef vector<ComboAddress> remoteIPs_t;
+      remoteIPs_t remoteIPs;
+      remoteIPs_t::iterator remoteIP;
+      bool pierceDontQuery = false;
+      bool sendRDQuery = false;
+      boost::optional<Netmask> ednsmask;
+      LWResult lwr;
+      const bool wasForwarded = tns->first.empty() && (!nameservers[tns->first].first.empty());
+      int rcode = RCode::NoError;
+      bool gotNewServers = false;
+
+      if (tns->first.empty() && !wasForwarded) {
+        static ComboAddress const s_oobRemote("255.255.255.255");
+        LOG(prefix << qname << ": Domain is out-of-band" << endl);
+        /* setting state to indeterminate since validation is disabled for local auth zone,
+           and Insecure would be misleading. */
+        context.state = vState::Indeterminate;
+        d_wasOutOfBand = doOOBResolve(qname, qtype, lwr.d_records, depth, prefix, lwr.d_rcode);
+        lwr.d_tcbit = false;
+        lwr.d_aabit = true;
+
+        /* we have received an answer, are we done ? */
+        bool done = processAnswer(depth, prefix, lwr, qname, qtype, auth, false, ednsmask, sendRDQuery, nameservers, ret, luaconfsLocal->dfe, &gotNewServers, &rcode, context.state, s_oobRemote);
+        if (done) {
+          return rcode;
+        }
+        if (gotNewServers) {
+          if (stopAtDelegation != nullptr && *stopAtDelegation == Stop) {
+            *stopAtDelegation = Stopped;
+            return rcode;
+          }
+          break;
+        }
+      }
+      else {
+        if (fallBack != nullptr) {
+          if (auto iter = fallBack->find(tns->first); iter != fallBack->end()) {
+            remoteIPs = iter->second;
+          }
+        }
+        if (remoteIPs.empty()) {
+          remoteIPs = retrieveAddressesForNS(prefix, qname, tns, depth, beenthere, rnameservers, nameservers, sendRDQuery, pierceDontQuery, flawedNSSet, cacheOnly, addressQueriesForNS);
+        }
+
+        if (remoteIPs.empty()) {
+          LOG(prefix << qname << ": Failed to get IP for NS " << tns->first << ", trying next if available" << endl);
+          flawedNSSet = true;
+          continue;
+        }
+        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;
+          }
+        }
+        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();
+          }
+        }
+
+        for (remoteIP = remoteIPs.begin(); remoteIP != remoteIPs.end(); ++remoteIP) {
+          LOG(prefix << qname << ": Trying IP " << remoteIP->toStringWithPort() << ", asking '" << qname << "|" << qtype << "'" << endl);
+
+          if (throttledOrBlocked(prefix, *remoteIP, qname, qtype, pierceDontQuery)) {
+            // As d_throttledqueries might be increased, check the max-qperq condition
+            checkMaxQperQ(qname);
+            continue;
+          }
+
+          bool truncated = false;
+          bool spoofed = false;
+          bool gotAnswer = false;
+          bool doDoT = false;
+
+          if (doDoTtoAuth(tns->first)) {
+            remoteIP->setPort(853);
+            doDoT = true;
+          }
+          if (SyncRes::s_dot_to_port_853 && remoteIP->getPort() == 853) {
+            doDoT = true;
+          }
+          bool forceTCP = doDoT;
+
+          if (!doDoT && s_max_busy_dot_probes > 0) {
+            submitTryDotTask(*remoteIP, auth, tns->first, d_now.tv_sec);
+          }
+          if (!forceTCP) {
+            gotAnswer = doResolveAtThisIP(prefix, qname, qtype, lwr, ednsmask, auth, sendRDQuery, wasForwarded,
+                                          tns->first, *remoteIP, false, false, truncated, spoofed, context.extendedError);
+          }
+          if (forceTCP || (spoofed || (gotAnswer && truncated))) {
+            /* retry, over TCP this time */
+            gotAnswer = doResolveAtThisIP(prefix, qname, qtype, lwr, ednsmask, auth, sendRDQuery, wasForwarded,
+                                          tns->first, *remoteIP, true, doDoT, truncated, spoofed, context.extendedError);
+          }
+
+          if (!gotAnswer) {
+            if (doDoT && s_max_busy_dot_probes > 0) {
+              // This is quite pessimistic...
+              updateDoTStatus(*remoteIP, DoTStatus::Bad, d_now.tv_sec + dotFailWait);
+            }
+            continue;
+          }
+
+          LOG(prefix << qname << ": Got " << (unsigned int)lwr.d_records.size() << " answers from " << tns->first << " (" << remoteIP->toString() << "), rcode=" << lwr.d_rcode << " (" << RCode::to_s(lwr.d_rcode) << "), aa=" << lwr.d_aabit << ", in " << lwr.d_usec / 1000 << "ms" << endl);
+
+          if (doDoT && s_max_busy_dot_probes > 0) {
+            updateDoTStatus(*remoteIP, DoTStatus::Good, d_now.tv_sec + dotSuccessWait);
+          }
+          /*  // for you IPv6 fanatics :-)
+              if(remoteIP->sin4.sin_family==AF_INET6)
+              lwr.d_usec/=3;
+          */
+          //        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, 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);
+          if (done) {
+            return rcode;
+          }
+          if (gotNewServers) {
+            if (stopAtDelegation != nullptr && *stopAtDelegation == Stop) {
+              *stopAtDelegation = Stopped;
+              return rcode;
+            }
+            break;
+          }
+          /* was lame */
+          doThrottle(d_now.tv_sec, *remoteIP, qname, qtype, 60, 100);
+        }
+
+        if (gotNewServers) {
+          break;
+        }
+
+        if (remoteIP == remoteIPs.cend()) { // we tried all IP addresses, none worked
+          continue;
+        }
+      }
+    }
+  }
+  return -1;
+}
+
+void SyncRes::setQuerySource(const Netmask& netmask)
+{
+  if (!netmask.empty()) {
+    d_outgoingECSNetwork = netmask;
+  }
+  else {
+    d_outgoingECSNetwork = boost::none;
+  }
+}
+
+void SyncRes::setQuerySource(const ComboAddress& requestor, const boost::optional<const EDNSSubnetOpts&>& incomingECS)
+{
+  d_requestor = requestor;
+
+  if (incomingECS && incomingECS->source.getBits() > 0) {
+    d_cacheRemote = incomingECS->source.getMaskedNetwork();
+    uint8_t bits = std::min(incomingECS->source.getBits(), (incomingECS->source.isIPv4() ? s_ecsipv4limit : s_ecsipv6limit));
+    ComboAddress trunc = incomingECS->source.getNetwork();
+    trunc.truncate(bits);
+    d_outgoingECSNetwork = boost::optional<Netmask>(Netmask(trunc, bits));
+  }
+  else {
+    d_cacheRemote = d_requestor;
+    if (!incomingECS && s_ednslocalsubnets.match(d_requestor)) {
+      ComboAddress trunc = d_requestor;
+      uint8_t bits = d_requestor.isIPv4() ? 32 : 128;
+      bits = std::min(bits, (trunc.isIPv4() ? s_ecsipv4limit : s_ecsipv6limit));
+      trunc.truncate(bits);
+      d_outgoingECSNetwork = boost::optional<Netmask>(Netmask(trunc, bits));
+    }
+    else if (s_ecsScopeZero.source.getBits() > 0) {
+      /* RFC7871 says we MUST NOT send any ECS if the source scope is 0.
+         But using an empty ECS in that case would mean inserting
+         a non ECS-specific entry into the cache, preventing any further
+         ECS-specific query to be sent.
+         So instead we use the trick described in section 7.1.2:
+         "The subsequent Recursive Resolver query to the Authoritative Nameserver
+         will then either not include an ECS option or MAY optionally include
+         its own address information, which is what the Authoritative
+         Nameserver will almost certainly use to generate any Tailored
+         Response in lieu of an option.  This allows the answer to be handled
+         by the same caching mechanism as other queries, with an explicit
+         indicator of the applicable scope.  Subsequent Stub Resolver queries
+         for /0 can then be answered from this cached response.
+      */
+      d_outgoingECSNetwork = boost::optional<Netmask>(s_ecsScopeZero.source.getMaskedNetwork());
+      d_cacheRemote = s_ecsScopeZero.source.getNetwork();
+    }
+    else {
+      // ECS disabled because no scope-zero address could be derived.
+      d_outgoingECSNetwork = boost::none;
+    }
+  }
+}
+
+boost::optional<Netmask> SyncRes::getEDNSSubnetMask(const DNSName& name, const ComboAddress& rem)
+{
+  if (d_outgoingECSNetwork && (s_ednsdomains.check(name) || s_ednsremotesubnets.match(rem))) {
+    return d_outgoingECSNetwork;
+  }
+  return boost::none;
+}
+
+void SyncRes::parseEDNSSubnetAllowlist(const std::string& alist)
+{
+  vector<string> parts;
+  stringtok(parts, alist, ",; ");
+  for (const auto& allow : parts) {
+    try {
+      s_ednsremotesubnets.addMask(Netmask(allow));
+    }
+    catch (...) {
+      s_ednsdomains.add(DNSName(allow));
+    }
+  }
+}
+
+void SyncRes::parseEDNSSubnetAddFor(const std::string& subnetlist)
+{
+  vector<string> parts;
+  stringtok(parts, subnetlist, ",; ");
+  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, 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, 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, nullptr);
+
+  SyncRes resolver(now);
+  resolver.setQNameMinimization(qnamemin);
+  if (pdl) {
+    resolver.setLuaEngine(pdl);
+  }
+
+  int res = -1;
+  const std::string msg = "Exception while resolving";
+  try {
+    res = resolver.beginResolve(qname, qtype, qclass, ret, 0);
+  }
+  catch (const PDNSException& e) {
+    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::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::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::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::Warning << "Failed to resolve " << qname << ", got an exception" << endl,
+         log->info(Logr::Warning, msg));
+    ret.clear();
+  }
+
+  return res;
+}
+
+int SyncRes::getRootNS(struct timeval now, asyncresolve_t asyncCallback, unsigned int depth, Logr::log_t log)
+{
+  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 = resolver.beginResolve(g_rootdnsname, QType::NS, 1, ret, depth + 1);
+    if (g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate) {
+      auto state = resolver.getValidationState();
+      if (vStateIsBogus(state)) {
+        throw PDNSException("Got Bogus validation result for .|NS");
+      }
+    }
+  }
+  catch (const PDNSException& e) {
+    SLOG(g_log << Logger::Error << "Failed to update . records, got an exception: " << e.reason << endl,
+         log->error(Logr::Error, e.reason, msg, "exception", Logging::Loggable("PDNSException")));
+  }
+  catch (const ImmediateServFailException& e) {
+    SLOG(g_log << Logger::Error << "Failed to update . records, got an exception: " << e.reason << endl,
+         log->error(Logr::Error, e.reason, msg, "exception", Logging::Loggable("ImmediateServFailException")));
+  }
+  catch (const PolicyHitException& e) {
+    SLOG(g_log << Logger::Error << "Failed to update . records, got a policy hit" << endl,
+         log->info(Logr::Error, msg, "exception", Logging::Loggable("PolicyHitException")));
+    ret.clear();
+  }
+  catch (const std::exception& e) {
+    SLOG(g_log << Logger::Error << "Failed to update . records, got an exception: " << e.what() << endl,
+         log->error(Logr::Error, e.what(), msg, "exception", Logging::Loggable("std::exception")));
+  }
+  catch (...) {
+    SLOG(g_log << Logger::Error << "Failed to update . records, got an exception" << endl,
+         log->info(Logr::Error, msg));
+  }
+
+  if (res == 0) {
+    SLOG(g_log << Logger::Debug << "Refreshed . records" << endl,
+         log->info(Logr::Debug, "Refreshed . records"));
+  }
+  else {
+    SLOG(g_log << Logger::Warning << "Failed to update root NS records, RCODE=" << res << endl,
+         log->info(Logr::Warning, msg, "rcode", Logging::Loggable(res)));
+  }
+  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
+}
deleted file mode 120000 (symlink)
index 54005e02d3dc80bea9ec208e71d58d211f6528f7..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../syncres.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..ac5cb196ec21b60b82384afa8f36da01da0d1429
--- /dev/null
@@ -0,0 +1,963 @@
+/*
+ * 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 <atomic>
+#include "utility.hh"
+#include "dns.hh"
+#include "qtype.hh"
+#include <vector>
+#include <set>
+#include <unordered_set>
+#include <map>
+#include <cmath>
+#include <iostream>
+#include <utility>
+#include "misc.hh"
+#include "lwres.hh"
+#include <boost/optional.hpp>
+#include <boost/utility.hpp>
+#include "circular_buffer.hh"
+#include "sstuff.hh"
+#include "recursor_cache.hh"
+#include "mtasker.hh"
+#include "iputils.hh"
+#include "validate-recursor.hh"
+#include "ednssubnet.hh"
+#include "filterpo.hh"
+#include "negcache.hh"
+#include "proxy-protocol.hh"
+#include "sholder.hh"
+#include "histogram.hh"
+#include "stat_t.hh"
+#include "tcpiohandler.hh"
+#include "rec-eventtrace.hh"
+#include "logr.hh"
+#include "rec-tcounters.hh"
+#include "ednsextendederror.hh"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <boost/uuid/uuid.hpp>
+#ifdef HAVE_FSTRM
+#include "fstrm_logger.hh"
+#endif /* HAVE_FSTRM */
+
+extern GlobalStateHolder<SuffixMatchNode> g_xdnssec;
+extern GlobalStateHolder<SuffixMatchNode> g_dontThrottleNames;
+extern GlobalStateHolder<NetmaskGroup> g_dontThrottleNetmasks;
+extern GlobalStateHolder<SuffixMatchNode> g_DoTToAuthNames;
+
+enum class AdditionalMode : uint8_t; // defined in rec-lua-conf.hh
+
+class RecursorLua4;
+
+using NsSet = std::unordered_map<DNSName, pair<vector<ComboAddress>, bool>>;
+
+extern std::unique_ptr<NegCache> g_negCache;
+
+class SyncRes : public boost::noncopyable
+{
+public:
+  enum LogMode
+  {
+    LogNone,
+    Log,
+    Store
+  };
+  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
+  {
+    No,
+    DNSSEC,
+    Yes
+  };
+
+  struct Context
+  {
+    boost::optional<EDNSExtendedError> extendedError;
+    vState state{vState::Indeterminate};
+  };
+
+  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:
+    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<>, std::less<>>>>>;
+
+    records_t d_records;
+    vector<ComboAddress> d_servers;
+    DNSName d_name;
+    bool d_rdForward{false};
+
+    bool operator==(const AuthDomain& rhs) const;
+
+    [[nodiscard]] std::string print(const std::string& indent = "",
+                                    const std::string& indentLevel = "  ") const;
+
+    int getRecords(const DNSName& qname, QType qtype, std::vector<DNSRecord>& records) const;
+    [[nodiscard]] bool isAuth() const
+    {
+      return d_servers.empty();
+    }
+    [[nodiscard]] bool isForward() const
+    {
+      return !isAuth();
+    }
+    [[nodiscard]] bool shouldRecurse() const
+    {
+      return d_rdForward;
+    }
+    [[nodiscard]] const DNSName& getName() const
+    {
+      return d_name;
+    }
+
+  private:
+    void addSOA(std::vector<DNSRecord>& records) const;
+  };
+
+  using domainmap_t = std::unordered_map<DNSName, AuthDomain>;
+
+  struct ThreadLocalStorage
+  {
+    std::shared_ptr<domainmap_t> domainmap;
+  };
+
+  static void setDefaultLogMode(LogMode logmode)
+  {
+    s_lm = logmode;
+  }
+
+  OptLog LogObject(const string& prefix);
+
+  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) {
+      s_dontQuery = std::make_unique<NetmaskGroup>();
+    }
+    s_dontQuery->addMask(mask);
+  }
+  static void addDontQuery(const Netmask& mask)
+  {
+    if (!s_dontQuery) {
+      s_dontQuery = std::make_unique<NetmaskGroup>();
+    }
+    s_dontQuery->addMask(mask);
+  }
+  static void clearDontQuery()
+  {
+    s_dontQuery = nullptr;
+  }
+  static void parseEDNSSubnetAllowlist(const std::string& alist);
+  static void parseEDNSSubnetAddFor(const std::string& subnetlist);
+  static void addEDNSLocalSubnet(const std::string& subnet)
+  {
+    s_ednslocalsubnets.addMask(subnet);
+  }
+  static void addEDNSRemoteSubnet(const std::string& subnet)
+  {
+    s_ednsremotesubnets.addMask(subnet);
+  }
+  static void addEDNSDomain(const DNSName& domain)
+  {
+    s_ednsdomains.add(domain);
+  }
+  static void clearEDNSLocalSubnets()
+  {
+    s_ednslocalsubnets.clear();
+  }
+  static void clearEDNSRemoteSubnets()
+  {
+    s_ednsremotesubnets.clear();
+  }
+  static void clearEDNSDomains()
+  {
+    s_ednsdomains = SuffixMatchNode();
+  }
+
+  static void pruneNSSpeeds(time_t limit);
+  static uint64_t getNSSpeedsSize();
+  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& address);
+
+  struct EDNSStatus
+  {
+    EDNSStatus(const ComboAddress& arg) :
+      address(arg) {}
+    ComboAddress address;
+    time_t ttd{0};
+    enum EDNSMode : uint8_t
+    {
+      EDNSOK = 0,
+      EDNSIGNORANT = 1,
+      NOEDNS = 2
+    } mode{EDNSOK};
+
+    [[nodiscard]] std::string toString() const
+    {
+      const std::array<std::string, 3> modes = {"OK", "Ignorant", "No"};
+      auto umode = static_cast<unsigned int>(mode);
+      if (umode >= modes.size()) {
+        return "?";
+      }
+      return modes.at(umode);
+    }
+  };
+
+  static EDNSStatus::EDNSMode getEDNSStatus(const ComboAddress& server);
+  static uint64_t getEDNSStatusesSize();
+  static void clearEDNSStatuses();
+  static void pruneEDNSStatuses(time_t cutoff);
+
+  static uint64_t getThrottledServersSize();
+  static void pruneThrottledServers(time_t now);
+  static void clearThrottle();
+  static bool isThrottled(time_t now, const ComboAddress& server, const DNSName& target, QType qtype);
+  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();
+  static void pruneFailedServers(time_t cutoff);
+  static unsigned long getServerFailsCount(const ComboAddress& server);
+
+  static void clearNonResolvingNS();
+  static uint64_t getNonResolvingNSSize();
+  static void pruneNonResolving(time_t cutoff);
+
+  static void clearSaveParentsNSSets();
+  static size_t getSaveParentsNSSetsSize();
+  static void pruneSaveParentsNSSets(time_t now);
+
+  static void pruneDoTProbeMap(time_t cutoff);
+
+  static void setDomainMap(std::shared_ptr<domainmap_t> newMap)
+  {
+    t_sstorage.domainmap = std::move(newMap);
+  }
+  static std::shared_ptr<domainmap_t> getDomainMap()
+  {
+    return t_sstorage.domainmap;
+  }
+
+  static void setECSScopeZeroAddress(const Netmask& scopeZeroMask)
+  {
+    s_ecsScopeZero.source = scopeZeroMask;
+  }
+
+  static void clearECSStats()
+  {
+    s_ecsqueries.store(0);
+    s_ecsresponses.store(0);
+
+    for (size_t idx = 0; idx < 32; idx++) {
+      SyncRes::s_ecsResponsesBySubnetSize4[idx].store(0);
+    }
+
+    for (size_t idx = 0; idx < 128; idx++) {
+      SyncRes::s_ecsResponsesBySubnetSize6[idx].store(0);
+    }
+  }
+
+  explicit SyncRes(const struct timeval& now);
+
+  int beginResolve(const DNSName& qname, QType qtype, QClass qclass, vector<DNSRecord>& ret, unsigned int depth = 0);
+  bool tryDoT(const DNSName& qname, QType qtype, const DNSName& nsName, ComboAddress address, time_t);
+
+  void setId(int threadid)
+  {
+    if (doLog()) {
+      d_prefix = "[" + std::to_string(threadid) + "] ";
+    }
+  }
+
+  void setId(const string& prefix)
+  {
+    if (doLog()) {
+      d_prefix = "[" + prefix + "] ";
+    }
+  }
+
+  void setLogMode(LogMode logmode)
+  {
+    d_lm = logmode;
+  }
+
+  bool doLog() const
+  {
+    return d_lm != LogNone;
+  }
+
+  bool setCacheOnly(bool state = true)
+  {
+    bool old = d_cacheonly;
+    d_cacheonly = state;
+    return old;
+  }
+
+  bool setRefreshAlmostExpired(bool doit)
+  {
+    auto old = d_refresh;
+    d_refresh = doit;
+    return old;
+  }
+
+  bool setQNameMinimization(bool state = true)
+  {
+    auto old = d_qNameMinimization;
+    d_qNameMinimization = state;
+    return old;
+  }
+
+  bool setQMFallbackMode(bool state = true)
+  {
+    auto old = d_qNameMinimizationFallbackMode;
+    d_qNameMinimizationFallbackMode = state;
+    return old;
+  }
+
+  bool getQMFallbackMode() const
+  {
+    return d_qNameMinimizationFallbackMode;
+  }
+
+  void setDoEDNS0(bool state = true)
+  {
+    d_doEDNS0 = state;
+  }
+
+  void setDoDNSSEC(bool state = true)
+  {
+    d_doDNSSEC = state;
+  }
+
+  void setDNSSECValidationRequested(bool requested = true)
+  {
+    d_DNSSECValidationRequested = requested;
+  }
+
+  bool isDNSSECValidationRequested() const
+  {
+    return d_DNSSECValidationRequested;
+  }
+
+  bool shouldValidate() const
+  {
+    return d_DNSSECValidationRequested && !d_wasOutOfBand;
+  }
+
+  void setWantsRPZ(bool state = true)
+  {
+    d_wantsRPZ = state;
+  }
+
+  bool getWantsRPZ() const
+  {
+    return d_wantsRPZ;
+  }
+
+  string getTrace() const
+  {
+    return d_trace.str();
+  }
+
+  bool getQNameMinimization() const
+  {
+    return d_qNameMinimization;
+  }
+
+  void setLuaEngine(shared_ptr<RecursorLua4> pdl)
+  {
+    d_pdl = std::move(pdl);
+  }
+
+  bool wasVariable() const
+  {
+    return d_wasVariable;
+  }
+
+  bool wasOutOfBand() const
+  {
+    return d_wasOutOfBand;
+  }
+
+  struct timeval getNow() const
+  {
+    return d_now;
+  }
+
+  // For debugging purposes
+  void setNow(const struct timeval& tval)
+  {
+    d_now = tval;
+  }
+
+  void setQuerySource(const ComboAddress& requestor, const boost::optional<const EDNSSubnetOpts&>& incomingECS);
+  void setQuerySource(const Netmask& netmask);
+
+  void setInitialRequestId(const boost::optional<const boost::uuids::uuid&>& initialRequestId)
+  {
+    d_initialRequestId = initialRequestId;
+  }
+
+  void setOutgoingProtobufServers(std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& servers)
+  {
+    d_outgoingProtobufServers = servers;
+  }
+
+#ifdef HAVE_FSTRM
+  void setFrameStreamServers(std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& servers)
+  {
+    d_frameStreamServers = servers;
+  }
+#endif /* HAVE_FSTRM */
+
+  void setAsyncCallback(asyncresolve_t func)
+  {
+    d_asyncResolve = std::move(func);
+  }
+
+  vState getValidationState() const
+  {
+    return d_queryValidationState;
+  }
+
+  void setQueryReceivedOverTCP(bool tcp)
+  {
+    d_queryReceivedOverTCP = tcp;
+  }
+
+  static bool isUnsupported(QType qtype)
+  {
+    auto qcode = qtype.getCode();
+    // rfc6895 section 3.1, note ANY is 255 and falls outside the range
+    if (qcode >= QType::rfc6895MetaLowerBound && qcode <= QType::rfc6895MetaUpperBound) {
+      return true;
+    }
+    switch (qcode) {
+      // Internal types
+    case QType::ENT: // aka TYPE0
+    case QType::ADDR:
+      // RFC
+    case QType::rfc6895Reserved:
+      // Other
+    case QType::RRSIG:
+    case QType::NSEC3: // We use the same logic as for an auth: NSEC is queryable, NSEC3 not
+    case QType::OPT:
+      return true;
+    }
+    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;
+  static pdns::stat_t s_ecsresponses;
+  static std::map<uint8_t, pdns::stat_t> s_ecsResponsesBySubnetSize4;
+  static std::map<uint8_t, pdns::stat_t> s_ecsResponsesBySubnetSize6;
+
+  static string s_serverID;
+  static unsigned int s_minimumTTL;
+  static unsigned int s_minimumECSTTL;
+  static unsigned int s_maxqperq;
+  static unsigned int s_maxnsperresolve;
+  static unsigned int s_maxnsaddressqperq;
+  static unsigned int s_maxtotusec;
+  static unsigned int s_maxdepth;
+  static unsigned int s_maxnegttl;
+  static unsigned int s_maxbogusttl;
+  static unsigned int s_maxcachettl;
+  static unsigned int s_packetcachettl;
+  static unsigned int s_packetcacheservfailttl;
+  static unsigned int s_packetcachenegativettl;
+  static unsigned int s_serverdownmaxfails;
+  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;
+  static uint8_t s_ecsipv6cachelimit;
+  static bool s_ecsipv4nevercache;
+  static bool s_ecsipv6nevercache;
+
+  static bool s_doIPv4;
+  static bool s_doIPv6;
+  static bool s_noEDNSPing;
+  static bool s_noEDNS;
+  static bool s_rootNXTrust;
+  static bool s_qnameminimization;
+  static HardenNXD s_hardenNXD;
+  static unsigned int s_refresh_ttlperc;
+  static unsigned int s_locked_ttlperc;
+  static int s_tcp_fast_open;
+  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;
+  static int s_event_trace_enabled;
+  static bool s_save_parent_ns_set;
+  static bool s_addExtendedResolutionDNSErrors;
+
+  std::unordered_map<std::string, bool> d_discardedPolicies;
+  DNSFilterEngine::Policy d_appliedPolicy;
+  std::unordered_set<std::string> d_policyTags;
+  boost::optional<string> d_routingTag;
+  ComboAddress d_fromAuthIP;
+  RecEventTrace d_eventTrace;
+  std::shared_ptr<Logr::Logger> d_slog = g_slog->withName("syncres");
+  boost::optional<EDNSExtendedError> d_extendedError;
+
+  unsigned int d_authzonequeries;
+  unsigned int d_outqueries;
+  unsigned int d_tcpoutqueries;
+  unsigned int d_dotoutqueries;
+  unsigned int d_throttledqueries;
+  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
+  struct timeval d_fixednow;
+
+private:
+  ComboAddress d_requestor;
+  ComboAddress d_cacheRemote;
+
+  static NetmaskGroup s_ednslocalsubnets;
+  static NetmaskGroup s_ednsremotesubnets;
+  static SuffixMatchNode s_ednsdomains;
+  static EDNSSubnetOpts s_ecsScopeZero;
+  static LogMode s_lm;
+  static std::unique_ptr<NetmaskGroup> s_dontQuery;
+  const static std::unordered_set<QType> s_redirectionQTypes;
+
+  struct GetBestNSAnswer
+  {
+    DNSName qname;
+    set<pair<DNSName, DNSName>> bestns;
+    uint8_t qtype;
+    bool operator<(const GetBestNSAnswer& bestAnswer) const
+    {
+      return std::tie(qtype, qname, bestns) < std::tie(bestAnswer.qtype, bestAnswer.qname, bestAnswer.bestns);
+    }
+  };
+
+  using zonesStates_t = std::map<DNSName, vState>;
+  enum StopAtDelegation
+  {
+    DontStop,
+    Stop,
+    Stopped
+  };
+
+  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);
+
+  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);
+  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 = 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);
+  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, 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);
+  bool nameserverIPBlockedByRPZ(const DNSFilterEngine& dfe, const ComboAddress&);
+  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, 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, 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, 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, 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, QClass qclass, vector<DNSRecord>& ret);
+
+  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& name, const ComboAddress& rem);
+
+  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, 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& 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);
+
+  void handleNewTarget(const std::string& prefix, const DNSName& qname, const DNSName& newtarget, QType qtype, std::vector<DNSRecord>& ret, int& rcode, unsigned int depth, const std::vector<DNSRecord>& recordsFromAnswer, vState& state);
+
+  void handlePolicyHit(const std::string& prefix, const DNSName& qname, QType qtype, vector<DNSRecord>& ret, bool& done, int& rcode, unsigned int depth);
+  unsigned int getAdjustedRecursionBound() const;
+
+  void setUpdatingRootNS()
+  {
+    d_updatingRootNS = true;
+  }
+
+  std::string getPrefix(unsigned int depth) const
+  {
+    if (!doLog()) {
+      return "";
+    }
+    auto prefix = d_prefix;
+    prefix.append(depth, ' ');
+    return prefix;
+  }
+
+  zonesStates_t d_cutStates;
+  ostringstream d_trace;
+  shared_ptr<RecursorLua4> d_pdl;
+  boost::optional<Netmask> d_outgoingECSNetwork;
+  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;
+  /* 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 */
+  DNSName d_externalDSQuery;
+  string d_prefix;
+  vState d_queryValidationState{vState::Indeterminate};
+
+  /* When d_cacheonly is set to true, we will only check the cache.
+   * This is set when the RD bit is unset in the incoming query
+   */
+  bool d_cacheonly;
+  bool d_doDNSSEC;
+  bool d_DNSSECValidationRequested{false};
+  bool d_doEDNS0{true};
+  bool d_requireAuthData{true};
+  bool d_updatingRootNS{false};
+  bool d_wantsRPZ{true};
+  bool d_wasOutOfBand{false};
+  bool d_wasVariable{false};
+  bool d_qNameMinimization{false};
+  bool d_qNameMinimizationFallbackMode{false};
+  bool d_queryReceivedOverTCP{false};
+  bool d_followCNAME{true};
+  bool d_refresh{false};
+  bool d_serveStale{false};
+
+  LogMode d_lm;
+};
+
+/* external functions, opaque to us */
+LWResult::Result asendtcp(const PacketBuffer& data, shared_ptr<TCPIOHandler>&);
+LWResult::Result arecvtcp(PacketBuffer& data, size_t len, shared_ptr<TCPIOHandler>&, bool incompleteOkay);
+
+enum TCPAction : uint8_t
+{
+  DoingRead,
+  DoingWrite
+};
+
+struct PacketID
+{
+  PacketID()
+  {
+    remote.reset();
+  }
+
+  ComboAddress remote; // this is the remote
+  DNSName domain; // this is the question
+
+  PacketBuffer inMSG; // they'll go here
+  PacketBuffer outMSG; // the outgoing message that needs to be sent
+
+  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
+  size_t inWanted{0}; // if this is set, we'll read until inWanted bytes are read
+  string::size_type outPos{0}; // how far we are along in the outMSG
+  mutable uint32_t nearMisses{0}; // number of near misses - host correct, id wrong
+  int fd{-1};
+  int tcpsock{0}; // or wait for an event on a TCP fd
+  mutable bool closed{false}; // Processing already started, don't accept new chained ids
+  bool inIncompleteOkay{false};
+  uint16_t id{0}; // wait for a specific id/remote pair
+  uint16_t type{0}; // and this is its type
+  TCPAction highState{TCPAction::DoingRead};
+  IOState lowState{IOState::NeedRead};
+
+  bool operator<(const PacketID& /* b */) const
+  {
+    // We don't want explicit PacketID compare here, but always via predicate classes below
+    assert(0); // NOLINT: lib
+  }
+};
+
+inline ostream& operator<<(ostream& ostr, const PacketID& pid)
+{
+  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& ostr, const shared_ptr<PacketID>& pid)
+{
+  return ostr << *pid;
+}
+
+/*
+ * The two compare predicates below must be consistent!
+ * PacketIDBirthdayCompare can omit minor fields, but not change the or skip fields
+ * order! See boost docs on CompatibleCompare.
+ */
+struct PacketIDCompare
+{
+  bool operator()(const std::shared_ptr<PacketID>& lhs, const std::shared_ptr<PacketID>& rhs) const
+  {
+    if (std::tie(lhs->remote, lhs->tcpsock, lhs->type) < std::tie(rhs->remote, rhs->tcpsock, rhs->type)) {
+      return true;
+    }
+    if (std::tie(lhs->remote, lhs->tcpsock, lhs->type) > std::tie(rhs->remote, rhs->tcpsock, rhs->type)) {
+      return false;
+    }
+
+    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>& lhs, const std::shared_ptr<PacketID>& rhs) const
+  {
+    if (std::tie(lhs->remote, lhs->tcpsock, lhs->type) < std::tie(rhs->remote, rhs->tcpsock, rhs->type)) {
+      return true;
+    }
+    if (std::tie(lhs->remote, lhs->tcpsock, lhs->type) > std::tie(rhs->remote, rhs->tcpsock, rhs->type)) {
+      return false;
+    }
+    return lhs->domain < rhs->domain;
+  }
+};
+extern std::unique_ptr<MemRecursorCache> g_recCache;
+
+extern rec::GlobalCounters g_Counters;
+extern thread_local rec::TCounters t_Counters;
+
+//! represents a running TCP/IP client session
+class TCPConnection
+{
+public:
+  TCPConnection(int fileDesc, const ComboAddress& addr);
+  ~TCPConnection();
+  TCPConnection(const TCPConnection&) = delete;
+  TCPConnection& operator=(const TCPConnection&) = delete;
+  TCPConnection(TCPConnection&&) = delete;
+  TCPConnection& operator=(TCPConnection&&) = delete;
+
+  [[nodiscard]] int getFD() const
+  {
+    return d_fd;
+  }
+  void setDropOnIdle()
+  {
+    d_dropOnIdle = true;
+  }
+  [[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;
+  ComboAddress d_remote;
+  ComboAddress d_source;
+  ComboAddress d_destination;
+  ComboAddress d_mappedSource;
+  size_t queriesCount{0};
+  size_t proxyProtocolGot{0};
+  ssize_t proxyProtocolNeed{0};
+  enum stateenum
+  {
+    PROXYPROTOCOLHEADER,
+    BYTE0,
+    BYTE1,
+    GETQUESTION,
+    DONE
+  } state{BYTE0};
+  uint16_t qlen{0};
+  uint16_t bytesread{0};
+  uint16_t d_requestsInFlight{0}; // number of mthreads spawned for this connection
+
+private:
+  int d_fd;
+  static std::atomic<uint32_t> s_currentConnections; //!< total number of current TCP connections
+  bool d_dropOnIdle{false};
+};
+
+class ImmediateServFailException
+{
+public:
+  ImmediateServFailException(string reason_) :
+    reason(std::move(reason_)){};
+
+  string reason; //! Print this to tell the user what went wrong
+};
+
+class PolicyHitException
+{
+};
+
+class ImmediateQueryDropException
+{
+};
+
+class SendTruncatedAnswerException
+{
+};
+
+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;
+extern thread_local std::shared_ptr<NetmaskGroup> t_allowFrom;
+extern thread_local std::shared_ptr<NetmaskGroup> t_allowNotifyFrom;
+extern unsigned int g_networkTimeoutMsec;
+extern uint16_t g_outgoingEDNSBufsize;
+extern std::atomic<uint32_t> g_maxCacheEntries, g_maxPacketCacheEntries;
+extern bool g_lowercaseOutgoing;
+
+std::string reloadZoneConfiguration(bool yaml);
+using pipefunc_t = std::function<void*()>;
+void broadcastFunction(const pipefunc_t& func);
+void distributeAsyncFunction(const std::string& packet, const pipefunc_t& func);
+
+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);
+
+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();
+uint64_t* pleaseGetConcurrentQueries();
+uint64_t* pleaseGetThrottleSize();
+void doCarbonDump(void*);
+bool primeHints(time_t now = time(nullptr));
+
+using timebuf_t = std::array<char, 64>;
+const char* isoDateTimeMillis(const struct timeval& tval, timebuf_t& buf);
+
+struct WipeCacheResult
+{
+  int record_count = 0;
+  int negative_record_count = 0;
+  int packet_count = 0;
+};
+
+struct WipeCacheResult wipeCaches(const DNSName& canon, bool subtree, uint16_t qtype);
+
+extern __thread struct timeval g_now;
+
+struct ThreadTimes
+{
+  uint64_t msec{0};
+  vector<uint64_t> times;
+  ThreadTimes& operator+=(const ThreadTimes& rhs)
+  {
+    times.push_back(rhs.msec);
+    return *this;
+  }
+};
index f933f99fac38af03853e24446ade2a74c2c5a8a1..7e268e4edfd9cb476965b24b8cbcfc8d14968c92 100644 (file)
 namespace pdns
 {
 
-void TaskQueue::push(ResolveTask&& task)
+bool TaskQueue::push(ResolveTask&& task)
 {
   // Insertion fails if it's already there, no problem since we're already scheduled
-  auto result = d_queue.insert(std::move(task));
-  if (result.second) {
+  auto result = d_queue.insert(std::move(task)).second;
+  if (result) {
     d_pushes++;
   }
+  return result;
 }
 
 ResolveTask TaskQueue::pop()
@@ -44,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);
@@ -66,3 +69,11 @@ bool ResolveTask::run(bool logErrors)
 }
 
 } /* namespace pdns */
+
+namespace boost
+{
+size_t hash_value(const ComboAddress& address)
+{
+  return ComboAddress::addressOnlyHash()(address);
+}
+}
index 1faaa30ef185dbb2bdfc8ceb4b9315ac7abccb03..6f174e05fec18b0beae11b9599949142370a2f83 100644 (file)
 #include <sys/time.h>
 #include <thread>
 
+union ComboAddress;
+namespace boost
+{
+size_t hash_value(const ComboAddress&);
+}
+
 #include <boost/multi_index_container.hpp>
-#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/ordered_index.hpp>
 #include <boost/multi_index/key_extractors.hpp>
 #include <boost/multi_index/member.hpp>
 #include <boost/multi_index/sequenced_index.hpp>
 #include <boost/multi_index/tag.hpp>
 
 #include "dnsname.hh"
+#include "iputils.hh"
 #include "qtype.hh"
 
 namespace pdns
@@ -43,40 +50,49 @@ struct ResolveTask
 {
   DNSName d_qname;
   uint16_t d_qtype;
+  // Deadline is not part of index and not used by operator<()
   time_t d_deadline;
-  bool d_refreshMode; // Whether to run this task in regular mode (false) or in the mode that refreshes almost expired tasks
+  // Whether to run this task in regular mode (false) or in the mode that refreshes almost expired tasks
+  bool d_refreshMode;
   // Use a function pointer as comparing std::functions is a nuisance
-  void (*d_func)(const struct timeval& now, bool logErrors, const ResolveTask& task);
-
-  bool operator<(const ResolveTask& a) const
+  using TaskFunction = void (*)(const struct timeval& now, bool logErrors, const ResolveTask& task);
+  TaskFunction d_func;
+  // IP used by DoT probe tasks
+  ComboAddress d_ip;
+  // NS name used by DoT probe task, not part of index and not used by operator<()
+  DNSName d_nsname;
+  Netmask d_netmask;
+
+  bool operator<(const ResolveTask& task) const
   {
-    return std::tie(d_qname, d_qtype, d_refreshMode, d_func) < std::tie(a.d_qname, a.d_qtype, a.d_refreshMode, a.d_func);
+    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();
   }
 
-  void push(ResolveTask&& task);
+  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;
   }
@@ -100,16 +116,17 @@ private:
   {
   };
 
-  typedef multi_index_container<
+  using queue_t = multi_index_container<
     ResolveTask,
-    indexed_by<
-      hashed_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>>>,
-      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};
diff --git a/pdns/recursordist/tcounters.hh b/pdns/recursordist/tcounters.hh
new file mode 120000 (symlink)
index 0000000..7bf637d
--- /dev/null
@@ -0,0 +1 @@
+../tcounters.hh
\ No newline at end of file
index 51693050c66c48c279e7b7a55fb8837f10312f72..7290c7319424971f21208802ea73e2b47fd2a006 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"
@@ -6,6 +9,29 @@
 
 BOOST_AUTO_TEST_SUITE(aggressive_nsec_cc)
 
+BOOST_AUTO_TEST_CASE(test_small_coverering_nsec3)
+{
+  AggressiveNSECCache::s_maxNSEC3CommonPrefix = 1;
+
+  const std::vector<std::tuple<string, string, uint8_t, bool>> table = {
+    {"gujhshp2lhmnpoo9qde4blg4gq3hgl99", "gujhshp2lhmnpoo9qde4blg4gq3hgl9a", 157, true},
+    {"gujhshp2lhmnpoo9qde4blg4gq3hgl99", "gujhshp2lhmnpoo9qde4blg4gq3hgl9a", 158, false},
+    {"0ujhshp2lhmnpoo9qde4blg4gq3hgl99", "vujhshp2lhmnpoo9qde4blg4gq3hgl9a", 0, false},
+    {"0ujhshp2lhmnpoo9qde4blg4gq3hgl99", "7ujhshp2lhmnpoo9qde4blg4gq3hgl9a", 1, true},
+    {"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) {
+    AggressiveNSECCache::s_maxNSEC3CommonPrefix = boundary;
+    BOOST_CHECK_EQUAL(AggressiveNSECCache::isSmallCoveringNSEC3(DNSName(owner), fromBase32Hex(next)), result);
+  }
+}
+
 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nxdomain)
 {
   std::unique_ptr<SyncRes> sr;
@@ -31,7 +57,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nxdomain)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target1, &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([target1, &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) {
@@ -127,7 +153,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_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([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) {
@@ -213,7 +239,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nodata_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([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) {
@@ -306,7 +332,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor)
 
   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([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) {
@@ -420,7 +446,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_wildcard_synthesis)
 
   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([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) {
@@ -514,6 +540,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nxdomain)
 {
   std::unique_ptr<SyncRes> sr;
   initSR(sr, true);
+  AggressiveNSECCache::s_maxNSEC3CommonPrefix = 159;
   g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
 
   setDNSSECValidation(sr, DNSSECMode::ValidateAll);
@@ -536,7 +563,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nxdomain)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target1, &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([target1, &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) {
@@ -639,7 +666,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_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([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) {
@@ -706,6 +733,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata_wildcard)
 {
   std::unique_ptr<SyncRes> sr;
   initSR(sr, true);
+  AggressiveNSECCache::s_maxNSEC3CommonPrefix = 159;
   g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(10000);
 
   setDNSSECValidation(sr, DNSSECMode::ValidateAll);
@@ -725,7 +753,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata_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([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) {
@@ -830,7 +858,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor)
 
   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([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) {
@@ -950,7 +978,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_wildcard_synthesis)
 
   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([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) {
@@ -1054,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);
@@ -1065,18 +1141,18 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_wiping)
   rec.d_name = DNSName("www.powerdns.com");
   rec.d_type = QType::NSEC;
   rec.d_ttl = now.tv_sec + 10;
-  rec.d_content = getRecordContent(QType::NSEC, "z.powerdns.com. A RRSIG NSEC");
+  rec.setContent(getRecordContent(QType::NSEC, "z.powerdns.com. A RRSIG NSEC"));
   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}, false);
 
   rec.d_name = DNSName("z.powerdns.com");
-  rec.d_content = getRecordContent(QType::NSEC, "zz.powerdns.com. AAAA RRSIG NSEC");
+  rec.setContent(getRecordContent(QType::NSEC, "zz.powerdns.com. AAAA RRSIG NSEC"));
   cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, false);
 
   rec.d_name = DNSName("www.powerdns.org");
   rec.d_type = QType::NSEC3;
   rec.d_ttl = now.tv_sec + 10;
-  rec.d_content = getRecordContent(QType::NSEC3, "1 0 500 ab HASG==== A RRSIG NSEC3");
+  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);
 
@@ -1114,12 +1190,12 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_pruning)
   rec.d_name = DNSName("www.powerdns.com");
   rec.d_type = QType::NSEC;
   rec.d_ttl = now.tv_sec + 10;
-  rec.d_content = getRecordContent(QType::NSEC, "z.powerdns.com. A RRSIG NSEC");
+  rec.setContent(getRecordContent(QType::NSEC, "z.powerdns.com. A RRSIG NSEC"));
   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}, false);
 
   rec.d_name = DNSName("z.powerdns.com");
-  rec.d_content = getRecordContent(QType::NSEC, "zz.powerdns.com. AAAA RRSIG NSEC");
+  rec.setContent(getRecordContent(QType::NSEC, "zz.powerdns.com. AAAA RRSIG NSEC"));
   cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, false);
 
   BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2U);
@@ -1130,26 +1206,21 @@ 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_content = getRecordContent(QType::NSEC3, "1 0 500 ab HASG==== A RRSIG NSEC3");
+  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);
 
   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);
 }
@@ -1159,53 +1230,84 @@ 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");
   rec.d_type = QType::NSEC;
   rec.d_ttl = now.tv_sec + 10;
-  rec.d_content = getRecordContent(QType::NSEC, "z.powerdns.com. A RRSIG NSEC");
+  rec.setContent(getRecordContent(QType::NSEC, "z.powerdns.com. A RRSIG NSEC"));
   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}, false);
 
   rec.d_name = DNSName("z.powerdns.com");
-  rec.d_content = getRecordContent(QType::NSEC, "zz.powerdns.com. AAAA RRSIG NSEC");
+  rec.setContent(getRecordContent(QType::NSEC, "zz.powerdns.com. AAAA RRSIG NSEC"));
   cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, false);
 
   rec.d_name = DNSName("www.powerdns.org");
   rec.d_type = QType::NSEC3;
   rec.d_ttl = now.tv_sec + 10;
-  rec.d_content = getRecordContent(QType::NSEC3, "1 0 50 ab HASG==== A RRSIG NSEC3");
+  rec.setContent(getRecordContent(QType::NSEC3, "1 0 50 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);
 
   BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U);
 
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(tmpfile(), fclose);
-  if (!fp) {
+  auto filePtr = std::unique_ptr<FILE, int (*)(FILE*)>(tmpfile(), fclose);
+  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");
     }
@@ -1215,13 +1317,30 @@ 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)
 {
   /* test that we don't compare a hash using the wrong (former) salt or iterations count in case of a rollover,
      or when different servers use different parameters */
+  AggressiveNSECCache::s_maxNSEC3CommonPrefix = 159;
   auto cache = make_unique<AggressiveNSECCache>(10000);
   g_recCache = std::make_unique<MemRecursorCache>();
 
@@ -1235,7 +1354,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_rollover)
   drSOA.d_name = zone;
   drSOA.d_type = QType::SOA;
   drSOA.d_class = QClass::IN;
-  drSOA.d_content = std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600");
+  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);
@@ -1266,18 +1385,15 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_rollover)
     nrc.set(type);
   }
 
-  rec.d_content = std::make_shared<NSEC3RecordContent>(nrc);
+  rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
   auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
   cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
 
   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 */
@@ -1295,7 +1411,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_rollover)
     nrc.set(type);
   }
 
-  rec.d_content = std::make_shared<NSEC3RecordContent>(nrc);
+  rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
   rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
   cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
 
@@ -1304,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");
@@ -1325,7 +1441,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_rollover)
     nrc.set(type);
   }
 
-  rec.d_content = std::make_shared<NSEC3RecordContent>(nrc);
+  rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
   rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
   cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
 
@@ -1334,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)
@@ -1355,7 +1471,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor_cases)
   drSOA.d_name = zone;
   drSOA.d_type = QType::SOA;
   drSOA.d_class = QClass::IN;
-  drSOA.d_content = std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600");
+  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);
@@ -1378,22 +1494,16 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor_cases)
       nrc.set(type);
     }
 
-    rec.d_content = std::make_shared<NSECRecordContent>(nrc);
+    rec.setContent(std::make_shared<NSECRecordContent>(nrc));
     auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 sub.powerdns.com. data");
     cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, false);
 
     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);
   }
 
   {
@@ -1411,21 +1521,16 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor_cases)
       nrc.set(type);
     }
 
-    rec.d_content = std::make_shared<NSECRecordContent>(nrc);
+    rec.setContent(std::make_shared<NSECRecordContent>(nrc));
     auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
     cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, false);
 
     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);
   }
 
   {
@@ -1443,23 +1548,16 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor_cases)
       nrc.set(type);
     }
 
-    rec.d_content = std::make_shared<NSECRecordContent>(nrc);
+    rec.setContent(std::make_shared<NSECRecordContent>(nrc));
     auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
     cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, false);
 
     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);
   }
 
   {
@@ -1481,7 +1579,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor_cases)
         nrc.set(type);
       }
 
-      rec.d_content = std::make_shared<NSECRecordContent>(nrc);
+      rec.setContent(std::make_shared<NSECRecordContent>(nrc));
       auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
       cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, false);
 
@@ -1500,7 +1598,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor_cases)
         nrc.set(type);
       }
 
-      rec.d_content = std::make_shared<NSECRecordContent>(nrc);
+      rec.setContent(std::make_shared<NSECRecordContent>(nrc));
       auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
       cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, false);
 
@@ -1508,22 +1606,16 @@ 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);
   }
 }
 
 BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases)
 {
+  AggressiveNSECCache::s_maxNSEC3CommonPrefix = 159;
   auto cache = make_unique<AggressiveNSECCache>(10000);
   g_recCache = std::make_unique<MemRecursorCache>();
 
@@ -1537,7 +1629,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases)
   drSOA.d_name = zone;
   drSOA.d_type = QType::SOA;
   drSOA.d_class = QClass::IN;
-  drSOA.d_content = std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600");
+  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);
@@ -1569,22 +1661,16 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases)
       nrc.set(type);
     }
 
-    rec.d_content = std::make_shared<NSEC3RecordContent>(nrc);
+    rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
     auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 sub.powerdns.com. data");
     cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
 
     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);
   }
 
   {
@@ -1608,21 +1694,16 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases)
       nrc.set(type);
     }
 
-    rec.d_content = std::make_shared<NSEC3RecordContent>(nrc);
+    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(), 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);
   }
 
   {
@@ -1646,23 +1727,16 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases)
       nrc.set(type);
     }
 
-    rec.d_content = std::make_shared<NSEC3RecordContent>(nrc);
+    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(), 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);
   }
 
   {
@@ -1693,7 +1767,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases)
         nrc.set(type);
       }
 
-      rec.d_content = std::make_shared<NSEC3RecordContent>(nrc);
+      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);
 
@@ -1721,7 +1795,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases)
         nrc.set(type);
       }
 
-      rec.d_content = std::make_shared<NSEC3RecordContent>(nrc);
+      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);
 
@@ -1749,7 +1823,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases)
         nrc.set(type);
       }
 
-      rec.d_content = std::make_shared<NSEC3RecordContent>(nrc);
+      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);
 
@@ -1757,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 */
@@ -1798,7 +1864,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases)
         nrc.set(type);
       }
 
-      rec.d_content = std::make_shared<NSEC3RecordContent>(nrc);
+      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);
 
@@ -1826,7 +1892,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases)
         nrc.set(type);
       }
 
-      rec.d_content = std::make_shared<NSEC3RecordContent>(nrc);
+      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);
 
@@ -1854,7 +1920,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases)
         nrc.set(type);
       }
 
-      rec.d_content = std::make_shared<NSEC3RecordContent>(nrc);
+      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);
 
@@ -1862,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 9b0ff1fe19095dbea1c3beb347ace5401bd7f674..f91abdf07570c89d73be16d58235540ee3572da6 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
@@ -190,7 +193,7 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
     /* blocked A */
     DNSRecord dr;
     dr.d_type = QType::A;
-    dr.d_content = 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);
@@ -205,7 +208,7 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
     /* allowed A */
     DNSRecord dr;
     dr.d_type = QType::A;
-    dr.d_content = 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);
@@ -341,7 +344,7 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data)
     const auto& record = records.at(0);
     BOOST_CHECK(record.d_type == QType::CNAME);
     BOOST_CHECK(record.d_class == QClass::IN);
-    auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+    auto content = getRR<CNAMERecordContent>(record);
     BOOST_CHECK(content != nullptr);
     BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden.example.net.");
   }
@@ -359,7 +362,7 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data)
         const auto& record = records.at(0);
         BOOST_CHECK(record.d_type == QType::A);
         BOOST_CHECK(record.d_class == QClass::IN);
-        auto content = std::dynamic_pointer_cast<ARecordContent>(record.d_content);
+        auto content = getRR<ARecordContent>(record);
         BOOST_CHECK(content != nullptr);
         BOOST_CHECK_EQUAL(content->getCA().toString(), "192.0.2.1");
       }
@@ -367,7 +370,7 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data)
         const auto& record = records.at(1);
         BOOST_CHECK(record.d_type == QType::A);
         BOOST_CHECK(record.d_class == QClass::IN);
-        auto content = std::dynamic_pointer_cast<ARecordContent>(record.d_content);
+        auto content = getRR<ARecordContent>(record);
         BOOST_CHECK(content != nullptr);
         BOOST_CHECK_EQUAL(content->getCA().toString(), "192.0.2.2");
       }
@@ -379,7 +382,7 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data)
       const auto& record = records.at(0);
       BOOST_CHECK(record.d_type == QType::MX);
       BOOST_CHECK(record.d_class == QClass::IN);
-      auto content = std::dynamic_pointer_cast<MXRecordContent>(record.d_content);
+      auto content = getRR<MXRecordContent>(record);
       BOOST_CHECK(content != nullptr);
       BOOST_CHECK_EQUAL(content->d_mxname.toString(), "garden-mail.example.net.");
     }
@@ -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);
 
   {
@@ -408,7 +411,7 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data)
         const auto& record = records.at(0);
         BOOST_CHECK(record.d_type == QType::A);
         BOOST_CHECK(record.d_class == QClass::IN);
-        auto content = std::dynamic_pointer_cast<ARecordContent>(record.d_content);
+        auto content = getRR<ARecordContent>(record);
         BOOST_CHECK(content != nullptr);
         BOOST_CHECK_EQUAL(content->getCA().toString(), "192.0.2.2");
       }
@@ -420,7 +423,7 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data)
       const auto& record = records.at(0);
       BOOST_CHECK(record.d_type == QType::MX);
       BOOST_CHECK(record.d_class == QClass::IN);
-      auto content = std::dynamic_pointer_cast<MXRecordContent>(record.d_content);
+      auto content = getRR<MXRecordContent>(record);
       BOOST_CHECK(content != nullptr);
       BOOST_CHECK_EQUAL(content->d_mxname.toString(), "garden-mail.example.net.");
     }
@@ -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);
@@ -460,14 +463,14 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data_netmask)
     const auto& record1 = records.at(0);
     BOOST_CHECK(record1.d_type == QType::A);
     BOOST_CHECK(record1.d_class == QClass::IN);
-    auto content1 = std::dynamic_pointer_cast<ARecordContent>(record1.d_content);
+    auto content1 = getRR<ARecordContent>(record1);
     BOOST_CHECK(content1 != nullptr);
     BOOST_CHECK_EQUAL(content1->getCA().toString(), "1.2.3.4");
 
     const auto& record2 = records.at(1);
     BOOST_CHECK(record2.d_type == QType::A);
     BOOST_CHECK(record2.d_class == QClass::IN);
-    auto content2 = std::dynamic_pointer_cast<ARecordContent>(record2.d_content);
+    auto content2 = getRR<ARecordContent>(record2);
     BOOST_CHECK(content2 != nullptr);
     BOOST_CHECK_EQUAL(content2->getCA().toString(), "1.2.3.5");
   }
@@ -481,22 +484,22 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data_netmask)
     const auto& record1 = records.at(0);
     BOOST_CHECK(record1.d_type == QType::AAAA);
     BOOST_CHECK(record1.d_class == QClass::IN);
-    auto content1 = std::dynamic_pointer_cast<AAAARecordContent>(record1.d_content);
+    auto content1 = getRR<AAAARecordContent>(record1);
     BOOST_CHECK(content1 != nullptr);
     BOOST_CHECK_EQUAL(content1->getCA().toString(), "::1234");
   }
 
   // Try to zap 1 nonexisting record
-  zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "1.1.1.1")}));
+  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);
@@ -507,7 +510,7 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data_netmask)
     const auto& record1 = records.at(0);
     BOOST_CHECK(record1.d_type == QType::A);
     BOOST_CHECK(record1.d_class == QClass::IN);
-    auto content1 = std::dynamic_pointer_cast<ARecordContent>(record1.d_content);
+    auto content1 = getRR<ARecordContent>(record1);
     BOOST_CHECK(content1 != nullptr);
     BOOST_CHECK_EQUAL(content1->getCA().toString(), "1.2.3.4");
   }
@@ -520,16 +523,16 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data_netmask)
     const auto& record1 = records.at(0);
     BOOST_CHECK(record1.d_type == QType::AAAA);
     BOOST_CHECK(record1.d_class == QClass::IN);
-    auto content1 = std::dynamic_pointer_cast<AAAARecordContent>(record1.d_content);
+    auto content1 = getRR<AAAARecordContent>(record1);
     BOOST_CHECK(content1 != nullptr);
     BOOST_CHECK_EQUAL(content1->getCA().toString(), "::1234");
   }
 
   // Zap one more A record
-  zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "1.2.3.4")}));
+  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);
@@ -540,13 +543,13 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data_netmask)
     const auto& record1 = records.at(0);
     BOOST_CHECK(record1.d_type == QType::AAAA);
     BOOST_CHECK(record1.d_class == QClass::IN);
-    auto content1 = std::dynamic_pointer_cast<AAAARecordContent>(record1.d_content);
+    auto content1 = getRR<AAAARecordContent>(record1);
     BOOST_CHECK(content1 != nullptr);
     BOOST_CHECK_EQUAL(content1->getCA().toString(), "::1234");
   }
 
   // Zap AAAA record
-  zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::AAAA, QClass::IN, "::1234")}));
+  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);
@@ -587,7 +590,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies)
     const auto& record = records.at(0);
     BOOST_CHECK(record.d_type == QType::CNAME);
     BOOST_CHECK(record.d_class == QClass::IN);
-    auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+    auto content = getRR<CNAMERecordContent>(record);
     BOOST_CHECK(content != nullptr);
     BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden1a.example.net.");
   }
@@ -602,7 +605,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies)
     const auto& record = records.at(0);
     BOOST_CHECK(record.d_type == QType::CNAME);
     BOOST_CHECK(record.d_class == QClass::IN);
-    auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+    auto content = getRR<CNAMERecordContent>(record);
     BOOST_CHECK(content != nullptr);
     BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden1b.example.net.");
   }
@@ -617,7 +620,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies)
     const auto& record = records.at(0);
     BOOST_CHECK(record.d_type == QType::CNAME);
     BOOST_CHECK(record.d_class == QClass::IN);
-    auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+    auto content = getRR<CNAMERecordContent>(record);
     BOOST_CHECK(content != nullptr);
     BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden1a.example.net.");
   }
@@ -632,7 +635,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies)
     const auto& record = records.at(0);
     BOOST_CHECK(record.d_type == QType::CNAME);
     BOOST_CHECK(record.d_class == QClass::IN);
-    auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+    auto content = getRR<CNAMERecordContent>(record);
     BOOST_CHECK(content != nullptr);
     BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden2a.example.net.");
   }
@@ -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);
@@ -687,7 +690,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies_order)
     const auto& record = records.at(0);
     BOOST_CHECK(record.d_type == QType::CNAME);
     BOOST_CHECK(record.d_class == QClass::IN);
-    auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+    auto content = getRR<CNAMERecordContent>(record);
     BOOST_CHECK(content != nullptr);
     BOOST_CHECK_EQUAL(content->getTarget().toString(), "client1a.example.net.");
   }
@@ -709,7 +712,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies_order)
     const auto& record = records.at(0);
     BOOST_CHECK(record.d_type == QType::CNAME);
     BOOST_CHECK(record.d_class == QClass::IN);
-    auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+    auto content = getRR<CNAMERecordContent>(record);
     BOOST_CHECK(content != nullptr);
     BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden1a.example.net.");
   }
@@ -724,7 +727,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies_order)
     const auto& record = records.at(0);
     BOOST_CHECK(record.d_type == QType::CNAME);
     BOOST_CHECK(record.d_class == QClass::IN);
-    auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+    auto content = getRR<CNAMERecordContent>(record);
     BOOST_CHECK(content != nullptr);
     BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden1a.example.net.");
   }
@@ -746,7 +749,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies_order)
     const auto& record = records.at(0);
     BOOST_CHECK(record.d_type == QType::CNAME);
     BOOST_CHECK(record.d_class == QClass::IN);
-    auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+    auto content = getRR<CNAMERecordContent>(record);
     BOOST_CHECK(content != nullptr);
     BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden2a.example.net.");
   }
@@ -768,7 +771,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies_order)
     const auto& record = records.at(0);
     BOOST_CHECK(record.d_type == QType::CNAME);
     BOOST_CHECK(record.d_class == QClass::IN);
-    auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+    auto content = getRR<CNAMERecordContent>(record);
     BOOST_CHECK(content != nullptr);
     BOOST_CHECK_EQUAL(content->getTarget().toString(), "nsname1a.example.net.");
   }
@@ -790,7 +793,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies_order)
     const auto& record = records.at(0);
     BOOST_CHECK(record.d_type == QType::CNAME);
     BOOST_CHECK(record.d_class == QClass::IN);
-    auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+    auto content = getRR<CNAMERecordContent>(record);
     BOOST_CHECK(content != nullptr);
     BOOST_CHECK_EQUAL(content->getTarget().toString(), "nsip1a.example.net.");
   }
@@ -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.d_content = 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);
@@ -815,7 +818,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies_order)
     const auto& record = records.at(0);
     BOOST_CHECK(record.d_type == QType::CNAME);
     BOOST_CHECK(record.d_class == QClass::IN);
-    auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+    auto content = getRR<CNAMERecordContent>(record);
     BOOST_CHECK(content != nullptr);
     BOOST_CHECK_EQUAL(content->getTarget().toString(), "response1a.example.net.");
   }
@@ -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.d_content = 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 4c71f8240e74cc099fd7650b7309fcd27a66f9d1..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
@@ -6,6 +9,7 @@
 #endif
 #include <boost/test/unit_test.hpp>
 #include "mtasker.hh"
+#include <fcntl.h>
 
 BOOST_AUTO_TEST_SUITE(mtasker_cc)
 
@@ -28,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;
@@ -38,9 +43,49 @@ 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;
+static const size_t headroom = 1536; // Decrease to hit stackoverflow
+
+static void doAlmostStackoverflow(void* arg)
+{
+  auto* mt = reinterpret_cast<MTasker<>*>(arg);
+  int localvar[stackSize / sizeof(int) - headroom]; // experimentally derived headroom
+  localvar[0] = 0;
+  localvar[sizeof(localvar) / sizeof(localvar[0]) - 1] = 12;
+  if (mt->waitEvent(localvar[sizeof(localvar) / sizeof(localvar[0]) - 1], &localvar[0]) == 1) {
+    g_result = localvar[0];
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_AlmostStackOverflow)
+{
+  MTasker<> mt(stackSize);
+  mt.makeThread(doAlmostStackoverflow, &mt);
+  struct timeval now;
+  gettimeofday(&now, 0);
+  bool first = true;
+  int o = 25;
+  for (;;) {
+    while (mt.schedule(now)) {
+      ;
+    }
+    if (first) {
+      mt.sendEvent(12, &o);
+      first = false;
+    }
+    if (mt.noProcesses()) {
+      break;
+    }
+  }
+  BOOST_CHECK_EQUAL(g_result, o);
 }
 
-static void willThrow(void* p)
+static void willThrow(void* /* p */)
 {
   throw std::runtime_error("Help!");
 }
@@ -51,11 +96,13 @@ BOOST_AUTO_TEST_CASE(test_MtaskerException)
     MTasker<> mt;
     mt.makeThread(willThrow, 0);
     struct timeval now;
+    now.tv_sec = now.tv_usec = 0;
 
     for (;;) {
-      mt.schedule(&now);
+      mt.schedule(now);
     }
   },
                     std::exception);
 }
+
 BOOST_AUTO_TEST_SUITE_END()
index 666083da756db079797e7ba65904085ff59734f3..f6ce64c4a60b32f3a2eddade9e03552ebf879620 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,13 +18,13 @@ static recordsAndSignatures genRecsAndSigs(const DNSName& name, const uint16_t q
   rec.d_type = qtype;
   rec.d_ttl = 600;
   rec.d_place = DNSResourceRecord::AUTHORITY;
-  rec.d_content = DNSRecordContent::mastermake(qtype, QClass::IN, content);
+  rec.setContent(DNSRecordContent::make(qtype, QClass::IN, content));
 
   ret.records.push_back(rec);
 
   if (sigs) {
     rec.d_type = QType::RRSIG;
-    rec.d_content = std::make_shared<RRSIGRecordContent>(QType(qtype).toString() + " 5 3 600 2037010100000000 2037010100000000 24567 dummy data");
+    rec.setContent(std::make_shared<RRSIGRecordContent>(QType(qtype).toString() + " 5 3 600 2037010100000000 2037010100000000 24567 dummy data"));
     ret.signatures.push_back(rec);
   }
 
@@ -36,6 +39,7 @@ static NegCache::NegCacheEntry genNegCacheEntry(const DNSName& name, const DNSNa
   ret.d_qtype = QType(qtype);
   ret.d_auth = auth;
   ret.d_ttd = now.tv_sec + 600;
+  ret.d_orig_ttl = 600;
   ret.authoritySOA = genRecsAndSigs(auth, QType::SOA, "ns1 hostmaster 1 2 3 4 5", true);
   ret.DNSSECRecords = genRecsAndSigs(auth, QType::NSEC, "deadbeef", true);
 
@@ -155,7 +159,7 @@ BOOST_AUTO_TEST_CASE(test_getRootNXTrust_entry)
   BOOST_CHECK_EQUAL(cache.size(), 1U);
 
   NegCache::NegCacheEntry ne;
-  bool ret = cache.getRootNXTrust(qname, now, ne);
+  bool ret = cache.getRootNXTrust(qname, now, ne, false, false);
 
   BOOST_CHECK(ret);
   BOOST_CHECK_EQUAL(ne.d_name, qname);
@@ -202,7 +206,7 @@ BOOST_AUTO_TEST_CASE(test_getRootNXTrust_expired_entry)
   NegCache::NegCacheEntry ne;
 
   now.tv_sec += 1000;
-  bool ret = cache.getRootNXTrust(qname, now, ne);
+  bool ret = cache.getRootNXTrust(qname, now, ne, false, false);
 
   BOOST_CHECK_EQUAL(ret, false);
 }
@@ -246,7 +250,7 @@ BOOST_AUTO_TEST_CASE(test_getRootNXTrust)
   cache.add(genNegCacheEntry(qname2, auth2, now));
 
   NegCache::NegCacheEntry ne;
-  bool ret = cache.getRootNXTrust(qname, now, ne);
+  bool ret = cache.getRootNXTrust(qname, now, ne, false, false);
 
   BOOST_CHECK(ret);
   BOOST_CHECK_EQUAL(ne.d_name, qname2);
@@ -268,7 +272,7 @@ BOOST_AUTO_TEST_CASE(test_getRootNXTrust_full_domain_only)
   cache.add(genNegCacheEntry(qname2, auth2, now, 1)); // Add the denial for COM|A
 
   NegCache::NegCacheEntry ne;
-  bool ret = cache.getRootNXTrust(qname, now, ne);
+  bool ret = cache.getRootNXTrust(qname, now, ne, false, false);
 
   BOOST_CHECK_EQUAL(ret, false);
 }
@@ -281,6 +285,28 @@ BOOST_AUTO_TEST_CASE(test_prune)
   struct timeval now;
   Utility::gettimeofday(&now, 0);
 
+  NegCache cache(1);
+  NegCache::NegCacheEntry ne;
+  for (int i = 0; i < 400; i++) {
+    ne = genNegCacheEntry(DNSName(std::to_string(i) + qname), auth, now);
+    cache.add(ne);
+  }
+
+  BOOST_CHECK_EQUAL(cache.size(), 400U);
+
+  cache.prune(now.tv_sec, 100);
+
+  BOOST_CHECK_EQUAL(cache.size(), 100U);
+}
+
+BOOST_AUTO_TEST_CASE(test_prune_many_shards)
+{
+  string qname(".powerdns.com");
+  DNSName auth("powerdns.com");
+
+  struct timeval now;
+  Utility::gettimeofday(&now, 0);
+
   NegCache cache;
   NegCache::NegCacheEntry ne;
   for (int i = 0; i < 400; i++) {
@@ -290,7 +316,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);
 }
@@ -317,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;
@@ -339,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();
@@ -415,6 +441,44 @@ BOOST_AUTO_TEST_CASE(test_wipe_subtree)
   BOOST_CHECK_EQUAL(cache.size(), 400U);
 }
 
+BOOST_AUTO_TEST_CASE(test_wipe_typed)
+{
+  string qname(".powerdns.com");
+  DNSName auth("powerdns.com");
+
+  struct timeval now;
+  Utility::gettimeofday(&now, 0);
+
+  NegCache cache;
+  NegCache::NegCacheEntry ne;
+  ne = genNegCacheEntry(auth, auth, now, QType::A);
+  cache.add(ne);
+
+  for (int i = 0; i < 400; i++) {
+    ne = genNegCacheEntry(DNSName(std::to_string(i) + qname), auth, now, QType::A);
+    cache.add(ne);
+  }
+
+  BOOST_CHECK_EQUAL(cache.size(), 401U);
+
+  // Should only wipe the powerdns.com entry
+  cache.wipeTyped(auth, QType::A);
+  BOOST_CHECK_EQUAL(cache.size(), 400U);
+
+  NegCache::NegCacheEntry ne2;
+  bool ret = cache.get(auth, QType(1), now, ne2);
+
+  BOOST_CHECK_EQUAL(ret, false);
+
+  cache.wipeTyped(DNSName("1.powerdns.com"), QType::A);
+  BOOST_CHECK_EQUAL(cache.size(), 399U);
+
+  NegCache::NegCacheEntry ne3;
+  ret = cache.get(auth, QType(1), now, ne3);
+
+  BOOST_CHECK_EQUAL(ret, false);
+}
+
 BOOST_AUTO_TEST_CASE(test_clear)
 {
   string qname(".powerdns.com");
@@ -439,17 +503,21 @@ BOOST_AUTO_TEST_CASE(test_clear)
 BOOST_AUTO_TEST_CASE(test_dumpToFile)
 {
   NegCache cache(1);
-  vector<string> expected;
-  expected.push_back("www1.powerdns.com. 600 IN TYPE0 VIA powerdns.com. ; (Indeterminate)\n");
-  expected.push_back("powerdns.com. 600 IN SOA ns1. hostmaster. 1 2 3 4 5 ; (Indeterminate)\n");
-  expected.push_back("powerdns.com. 600 IN RRSIG SOA 5 3 600 20370101000000 20370101000000 24567 dummy. data ;\n");
-  expected.push_back("powerdns.com. 600 IN NSEC deadbeef. ; (Indeterminate)\n");
-  expected.push_back("powerdns.com. 600 IN RRSIG NSEC 5 3 600 20370101000000 20370101000000 24567 dummy. data ;\n");
-  expected.push_back("www2.powerdns.com. 600 IN TYPE0 VIA powerdns.com. ; (Indeterminate)\n");
-  expected.push_back("powerdns.com. 600 IN SOA ns1. hostmaster. 1 2 3 4 5 ; (Indeterminate)\n");
-  expected.push_back("powerdns.com. 600 IN RRSIG SOA 5 3 600 20370101000000 20370101000000 24567 dummy. data ;\n");
-  expected.push_back("powerdns.com. 600 IN NSEC deadbeef. ; (Indeterminate)\n");
-  expected.push_back("powerdns.com. 600 IN RRSIG NSEC 5 3 600 20370101000000 20370101000000 24567 dummy. data ;\n");
+  vector<string> expected = {
+    "; negcache dump follows\n",
+    ";\n",
+    "; negcache shard 0; size 2\n",
+    "www1.powerdns.com. 600 IN TYPE0 VIA powerdns.com. ; (Indeterminate) origttl=600 ss=0\n",
+    "powerdns.com. 600 IN SOA ns1. hostmaster. 1 2 3 4 5 ; (Indeterminate)\n",
+    "powerdns.com. 600 IN RRSIG SOA 5 3 600 20370101000000 20370101000000 24567 dummy. data ;\n",
+    "powerdns.com. 600 IN NSEC deadbeef. ; (Indeterminate)\n",
+    "powerdns.com. 600 IN RRSIG NSEC 5 3 600 20370101000000 20370101000000 24567 dummy. data ;\n",
+    "www2.powerdns.com. 600 IN TYPE0 VIA powerdns.com. ; (Indeterminate) origttl=600 ss=0\n",
+    "powerdns.com. 600 IN SOA ns1. hostmaster. 1 2 3 4 5 ; (Indeterminate)\n",
+    "powerdns.com. 600 IN RRSIG SOA 5 3 600 20370101000000 20370101000000 24567 dummy. data ;\n",
+    "powerdns.com. 600 IN NSEC deadbeef. ; (Indeterminate)\n",
+    "powerdns.com. 600 IN RRSIG NSEC 5 3 600 20370101000000 20370101000000 24567 dummy. data ;\n",
+    "; negcache size: 2/0 shards: 1 min/max shard size: 2/2\n"};
 
   struct timeval now;
   Utility::gettimeofday(&now, 0);
@@ -461,7 +529,7 @@ BOOST_AUTO_TEST_CASE(test_dumpToFile)
   if (!fp)
     BOOST_FAIL("Temporary file could not be opened");
 
-  cache.dumpToFile(fp.get(), now);
+  cache.doDump(fileno(fp.get()), 0, now.tv_sec);
 
   rewind(fp.get());
   char* line = nullptr;
@@ -473,7 +541,7 @@ BOOST_AUTO_TEST_CASE(test_dumpToFile)
     if (read == -1)
       BOOST_FAIL("Unable to read a line from the temp file");
     // The clock might have ticked so the 600 becomes 599
-    BOOST_CHECK(line == str);
+    BOOST_CHECK_EQUAL(line, str);
   }
 
   /* getline() allocates a buffer when called with a nullptr,
index 9748b55783d8d54947f88a4e2dd2788bb1a3a0de..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"
@@ -9,7 +12,7 @@ using namespace nod;
 
 BOOST_AUTO_TEST_SUITE(nod_cc)
 
-static bool pdns_exception(PDNSException const& ex) { return true; }
+static bool pdns_exception(PDNSException const& /* ex */) { return true; }
 
 BOOST_AUTO_TEST_CASE(test_basic)
 {
index 8c997e0696dbfb599725c3811f59b50232e6e6e1..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>
@@ -14,33 +17,33 @@ BOOST_AUTO_TEST_SUITE(rec_taskqueue)
 BOOST_AUTO_TEST_CASE(test_almostexpired_queue_no_dups)
 {
   taskQueueClear();
-  pushAlmostExpiredTask(DNSName("foo"), QType::AAAA, 0);
-  pushAlmostExpiredTask(DNSName("foo"), QType::AAAA, 0);
-  pushAlmostExpiredTask(DNSName("foo"), QType::A, 0);
+  pushAlmostExpiredTask(DNSName("foo"), QType::AAAA, 0, Netmask());
+  pushAlmostExpiredTask(DNSName("foo"), QType::AAAA, 0, Netmask());
+  pushAlmostExpiredTask(DNSName("foo"), QType::A, 0, Netmask());
 
   BOOST_CHECK_EQUAL(getTaskSize(), 2U);
   taskQueuePop();
   taskQueuePop();
   BOOST_CHECK_EQUAL(getTaskSize(), 0U);
   // AE queue is not rate limited
-  pushAlmostExpiredTask(DNSName("foo"), QType::A, 0);
+  pushAlmostExpiredTask(DNSName("foo"), QType::A, 0, Netmask());
   BOOST_CHECK_EQUAL(getTaskSize(), 1U);
 }
 
 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);
 }
 
diff --git a/pdns/recursordist/test-rec-tcounters_cc.cc b/pdns/recursordist/test-rec-tcounters_cc.cc
new file mode 100644 (file)
index 0000000..82faf53
--- /dev/null
@@ -0,0 +1,153 @@
+#ifndef BOOST_TEST_DYN_LINK
+#define BOOST_TEST_DYN_LINK
+#endif
+
+#define BOOST_TEST_NO_MAIN
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <boost/test/unit_test.hpp>
+
+#include <unistd.h>
+#include <thread>
+#include "dns_random.hh"
+#include "rec-tcounters.hh"
+
+static rec::GlobalCounters global;
+static thread_local rec::TCounters tlocal(global);
+
+BOOST_AUTO_TEST_SUITE(test_rec_tcounters_cc)
+
+BOOST_AUTO_TEST_CASE(destruct)
+{
+  global.reset();
+
+  const size_t count = 100000;
+  std::thread thread1([] {
+    for (size_t i = 0; i < count; i++) {
+      ++tlocal.at(rec::Counter::servFails);
+    }
+  });
+  std::thread thread2([] {
+    for (size_t i = 0; i < count; i++) {
+      ++tlocal.at(rec::Counter::nxDomains);
+    }
+  });
+  thread1.join();
+  thread2.join();
+  BOOST_CHECK_EQUAL(global.sum(rec::Counter::servFails), count);
+  BOOST_CHECK_EQUAL(global.sum(rec::Counter::nxDomains), count);
+}
+
+BOOST_AUTO_TEST_CASE(update_fast)
+{
+  global.reset();
+
+  std::atomic<uint64_t> done{};
+
+  const size_t count = 10000000;
+  std::thread thread1([&done] {
+    for (size_t i = 0; i < count; i++) {
+      ++tlocal.at(rec::Counter::servFails);
+      ++tlocal.at(rec::Counter::nxDomains);
+      tlocal.at(rec::DoubleWAvgCounter::avgLatencyUsec).add(1.1);
+      if (dns_random(10000) == 0) {
+        tlocal.updateSnap();
+      }
+    }
+    done++;
+  });
+  std::thread thread2([&done] {
+    for (size_t i = 0; i < count / 2; i++) {
+      ++tlocal.at(rec::Counter::servFails);
+      ++tlocal.at(rec::Counter::nxDomains);
+      tlocal.at(rec::DoubleWAvgCounter::avgLatencyUsec).add(2.2);
+      if (dns_random(10000) == 0) {
+        tlocal.updateSnap();
+      }
+    }
+    done++;
+  });
+  std::thread thread3([&done] {
+    while (done < 2) {
+      auto counts = global.aggregatedSnap();
+      BOOST_CHECK_EQUAL(counts.uint64Count[0], counts.uint64Count[1]);
+      auto avg = counts.at(rec::DoubleWAvgCounter::avgLatencyUsec).avg;
+      BOOST_CHECK(avg == 0.0 || (avg >= 1.1 && avg <= 2.2));
+    }
+  });
+  thread1.join();
+  thread2.join();
+  thread3.join();
+  BOOST_CHECK_EQUAL(global.sum(rec::Counter::servFails), count + count / 2);
+  BOOST_CHECK_EQUAL(global.sum(rec::Counter::nxDomains), count + count / 2);
+  auto avg = global.avg(rec::DoubleWAvgCounter::avgLatencyUsec);
+  BOOST_CHECK(avg >= 1.1 && avg <= 2.2);
+}
+
+BOOST_AUTO_TEST_CASE(update_with_sleep)
+{
+
+  global.reset();
+
+  std::atomic<int> done{};
+
+  const size_t count = 100;
+  std::thread thread1([&done] {
+    for (size_t i = 0; i < count; i++) {
+      ++tlocal.at(rec::Counter::servFails);
+      ++tlocal.at(rec::Counter::nxDomains);
+      tlocal.at(rec::DoubleWAvgCounter::avgLatencyUsec).add(1.1);
+      if (dns_random(10000) == 0) {
+        tlocal.updateSnap();
+      }
+      struct timespec interval
+      {
+        0, dns_random(20 * 1000 * 1000)
+      };
+      nanosleep(&interval, nullptr);
+    }
+    done++;
+  });
+  std::thread thread2([&done] {
+    for (size_t i = 0; i < count / 2; i++) {
+      ++tlocal.at(rec::Counter::servFails);
+      ++tlocal.at(rec::Counter::nxDomains);
+      tlocal.at(rec::DoubleWAvgCounter::avgLatencyUsec).add(2.2);
+      if (dns_random(10000) == 0) {
+        tlocal.updateSnap();
+      }
+      struct timespec interval
+      {
+        0, dns_random(40 * 1000 * 1000)
+      };
+      nanosleep(&interval, nullptr);
+    }
+    done++;
+  });
+  std::thread thread3([&done] {
+    while (done < 2) {
+      auto counts = global.aggregatedSnap();
+      BOOST_CHECK_EQUAL(counts.uint64Count[0], counts.uint64Count[1]);
+      auto avg = counts.at(rec::DoubleWAvgCounter::avgLatencyUsec).avg;
+      // std::cerr << avg << std::endl;
+      BOOST_CHECK(avg == 0.0 || (avg >= 1.1 && avg <= 2.2));
+      struct timespec interval
+      {
+        0, dns_random(80 * 1000 * 1000)
+      };
+      nanosleep(&interval, nullptr);
+    }
+  });
+  thread1.join();
+  thread2.join();
+  thread3.join();
+  BOOST_CHECK_EQUAL(global.sum(rec::Counter::servFails), count + count / 2);
+  BOOST_CHECK_EQUAL(global.sum(rec::Counter::nxDomains), count + count / 2);
+  auto avg = global.avg(rec::DoubleWAvgCounter::avgLatencyUsec);
+  BOOST_CHECK(avg >= 1.1 && avg <= 2.2);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
index ffec8883bbdd8505ed778930d3d152bb1835e960..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>
@@ -87,11 +90,11 @@ static void zonemdTest(const std::string& lines, pdns::ZoneMD::Config mode, pdns
     std::vector<DNSRecord> retrieved;
     time_t now = time(nullptr);
     ComboAddress who;
-    BOOST_CHECK_GT(g_recCache->get(now, DNSName("."), QType::SOA, true, &retrieved, who), 0);
+    BOOST_CHECK_GT(g_recCache->get(now, DNSName("."), QType::SOA, MemRecursorCache::RequireAuth, &retrieved, who), 0);
     // not auth
-    BOOST_CHECK_LT(g_recCache->get(now, DNSName("aaa."), QType::NS, true, &retrieved, who), 0);
+    BOOST_CHECK_LT(g_recCache->get(now, DNSName("aaa."), QType::NS, MemRecursorCache::RequireAuth, &retrieved, who), 0);
     // auth
-    BOOST_CHECK_GT(g_recCache->get(now, DNSName("aaa."), QType::NS, false, &retrieved, who), 0);
+    BOOST_CHECK_GT(g_recCache->get(now, DNSName("aaa."), QType::NS, MemRecursorCache::None, &retrieved, who), 0);
   }
 }
 
@@ -99,6 +102,8 @@ BOOST_AUTO_TEST_CASE(test_zonetocache)
 {
   std::unique_ptr<SyncRes> sr;
   initSR(sr, true);
+  g_log.setLoglevel(Logger::Critical);
+  g_log.toConsole(Logger::Critical);
   setDNSSECValidation(sr, DNSSECMode::ValidateAll);
 
   zonemdTest(zone, pdns::ZoneMD::Config::Ignore, pdns::ZoneMD::Config::Ignore, 17U);
@@ -116,4 +121,61 @@ BOOST_AUTO_TEST_CASE(test_zonetocache)
   zonemdTest(zoneWithBadZONEMD, pdns::ZoneMD::Config::Require, pdns::ZoneMD::Config::Ignore, 0U);
   zonemdTest(zoneWithBadZONEMD, pdns::ZoneMD::Config::Ignore, pdns::ZoneMD::Config::Require, 0U);
 }
+
+// Example from https://github.com/verisign/zonemd-test-cases/blob/master/zones/20-generic-zonemd/example.zone
+const std::string genericTest = "example.      86400   IN      NS      ns.example.\n"
+                                "example.      86400   IN      SOA     ns.example. admin.example. 2018031900 1800 900 604800 86400\n"
+                                "example.      86400   IN      TYPE63  \\# 54 7848b91c01018ee54f64ce0d57fd70e1a4811a9ca9e849e2e50cb598edf3ba9c2a58625335c1f966835f0d4338d9f78f557227d63bf6\n"
+                                "ns.example.   3600    IN      A       127.0.0.1\n";
+
+const std::string genericBadTest = "example.   86400   IN      NS      ns.example.\n"
+                                   "example.   86400   IN      SOA     ns.example. admin.example. 2018031900 1800 900 604800 86400\n"
+                                   "example.   86400   IN      TYPE63  \\# 54 8848b91c01018ee54f64ce0d57fd70e1a4811a9ca9e849e2e50cb598edf3ba9c2a58625335c1f966835f0d4338d9f78f557227d63bf6\n"
+                                   "ns.example.        3600    IN      A       127.0.0.1\n";
+
+static void zonemdGenericTest(const std::string& lines, pdns::ZoneMD::Config mode, pdns::ZoneMD::Config dnssec, size_t expectedCacheSize)
+{
+  char temp[] = "/tmp/ztcXXXXXXXXXX";
+  int fd = mkstemp(temp);
+  BOOST_REQUIRE(fd > 0);
+  FILE* fp = fdopen(fd, "w");
+  BOOST_REQUIRE(fp != nullptr);
+  size_t written = fwrite(lines.data(), 1, lines.length(), fp);
+  BOOST_REQUIRE(written == lines.length());
+  BOOST_REQUIRE(fclose(fp) == 0);
+
+  RecZoneToCache::Config config{"example.", "file", {temp}, ComboAddress(), TSIGTriplet()};
+  config.d_refreshPeriod = 0;
+  config.d_retryOnError = 0;
+  config.d_zonemd = mode;
+  config.d_dnssec = dnssec;
+
+  // Start with a new, empty cache
+  g_recCache = std::make_unique<MemRecursorCache>();
+  BOOST_CHECK_EQUAL(g_recCache->size(), 0U);
+  RecZoneToCache::State state;
+  RecZoneToCache::ZoneToCache(config, state);
+  unlink(temp);
+
+  BOOST_CHECK_EQUAL(g_recCache->size(), expectedCacheSize);
+
+  if (expectedCacheSize > 0) {
+    std::vector<DNSRecord> retrieved;
+    time_t now = time(nullptr);
+    ComboAddress who;
+    BOOST_CHECK_GT(g_recCache->get(now, DNSName("example."), QType::SOA, true, &retrieved, who), 0);
+    BOOST_CHECK_GT(g_recCache->get(now, DNSName("example."), QType::NS, true, &retrieved, who), 0);
+    BOOST_CHECK_GT(g_recCache->get(now, DNSName("example."), QType::ZONEMD, true, &retrieved, who), 0);
+    BOOST_CHECK_GT(g_recCache->get(now, DNSName("ns.example."), QType::A, true, &retrieved, who), 0);
+  }
+}
+
+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);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
deleted file mode 120000 (symlink)
index cd15923d0fa232aab34af32cc76493b4c62724ad..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../test-recpacketcache_cc.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..df74d9a7430d59abfbfe8195fecbdeb03470d9aa
--- /dev/null
@@ -0,0 +1,453 @@
+#ifndef BOOST_TEST_DYN_LINK
+#define BOOST_TEST_DYN_LINK
+#endif
+
+#define BOOST_TEST_NO_MAIN
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <boost/test/unit_test.hpp>
+#include "dnswriter.hh"
+#include "dnsrecords.hh"
+#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)
+
+BOOST_AUTO_TEST_CASE(test_recPacketCacheSimple)
+{
+  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);
+
+  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, 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), now, ttd, vState::Indeterminate, boost::none, false);
+  BOOST_CHECK_EQUAL(rpc.size(), 1U);
+  rpc.doPruneTo(now, 0);
+  BOOST_CHECK_EQUAL(rpc.size(), 0U);
+  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), now, ttd, vState::Indeterminate, boost::none, false);
+  BOOST_CHECK_EQUAL(rpc.size(), 1U);
+  uint32_t qhash2 = 0;
+  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, now, &fpacket, &age, &qhash2);
+  BOOST_CHECK_EQUAL(found, true);
+  BOOST_CHECK_EQUAL(qhash, qhash2);
+  BOOST_CHECK_EQUAL(fpacket, rpacket);
+
+  packet.clear();
+  qname += DNSName("co.uk");
+  DNSPacketWriter pw2(packet, qname, QType::A);
+
+  pw2.getHeader()->rd = true;
+  pw2.getHeader()->qr = false;
+  pw2.getHeader()->id = dns_random_uint16();
+  qpacket.assign((const char*)&packet[0], packet.size());
+
+  found = rpc.getResponsePacket(tag, qpacket, now, &fpacket, &age, &qhash);
+  BOOST_CHECK_EQUAL(found, false);
+  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);
+  string fpacket;
+  unsigned int tag = 0;
+  uint32_t age = 0;
+  uint32_t qhash = 0;
+  uint32_t ttd = 3600;
+  BOOST_CHECK_EQUAL(rpc.size(), 0U);
+
+  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 future = INT_MAX - 1800; // with ttd of 3600, we pass the cliff
+
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag, qpacket, future, &fpacket, &age, &qhash), false);
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, future, &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), future, ttd, vState::Indeterminate, boost::none, false);
+  BOOST_CHECK_EQUAL(rpc.size(), 1U);
+  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);
+
+  rpc.doWipePacketCache(qname);
+  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);
+  uint32_t qhash2 = 0;
+  bool found = rpc.getResponsePacket(tag, qpacket, future, &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, future, &fpacket, &age, &qhash2);
+  BOOST_CHECK_EQUAL(found, true);
+  BOOST_CHECK_EQUAL(qhash, qhash2);
+  BOOST_CHECK_EQUAL(fpacket, rpacket);
+
+  found = rpc.getResponsePacket(tag, qpacket, future + 3601, &fpacket, &age, &qhash2);
+  BOOST_CHECK_EQUAL(found, false);
+  found = rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, future + 3601, &fpacket, &age, &qhash2);
+  BOOST_CHECK_EQUAL(found, false);
+
+  packet.clear();
+  qname += DNSName("co.uk");
+  DNSPacketWriter pw2(packet, qname, QType::A);
+
+  pw2.getHeader()->rd = true;
+  pw2.getHeader()->qr = false;
+  pw2.getHeader()->id = dns_random_uint16();
+  qpacket.assign((const char*)&packet[0], packet.size());
+
+  found = rpc.getResponsePacket(tag, qpacket, future, &fpacket, &age, &qhash);
+  BOOST_CHECK_EQUAL(found, false);
+  found = rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, future, &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_recPacketCache_Tags)
+{
+  /* Insert a response with tag1, the exact same query with a different tag
+     should lead to a miss. Inserting a different response with the second tag
+     should not override the first one, and we should get a hit for the
+     query with either tags, with the response matching the tag.
+  */
+  RecursorPacketCache rpc(1000);
+  string fpacket;
+  const unsigned int tag1 = 0;
+  const unsigned int tag2 = 42;
+  uint32_t age = 0;
+  uint32_t qhash = 0;
+  uint32_t temphash = 0;
+  uint32_t ttd = 3600;
+  BOOST_CHECK_EQUAL(rpc.size(), 0U);
+
+  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(reinterpret_cast<const char*>(&packet[0]), packet.size());
+  pw.startRecord(qname, QType::A, ttd);
+
+  /* Both interfaces (with and without the qname/qtype/qclass) should get the same hash */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, time(nullptr), &fpacket, &age, &qhash), false);
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), false);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+
+  /* Different tag, should still get get the same hash, for both interfaces */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, time(nullptr), &fpacket, &age, &temphash), false);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), false);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+
+  {
+    ARecordContent ar("127.0.0.1");
+    ar.toPacket(pw);
+    pw.commit();
+  }
+  string r1packet(reinterpret_cast<const char*>(&packet[0]), packet.size());
+
+  {
+    ARecordContent ar("127.0.0.2");
+    ar.toPacket(pw);
+    pw.commit();
+  }
+  string r2packet(reinterpret_cast<const char*>(&packet[0]), packet.size());
+
+  BOOST_CHECK(r1packet != r2packet);
+
+  /* inserting a response for tag1 */
+  rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, boost::none, false);
+  BOOST_CHECK_EQUAL(rpc.size(), 1U);
+
+  /* inserting a different response for tag2, should not override the first one */
+  rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, boost::none, false);
+  BOOST_CHECK_EQUAL(rpc.size(), 2U);
+
+  /* remove all responses from the cache */
+  rpc.doPruneTo(time(nullptr), 0);
+  BOOST_CHECK_EQUAL(rpc.size(), 0U);
+
+  /* reinsert both */
+  rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, boost::none, false);
+  BOOST_CHECK_EQUAL(rpc.size(), 1U);
+
+  rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, boost::none, false);
+  BOOST_CHECK_EQUAL(rpc.size(), 2U);
+
+  /* remove the responses by qname, should remove both */
+  rpc.doWipePacketCache(qname);
+  BOOST_CHECK_EQUAL(rpc.size(), 0U);
+
+  /* insert the response for tag1 */
+  rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, boost::none, false);
+  BOOST_CHECK_EQUAL(rpc.size(), 1U);
+
+  /* we can retrieve it */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), true);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+  BOOST_CHECK_EQUAL(fpacket, r1packet);
+
+  /* with both interfaces */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, time(nullptr), &fpacket, &age, &temphash), true);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+  BOOST_CHECK_EQUAL(fpacket, r1packet);
+
+  /* but not with the second tag */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), false);
+  /* we should still get the same hash */
+  BOOST_CHECK_EQUAL(temphash, qhash);
+
+  /* adding a response for the second tag */
+  rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, boost::none, false);
+  BOOST_CHECK_EQUAL(rpc.size(), 2U);
+
+  /* We still get the correct response for the first tag */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, time(nullptr), &fpacket, &age, &temphash), true);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+  BOOST_CHECK_EQUAL(fpacket, r1packet);
+
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), true);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+  BOOST_CHECK_EQUAL(fpacket, r1packet);
+
+  /* and the correct response for the second tag */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, time(nullptr), &fpacket, &age, &temphash), true);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+  BOOST_CHECK_EQUAL(fpacket, r2packet);
+
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), true);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+  BOOST_CHECK_EQUAL(fpacket, r2packet);
+}
+
+BOOST_AUTO_TEST_CASE(test_recPacketCache_TCP)
+{
+  /* Insert a response with UDP, the exact same query with a TCP flag
+     should lead to a miss.
+  */
+  RecursorPacketCache rpc(1000);
+  string fpacket;
+  uint32_t age = 0;
+  uint32_t qhash = 0;
+  uint32_t temphash = 0;
+  uint32_t ttd = 3600;
+  BOOST_CHECK_EQUAL(rpc.size(), 0U);
+
+  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(reinterpret_cast<const char*>(&packet[0]), packet.size());
+  pw.startRecord(qname, QType::A, ttd);
+
+  /* Both interfaces (with and without the qname/qtype/qclass) should get the same hash */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, time(nullptr), &fpacket, &age, &qhash), false);
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, nullptr, &temphash, nullptr, false), false);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+
+  /* Different tcp/udp, should still get get the same hash, for both interfaces */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, time(nullptr), &fpacket, &age, &qhash), false);
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, nullptr, &temphash, nullptr, true), false);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+
+  {
+    ARecordContent ar("127.0.0.1");
+    ar.toPacket(pw);
+    pw.commit();
+  }
+  string r1packet(reinterpret_cast<const char*>(&packet[0]), packet.size());
+
+  {
+    ARecordContent ar("127.0.0.2");
+    ar.toPacket(pw);
+    pw.commit();
+  }
+  string r2packet(reinterpret_cast<const char*>(&packet[0]), packet.size());
+
+  BOOST_CHECK(r1packet != r2packet);
+
+  /* inserting a response for udp */
+  rpc.insertResponsePacket(0, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, boost::none, false);
+  BOOST_CHECK_EQUAL(rpc.size(), 1U);
+
+  /* inserting a different response for tcp, should not override the first one */
+  rpc.insertResponsePacket(0, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, boost::none, true);
+  BOOST_CHECK_EQUAL(rpc.size(), 2U);
+
+  /* remove all responses from the cache */
+  rpc.doPruneTo(time(nullptr), 0);
+  BOOST_CHECK_EQUAL(rpc.size(), 0U);
+
+  /* reinsert both */
+  rpc.insertResponsePacket(0, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, boost::none, false);
+  BOOST_CHECK_EQUAL(rpc.size(), 1U);
+
+  rpc.insertResponsePacket(0, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, boost::none, true);
+  BOOST_CHECK_EQUAL(rpc.size(), 2U);
+
+  /* remove the responses by qname, should remove both */
+  rpc.doWipePacketCache(qname);
+  BOOST_CHECK_EQUAL(rpc.size(), 0U);
+
+  /* insert the response for tcp */
+  rpc.insertResponsePacket(0, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, boost::none, true);
+  BOOST_CHECK_EQUAL(rpc.size(), 1U);
+
+  vState vState;
+  /* we can retrieve it */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &vState, &temphash, nullptr, true), true);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+  BOOST_CHECK_EQUAL(fpacket, r1packet);
+
+  /* first interface assumes udp */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, time(nullptr), &fpacket, &age, &temphash), false);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+  BOOST_CHECK_EQUAL(fpacket, r1packet);
+
+  /* and not with explicit udp */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &vState, &temphash, nullptr, false), false);
+  /* we should still get the same hash */
+  BOOST_CHECK_EQUAL(temphash, qhash);
+
+  /* adding a response for udp */
+  rpc.insertResponsePacket(0, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, boost::none, false);
+  BOOST_CHECK_EQUAL(rpc.size(), 2U);
+
+  /* We get the correct response for udp now */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, time(nullptr), &fpacket, &age, &temphash), true);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+  BOOST_CHECK_EQUAL(fpacket, r2packet);
+
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), true);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+  BOOST_CHECK_EQUAL(fpacket, r2packet);
+
+  /* and the correct response for tcp */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &vState, &temphash, nullptr, true), true);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+  BOOST_CHECK_EQUAL(fpacket, r1packet);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
index 89ed63d426af23d7fedd616e3d5fabcfbee08ed2..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,11 +16,12 @@ BOOST_AUTO_TEST_SUITE(recursorcache_cc)
 
 static void simple(time_t now)
 {
+  MemRecursorCache::resetStaticsForTests();
   MemRecursorCache MRC;
 
   std::vector<DNSRecord> records;
   std::vector<std::shared_ptr<DNSRecord>> authRecords;
-  std::vector<std::shared_ptr<RRSIGRecordContent>> signatures;
+  std::vector<std::shared_ptr<const RRSIGRecordContent>> signatures;
   const DNSName authZone(".");
 
   time_t ttd = now + 30;
@@ -27,7 +31,7 @@ static void simple(time_t now)
   dr0.d_name = power;
   dr0.d_type = QType::AAAA;
   dr0.d_class = QClass::IN;
-  dr0.d_content = std::make_shared<AAAARecordContent>(dr0Content);
+  dr0.setContent(std::make_shared<AAAARecordContent>(dr0Content));
   dr0.d_ttl = static_cast<uint32_t>(ttd); // XXX truncation
   dr0.d_place = DNSResourceRecord::ANSWER;
 
@@ -69,7 +73,7 @@ static void simple(time_t now)
     int64_t expected = counter - delcounter;
 
     for (; delcounter < counter; ++delcounter) {
-      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(delcounter)), QType(QType::A), false, &retrieved, who, false) > 0) {
+      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(delcounter)), QType(QType::A), MemRecursorCache::None, &retrieved, who) > 0) {
         matches++;
         BOOST_REQUIRE_EQUAL(retrieved.size(), records.size());
         BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(retrieved.at(0))->getCA().toString(), dr0Content.toString());
@@ -85,7 +89,7 @@ static void simple(time_t now)
     dr1.d_name = power;
     dr1.d_type = QType::AAAA;
     dr1.d_class = QClass::IN;
-    dr1.d_content = std::make_shared<AAAARecordContent>(dr1Content);
+    dr1.setContent(std::make_shared<AAAARecordContent>(dr1Content));
     dr1.d_ttl = static_cast<uint32_t>(ttd); // XXX truncation
     dr1.d_place = DNSResourceRecord::ANSWER;
 
@@ -94,7 +98,7 @@ static void simple(time_t now)
     dr2.d_name = power;
     dr2.d_type = QType::A;
     dr2.d_class = QClass::IN;
-    dr2.d_content = std::make_shared<ARecordContent>(dr2Content);
+    dr2.setContent(std::make_shared<ARecordContent>(dr2Content));
     dr2.d_ttl = static_cast<uint32_t>(ttd); // XXX truncation
     // the place should not matter to the cache
     dr2.d_place = DNSResourceRecord::AUTHORITY;
@@ -106,12 +110,12 @@ static void simple(time_t now)
     BOOST_CHECK_EQUAL(MRC.size(), 1U);
 
     // subnet specific should be returned for a matching subnet
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::AAAA), false, &retrieved, ComboAddress("192.0.2.2")), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::AAAA), MemRecursorCache::None, &retrieved, ComboAddress("192.0.2.2")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
 
     // subnet specific should not be returned for a different subnet
-    BOOST_CHECK_LT(MRC.get(now, power, QType(QType::AAAA), false, &retrieved, ComboAddress("127.0.0.1")), 0);
+    BOOST_CHECK_LT(MRC.get(now, power, QType(QType::AAAA), MemRecursorCache::None, &retrieved, ComboAddress("127.0.0.1")), 0);
     BOOST_CHECK_EQUAL(retrieved.size(), 0U);
 
     // remove everything
@@ -125,7 +129,7 @@ static void simple(time_t now)
     BOOST_CHECK_EQUAL(MRC.size(), 1U);
 
     // NON-subnet specific should always be returned
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr2Content.toString());
 
@@ -144,21 +148,21 @@ static void simple(time_t now)
     BOOST_CHECK_EQUAL(MRC.size(), 3U);
 
     // we should still get the NON-subnet specific entry
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr2Content.toString());
 
     // we should get the subnet specific entry if we are from the right subnet
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::AAAA), false, &retrieved, ComboAddress("192.0.2.3")), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::AAAA), MemRecursorCache::None, &retrieved, ComboAddress("192.0.2.3")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
 
     // but nothing from a different subnet
-    BOOST_CHECK_LT(MRC.get(now, power, QType(QType::AAAA), false, &retrieved, ComboAddress("127.0.0.1")), 0);
+    BOOST_CHECK_LT(MRC.get(now, power, QType(QType::AAAA), MemRecursorCache::None, &retrieved, ComboAddress("127.0.0.1")), 0);
     BOOST_CHECK_EQUAL(retrieved.size(), 0U);
 
     // QType::ANY should return any qtype, so from the right subnet we should get all of them
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ANY), false, &retrieved, ComboAddress("192.0.2.3")), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ANY), MemRecursorCache::None, &retrieved, ComboAddress("192.0.2.3")), (ttd - now));
     BOOST_CHECK_EQUAL(retrieved.size(), 3U);
     for (const auto& rec : retrieved) {
       BOOST_CHECK(rec.d_type == QType::A || rec.d_type == QType::AAAA || rec.d_type == QType::TXT);
@@ -169,14 +173,14 @@ static void simple(time_t now)
     }
 
     // but only the non-subnet specific from the another subnet
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ANY), false, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ANY), MemRecursorCache::None, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
     BOOST_CHECK_EQUAL(retrieved.size(), 2U);
     for (const auto& rec : retrieved) {
       BOOST_CHECK(rec.d_type == QType::A || rec.d_type == QType::TXT);
     }
 
     // QType::ADDR should return both A and AAAA but no TXT, so two entries from the right subnet
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ADDR), false, &retrieved, ComboAddress("192.0.2.3")), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ADDR), MemRecursorCache::None, &retrieved, ComboAddress("192.0.2.3")), (ttd - now));
     BOOST_CHECK_EQUAL(retrieved.size(), 2U);
     bool gotA = false;
     bool gotAAAA = false;
@@ -193,12 +197,12 @@ static void simple(time_t now)
     BOOST_CHECK(gotAAAA);
 
     // but only the non-subnet specific one from the another subnet
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ADDR), false, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ADDR), MemRecursorCache::None, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK(retrieved.at(0).d_type == QType::A);
 
     // entries are only valid until ttd, we should not get anything after that because they are expired
-    BOOST_CHECK_LT(MRC.get(ttd + 5, power, QType(QType::ADDR), false, &retrieved, ComboAddress("127.0.0.1")), 0);
+    BOOST_CHECK_LT(MRC.get(ttd + 5, power, QType(QType::ADDR), MemRecursorCache::None, &retrieved, ComboAddress("127.0.0.1")), 0);
     BOOST_CHECK_EQUAL(retrieved.size(), 0U);
 
     // let's age the records for our existing QType::TXT entry so they are now only valid for 5s
@@ -206,14 +210,14 @@ static void simple(time_t now)
     BOOST_CHECK_EQUAL(MRC.doAgeCache(now, power, QType::TXT, newTTL), true);
 
     // we should still be able to retrieve it
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::TXT), false, &retrieved, ComboAddress("127.0.0.1")), static_cast<int32_t>(newTTL));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::TXT), MemRecursorCache::None, &retrieved, ComboAddress("127.0.0.1")), static_cast<int32_t>(newTTL));
     BOOST_CHECK_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK(retrieved.at(0).d_type == QType::TXT);
     // please note that this is still a TTD at this point
     BOOST_CHECK_EQUAL(retrieved.at(0).d_ttl, now + newTTL);
 
     // but 10s later it should be gone
-    BOOST_CHECK_LT(MRC.get(now + 10, power, QType(QType::TXT), false, &retrieved, ComboAddress("127.0.0.1")), 0);
+    BOOST_CHECK_LT(MRC.get(now + 10, power, QType(QType::TXT), MemRecursorCache::None, &retrieved, ComboAddress("127.0.0.1")), 0);
     BOOST_CHECK_EQUAL(retrieved.size(), 0U);
 
     // wipe everything
@@ -225,7 +229,7 @@ static void simple(time_t now)
     records.push_back(dr2);
     MRC.replace(now, power, QType(QType::A), records, signatures, authRecords, true, authZone, boost::none);
     BOOST_CHECK_EQUAL(MRC.size(), 1U);
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
     BOOST_CHECK_EQUAL(retrieved.size(), 1U);
 
     DNSRecord dr3;
@@ -233,7 +237,7 @@ static void simple(time_t now)
     dr3.d_name = power;
     dr3.d_type = QType::A;
     dr3.d_class = QClass::IN;
-    dr3.d_content = std::make_shared<ARecordContent>(dr3Content);
+    dr3.setContent(std::make_shared<ARecordContent>(dr3Content));
     dr3.d_ttl = static_cast<uint32_t>(ttd + 100); // XXX truncation
     // the place should not matter to the cache
     dr3.d_place = DNSResourceRecord::AUTHORITY;
@@ -247,14 +251,14 @@ static void simple(time_t now)
     // non-auth should not replace valid auth
     MRC.replace(now, power, QType(QType::A), records, signatures, authRecords, false, authZone, boost::none);
     BOOST_CHECK_EQUAL(MRC.size(), 1U);
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr2Content.toString());
 
     // but non-auth _should_ replace expired auth
     MRC.replace(ttd + 1, power, QType(QType::A), records, signatures, authRecords, false, authZone, boost::none);
     BOOST_CHECK_EQUAL(MRC.size(), 1U);
-    BOOST_CHECK_EQUAL(MRC.get(ttd + 1, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1")), (dr3.d_ttl - (ttd + 1)));
+    BOOST_CHECK_EQUAL(MRC.get(ttd + 1, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("127.0.0.1")), (dr3.d_ttl - (ttd + 1)));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr3Content.toString());
 
@@ -264,8 +268,8 @@ static void simple(time_t now)
     MRC.replace(now, power, QType(QType::A), records, signatures, authRecords, false, authZone, boost::none);
     BOOST_CHECK_EQUAL(MRC.size(), 1U);
     // let's first check that non-auth is not returned when we need authoritative data
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), true, &retrieved, ComboAddress("127.0.0.1")), -1);
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::RequireAuth, &retrieved, ComboAddress("127.0.0.1")), -1);
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr2Content.toString());
 
@@ -280,7 +284,7 @@ static void simple(time_t now)
     BOOST_CHECK_EQUAL(MRC.size(), 1U);
     vState retrievedState = vState::Indeterminate;
     bool wasAuth = false;
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1"), false, boost::none, nullptr, nullptr, nullptr, &retrievedState, &wasAuth), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("127.0.0.1"), boost::none, nullptr, nullptr, nullptr, &retrievedState, &wasAuth), (ttd - now));
     BOOST_CHECK_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(vStateToString(retrievedState), vStateToString(vState::Secure));
     BOOST_CHECK_EQUAL(wasAuth, true);
@@ -289,7 +293,7 @@ static void simple(time_t now)
     BOOST_CHECK_EQUAL(MRC.size(), 1U);
     retrievedState = vState::Indeterminate;
     wasAuth = false;
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1"), false, boost::none, nullptr, nullptr, nullptr, &retrievedState, &wasAuth), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("127.0.0.1"), boost::none, nullptr, nullptr, nullptr, &retrievedState, &wasAuth), (ttd - now));
     BOOST_CHECK_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(vStateToString(retrievedState), vStateToString(vState::Secure));
     BOOST_CHECK_EQUAL(wasAuth, true);
@@ -313,7 +317,7 @@ static void simple(time_t now)
     dr4.d_name = power;
     dr4.d_type = QType::A;
     dr4.d_class = QClass::IN;
-    dr4.d_content = std::make_shared<ARecordContent>(dr4Content);
+    dr4.setContent(std::make_shared<ARecordContent>(dr4Content));
     dr4.d_ttl = static_cast<uint32_t>(ttd); // XXX truncation
     dr4.d_place = DNSResourceRecord::AUTHORITY;
 
@@ -332,7 +336,7 @@ static void simple(time_t now)
     BOOST_CHECK_EQUAL(MRC.size(), 3U);
 
     // we should get the most specific entry for 192.168.0.1, so the second one
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.168.0.1")), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("192.168.0.1")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr4Content.toString());
 
@@ -348,11 +352,11 @@ static void simple(time_t now)
     BOOST_CHECK_EQUAL(MRC.size(), 1U);
 
     // we should not get it when we need authoritative data
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), true, &retrieved, ComboAddress("192.168.0.1")), -1);
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::RequireAuth, &retrieved, ComboAddress("192.168.0.1")), -1);
     BOOST_REQUIRE_EQUAL(retrieved.size(), 0U);
 
     // but we should when we are OK with non-auth
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.168.0.1")), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("192.168.0.1")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr2Content.toString());
   }
@@ -387,11 +391,12 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheSimpleDistantFuture)
 
 BOOST_AUTO_TEST_CASE(test_RecursorCacheGhost)
 {
+  MemRecursorCache::resetStaticsForTests();
   MemRecursorCache MRC;
 
   std::vector<DNSRecord> records;
   std::vector<std::shared_ptr<DNSRecord>> authRecords;
-  std::vector<std::shared_ptr<RRSIGRecordContent>> signatures;
+  std::vector<std::shared_ptr<const RRSIGRecordContent>> signatures;
   time_t now = time(nullptr);
 
   BOOST_CHECK_EQUAL(MRC.size(), 0U);
@@ -404,7 +409,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheGhost)
   ns1.d_name = ghost;
   ns1.d_type = QType::NS;
   ns1.d_class = QClass::IN;
-  ns1.d_content = std::make_shared<NSRecordContent>(ns1Content);
+  ns1.setContent(std::make_shared<NSRecordContent>(ns1Content));
   ns1.d_ttl = static_cast<uint32_t>(ttd); // XXX truncation
   ns1.d_place = DNSResourceRecord::ANSWER;
   records.push_back(ns1);
@@ -421,17 +426,66 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheGhost)
 
   /* the TTL should not have been raised */
   std::vector<DNSRecord> retrieved;
-  BOOST_CHECK_EQUAL(MRC.get(now, ghost, QType(QType::NS), false, &retrieved, ComboAddress("192.0.2.2")), (ttd - now));
+  BOOST_CHECK_EQUAL(MRC.get(now, ghost, QType(QType::NS), MemRecursorCache::None, &retrieved, ComboAddress("192.0.2.2")), (ttd - now));
+  BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
+  BOOST_CHECK_EQUAL(retrieved.at(0).d_ttl, static_cast<uint32_t>(ttd));
+}
+
+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;
+  std::vector<std::shared_ptr<DNSRecord>> authRecords;
+  std::vector<std::shared_ptr<const RRSIGRecordContent>> signatures;
+  time_t now = time(nullptr);
+
+  BOOST_CHECK_EQUAL(MRC.size(), 0U);
+
+  const time_t expiry = 30;
+  time_t ttd = now + expiry;
+
+  DNSRecord ns1;
+  auto record1 = DNSName("ns1.powerdns.com.");
+  ns1.d_name = record1;
+  ns1.d_type = QType::NS;
+  ns1.d_class = QClass::IN;
+  ns1.setContent(std::make_shared<NSRecordContent>(record1));
+  ns1.d_ttl = static_cast<uint32_t>(ttd); // XXX truncation
+  ns1.d_place = DNSResourceRecord::ANSWER;
+  records.push_back(ns1);
+  MRC.replace(now, ns1.d_name, QType(ns1.d_type), records, signatures, authRecords, true, DNSName("powerdns.com."), boost::none);
+  BOOST_CHECK_EQUAL(MRC.size(), 1U);
+
+  // Record will expire soon so we should hit the 5s margin, replace by non-auth data should succeed
+  now = ttd - 1;
+  ttd = now + expiry;
+  ns1.d_ttl = static_cast<uint32_t>(ttd); // XXX truncation
+  records.clear();
+  records.push_back(ns1);
+
+  MRC.replace(now, ns1.d_name, QType(ns1.d_type), records, signatures, authRecords, false, DNSName("powerdns.com."), boost::none);
+  BOOST_CHECK_EQUAL(MRC.size(), 1U);
+
+  // Let time pass, if we did not insert the non-auth record, it will be expired
+  now += expiry / 2;
+
+  std::vector<DNSRecord> retrieved;
+  BOOST_CHECK_EQUAL(MRC.get(now, record1, QType(QType::NS), MemRecursorCache::None, &retrieved, ComboAddress("192.0.2.2")), (ttd - now));
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(retrieved.at(0).d_ttl, static_cast<uint32_t>(ttd));
 }
 
 BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingExpiredEntries)
 {
+  MemRecursorCache::resetStaticsForTests();
   MemRecursorCache MRC(1);
 
   std::vector<DNSRecord> records;
-  std::vector<std::shared_ptr<RRSIGRecordContent>> signatures;
+  std::vector<std::shared_ptr<const RRSIGRecordContent>> signatures;
   std::vector<std::shared_ptr<DNSRecord>> authRecs;
   const DNSName authZone(".");
   BOOST_CHECK_EQUAL(MRC.size(), 0U);
@@ -448,7 +502,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingExpiredEntries)
   dr1.d_name = power1;
   dr1.d_type = QType::AAAA;
   dr1.d_class = QClass::IN;
-  dr1.d_content = std::make_shared<AAAARecordContent>(dr1Content);
+  dr1.setContent(std::make_shared<AAAARecordContent>(dr1Content));
   dr1.d_ttl = static_cast<uint32_t>(ttd); // XXX truncation
   dr1.d_place = DNSResourceRecord::ANSWER;
 
@@ -458,7 +512,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingExpiredEntries)
   dr2.d_name = power2;
   dr2.d_type = QType::AAAA;
   dr2.d_class = QClass::IN;
-  dr2.d_content = std::make_shared<AAAARecordContent>(dr2Content);
+  dr2.setContent(std::make_shared<AAAARecordContent>(dr2Content));
   dr2.d_ttl = static_cast<uint32_t>(ttd); // XXX truncation
   dr2.d_place = DNSResourceRecord::ANSWER;
 
@@ -471,19 +525,20 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingExpiredEntries)
   records.clear();
   BOOST_CHECK_EQUAL(MRC.size(), 2U);
 
-  /* 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);
+  /* the one for power2 having been inserted more recently should be removed last */
+  /* 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(now, 10);
   BOOST_CHECK_EQUAL(MRC.size(), 1U);
 
   /* the remaining entry should be power2, but to get it
      we need to go back in the past a bit */
-  BOOST_CHECK_EQUAL(MRC.get(ttd - 1, power2, QType(dr2.d_type), false, &retrieved, who, 0, boost::none, nullptr), 1);
+  BOOST_CHECK_EQUAL(MRC.get(ttd - 1, power2, QType(dr2.d_type), MemRecursorCache::None, &retrieved, who, boost::none, nullptr), 1);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(retrieved.at(0))->getCA().toString(), dr2Content.toString());
   /* check that power1 is gone */
-  BOOST_CHECK_EQUAL(MRC.get(ttd - 1, power1, QType(dr1.d_type), false, &retrieved, who, 0, boost::none, nullptr), -1);
+  BOOST_CHECK_EQUAL(MRC.get(ttd - 1, power1, QType(dr1.d_type), MemRecursorCache::None, &retrieved, who, boost::none, nullptr), -1);
 
   /* clear everything up */
   MRC.doWipeCache(DNSName("."), true);
@@ -500,29 +555,32 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingExpiredEntries)
   BOOST_CHECK_EQUAL(MRC.size(), 2U);
 
   /* trigger a miss (expired) for power2 */
-  BOOST_CHECK_EQUAL(MRC.get(now, power2, QType(dr2.d_type), false, &retrieved, who, 0, boost::none, nullptr), -1);
+  BOOST_CHECK_EQUAL(MRC.get(now, power2, QType(dr2.d_type), MemRecursorCache::None, &retrieved, who, boost::none, nullptr), -1);
 
   /* power2 should have been moved to the front of the expunge
      queue, and should this time be removed first */
-  /* we ask that only entry remains in the cache */
-  MRC.doPrune(1);
+  /* 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(now, 10);
   BOOST_CHECK_EQUAL(MRC.size(), 1U);
 
   /* the remaining entry should be power1, but to get it
      we need to go back in the past a bit */
-  BOOST_CHECK_EQUAL(MRC.get(ttd - 1, power1, QType(dr1.d_type), false, &retrieved, who, 0, boost::none, nullptr), 1);
+  BOOST_CHECK_EQUAL(MRC.get(ttd - 1, power1, QType(dr1.d_type), MemRecursorCache::None, &retrieved, who, boost::none, nullptr), 1);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
   /* check that power2 is gone */
-  BOOST_CHECK_EQUAL(MRC.get(ttd - 1, power2, QType(dr2.d_type), false, &retrieved, who, 0, boost::none, nullptr), -1);
+  BOOST_CHECK_EQUAL(MRC.get(ttd - 1, power2, QType(dr2.d_type), MemRecursorCache::None, &retrieved, who, boost::none, nullptr), -1);
 }
 
 BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
 {
+  MemRecursorCache::resetStaticsForTests();
   MemRecursorCache MRC(1);
 
   std::vector<DNSRecord> records;
-  std::vector<std::shared_ptr<RRSIGRecordContent>> signatures;
+  std::vector<std::shared_ptr<const RRSIGRecordContent>> signatures;
   std::vector<std::shared_ptr<DNSRecord>> authRecs;
   const DNSName authZone(".");
   BOOST_CHECK_EQUAL(MRC.size(), 0U);
@@ -539,7 +597,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
   dr1.d_name = power1;
   dr1.d_type = QType::AAAA;
   dr1.d_class = QClass::IN;
-  dr1.d_content = std::make_shared<AAAARecordContent>(dr1Content);
+  dr1.setContent(std::make_shared<AAAARecordContent>(dr1Content));
   dr1.d_ttl = static_cast<uint32_t>(ttd);
   dr1.d_place = DNSResourceRecord::ANSWER;
 
@@ -549,7 +607,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
   dr2.d_name = power2;
   dr2.d_type = QType::AAAA;
   dr2.d_class = QClass::IN;
-  dr2.d_content = std::make_shared<AAAARecordContent>(dr2Content);
+  dr2.setContent(std::make_shared<AAAARecordContent>(dr2Content));
   dr2.d_ttl = static_cast<uint32_t>(ttd);
   dr2.d_place = DNSResourceRecord::ANSWER;
 
@@ -565,15 +623,15 @@ 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 */
-  BOOST_CHECK_EQUAL(MRC.get(now, power2, QType(dr2.d_type), false, &retrieved, who, 0, boost::none, nullptr), ttd - now);
+  BOOST_CHECK_EQUAL(MRC.get(now, power2, QType(dr2.d_type), MemRecursorCache::None, &retrieved, who, boost::none, nullptr), ttd - now);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(retrieved.at(0))->getCA().toString(), dr2Content.toString());
   /* check that power1 is gone */
-  BOOST_CHECK_EQUAL(MRC.get(now, power1, QType(dr1.d_type), false, &retrieved, who, 0, boost::none, nullptr), -1);
+  BOOST_CHECK_EQUAL(MRC.get(now, power1, QType(dr1.d_type), MemRecursorCache::None, &retrieved, who, boost::none, nullptr), -1);
 
   /* clear everything up */
   MRC.doWipeCache(DNSName("."), true);
@@ -599,15 +657,15 @@ 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 */
-  BOOST_CHECK_EQUAL(MRC.get(now, power1, QType(dr1.d_type), false, &retrieved, who, 0, boost::none, nullptr), ttd - now);
+  BOOST_CHECK_EQUAL(MRC.get(now, power1, QType(dr1.d_type), MemRecursorCache::None, &retrieved, who, boost::none, nullptr), ttd - now);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
   /* check that power2 is gone */
-  BOOST_CHECK_EQUAL(MRC.get(now, power2, QType(dr2.d_type), false, &retrieved, who, 0, boost::none, nullptr), -1);
+  BOOST_CHECK_EQUAL(MRC.get(now, power2, QType(dr2.d_type), MemRecursorCache::None, &retrieved, who, boost::none, nullptr), -1);
 
   /* clear everything up */
   MRC.doWipeCache(DNSName("."), true);
@@ -624,24 +682,24 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
   BOOST_CHECK_EQUAL(MRC.size(), 2U);
 
   /* get a hit for power1 */
-  BOOST_CHECK_EQUAL(MRC.get(now, power1, QType(dr1.d_type), false, &retrieved, who, 0, boost::none, nullptr), ttd - now);
+  BOOST_CHECK_EQUAL(MRC.get(now, power1, QType(dr1.d_type), MemRecursorCache::None, &retrieved, who, boost::none, nullptr), ttd - now);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
 
   /* 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 */
-  BOOST_CHECK_EQUAL(MRC.get(now, power1, QType(dr1.d_type), false, &retrieved, who, 0, boost::none, nullptr), ttd - now);
+  BOOST_CHECK_EQUAL(MRC.get(now, power1, QType(dr1.d_type), MemRecursorCache::None, &retrieved, who, boost::none, nullptr), ttd - now);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
   /* check that power2 is gone */
-  BOOST_CHECK_EQUAL(MRC.get(now, power2, QType(dr2.d_type), false, &retrieved, who, 0, boost::none, nullptr), -1);
+  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 */
@@ -653,7 +711,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
     r1.d_name = power1;
     r1.d_type = QType::A;
     r1.d_class = QClass::IN;
-    r1.d_content = std::make_shared<ARecordContent>(r1Content);
+    r1.setContent(std::make_shared<ARecordContent>(r1Content));
     r1.d_ttl = static_cast<uint32_t>(ttd);
     r1.d_place = DNSResourceRecord::ANSWER;
     records.push_back(r1);
@@ -666,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);
 
@@ -675,7 +733,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
   for (size_t i = 0; i <= 255; i++) {
     ComboAddress whoLoop("192.0.2." + std::to_string(i));
 
-    auto ret = MRC.get(now, power1, QType(QType::A), false, &retrieved, whoLoop, 0);
+    auto ret = MRC.get(now, power1, QType(QType::A), MemRecursorCache::None, &retrieved, whoLoop);
     if (ret > 0) {
       BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
       BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), whoLoop.toString());
@@ -690,20 +748,21 @@ 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.");
   const DNSName authZone(".");
   std::vector<DNSRecord> records;
   std::vector<std::shared_ptr<DNSRecord>> authRecords;
-  std::vector<std::shared_ptr<RRSIGRecordContent>> signatures;
+  std::vector<std::shared_ptr<const RRSIGRecordContent>> signatures;
   time_t now = time(nullptr);
   std::vector<DNSRecord> retrieved;
   ComboAddress who("192.0.2.1");
@@ -715,7 +774,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
   dr1.d_name = power;
   dr1.d_type = QType::A;
   dr1.d_class = QClass::IN;
-  dr1.d_content = std::make_shared<ARecordContent>(dr1Content);
+  dr1.setContent(std::make_shared<ARecordContent>(dr1Content));
   dr1.d_ttl = static_cast<uint32_t>(ttd);
   dr1.d_place = DNSResourceRecord::ANSWER;
 
@@ -724,7 +783,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
   dr2.d_name = power;
   dr2.d_type = QType::A;
   dr2.d_class = QClass::IN;
-  dr2.d_content = std::make_shared<ARecordContent>(dr2Content);
+  dr2.setContent(std::make_shared<ARecordContent>(dr2Content));
   dr2.d_ttl = static_cast<uint32_t>(now + 5);
   dr2.d_place = DNSResourceRecord::ANSWER;
 
@@ -732,7 +791,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0U);
 
   /* no entry in the ECS index, no non-specific entry either */
-  BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, who), -1);
+  BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, who), -1);
 
   /* insert a non-specific entry */
   records.push_back(dr1);
@@ -742,12 +801,12 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0U);
 
   /* retrieve the non-specific entry */
-  BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, who), ttd - now);
+  BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, who), ttd - now);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   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);
 
@@ -758,17 +817,17 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1U);
 
   /* there is an ECS index for that entry but no match, and no non-specific entry */
-  BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.4")), -1);
+  BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("192.0.2.4")), -1);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 0U);
 
   /* there is an ECS index for that entry and we get a match */
-  BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.1")), ttd - now);
+  BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("192.0.2.1")), ttd - now);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
 
   /* there is an ECS index for that entry and we get a match,
      but it has expired. No other match, no non-specific entry */
-  BOOST_CHECK_EQUAL(MRC.get(now + ttl + 1, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.1")), -1);
+  BOOST_CHECK_EQUAL(MRC.get(now + ttl + 1, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("192.0.2.1")), -1);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 0U);
 
   /* The ECS index should now be empty, but the cache entry has not been expunged yet */
@@ -780,12 +839,12 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
   MRC.replace(now + ttl + 1, power, QType(QType::A), records, signatures, authRecords, true, authZone, Netmask("192.0.2.0/31"));
   BOOST_CHECK_EQUAL(MRC.size(), 1U);
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1U);
-  BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.1")), ttd - now);
+  BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("192.0.2.1")), ttd - now);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
 
   /* wipe everything */
-  MRC.doPrune(0);
+  MRC.doPrune(now, 0);
   BOOST_CHECK_EQUAL(MRC.size(), 0U);
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0U);
 
@@ -804,14 +863,14 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1U);
 
   /* check that we get the most specific one as long as it's still valid */
-  BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.1")), 5);
+  BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("192.0.2.1")), 5);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr2Content.toString());
 
   /* there is an ECS index for that entry and we get a match,
      but it has expired.
      The second ECS is a match too, and is valid. */
-  BOOST_CHECK_EQUAL(MRC.get(now + 5 + 1, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.1")), (ttd - (now + 5 + 1)));
+  BOOST_CHECK_EQUAL(MRC.get(now + 5 + 1, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("192.0.2.1")), (ttd - (now + 5 + 1)));
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
 
@@ -820,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);
 
@@ -841,7 +900,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1U);
 
   /* there is an ECS index for that entry and it doesn't match. No other match, but we have a non-specific entry */
-  BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.255")), ttd - now);
+  BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("192.0.2.255")), ttd - now);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
 
@@ -849,20 +908,21 @@ 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.");
   const DNSName authZone(".");
   std::vector<DNSRecord> records;
   std::vector<std::shared_ptr<DNSRecord>> authRecords;
-  std::vector<std::shared_ptr<RRSIGRecordContent>> signatures;
+  std::vector<std::shared_ptr<const RRSIGRecordContent>> signatures;
   time_t now = time(nullptr);
   std::vector<DNSRecord> retrieved;
   ComboAddress who("192.0.2.1");
@@ -874,7 +934,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_Wipe)
   dr1.d_name = power;
   dr1.d_type = QType::A;
   dr1.d_class = QClass::IN;
-  dr1.d_content = std::make_shared<ARecordContent>(dr1Content);
+  dr1.setContent(std::make_shared<ARecordContent>(dr1Content));
   dr1.d_ttl = static_cast<uint32_t>(ttd);
   dr1.d_place = DNSResourceRecord::ANSWER;
 
@@ -882,7 +942,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_Wipe)
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0U);
 
   /* no entry in the ECS index, no non-specific entry either */
-  BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, who), -1);
+  BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, who), -1);
 
   /* insert a specific entry */
   records.push_back(dr1);
@@ -946,11 +1006,12 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_Wipe)
 
 BOOST_AUTO_TEST_CASE(test_RecursorCacheTagged)
 {
+  MemRecursorCache::resetStaticsForTests();
   MemRecursorCache MRC;
 
   const DNSName authZone(".");
   std::vector<std::shared_ptr<DNSRecord>> authRecords;
-  std::vector<std::shared_ptr<RRSIGRecordContent>> signatures;
+  std::vector<std::shared_ptr<const RRSIGRecordContent>> signatures;
   time_t now = time(nullptr);
   time_t ttd = now + 30;
 
@@ -960,7 +1021,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheTagged)
   dr0.d_name = power;
   dr0.d_type = QType::A;
   dr0.d_class = QClass::IN;
-  dr0.d_content = std::make_shared<ARecordContent>(dr0Content);
+  dr0.setContent(std::make_shared<ARecordContent>(dr0Content));
   dr0.d_ttl = static_cast<uint32_t>(ttd);
   dr0.d_place = DNSResourceRecord::ANSWER;
   std::vector<DNSRecord> rset0;
@@ -971,7 +1032,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheTagged)
   dr0tagged.d_name = power;
   dr0tagged.d_type = QType::A;
   dr0tagged.d_class = QClass::IN;
-  dr0tagged.d_content = std::make_shared<ARecordContent>(dr0taggedContent);
+  dr0tagged.setContent(std::make_shared<ARecordContent>(dr0taggedContent));
   dr0tagged.d_ttl = static_cast<uint32_t>(ttd);
   dr0tagged.d_place = DNSResourceRecord::ANSWER;
   std::vector<DNSRecord> rset0tagged;
@@ -1007,7 +1068,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheTagged)
     int64_t expected = counter;
 
     for (counter = 0; counter < 110; counter++) {
-      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(counter)), QType(QType::A), false, &retrieved, nobody, 0, boost::none) > 0) {
+      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(counter)), QType(QType::A), MemRecursorCache::None, &retrieved, nobody, boost::none) > 0) {
         matches++;
         BOOST_CHECK_EQUAL(retrieved.size(), rset0.size());
         BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr0Content.toString());
@@ -1017,7 +1078,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheTagged)
 
     matches = 0;
     for (counter = 0; counter < 110; ++counter) {
-      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(counter)), QType(QType::A), false, &retrieved, who, 0, string("mytagB")) > 0) {
+      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(counter)), QType(QType::A), MemRecursorCache::None, &retrieved, who, string("mytagB")) > 0) {
         matches++;
         BOOST_CHECK_EQUAL(retrieved.size(), rset0.size());
         BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr0Content.toString());
@@ -1027,7 +1088,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheTagged)
 
     matches = 0;
     for (counter = 0; counter < 110; counter++) {
-      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(counter)), QType(QType::A), false, &retrieved, who, 0, string("mytagX")) > 0) {
+      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(counter)), QType(QType::A), MemRecursorCache::None, &retrieved, who, string("mytagX")) > 0) {
         matches++;
         BOOST_CHECK_EQUAL(retrieved.size(), rset0.size());
         BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr0Content.toString());
@@ -1044,7 +1105,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheTagged)
 
     matches = 0;
     for (counter = 0; counter < 110; counter++) {
-      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(counter)), QType(QType::A), false, &retrieved, nobody, 0, boost::none) > 0) {
+      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(counter)), QType(QType::A), MemRecursorCache::None, &retrieved, nobody, boost::none) > 0) {
         matches++;
         BOOST_CHECK_EQUAL(retrieved.size(), rset0.size());
         BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr0Content.toString());
@@ -1054,7 +1115,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheTagged)
 
     matches = 0;
     for (counter = 0; counter < 110; ++counter) {
-      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(counter)), QType(QType::A), false, &retrieved, nobody, 0, string("mytagA")) > 0) {
+      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(counter)), QType(QType::A), MemRecursorCache::None, &retrieved, nobody, string("mytagA")) > 0) {
         matches++;
         if (counter < 50) {
           BOOST_CHECK_EQUAL(retrieved.size(), rset0tagged.size());
@@ -1070,7 +1131,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheTagged)
 
     matches = 0;
     for (counter = 0; counter < 110; counter++) {
-      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(counter)), QType(QType::A), false, &retrieved, nobody, 0, string("mytagX")) > 0) {
+      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(counter)), QType(QType::A), MemRecursorCache::None, &retrieved, nobody, string("mytagX")) > 0) {
         matches++;
         BOOST_CHECK_EQUAL(retrieved.size(), rset0.size());
         BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr0Content.toString());
@@ -1086,7 +1147,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheTagged)
     dr1.d_name = power;
     dr1.d_type = QType::A;
     dr1.d_class = QClass::IN;
-    dr1.d_content = std::make_shared<ARecordContent>(dr1Content);
+    dr1.setContent(std::make_shared<ARecordContent>(dr1Content));
     dr1.d_ttl = static_cast<uint32_t>(ttd);
     dr1.d_place = DNSResourceRecord::ANSWER;
     std::vector<DNSRecord> rset1;
@@ -1097,7 +1158,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheTagged)
     dr2.d_name = power;
     dr2.d_type = QType::A;
     dr2.d_class = QClass::IN;
-    dr2.d_content = std::make_shared<ARecordContent>(dr2Content);
+    dr2.setContent(std::make_shared<ARecordContent>(dr2Content));
     dr2.d_ttl = static_cast<uint32_t>(ttd);
     dr2.d_place = DNSResourceRecord::ANSWER;
     std::vector<DNSRecord> rset2;
@@ -1108,7 +1169,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheTagged)
     dr3.d_name = power;
     dr3.d_type = QType::A;
     dr3.d_class = QClass::IN;
-    dr3.d_content = std::make_shared<ARecordContent>(dr3Content);
+    dr3.setContent(std::make_shared<ARecordContent>(dr3Content));
     dr3.d_ttl = static_cast<uint32_t>(ttd);
     dr3.d_place = DNSResourceRecord::ANSWER;
     std::vector<DNSRecord> rset3;
@@ -1119,12 +1180,12 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheTagged)
     BOOST_CHECK_EQUAL(MRC.size(), 1U);
 
     // tagged specific should be returned for a matching tag
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.2"), 0, string("mytag")), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("192.0.2.2"), string("mytag")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
 
     // tag specific should not be returned for a different tag
-    BOOST_CHECK_LT(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.2"), 0, string("othertag")), 0);
+    BOOST_CHECK_LT(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("192.0.2.2"), string("othertag")), 0);
     BOOST_CHECK_EQUAL(retrieved.size(), 0U);
 
     // insert a new  entry without tag
@@ -1132,16 +1193,16 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheTagged)
     BOOST_CHECK_EQUAL(MRC.size(), 2U);
 
     // tagged specific should be returned for a matching tag
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.2"), 0, string("mytag")), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("192.0.2.2"), string("mytag")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
 
     // if no tag given nothing should be retrieved if address doesn't match
-    BOOST_CHECK_LT(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1"), 0, boost::none), 0);
+    BOOST_CHECK_LT(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("127.0.0.1"), boost::none), 0);
     BOOST_REQUIRE_EQUAL(retrieved.size(), 0U);
 
     // if no tag given and no-non-tagged entries matches nothing should be returned
-    BOOST_CHECK_LT(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.2"), 0, boost::none), 0);
+    BOOST_CHECK_LT(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("192.0.2.2"), boost::none), 0);
     BOOST_REQUIRE_EQUAL(retrieved.size(), 0U);
 
     // Insert untagged entry with no netmask
@@ -1149,21 +1210,21 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheTagged)
     BOOST_CHECK_EQUAL(MRC.size(), 3U);
 
     // Retrieval with no address and no tag should get that one
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress(), 0, boost::none), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress(), boost::none), (ttd - now));
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr3Content.toString());
 
     // If no tag given match non-tagged entry
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.2"), 0, boost::none), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("192.0.2.2"), boost::none), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr3Content.toString());
 
     // If no tag given we should be able to retrieve the netmask specific record
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.3.1"), 0, boost::none), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("192.0.3.1"), boost::none), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr2Content.toString());
 
     // tagged specific should still be returned for a matching tag, address is not used
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.2"), 0, string("mytag")), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), MemRecursorCache::None, &retrieved, ComboAddress("192.0.2.2"), string("mytag")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
 
diff --git a/pdns/recursordist/test-reczones-helpers.cc b/pdns/recursordist/test-reczones-helpers.cc
new file mode 100644 (file)
index 0000000..a010c13
--- /dev/null
@@ -0,0 +1,308 @@
+#ifndef BOOST_TEST_DYN_LINK
+#define BOOST_TEST_DYN_LINK
+#endif
+
+#include <boost/test/unit_test.hpp>
+
+#include <cstdio>
+
+#include "test-syncres_cc.hh"
+#include "reczones-helpers.hh"
+
+BOOST_AUTO_TEST_SUITE(reczones_helpers)
+
+static const std::array<std::string, 10> hostLines = {
+  "192.168.0.1             foo bar\n",
+  "192.168.0.1             dupfoo\n",
+  "192.168.0.2             baz\n",
+  "1.1.1.1                 fancy\n",
+  "2.2.2.2                 more.fancy\n",
+  "2001:db8::567:89ab      foo6 bar6\n",
+  "2001:db8::567:89ab      dupfoo6\n",
+  "127.0.0.1               localhost\n",
+  "::1                     localhost self\n",
+  "2001:db8::567:89ac      some.address.somewhere some some.address\n",
+};
+
+struct Fixture
+{
+  static std::shared_ptr<DNSRecordContent> makeLocalhostRootDRC()
+  {
+    return DNSRecordContent::make(QType::SOA, QClass::IN, "localhost. root 1 604800 86400 2419200 604800");
+  }
+
+  static std::shared_ptr<DNSRecordContent> makeLocalhostDRC()
+  {
+    return DNSRecordContent::make(QType::NS, QClass::IN, "localhost.");
+  }
+
+  static std::shared_ptr<DNSRecordContent> makePtrDRC(const std::string& name)
+  {
+    return DNSRecordContent::make(QType::PTR, QClass::IN, name);
+  }
+
+  static void addDomainMapFixtureEntry(SyncRes::domainmap_t& domainMap,
+                                       const std::string& name,
+                                       const SyncRes::AuthDomain::records_t& records)
+  {
+    domainMap[DNSName{name}] = SyncRes::AuthDomain{
+      .d_records = records,
+      .d_servers = {},
+      .d_name = DNSName{name},
+      .d_rdForward = false,
+    };
+  }
+
+  static void addDomainMapFixtureEntry(SyncRes::domainmap_t& domainMap,
+                                       const std::string& name,
+                                       const QType type,
+                                       const std::string& address)
+  {
+    domainMap[DNSName{name}] = SyncRes::AuthDomain{
+      .d_records = {
+        DNSRecord(name, DNSRecordContent::make(type, QClass::IN, address), type),
+        DNSRecord(name, makeLocalhostDRC(), QType::NS),
+        DNSRecord(name, makeLocalhostRootDRC(), QType::SOA),
+      },
+      .d_servers = {},
+      .d_name = DNSName{name},
+      .d_rdForward = false,
+    };
+  }
+
+  static void populateDomainMapFixture(SyncRes::domainmap_t& domainMap,
+                                       const std::string& searchSuffix = "")
+  {
+    const auto actualSearchSuffix = searchSuffix.empty() ? "" : "." + searchSuffix;
+
+    addDomainMapFixtureEntry(domainMap, "foo" + actualSearchSuffix, QType::A, "192.168.0.1");
+    addDomainMapFixtureEntry(domainMap, "bar" + actualSearchSuffix, QType::A, "192.168.0.1");
+    addDomainMapFixtureEntry(domainMap, "dupfoo" + actualSearchSuffix, QType::A, "192.168.0.1");
+    addDomainMapFixtureEntry(
+      domainMap,
+      "1.0.168.192.in-addr.arpa",
+      {
+        DNSRecord("1.0.168.192.in-addr.arpa", makeLocalhostDRC(), QType::NS),
+        DNSRecord("1.0.168.192.in-addr.arpa", makeLocalhostRootDRC(), QType::SOA),
+        DNSRecord("1.0.168.192.in-addr.arpa", makePtrDRC("foo" + actualSearchSuffix), QType::PTR),
+      });
+    addDomainMapFixtureEntry(domainMap, "baz" + actualSearchSuffix, QType::A, "192.168.0.2");
+    addDomainMapFixtureEntry(
+      domainMap,
+      "2.0.168.192.in-addr.arpa",
+      {
+        DNSRecord("2.0.168.192.in-addr.arpa", makeLocalhostDRC(), QType::NS),
+        DNSRecord("2.0.168.192.in-addr.arpa", makeLocalhostRootDRC(), QType::SOA),
+        DNSRecord("2.0.168.192.in-addr.arpa", makePtrDRC("baz" + actualSearchSuffix), QType::PTR),
+      });
+    addDomainMapFixtureEntry(domainMap, "fancy" + actualSearchSuffix, QType::A, "1.1.1.1");
+    addDomainMapFixtureEntry(
+      domainMap,
+      "1.1.1.1.in-addr.arpa",
+      {
+        DNSRecord("1.1.1.1.in-addr.arpa", makeLocalhostDRC(), QType::NS),
+        DNSRecord("1.1.1.1.in-addr.arpa", makeLocalhostRootDRC(), QType::SOA),
+        DNSRecord("1.1.1.1.in-addr.arpa", makePtrDRC("fancy" + actualSearchSuffix), QType::PTR),
+      });
+    addDomainMapFixtureEntry(domainMap, "more.fancy", QType::A, "2.2.2.2");
+    addDomainMapFixtureEntry(
+      domainMap,
+      "2.2.2.2.in-addr.arpa",
+      {
+        DNSRecord("2.2.2.2.in-addr.arpa", makeLocalhostDRC(), QType::NS),
+        DNSRecord("2.2.2.2.in-addr.arpa", makeLocalhostRootDRC(), QType::SOA),
+        DNSRecord("2.2.2.2.in-addr.arpa", makePtrDRC("more.fancy."), QType::PTR),
+      });
+
+    addDomainMapFixtureEntry(domainMap, "foo6" + actualSearchSuffix, QType::AAAA, "2001:db8::567:89ab");
+    addDomainMapFixtureEntry(domainMap, "bar6" + actualSearchSuffix, QType::AAAA, "2001:db8::567:89ab");
+    addDomainMapFixtureEntry(domainMap, "dupfoo6" + actualSearchSuffix, QType::AAAA, "2001:db8::567:89ab");
+    addDomainMapFixtureEntry(
+      domainMap,
+      "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa",
+      {
+        DNSRecord("b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", makeLocalhostDRC(), QType::NS),
+        DNSRecord("b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", makeLocalhostRootDRC(), QType::SOA),
+        DNSRecord("b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", makePtrDRC("foo6" + actualSearchSuffix), QType::PTR),
+      });
+
+    addDomainMapFixtureEntry(
+      domainMap,
+      "localhost" + actualSearchSuffix,
+      {DNSRecord("localhost" + actualSearchSuffix, makeLocalhostDRC(), QType::NS),
+       DNSRecord("localhost" + actualSearchSuffix, makeLocalhostRootDRC(), QType::SOA),
+       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,
+      "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",
+      {
+        DNSRecord("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", makeLocalhostDRC(), QType::NS),
+        DNSRecord("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", makeLocalhostRootDRC(), QType::SOA),
+        DNSRecord("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", makePtrDRC("localhost" + actualSearchSuffix), QType::PTR),
+      });
+    addDomainMapFixtureEntry(
+      domainMap,
+      "1.0.0.127.in-addr.arpa",
+      {
+        DNSRecord("1.0.0.127.in-addr.arpa", makeLocalhostDRC(), QType::NS),
+        DNSRecord("1.0.0.127.in-addr.arpa", makeLocalhostRootDRC(), QType::SOA),
+        DNSRecord("1.0.0.127.in-addr.arpa", makePtrDRC("localhost" + actualSearchSuffix), QType::PTR),
+      });
+
+    addDomainMapFixtureEntry(domainMap, "some" + actualSearchSuffix, QType::AAAA, "2001:db8::567:89ac");
+    addDomainMapFixtureEntry(domainMap, "some.address.somewhere", QType::AAAA, "2001:db8::567:89ac");
+    addDomainMapFixtureEntry(domainMap, "some.address", QType::AAAA, "2001:db8::567:89ac");
+    addDomainMapFixtureEntry(
+      domainMap,
+      "c.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa",
+      {
+        DNSRecord("c.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", makeLocalhostDRC(), QType::NS),
+        DNSRecord("c.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", makeLocalhostRootDRC(), QType::SOA),
+        DNSRecord("c.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", makePtrDRC("some.address.somewhere."), QType::PTR),
+      });
+  }
+
+  Fixture()
+  {
+    populateDomainMapFixture(domainMapFixture);
+    populateDomainMapFixture(domainMapFixtureWithSearchSuffix, "search.suffix");
+  }
+
+  using DomainMapEntry = std::pair<DNSName, SyncRes::AuthDomain>;
+
+  static std::vector<DomainMapEntry> sortDomainMap(const SyncRes::domainmap_t& domainMap)
+  {
+    std::vector<DomainMapEntry> sorted{};
+    sorted.reserve(domainMap.size());
+    for (const auto& pair : domainMap) {
+      sorted.emplace_back(pair.first, pair.second);
+    }
+    std::stable_sort(std::begin(sorted), std::end(sorted), [](const DomainMapEntry& a, const DomainMapEntry& b) {
+      return a.first < b.first && a.second.d_name < b.second.d_name;
+    });
+    return sorted;
+  }
+
+  static std::string printDomainMap(const std::vector<DomainMapEntry>& domainMap)
+  {
+    std::stringstream s{};
+    for (const auto& entry : domainMap) {
+      s << "Entry `" << entry.first << "` {" << std::endl;
+      s << entry.second.print("  ");
+      s << "}" << std::endl;
+    }
+    return s.str();
+  }
+
+  std::vector<DomainMapEntry> getDomainMapFixture() const
+  {
+    return sortDomainMap(domainMapFixture);
+  }
+
+  std::vector<DomainMapEntry> getDomainMapFixtureWithSearchSuffix() const
+  {
+    return sortDomainMap(domainMapFixtureWithSearchSuffix);
+  }
+
+private:
+  SyncRes::domainmap_t domainMapFixture{};
+  SyncRes::domainmap_t domainMapFixtureWithSearchSuffix{};
+};
+
+BOOST_FIXTURE_TEST_CASE(test_loading_etc_hosts, Fixture)
+{
+  auto log = g_slog->withName("config");
+
+  auto domainMap = std::make_shared<SyncRes::domainmap_t>();
+  auto domainMapWithSearchSuffix = std::make_shared<SyncRes::domainmap_t>();
+  std::vector<std::string> parts{};
+  for (auto line : hostLines) {
+    BOOST_REQUIRE(parseEtcHostsLine(parts, line));
+    addForwardAndReverseLookupEntries(*domainMap, "", parts, log);
+    addForwardAndReverseLookupEntries(*domainMapWithSearchSuffix, "search.suffix", parts, log);
+  }
+
+  BOOST_TEST_MESSAGE("Actual and expected outputs without search suffixes:");
+
+  auto actual = sortDomainMap(*domainMap);
+  BOOST_TEST_MESSAGE("Actual:");
+  BOOST_TEST_MESSAGE(printDomainMap(actual));
+
+  auto expected = getDomainMapFixture();
+  BOOST_TEST_MESSAGE("Expected:");
+  BOOST_TEST_MESSAGE(printDomainMap(expected));
+
+  BOOST_CHECK_EQUAL(actual.size(), expected.size());
+  for (std::vector<DomainMapEntry>::size_type i = 0; i < actual.size(); i++) {
+    BOOST_CHECK(actual[i].first == expected[i].first);
+    BOOST_CHECK(actual[i].second == expected[i].second);
+  }
+
+  BOOST_TEST_MESSAGE("-----------------------------------------------------");
+
+  BOOST_TEST_MESSAGE("Actual and expected outputs with search suffixes:");
+
+  auto actualSearchSuffix = sortDomainMap(*domainMapWithSearchSuffix);
+  BOOST_TEST_MESSAGE("Actual (with search suffix):");
+  BOOST_TEST_MESSAGE(printDomainMap(actualSearchSuffix));
+
+  auto expectedSearchSuffix = getDomainMapFixtureWithSearchSuffix();
+  BOOST_TEST_MESSAGE("Expected (with search suffix):");
+  BOOST_TEST_MESSAGE(printDomainMap(expectedSearchSuffix));
+
+  BOOST_CHECK_EQUAL(actualSearchSuffix.size(), expectedSearchSuffix.size());
+  for (std::vector<DomainMapEntry>::size_type i = 0; i < actualSearchSuffix.size(); i++) {
+    BOOST_CHECK(actualSearchSuffix[i].first == expectedSearchSuffix[i].first);
+    BOOST_CHECK(actualSearchSuffix[i].second == expectedSearchSuffix[i].second);
+  }
+
+  BOOST_TEST_MESSAGE("-----------------------------------------------------");
+}
+
+const std::string hints = ". 3600 IN NS ns.\n"
+                          ". 3600 IN NS ns1.\n"
+                          "ns. 3600 IN A 192.168.178.16\n"
+                          "ns. 3600 IN A 192.168.178.17\n"
+                          "ns. 3600 IN A 192.168.178.18\n"
+                          "ns. 3600 IN AAAA 1::2\n"
+                          "ns. 3600 IN AAAA 1::3\n"
+                          "ns1. 3600 IN A 192.168.178.18\n";
+
+BOOST_AUTO_TEST_CASE(test_UserHints)
+{
+
+  g_recCache = make_unique<MemRecursorCache>();
+
+  ::arg().set("max-generate-steps") = "0";
+  ::arg().set("max-include-depth") = "0";
+  char temp[] = "/tmp/hintsXXXXXXXXXX";
+  int fd = mkstemp(temp);
+  BOOST_REQUIRE(fd > 0);
+  FILE* fp = fdopen(fd, "w");
+  BOOST_REQUIRE(fp != nullptr);
+  size_t written = fwrite(hints.data(), 1, hints.length(), fp);
+  BOOST_REQUIRE(written == hints.length());
+  BOOST_REQUIRE(fclose(fp) == 0);
+
+  time_t now = time(nullptr);
+  std::vector<DNSRecord> nsvec;
+
+  auto ok = readHintsIntoCache(now, std::string(temp), nsvec);
+  BOOST_CHECK(ok);
+  BOOST_CHECK_EQUAL(nsvec.size(), 2U);
+
+  const MemRecursorCache::Flags flags = 0;
+
+  BOOST_CHECK(g_recCache->get(now, DNSName("ns"), QType::A, flags, &nsvec, ComboAddress()) > 0);
+  BOOST_CHECK_EQUAL(nsvec.size(), 3U);
+
+  BOOST_CHECK(g_recCache->get(now, DNSName("ns"), QType::AAAA, flags, &nsvec, ComboAddress()) > 0);
+  BOOST_CHECK_EQUAL(nsvec.size(), 2U);
+
+  BOOST_CHECK(g_recCache->get(now, DNSName("ns1"), QType::A, flags, &nsvec, ComboAddress()) > 0);
+  BOOST_CHECK_EQUAL(nsvec.size(), 1U);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
index 8d4d70345361fe8a6d431b450ae185c867ea8aea..ba19c66ba230b0f2f5735041a56b7c0355a20f8a 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 = std::unique_ptr<FILE, decltype(&fclose)>(fdopen(fileDesc, "w"), fclose);
+  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..f94dbf6
--- /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 = std::unique_ptr<FILE, decltype(&fclose)>(fdopen(fileDesc, "w"), fclose);
+  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 8f05f922e2382143c0bc817d57c916727c3f0cdf..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,8 +10,8 @@
 #include "root-dnssec.hh"
 #include "rec-taskqueue.hh"
 #include "test-syncres_cc.hh"
+#include "recpacketcache.hh"
 
-RecursorStats g_stats;
 GlobalStateHolder<LuaConfigItems> g_luaconfs;
 GlobalStateHolder<SuffixMatchNode> g_xdnssec;
 GlobalStateHolder<SuffixMatchNode> g_dontThrottleNames;
@@ -29,24 +32,20 @@ ArgvMap& arg()
   return theArg;
 }
 
-void primeRootNSZones(DNSSECMode, unsigned int)
-{
-}
-
 BaseLua4::~BaseLua4()
 {
 }
 
-void BaseLua4::getFeatures(Features&)
+void BaseLua4::getFeatures(Features& /* features */)
 {
 }
 
-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
+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
 {
   return false;
 }
 
-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
+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
 {
   return false;
 }
@@ -63,11 +62,11 @@ void RecursorLua4::postLoad()
 {
 }
 
-void RecursorLua4::getFeatures(Features& features)
+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;
 }
@@ -101,22 +100,22 @@ bool primeHints(time_t now)
     templ[sizeof(templ) - 1] = '\0';
     *templ = c;
     aaaarr.d_name = arr.d_name = DNSName(templ);
-    nsrr.d_content = std::make_shared<NSRecordContent>(DNSName(templ));
-    arr.d_content = std::make_shared<ARecordContent>(ComboAddress(rootIps4[c - 'a']));
+    nsrr.setContent(std::make_shared<NSRecordContent>(DNSName(templ)));
+    arr.setContent(std::make_shared<ARecordContent>(ComboAddress(rootIps4[c - 'a'])));
     vector<DNSRecord> aset;
     aset.push_back(arr);
-    g_recCache->replace(now, DNSName(templ), QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), false, g_rootdnsname);
-    if (rootIps6[c - 'a'] != NULL) {
-      aaaarr.d_content = std::make_shared<AAAARecordContent>(ComboAddress(rootIps6[c - 'a']));
+    g_recCache->replace(now, DNSName(templ), QType(QType::A), aset, vector<std::shared_ptr<const RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), false, g_rootdnsname);
+    if (!rootIps6[c - 'a'].empty()) {
+      aaaarr.setContent(std::make_shared<AAAARecordContent>(ComboAddress(rootIps6[c - 'a'])));
 
       vector<DNSRecord> aaaaset;
       aaaaset.push_back(aaaarr);
-      g_recCache->replace(now, DNSName(templ), QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), false, g_rootdnsname);
+      g_recCache->replace(now, DNSName(templ), QType(QType::AAAA), aaaaset, vector<std::shared_ptr<const RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), false, g_rootdnsname);
     }
 
     nsset.push_back(nsrr);
   }
-  g_recCache->replace(now, g_rootdnsname, QType(QType::NS), nsset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), false, g_rootdnsname); // and stuff in the cache
+  g_recCache->replace(now, g_rootdnsname, QType(QType::NS), nsset, vector<std::shared_ptr<const RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), false, g_rootdnsname); // and stuff in the cache
   return true;
 }
 
@@ -144,6 +143,9 @@ 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>();
   g_negCache = std::make_unique<NegCache>();
 
@@ -181,6 +183,11 @@ void initSR(bool debug)
   SyncRes::s_nonresolvingnsmaxfails = 0;
   SyncRes::s_nonresolvingnsthrottletime = 0;
   SyncRes::s_refresh_ttlperc = 0;
+  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);
@@ -192,6 +199,8 @@ void initSR(bool debug)
   BOOST_CHECK_EQUAL(SyncRes::getFailedServersSize(), 0U);
   SyncRes::clearNonResolvingNS();
   BOOST_CHECK_EQUAL(SyncRes::getNonResolvingNSSize(), 0U);
+  SyncRes::clearSaveParentsNSSets();
+  BOOST_CHECK_EQUAL(SyncRes::getSaveParentsNSSetsSize(), 0U);
 
   SyncRes::clearECSStats();
 
@@ -206,15 +215,17 @@ void initSR(bool debug)
   g_luaconfs.setState(luaconfsCopy);
 
   g_dnssecmode = DNSSECMode::Off;
-  g_dnssecLOG = debug;
   g_maxNSEC3Iterations = 2500;
 
   g_aggressiveNSECCache.reset();
+  AggressiveNSECCache::s_maxNSEC3CommonPrefix = AggressiveNSECCache::s_default_maxNSEC3CommonPrefix;
+
   taskQueueClear();
 
   ::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)
@@ -270,14 +281,14 @@ void addRecordToLW(LWResult* res, const std::string& name, uint16_t type, const
 bool isRootServer(const ComboAddress& ip)
 {
   if (ip.isIPv4()) {
-    for (size_t idx = 0; idx < rootIps4Count; idx++) {
+    for (size_t idx = 0; idx < rootIps4.size(); idx++) {
       if (ip.toString() == rootIps4[idx]) {
         return true;
       }
     }
   }
   else {
-    for (size_t idx = 0; idx < rootIps6Count; idx++) {
+    for (size_t idx = 0; idx < rootIps6.size(); idx++) {
       if (ip.toString() == rootIps6[idx]) {
         return true;
       }
@@ -322,30 +333,48 @@ 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) {
     if (record.d_name == name && record.d_type == type) {
-      recordcontents.insert(record.d_content);
+      recordcontents.insert(record.getContent());
     }
   }
 
   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.d_content = std::make_shared<RRSIGRecordContent>(rrc);
+  rec.setContent(std::make_shared<RRSIGRecordContent>(rrc));
   records.push_back(rec);
 
   return true;
@@ -364,7 +393,7 @@ void addDNSKEY(const testkeysset_t& keys, const DNSName& signer, uint32_t ttl, s
   rec.d_type = QType::DNSKEY;
   rec.d_ttl = ttl;
 
-  rec.d_content = std::make_shared<DNSKEYRecordContent>(it->second.first.getDNSKEY());
+  rec.setContent(std::make_shared<DNSKEYRecordContent>(it->second.first.getDNSKEY()));
   records.push_back(rec);
 }
 
@@ -380,7 +409,7 @@ bool addDS(const DNSName& domain, uint32_t ttl, std::vector<DNSRecord>& records,
   rec.d_type = QType::DS;
   rec.d_place = place;
   rec.d_ttl = ttl;
-  rec.d_content = std::make_shared<DSRecordContent>(it->second.second);
+  rec.setContent(std::make_shared<DSRecordContent>(it->second.second));
 
   records.push_back(rec);
   return true;
@@ -398,7 +427,7 @@ void addNSECRecordToLW(const DNSName& domain, const DNSName& next, const std::se
   rec.d_name = domain;
   rec.d_ttl = ttl;
   rec.d_type = QType::NSEC;
-  rec.d_content = std::make_shared<NSECRecordContent>(std::move(nrc));
+  rec.setContent(std::make_shared<NSECRecordContent>(std::move(nrc)));
   rec.d_place = DNSResourceRecord::AUTHORITY;
 
   records.push_back(rec);
@@ -420,7 +449,7 @@ void addNSEC3RecordToLW(const DNSName& hashedName, const std::string& hashedNext
   rec.d_name = hashedName;
   rec.d_ttl = ttl;
   rec.d_type = QType::NSEC3;
-  rec.d_content = std::make_shared<NSEC3RecordContent>(std::move(nrc));
+  rec.setContent(std::make_shared<NSEC3RecordContent>(std::move(nrc)));
   rec.d_place = DNSResourceRecord::AUTHORITY;
 
   records.push_back(rec);
@@ -461,8 +490,7 @@ void generateKeyMaterial(const DNSName& name, unsigned int algo, uint8_t digest,
   auto dcke = std::shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::make(algo));
   dcke->create((algo <= 10) ? 2048 : dcke->getBits());
   DNSSECPrivateKey dpk;
-  dpk.d_flags = 256;
-  dpk.setKey(dcke);
+  dpk.setKey(dcke, 256);
   DSRecordContent ds = makeDSFromDNSKey(name, dpk.getDNSKEY(), digest);
   keys[name] = std::pair<DNSSECPrivateKey, DSRecordContent>(dpk, ds);
 }
index e71c7381a3a1dfcbe02650e5da1364dd7f2e6b5f..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) {
@@ -239,13 +242,13 @@ BOOST_AUTO_TEST_CASE(test_root_primed_ns_update)
   BOOST_CHECK_EQUAL(queriesCount, 1U);
 
   ret.clear();
-  time_t cached = g_recCache->get(now.tv_sec, aroot, QType::A, false, &ret, ComboAddress());
+  time_t cached = g_recCache->get(now.tv_sec, aroot, QType::A, MemRecursorCache::None, &ret, ComboAddress());
   BOOST_CHECK(cached > 0);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK(getRR<ARecordContent>(ret[0])->getCA() == ComboAddress(newA));
 
   ret.clear();
-  cached = g_recCache->get(now.tv_sec, aroot, QType::AAAA, false, &ret, ComboAddress());
+  cached = g_recCache->get(now.tv_sec, aroot, QType::AAAA, MemRecursorCache::None, &ret, ComboAddress());
   BOOST_CHECK(cached > 0);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK(getRR<AAAARecordContent>(ret[0])->getCA() == ComboAddress(newAAAA));
@@ -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);
@@ -348,7 +351,7 @@ BOOST_AUTO_TEST_CASE(test_edns_formerr_but_edns_enabled)
   BOOST_CHECK_EQUAL(ret.size(), 0U);
   BOOST_CHECK_EQUAL(queriesWithEDNS, 26U);
   BOOST_CHECK_EQUAL(queriesWithoutEDNS, 0U);
-  BOOST_CHECK_EQUAL(SyncRes::getEDNSStatusesSize(), 26U);
+  BOOST_CHECK_EQUAL(SyncRes::getEDNSStatusesSize(), 0U);
   BOOST_CHECK_EQUAL(usedServers.size(), 26U);
   for (const auto& server : usedServers) {
     BOOST_CHECK_EQUAL(SyncRes::getEDNSStatus(server), SyncRes::EDNSStatus::EDNSOK);
@@ -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);
@@ -1571,7 +1610,7 @@ BOOST_AUTO_TEST_CASE(test_cname_loop)
 
   // Again to check cache
   try {
-    res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+    sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
     BOOST_CHECK(false);
   }
   catch (const ImmediateServFailException& ex) {
@@ -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;
@@ -1639,7 +1678,7 @@ BOOST_AUTO_TEST_CASE(test_cname_long_loop)
 
   // And again to check cache
   try {
-    res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret);
+    sr->beginResolve(target1, QType(QType::A), QClass::IN, ret);
     BOOST_CHECK(false);
   }
   catch (const ImmediateServFailException& ex) {
@@ -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");
@@ -2362,4 +2486,95 @@ BOOST_AUTO_TEST_CASE(test_dname_processing_no_CNAME)
 - check preoutquery
 
 */
+
+BOOST_AUTO_TEST_CASE(test_glued_referral_child_ns_set_wrong)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  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(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;
+    }
+    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);
+      addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 172800);
+      addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 172800);
+      addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 172800);
+      addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
+      return LWResult::Result::Success;
+    }
+    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;
+      }
+      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);
+        addRecordToLW(res, "pdns-public-nsX1.powerdns.com.", QType::A, "192.0.2.11", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-nsX1.powerdns.com.", QType::AAAA, "2001:DB8::11", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-nsX2.powerdns.com.", QType::A, "192.0.2.12", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-nsX2.powerdns.com.", QType::AAAA, "2001:DB8::12", DNSResourceRecord::ADDITIONAL, 172800);
+        return LWResult::Result::Success;
+      }
+      return LWResult::Result::Timeout;
+    }
+    return LWResult::Result::Timeout;
+  });
+
+  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(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+
+  // Now resolve NS to get auth NS set in cache and save the parent NS set
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+  BOOST_CHECK(ret[0].d_type == QType::NS);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(SyncRes::getSaveParentsNSSetsSize(), 1U);
+
+  g_recCache->doWipeCache(target, false, QType::A);
+  SyncRes::s_save_parent_ns_set = false;
+
+  // Try to resolve now via the broken child NS set... should not work
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::ServFail);
+  BOOST_REQUIRE_EQUAL(ret.size(), 0U);
+
+  SyncRes::s_save_parent_ns_set = true;
+
+  // Try to resolve now via the broken child... should work now via fallback to parent NS set
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
index 2397952941152da6fbe24020201bc1e29b3237bc..fe0738a7392c9260c48a0d4dfa67eb9c383043da 100644 (file)
@@ -1,7 +1,12 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #include <boost/test/unit_test.hpp>
 
 #include "test-syncres_cc.hh"
+#include "taskqueue.hh"
+#include "rec-taskqueue.hh"
 
 BOOST_AUTO_TEST_SUITE(syncres_cc10)
 BOOST_AUTO_TEST_CASE(test_outgoing_v4_only)
@@ -15,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);
@@ -29,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.")) {
@@ -37,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.")) {
@@ -68,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.")) {
@@ -79,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");
@@ -107,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);
@@ -117,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");
@@ -158,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) {
@@ -172,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);
@@ -181,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);
@@ -197,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);
@@ -208,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);
@@ -289,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) {
@@ -304,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);
@@ -320,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);
@@ -331,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;
@@ -404,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) {
@@ -419,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);
@@ -441,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);
@@ -452,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;
@@ -550,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) {
@@ -565,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);
@@ -587,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);
@@ -596,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);
@@ -607,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;
@@ -680,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) {
@@ -695,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);
@@ -717,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);
@@ -726,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);
@@ -737,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;
@@ -810,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) {
@@ -825,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);
@@ -847,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);
@@ -858,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;
@@ -926,16 +919,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_loop)
   auto dcke = DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256);
   dcke->create(dcke->getBits());
   DNSSECPrivateKey key;
-  key.d_flags = 257;
-  key.setKey(std::move(dcke));
+  key.setKey(std::move(dcke), 257);
   DSRecordContent drc = makeDSFromDNSKey(DNSName("powerdns.com."), key.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
 
   testkeysset_t wrongKeys;
   auto wrongDcke = DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256);
   wrongDcke->create(wrongDcke->getBits());
   DNSSECPrivateKey wrongKey;
-  wrongKey.d_flags = 256;
-  wrongKey.setKey(std::move(wrongDcke));
+  wrongKey.setKey(std::move(wrongDcke), 256);
   DSRecordContent uselessdrc = makeDSFromDNSKey(DNSName("powerdns.com."), wrongKey.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
 
   wrongKeys[DNSName("powerdns.com.")] = std::pair<DNSSECPrivateKey, DSRecordContent>(wrongKey, uselessdrc);
@@ -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);
@@ -1059,4 +1048,1086 @@ BOOST_AUTO_TEST_CASE(test_PacketIDCompare)
   BOOST_CHECK(!br2);
 }
 
+BOOST_AUTO_TEST_CASE(test_servestale)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr);
+  MemRecursorCache::s_maxServedStaleExtensions = 1440;
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  std::set<ComboAddress> downServers;
+  size_t downCount = 0;
+  size_t lookupCount = 0;
+
+  const int theTTL = 5;
+
+  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(address) != downServers.end()) {
+      downCount++;
+      return LWResult::Result::Timeout;
+    }
+
+    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;
+    }
+    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);
+      addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 5);
+      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, 5);
+      addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, theTTL);
+      return LWResult::Result::Success;
+    }
+    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;
+    }
+    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(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(downCount, 0U);
+  BOOST_CHECK_EQUAL(lookupCount, 1U);
+
+  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});
+
+  BOOST_REQUIRE_EQUAL(getTaskSize(), 0U);
+
+  // 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(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(downCount, 4U);
+  BOOST_CHECK_EQUAL(lookupCount, 1U);
+
+  BOOST_REQUIRE_EQUAL(getTaskSize(), 1U);
+  auto task = taskQueuePop();
+  BOOST_CHECK(task.d_qname == target);
+  BOOST_CHECK_EQUAL(task.d_qtype, QType::A);
+
+  // 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(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(downCount, 4U);
+  BOOST_CHECK_EQUAL(lookupCount, 1U);
+
+  // 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(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(downCount, 4U);
+  BOOST_CHECK_EQUAL(lookupCount, 1U);
+
+  BOOST_REQUIRE_EQUAL(getTaskSize(), 1U);
+  task = taskQueuePop();
+  BOOST_CHECK(task.d_qname == target);
+  BOOST_CHECK_EQUAL(task.d_qtype, QType::A);
+
+  // Now simulate a succeeding task execution
+  sr->setNow(timeval{now + 3 * (theTTL + 1), 0});
+  downServers.clear();
+  sr->setRefreshAlmostExpired(true);
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(downCount, 4U);
+  BOOST_CHECK_EQUAL(lookupCount, 2U);
+
+  // And again, result should come from cache
+  sr->setRefreshAlmostExpired(false);
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(downCount, 4U);
+  BOOST_CHECK_EQUAL(lookupCount, 2U);
+}
+
+BOOST_AUTO_TEST_CASE(test_servestale_neg)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr);
+  MemRecursorCache::s_maxServedStaleExtensions = 1440;
+  NegCache::s_maxServedStaleExtensions = 1440;
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  std::set<ComboAddress> downServers;
+  size_t downCount = 0;
+  size_t lookupCount = 0;
+
+  const int theTTL = 5;
+
+  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(address) != downServers.end()) {
+      downCount++;
+      return LWResult::Result::Timeout;
+    }
+
+    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;
+    }
+    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);
+      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 (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++;
+      return LWResult::Result::Success;
+    }
+    else {
+      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(), 1U);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK(ret[0].d_type == QType::SOA);
+  BOOST_CHECK_EQUAL(downCount, 0U);
+  BOOST_CHECK_EQUAL(lookupCount, 1U);
+
+  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"));
+
+  const int negTTL = 60;
+
+  sr->setNow(timeval{now + negTTL + 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(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::SOA);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(downCount, 4U);
+  BOOST_CHECK_EQUAL(lookupCount, 1U);
+
+  // 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(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::SOA);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(downCount, 4U);
+  BOOST_CHECK_EQUAL(lookupCount, 1U);
+
+  // 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 * (negTTL + 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(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::SOA);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(downCount, 4U);
+  BOOST_CHECK_EQUAL(lookupCount, 1U);
+
+  BOOST_REQUIRE_EQUAL(getTaskSize(), 1U);
+  auto task = taskQueuePop();
+  BOOST_CHECK(task.d_qname == target);
+  BOOST_CHECK_EQUAL(task.d_qtype, QType::A);
+
+  // Now simulate a succeeding task execution
+  sr->setNow(timeval{now + 3 * (negTTL + 1), 0});
+  downServers.clear();
+  sr->setRefreshAlmostExpired(true);
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), 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, target);
+  BOOST_CHECK_EQUAL(downCount, 4U);
+  BOOST_CHECK_EQUAL(lookupCount, 2U);
+
+  // And again, result should come from cache
+  sr->setRefreshAlmostExpired(false);
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), 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, target);
+  BOOST_CHECK_EQUAL(downCount, 4U);
+  BOOST_CHECK_EQUAL(lookupCount, 2U);
+}
+
+BOOST_AUTO_TEST_CASE(test_servestale_neg_to_available)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr);
+  MemRecursorCache::s_maxServedStaleExtensions = 1440;
+  NegCache::s_maxServedStaleExtensions = 1440;
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  std::set<ComboAddress> downServers;
+  size_t downCount = 0;
+  size_t lookupCount = 0;
+  bool negLookup = true;
+
+  const int theTTL = 5;
+  const int negTTL = 60;
+
+  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(address) != downServers.end()) {
+      downCount++;
+      return LWResult::Result::Timeout;
+    }
+
+    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;
+    }
+    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);
+      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 (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;
+      }
+      {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, target, QType::A, "192.0.2.4", DNSResourceRecord::ANSWER, theTTL);
+        lookupCount++;
+        return LWResult::Result::Success;
+      }
+    }
+    else {
+      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(), 1U);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK(ret[0].d_type == QType::SOA);
+  BOOST_CHECK_EQUAL(downCount, 0U);
+  BOOST_CHECK_EQUAL(lookupCount, 1U);
+
+  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 + negTTL + 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(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::SOA);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(downCount, 4U);
+  BOOST_CHECK_EQUAL(lookupCount, 1U);
+
+  // 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(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::SOA);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(downCount, 4U);
+  BOOST_CHECK_EQUAL(lookupCount, 1U);
+
+  // 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 * (negTTL + 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(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::SOA);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(downCount, 4U);
+  BOOST_CHECK_EQUAL(lookupCount, 1U);
+
+  BOOST_REQUIRE_EQUAL(getTaskSize(), 1U);
+  auto task = taskQueuePop();
+  BOOST_CHECK(task.d_qname == target);
+  BOOST_CHECK_EQUAL(task.d_qtype, QType::A);
+
+  // Now simulate a succeeding task execution an record has become available
+  negLookup = false;
+  sr->setNow(timeval{now + 3 * (negTTL + 1), 0});
+  downServers.clear();
+  sr->setRefreshAlmostExpired(true);
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(downCount, 4U);
+  BOOST_CHECK_EQUAL(lookupCount, 2U);
+
+  // And again, result should come from cache
+  sr->setRefreshAlmostExpired(false);
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(downCount, 4U);
+  BOOST_CHECK_EQUAL(lookupCount, 2U);
+}
+
+BOOST_AUTO_TEST_CASE(test_servestale_cname_to_nxdomain)
+{
+  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 int theTTL = 5;
+  const int negTTL = 60;
+
+  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(address) != downServers.end()) {
+      downCount++;
+      return LWResult::Result::Timeout;
+    }
+
+    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;
+    }
+    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);
+      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 (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);
+        addRecordToLW(res, DNSName("cname.powerdns.com"), QType::A, "192.0.2.4", DNSResourceRecord::ANSWER, theTTL);
+        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;
+    }
+  });
+
+  time_t now = time(nullptr);
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  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 NxDomain 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::NXDomain);
+  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::NXDomain);
+  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_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;
+  initSR(sr);
+  MemRecursorCache::s_maxServedStaleExtensions = 1440;
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  std::set<ComboAddress> downServers;
+  size_t downCount = 0;
+  size_t lookupCount = 0;
+
+  const int theTTL = 5;
+
+  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(address) != downServers.end()) {
+      downCount++;
+      throw ImmediateServFailException("FAIL!");
+    }
+
+    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;
+    }
+    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);
+      addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 5);
+      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, 5);
+      addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, theTTL);
+      return LWResult::Result::Success;
+    }
+    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;
+    }
+  });
+
+  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(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(downCount, 0U);
+  BOOST_CHECK_EQUAL(lookupCount, 1U);
+
+  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});
+
+  BOOST_REQUIRE_EQUAL(getTaskSize(), 0U);
+
+  // record is expired, so serve stale should kick in
+  ret.clear();
+  BOOST_REQUIRE_THROW(sr->beginResolve(target, QType(QType::A), QClass::IN, ret), ImmediateServFailException);
+  BOOST_CHECK_EQUAL(downCount, 1U);
+}
+
+BOOST_AUTO_TEST_CASE(test_glued_referral_additional_update)
+{
+  // Test that additional records update the cache
+  // We use two zones that share NS and their addresses
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr);
+  primeHints();
+
+  const DNSName target1("powerdns.com.");
+  const DNSName target2("pdns.com.");
+
+  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(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;
+    }
+    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);
+        addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
+        return LWResult::Result::Success;
+      }
+      if (domain == target2) {
+        setLWResult(res, 0, false, false, true);
+        addRecordToLW(res, "pdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "pdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
+        return LWResult::Result::Success;
+      }
+      return LWResult::Result::Timeout;
+    }
+    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");
+      }
+      else if (domain == target2) {
+        addRecordToLW(res, target2, QType::A, "192.0.2.5");
+      }
+      return LWResult::Result::Success;
+    }
+    else {
+      return LWResult::Result::Timeout;
+    }
+  });
+
+  // Lookup first name. We should see the address of an nameserver in the cache
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target1);
+
+  auto firstTTL = g_recCache->get(sr->getNow().tv_sec, DNSName("pdns-public-ns1.powerdns.com"), QType::A, MemRecursorCache::None, nullptr, ComboAddress());
+
+  // Move the time
+  sr->setNow({sr->getNow().tv_sec + 2, sr->getNow().tv_usec});
+
+  // Lookup second name. We should see the address rec of a nameserver in the cache being updated
+  ret.clear();
+  res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target2);
+
+  auto secondTTL = g_recCache->get(sr->getNow().tv_sec, DNSName("pdns-public-ns1.powerdns.com"), QType::A, MemRecursorCache::None, nullptr, ComboAddress());
+  // TTL shoud be back to original value
+  BOOST_CHECK_EQUAL(firstTTL, secondTTL);
+}
+
+BOOST_AUTO_TEST_CASE(test_glued_referral_additional_no_update_because_locked)
+{
+  // Test that additional records do not update the cache
+  // We use two zones that share NS and their addresses
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr);
+  // Set the lock
+  SyncRes::s_locked_ttlperc = 50;
+
+  primeHints();
+
+  const DNSName target1("powerdns.com.");
+  const DNSName target2("pdns.com.");
+
+  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(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;
+    }
+    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);
+        addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
+        return LWResult::Result::Success;
+      }
+      if (domain == target2) {
+        setLWResult(res, 0, false, false, true);
+        addRecordToLW(res, "pdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "pdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
+        return LWResult::Result::Success;
+      }
+      return LWResult::Result::Timeout;
+    }
+    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");
+      }
+      else if (domain == target2) {
+        addRecordToLW(res, target2, QType::A, "192.0.2.5");
+      }
+      return LWResult::Result::Success;
+    }
+    else {
+      return LWResult::Result::Timeout;
+    }
+  });
+
+  // Lookup first name. We should see the address of an nameserver in the cache
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target1);
+
+  auto firstTTL = g_recCache->get(sr->getNow().tv_sec, DNSName("pdns-public-ns1.powerdns.com"), QType::A, MemRecursorCache::None, nullptr, ComboAddress());
+
+  // Move the time
+  sr->setNow({sr->getNow().tv_sec + 2, sr->getNow().tv_usec});
+
+  // Lookup second name. We should see the address of an nameserver in the cache *not* being updated
+  ret.clear();
+  res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target2);
+
+  auto secondTTL = g_recCache->get(sr->getNow().tv_sec, DNSName("pdns-public-ns1.powerdns.com"), QType::A, MemRecursorCache::None, nullptr, ComboAddress());
+  // Time has passed, so ttl1 != ttl2
+  BOOST_CHECK_NE(firstTTL, secondTTL);
+}
+
+BOOST_AUTO_TEST_CASE(test_locked_nonauth_update_to_auth)
+{
+  // Test that a non-bogus authoritative record replaces a non-authoritative one
+  // even if the cache is locked
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr);
+  // Set the lock
+  SyncRes::s_locked_ttlperc = 50;
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  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(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;
+    }
+    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);
+      addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 172800);
+      addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 172800);
+      addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 172800);
+      addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
+      return LWResult::Result::Success;
+    }
+    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;
+      }
+      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);
+        return LWResult::Result::Success;
+      }
+    }
+    return LWResult::Result::Timeout;
+  });
+
+  // Lookup first name. We should see the (unauth) nameserver in the cache
+  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(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+
+  auto firstTTL = g_recCache->get(sr->getNow().tv_sec, target, QType::NS, MemRecursorCache::None, nullptr, ComboAddress());
+  BOOST_CHECK_GT(firstTTL, 0);
+
+  // Lookup NS records. We should see the nameserver in the cache being updated to auth
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+  BOOST_CHECK(ret[0].d_type == QType::NS);
+
+  auto secondTTL = g_recCache->get(sr->getNow().tv_sec, target, QType::NS, MemRecursorCache::RequireAuth, nullptr, ComboAddress());
+  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 4cc535743e0352b9528edbbca21758516abc5785..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");
 
@@ -1199,7 +1199,7 @@ BOOST_AUTO_TEST_CASE(test_ecs_cache_limit_allowed)
   /* should have been cached */
   const ComboAddress who("192.0.2.128");
   vector<DNSRecord> cached;
-  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), MemRecursorCache::RequireAuth, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
 }
 
@@ -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");
 
@@ -1238,7 +1238,7 @@ BOOST_AUTO_TEST_CASE(test_ecs_cache_limit_no_ttl_limit_allowed)
   /* should have been cached because /24 is more specific than /16 but TTL limit is nof effective */
   const ComboAddress who("192.0.2.128");
   vector<DNSRecord> cached;
-  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), MemRecursorCache::RequireAuth, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
 }
 
@@ -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");
 
@@ -1277,7 +1277,7 @@ BOOST_AUTO_TEST_CASE(test_ecs_cache_ttllimit_allowed)
   /* should have been cached */
   const ComboAddress who("192.0.2.128");
   vector<DNSRecord> cached;
-  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), MemRecursorCache::RequireAuth, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
 }
 
@@ -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");
 
@@ -1317,7 +1317,7 @@ BOOST_AUTO_TEST_CASE(test_ecs_cache_ttllimit_and_scope_allowed)
   /* should have been cached */
   const ComboAddress who("192.0.2.128");
   vector<DNSRecord> cached;
-  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), MemRecursorCache::RequireAuth, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
 }
 
@@ -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");
 
@@ -1357,7 +1357,7 @@ BOOST_AUTO_TEST_CASE(test_ecs_cache_ttllimit_notallowed)
   /* should have NOT been cached because TTL of 60 is too small and /24 is more specific than /16 */
   const ComboAddress who("192.0.2.128");
   vector<DNSRecord> cached;
-  BOOST_REQUIRE_LT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_LT(g_recCache->get(now, target, QType(QType::A), MemRecursorCache::RequireAuth, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 0U);
 }
 
@@ -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;
@@ -1462,7 +1462,7 @@ BOOST_AUTO_TEST_CASE(test_flawed_nsset)
   /* we populate the cache with a flawed NSset, i.e. there is a NS entry but no corresponding glue */
   time_t now = sr->getNow().tv_sec;
   std::vector<DNSRecord> records;
-  std::vector<shared_ptr<RRSIGRecordContent>> sigs;
+  std::vector<shared_ptr<const RRSIGRecordContent>> sigs;
   addRecordToList(records, target, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, now + 3600);
 
   g_recCache->replace(now, target, QType(QType::NS), records, sigs, vector<std::shared_ptr<DNSRecord>>(), true, g_rootdnsname, boost::optional<Netmask>());
@@ -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,14 +1561,14 @@ 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;
   });
 
   /* we populate the cache with everything we need */
   time_t now = sr->getNow().tv_sec;
   std::vector<DNSRecord> records;
-  std::vector<shared_ptr<RRSIGRecordContent>> sigs;
+  std::vector<shared_ptr<const RRSIGRecordContent>> sigs;
 
   addRecordToList(records, target, QType::A, "192.0.2.1", DNSResourceRecord::ANSWER, now + 3600);
   g_recCache->replace(now, target, QType(QType::A), records, sigs, vector<std::shared_ptr<DNSRecord>>(), true, g_rootdnsname, boost::optional<Netmask>());
@@ -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);
@@ -1644,13 +1644,13 @@ BOOST_AUTO_TEST_CASE(test_cache_min_max_ttl)
 
   const ComboAddress who;
   vector<DNSRecord> cached;
-  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), MemRecursorCache::RequireAuth, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
   BOOST_REQUIRE_GT(cached[0].d_ttl, now);
   BOOST_CHECK_EQUAL((cached[0].d_ttl - now), SyncRes::s_minimumTTL);
 
   cached.clear();
-  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::NS), false, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::NS), MemRecursorCache::None, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
   BOOST_REQUIRE_GT(cached[0].d_ttl, now);
   BOOST_CHECK_LE((cached[0].d_ttl - now), SyncRes::s_maxcachettl);
@@ -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);
@@ -1708,19 +1708,19 @@ BOOST_AUTO_TEST_CASE(test_cache_min_max_ecs_ttl)
 
   const ComboAddress who("192.0.2.128");
   vector<DNSRecord> cached;
-  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), MemRecursorCache::RequireAuth, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
   BOOST_REQUIRE_GT(cached[0].d_ttl, now);
   BOOST_CHECK_EQUAL((cached[0].d_ttl - now), SyncRes::s_minimumECSTTL);
 
   cached.clear();
-  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::NS), false, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::NS), MemRecursorCache::None, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
   BOOST_REQUIRE_GT(cached[0].d_ttl, now);
   BOOST_CHECK_LE((cached[0].d_ttl - now), SyncRes::s_maxcachettl);
 
   cached.clear();
-  BOOST_REQUIRE_GT(g_recCache->get(now, DNSName("a.gtld-servers.net."), QType(QType::A), false, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, DNSName("a.gtld-servers.net."), QType(QType::A), MemRecursorCache::None, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
   BOOST_REQUIRE_GT(cached[0].d_ttl, now);
   BOOST_CHECK_LE((cached[0].d_ttl - now), SyncRes::s_minimumTTL);
@@ -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;
@@ -1757,7 +1757,7 @@ BOOST_AUTO_TEST_CASE(test_cache_expired_ttl)
   const time_t now = sr->getNow().tv_sec;
 
   std::vector<DNSRecord> records;
-  std::vector<shared_ptr<RRSIGRecordContent>> sigs;
+  std::vector<shared_ptr<const RRSIGRecordContent>> sigs;
   addRecordToList(records, target, QType::A, "192.0.2.42", DNSResourceRecord::ANSWER, now - 60);
 
   g_recCache->replace(now - 3600, target, QType(QType::A), records, sigs, vector<std::shared_ptr<DNSRecord>>(), true, g_rootdnsname, boost::optional<Netmask>());
@@ -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,21 +1797,21 @@ 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;
 
   std::vector<DNSRecord> records;
-  std::vector<shared_ptr<RRSIGRecordContent>> sigs;
+  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();
 
@@ -1840,7 +1840,7 @@ BOOST_AUTO_TEST_CASE(test_cache_almost_expired_ttl)
 
   // Also check if NS record was updated
   ret.clear();
-  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::NS), false, &ret, ComboAddress()), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::NS), MemRecursorCache::None, &ret, ComboAddress()), 0);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_REQUIRE(ret[0].d_type == QType::NS);
   BOOST_CHECK_EQUAL(getRR<NSRecordContent>(ret[0])->getNS(), DNSName("pdns-public-ns1.powerdns.com."));
index bcd6ddbb2324b21cdcb2a3ed9ee1b0b6f8ccbca7..1bb73faa60f15086b29a6302dcf3557a95ba74f7 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);
@@ -36,7 +39,7 @@ BOOST_AUTO_TEST_CASE(test_cache_auth)
   /* check that we correctly cached only the answer entry, not the additional one */
   const ComboAddress who;
   vector<DNSRecord> cached;
-  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), MemRecursorCache::RequireAuth, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
   BOOST_REQUIRE_EQUAL(QType(cached.at(0).d_type).toString(), QType(QType::A).toString());
   BOOST_CHECK_EQUAL(getRR<ARecordContent>(cached.at(0))->getCA().toString(), ComboAddress("192.0.2.2").toString());
@@ -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);
@@ -150,19 +153,19 @@ BOOST_AUTO_TEST_CASE(test_extra_answers)
   // Also check the cache
   const ComboAddress who;
   vector<DNSRecord> cached;
-  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), MemRecursorCache::RequireAuth, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
   BOOST_REQUIRE_EQUAL(QType(cached.at(0).d_type).toString(), QType(QType::A).toString());
   BOOST_CHECK_EQUAL(getRR<ARecordContent>(cached.at(0))->getCA().toString(), ComboAddress("192.0.2.2").toString());
 
   // The cache should also have an authoritative record for the extra in-bailiwick record
-  BOOST_REQUIRE_GT(g_recCache->get(now, target2, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target2, QType(QType::A), MemRecursorCache::RequireAuth, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
   BOOST_REQUIRE_EQUAL(QType(cached.at(0).d_type).toString(), QType(QType::A).toString());
   BOOST_CHECK_EQUAL(getRR<ARecordContent>(cached.at(0))->getCA().toString(), ComboAddress("192.0.2.3").toString());
 
   // But the out-of-bailiwick record should not be there
-  BOOST_REQUIRE_LT(g_recCache->get(now, target3, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_LT(g_recCache->get(now, target3, QType(QType::A), MemRecursorCache::RequireAuth, &cached, who), 0);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_extra_answers)
@@ -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);
@@ -221,19 +224,19 @@ BOOST_AUTO_TEST_CASE(test_dnssec_extra_answers)
   // Also check the cache
   const ComboAddress who;
   vector<DNSRecord> cached;
-  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), MemRecursorCache::RequireAuth, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
   BOOST_REQUIRE_EQUAL(QType(cached.at(0).d_type).toString(), QType(QType::A).toString());
   BOOST_CHECK_EQUAL(getRR<ARecordContent>(cached.at(0))->getCA().toString(), ComboAddress("192.0.2.2").toString());
 
   // The cache should also have an authoritative record for the extra in-bailiwick record
-  BOOST_REQUIRE_GT(g_recCache->get(now, target2, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target2, QType(QType::A), MemRecursorCache::RequireAuth, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
   BOOST_REQUIRE_EQUAL(QType(cached.at(0).d_type).toString(), QType(QType::A).toString());
   BOOST_CHECK_EQUAL(getRR<ARecordContent>(cached.at(0))->getCA().toString(), ComboAddress("192.0.2.3").toString());
 
   // But the out-of-bailiwick record should not be there
-  BOOST_REQUIRE_LT(g_recCache->get(now, target3, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_LT(g_recCache->get(now, target3, QType(QType::A), MemRecursorCache::RequireAuth, &cached, who), 0);
 }
 
 BOOST_AUTO_TEST_CASE(test_skip_opt_any)
@@ -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;
@@ -407,8 +410,8 @@ BOOST_AUTO_TEST_CASE(test_answer_no_aa)
   /* check that the record in the answer section has not been cached */
   const ComboAddress who;
   vector<DNSRecord> cached;
-  vector<std::shared_ptr<RRSIGRecordContent>> signatures;
-  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), false, &cached, who, 0, boost::none, &signatures), 0);
+  vector<std::shared_ptr<const RRSIGRecordContent>> signatures;
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), MemRecursorCache::None, &cached, who, boost::none, &signatures), 0);
 }
 
 BOOST_AUTO_TEST_CASE(test_special_types)
@@ -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");
@@ -762,8 +765,11 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_nord)
   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) {
+  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 (address == forwardedNS) {
       BOOST_CHECK_EQUAL(sendRDQuery, false);
 
       setLWResult(res, 0, true, false, true);
@@ -774,13 +780,33 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_nord)
     return LWResult::Result::Timeout;
   });
 
+  BOOST_CHECK_EQUAL(queriesCount, 0U);
   /* simulate a no-RD query */
   sr->setCacheOnly();
 
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(ret.size(), 0U);
+  BOOST_CHECK_EQUAL(queriesCount, 0U);
+
+  /* simulate a RD query */
+  sr->setCacheOnly(false);
+
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
+  BOOST_CHECK_EQUAL(queriesCount, 1U);
+
+  /* simulate a no-RD query */
+  sr->setCacheOnly();
+
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(ret.size(), 1U);
+  BOOST_CHECK_EQUAL(queriesCount, 1U);
 }
 
 BOOST_AUTO_TEST_CASE(test_forward_zone_rd)
@@ -800,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 */
@@ -849,9 +875,12 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_nord)
   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) {
-      BOOST_CHECK_EQUAL(sendRDQuery, false);
+  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 (address == forwardedNS) {
+      BOOST_CHECK_EQUAL(sendRDQuery, true);
 
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, domain, QType::A, "192.0.2.42");
@@ -861,13 +890,33 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_nord)
     return LWResult::Result::Timeout;
   });
 
+  BOOST_CHECK_EQUAL(queriesCount, 0U);
   /* simulate a no-RD query */
   sr->setCacheOnly();
 
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(ret.size(), 0U);
+  BOOST_CHECK_EQUAL(queriesCount, 0U);
+
+  /* simulate a RD query */
+  sr->setCacheOnly(false);
+
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(ret.size(), 1U);
+  BOOST_CHECK_EQUAL(queriesCount, 1U);
+
+  /* simulate a no-RD query */
+  sr->setCacheOnly();
+
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
+  BOOST_CHECK_EQUAL(queriesCount, 1U);
 }
 
 BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd)
@@ -886,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);
@@ -932,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;
     }
 
@@ -1003,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);
@@ -1015,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;
     }
 
@@ -1107,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;
     }
 
@@ -1175,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;
@@ -1233,12 +1276,12 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_oob)
   dr.d_name = target;
   dr.d_type = QType::A;
   dr.d_ttl = 1800;
-  dr.d_content = std::make_shared<ARecordContent>(targetAddr);
+  dr.setContent(std::make_shared<ARecordContent>(targetAddr));
   ad.d_records.insert(dr);
 
   (*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;
   });
@@ -1294,19 +1337,19 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_oob_cname)
   dr.d_name = target;
   dr.d_type = QType::CNAME;
   dr.d_ttl = 1800;
-  dr.d_content = std::make_shared<CNAMERecordContent>(targetCname);
+  dr.setContent(std::make_shared<CNAMERecordContent>(targetCname));
   ad.d_records.insert(dr);
 
   dr.d_place = DNSResourceRecord::ANSWER;
   dr.d_name = targetCname;
   dr.d_type = QType::A;
   dr.d_ttl = 1800;
-  dr.d_content = std::make_shared<ARecordContent>(targetCnameAddr);
+  dr.setContent(std::make_shared<ARecordContent>(targetCnameAddr));
   ad.d_records.insert(dr);
 
   (*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;
   });
@@ -1363,21 +1406,21 @@ BOOST_AUTO_TEST_CASE(test_auth_zone)
   dr.d_name = target;
   dr.d_type = QType::SOA;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600");
+  dr.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
   ad.d_records.insert(dr);
 
   dr.d_place = DNSResourceRecord::ANSWER;
   dr.d_name = target;
   dr.d_type = QType::A;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<ARecordContent>(addr);
+  dr.setContent(std::make_shared<ARecordContent>(addr));
   ad.d_records.insert(dr);
 
   auto map = std::make_shared<SyncRes::domainmap_t>();
   (*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");
@@ -1412,21 +1455,21 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_cname_lead_to_oob)
   dr.d_name = authZone;
   dr.d_type = QType::SOA;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600");
+  dr.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
   ad.d_records.insert(dr);
 
   dr.d_place = DNSResourceRecord::ANSWER;
   dr.d_name = authZone;
   dr.d_type = QType::A;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<ARecordContent>(addr);
+  dr.setContent(std::make_shared<ARecordContent>(addr));
   ad.d_records.insert(dr);
 
   auto map = std::make_shared<SyncRes::domainmap_t>();
   (*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) {
@@ -1468,21 +1511,21 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_oob_lead_to_outgoing_queryb)
   dr.d_name = target;
   dr.d_type = QType::SOA;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600");
+  dr.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
   ad.d_records.insert(dr);
 
   dr.d_place = DNSResourceRecord::ANSWER;
   dr.d_name = target;
   dr.d_type = QType::CNAME;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<CNAMERecordContent>(externalCNAME);
+  dr.setContent(std::make_shared<CNAMERecordContent>(externalCNAME));
   ad.d_records.insert(dr);
 
   auto map = std::make_shared<SyncRes::domainmap_t>();
   (*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) {
@@ -1524,21 +1567,21 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_ds)
   dr.d_name = target;
   dr.d_type = QType::SOA;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.corp. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600");
+  dr.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.corp. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
   ad.d_records.insert(dr);
 
   dr.d_place = DNSResourceRecord::ANSWER;
   dr.d_name = target;
   dr.d_type = QType::A;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<ARecordContent>(addr);
+  dr.setContent(std::make_shared<ARecordContent>(addr));
   ad.d_records.insert(dr);
 
   auto map = std::make_shared<SyncRes::domainmap_t>();
   (*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 23358d9bc8e7a774474d9957a8eea33f2339bc64..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"
@@ -23,21 +26,21 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_nodata)
   dr.d_name = target;
   dr.d_type = QType::A;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<ARecordContent>(ComboAddress("192.0.2.1"));
+  dr.setContent(std::make_shared<ARecordContent>(ComboAddress("192.0.2.1")));
   ad.d_records.insert(dr);
 
   dr.d_place = DNSResourceRecord::ANSWER;
   dr.d_name = authZone;
   dr.d_type = QType::SOA;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600");
+  dr.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
   ad.d_records.insert(dr);
 
   auto map = std::make_shared<SyncRes::domainmap_t>();
   (*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;
@@ -69,14 +72,14 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_nx)
   dr.d_name = DNSName("powerdns.com.");
   dr.d_type = QType::SOA;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600");
+  dr.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
   ad.d_records.insert(dr);
 
   auto map = std::make_shared<SyncRes::domainmap_t>();
   (*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;
@@ -111,21 +114,21 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_delegation)
   dr.d_name = authZone;
   dr.d_type = QType::SOA;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600");
+  dr.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
   ad.d_records.insert(dr);
 
   dr.d_place = DNSResourceRecord::ANSWER;
   dr.d_name = DNSName("test.powerdns.com.");
   dr.d_type = QType::NS;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<NSRecordContent>(ns);
+  dr.setContent(std::make_shared<NSRecordContent>(ns));
   ad.d_records.insert(dr);
 
   dr.d_place = DNSResourceRecord::ANSWER;
   dr.d_name = ns;
   dr.d_type = QType::A;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<ARecordContent>(nsAddr);
+  dr.setContent(std::make_shared<ARecordContent>(nsAddr));
   ad.d_records.insert(dr);
 
   auto map = std::make_shared<SyncRes::domainmap_t>();
@@ -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;
@@ -189,31 +192,31 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_delegation_point)
   dr.d_name = authZone;
   dr.d_type = QType::SOA;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600");
+  dr.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
   ad.d_records.insert(dr);
 
   dr.d_place = DNSResourceRecord::ANSWER;
   dr.d_name = DNSName("test.powerdns.com.");
   dr.d_type = QType::NS;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<NSRecordContent>(ns);
+  dr.setContent(std::make_shared<NSRecordContent>(ns));
   ad.d_records.insert(dr);
 
   dr.d_place = DNSResourceRecord::ANSWER;
   dr.d_name = ns;
   dr.d_type = QType::A;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<ARecordContent>(nsAddr);
+  dr.setContent(std::make_shared<ARecordContent>(nsAddr));
   ad.d_records.insert(dr);
 
   auto map = std::make_shared<SyncRes::domainmap_t>();
   (*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;
@@ -249,21 +252,21 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_wildcard)
   dr.d_name = authZone;
   dr.d_type = QType::SOA;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600");
+  dr.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
   ad.d_records.insert(dr);
 
   dr.d_place = DNSResourceRecord::ANSWER;
   dr.d_name = DNSName("*.powerdns.com.");
   dr.d_type = QType::A;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<ARecordContent>(targetAddr);
+  dr.setContent(std::make_shared<ARecordContent>(targetAddr));
   ad.d_records.insert(dr);
 
   auto map = std::make_shared<SyncRes::domainmap_t>();
   (*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;
@@ -297,28 +300,28 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_wildcard_with_ent)
   dr.d_name = authZone;
   dr.d_type = QType::SOA;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600");
+  dr.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
   ad.d_records.insert(dr);
 
   dr.d_place = DNSResourceRecord::ANSWER;
   dr.d_name = DNSName("abc.xyz.test.powerdns.com.");
   dr.d_type = QType::A;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<ARecordContent>(targetAddr1);
+  dr.setContent(std::make_shared<ARecordContent>(targetAddr1));
   ad.d_records.insert(dr);
 
   dr.d_place = DNSResourceRecord::ANSWER;
   dr.d_name = DNSName("*.powerdns.com.");
   dr.d_type = QType::A;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<ARecordContent>(targetAddr2);
+  dr.setContent(std::make_shared<ARecordContent>(targetAddr2));
   ad.d_records.insert(dr);
 
   auto map = std::make_shared<SyncRes::domainmap_t>();
   (*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;
@@ -354,21 +357,21 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_wildcard_nodata)
   dr.d_name = authZone;
   dr.d_type = QType::SOA;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600");
+  dr.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
   ad.d_records.insert(dr);
 
   dr.d_place = DNSResourceRecord::ANSWER;
   dr.d_name = DNSName("*.powerdns.com.");
   dr.d_type = QType::A;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<ARecordContent>(targetAddr);
+  dr.setContent(std::make_shared<ARecordContent>(targetAddr));
   ad.d_records.insert(dr);
 
   auto map = std::make_shared<SyncRes::domainmap_t>();
   (*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;
@@ -400,21 +403,21 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_cache_only)
   dr.d_name = target;
   dr.d_type = QType::SOA;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600");
+  dr.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
   ad.d_records.insert(dr);
 
   dr.d_place = DNSResourceRecord::ANSWER;
   dr.d_name = target;
   dr.d_type = QType::A;
   dr.d_ttl = 3600;
-  dr.d_content = std::make_shared<ARecordContent>(addr);
+  dr.setContent(std::make_shared<ARecordContent>(addr));
   ad.d_records.insert(dr);
 
   auto map = std::make_shared<SyncRes::domainmap_t>();
   (*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,10 +442,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_rrsig)
 
   auto dcke = DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256);
   dcke->create(dcke->getBits());
-  // cerr<<dcke->convertToISC()<<endl;
   DNSSECPrivateKey dpk;
-  dpk.d_flags = 256;
-  dpk.setKey(std::move(dcke));
+  dpk.setKey(std::move(dcke), 256);
 
   sortedRecords_t recordcontents;
   recordcontents.insert(getRecordContent(QType::A, "192.0.2.1"));
@@ -457,10 +458,12 @@ BOOST_AUTO_TEST_CASE(test_dnssec_rrsig)
   skeyset_t keyset;
   keyset.insert(std::make_shared<DNSKEYRecordContent>(dpk.getDNSKEY()));
 
-  std::vector<std::shared_ptr<RRSIGRecordContent>> sigs;
+  std::vector<std::shared_ptr<const RRSIGRecordContent>> sigs;
   sigs.push_back(std::make_shared<RRSIGRecordContent>(rrc));
 
-  BOOST_CHECK(validateWithKeySet(now, qname, recordcontents, sigs, keyset) == 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)
@@ -481,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) {
@@ -500,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);
 
@@ -546,15 +549,13 @@ BOOST_AUTO_TEST_CASE(test_dnssec_root_validation_ksk_zsk)
   auto dckeZ = DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256);
   dckeZ->create(dckeZ->getBits());
   DNSSECPrivateKey ksk;
-  ksk.d_flags = 257;
-  ksk.setKey(std::move(dckeZ));
+  ksk.setKey(std::move(dckeZ), 257);
   DSRecordContent kskds = makeDSFromDNSKey(target, ksk.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
 
   auto dckeK = DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256);
   dckeK->create(dckeK->getBits());
   DNSSECPrivateKey zsk;
-  zsk.d_flags = 256;
-  zsk.setKey(std::move(dckeK));
+  zsk.setKey(std::move(dckeK), 256);
   DSRecordContent zskds = makeDSFromDNSKey(target, zsk.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
 
   kskeys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(ksk, kskds);
@@ -568,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) {
@@ -587,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);
 
@@ -636,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) {
@@ -655,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);
 
@@ -699,8 +700,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_without_zone_flag)
   auto dcke = DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256);
   dcke->create(dcke->getBits());
   DNSSECPrivateKey csk;
-  csk.d_flags = 0;
-  csk.setKey(std::move(dcke));
+  csk.setKey(std::move(dcke), 0);
   DSRecordContent ds = makeDSFromDNSKey(target, csk.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
 
   keys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(csk, ds);
@@ -713,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) {
@@ -731,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);
 
@@ -776,8 +776,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_revoked)
   auto dcke = DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256);
   dcke->create(dcke->getBits());
   DNSSECPrivateKey csk;
-  csk.d_flags = 257 | 128;
-  csk.setKey(std::move(dcke));
+  csk.setKey(std::move(dcke), 257 | 128);
   DSRecordContent ds = makeDSFromDNSKey(target, csk.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
 
   keys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(csk, ds);
@@ -790,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) {
@@ -808,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);
 
@@ -837,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;
@@ -853,19 +853,17 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_doesnt_match_ds)
   auto dckeDS = DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256);
   dckeDS->create(dckeDS->getBits());
   DNSSECPrivateKey dskey;
-  dskey.d_flags = 257;
-  dskey.setKey(std::move(dckeDS));
+  dskey.setKey(std::move(dckeDS), 257);
   DSRecordContent drc = makeDSFromDNSKey(target, dskey.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
 
   auto dcke = DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256);
   dcke->create(dcke->getBits());
   DNSSECPrivateKey dpk;
-  dpk.d_flags = 256;
-  dpk.setKey(std::move(dcke));
-  DSRecordContent uselessdrc = makeDSFromDNSKey(target, dpk.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
+  dpk.setKey(std::move(dcke), 256);
+  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();
@@ -875,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) {
@@ -894,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);
 
@@ -959,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,15 +1254,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_rrsig_signed_with_unknown_dnskey)
   auto dckeRRSIG = DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256);
   dckeRRSIG->create(dckeRRSIG->getBits());
   DNSSECPrivateKey rrsigkey;
-  rrsigkey.d_flags = 257;
-  rrsigkey.setKey(std::move(dckeRRSIG));
+  rrsigkey.setKey(std::move(dckeRRSIG), 257);
   DSRecordContent rrsigds = makeDSFromDNSKey(target, rrsigkey.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
 
   rrsigkeys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(rrsigkey, rrsigds);
 
   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) {
@@ -1006,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);
 
@@ -1054,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) {
@@ -1073,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);
 
@@ -1129,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) {
@@ -1149,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);
 
@@ -1202,10 +1476,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_unknown_ds_algorithm)
   auto dcke = DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256);
   dcke->create(dcke->getBits());
   DNSSECPrivateKey dpk;
-  dpk.d_flags = 256;
-  dpk.setKey(std::move(dcke));
   /* Fake algorithm number (private) */
-  dpk.d_algorithm = 253;
+  dpk.setKey(std::move(dcke), 256, 253);
 
   DSRecordContent drc = makeDSFromDNSKey(target, dpk.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
   keys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(dpk, drc);
@@ -1220,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) {
@@ -1239,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);
 
@@ -1285,8 +1557,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_unknown_ds_digest)
   auto dcke = DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256);
   dcke->create(dcke->getBits());
   DNSSECPrivateKey dpk;
-  dpk.d_flags = 256;
-  dpk.setKey(std::move(dcke));
+  dpk.setKey(std::move(dcke), 256);
   DSRecordContent drc = makeDSFromDNSKey(target, dpk.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
   /* Fake digest number (reserved) */
   drc.d_digesttype = 0;
@@ -1301,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) {
@@ -1320,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);
 
@@ -1374,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) {
@@ -1393,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);
 
@@ -1423,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;
@@ -1442,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) {
@@ -1462,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);
 
@@ -1513,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;
@@ -1531,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*/
@@ -1540,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);
@@ -1594,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;
@@ -1612,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 b6230b98e2175bcee51394e777335f3a940bd403..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);
@@ -1260,7 +1257,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard_duplicated_n
 
   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([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) {
@@ -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.");
@@ -2179,12 +2347,12 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_servfail_ds)
   }
 }
 
-BOOST_AUTO_TEST_CASE(test_dnssec_secure_servfail_dnskey)
+static void dnssec_secure_servfail_dnskey(DNSSECMode mode, vState /* expectedValidationResult */)
 {
   std::unique_ptr<SyncRes> sr;
   initSR(sr, true);
 
-  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+  setDNSSECValidation(sr, mode);
 
   primeHints();
   const DNSName target("powerdns.com.");
@@ -2207,7 +2375,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_servfail_dnskey)
 
   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 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_servfail_dnskey)
       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 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_servfail_dnskey)
       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 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_servfail_dnskey)
       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.");
@@ -2289,4 +2457,121 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_servfail_dnskey)
   }
 }
 
+BOOST_AUTO_TEST_CASE(test_dnssec_secure_servfail_dnskey)
+{
+  dnssec_secure_servfail_dnskey(DNSSECMode::ValidateAll, vState::Indeterminate);
+  dnssec_secure_servfail_dnskey(DNSSECMode::Off, vState::Indeterminate);
+}
+
+// Same test as above but powerdns.com is now Insecure according to parent, so failure to retrieve DNSSKEYs
+// should be mostly harmless.
+static void dnssec_secure_servfail_dnskey_insecure(DNSSECMode mode, vState expectedValidationResult)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, mode);
+
+  primeHints();
+  const DNSName target("powerdns.com.");
+  const ComboAddress targetAddr("192.0.2.42");
+
+  // We use two sets of keys, as powerdns.com is Insecure according to parent but returns signed results,
+  // triggering a (failing) DNSKEY retrieval.
+  testkeysset_t keys;
+  testkeysset_t pdnskeys;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+  generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, pdnskeys);
+
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+
+  /* make sure that the signature inception and validity times are computed
+     based on the SyncRes time, not the current one, in case the function
+     takes too long. */
+
+  const time_t fixedNow = sr->getNow().tv_sec;
+
+  sr->setAsyncCallback([&](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;
+    if (domain == target) {
+      auth = DNSName("powerdns.com.");
+    }
+
+    if (type == QType::DNSKEY && domain == DNSName("powerdns.com.")) {
+      /* time out */
+      return LWResult::Result::Timeout;
+    }
+
+    if (type == QType::DS || type == QType::DNSKEY) {
+      // This one does not know about pdnskeys, so it will declare powerdns.com as Insecure
+      return genericDSAndDNSKEYHandler(res, domain, auth, type, keys, true, fixedNow);
+    }
+
+    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, false, boost::none, boost::none, fixedNow);
+      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")) {
+      if (domain == DNSName("com.")) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
+        addRRSIG(keys, res->d_records, domain, 300, false, boost::none, boost::none, fixedNow);
+        addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        addRRSIG(keys, res->d_records, domain, 300);
+      }
+      else {
+        setLWResult(res, 0, false, false, true);
+        addRecordToLW(res, auth, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
+        addDS(auth, 300, res->d_records, keys);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300, false, boost::none, boost::none, fixedNow);
+        addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+      }
+      return LWResult::Result::Success;
+    }
+
+    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.");
+        addRRSIG(pdnskeys, res->d_records, auth, 300, false, boost::none, boost::none, fixedNow);
+        addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+        addRRSIG(pdnskeys, res->d_records, auth, 300);
+      }
+      else {
+        setLWResult(res, RCode::NoError, true, false, true);
+        addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
+        addRRSIG(pdnskeys, res->d_records, auth, 300, false, boost::none, boost::none, fixedNow);
+      }
+      return LWResult::Result::Success;
+    }
+
+    return LWResult::Result::Timeout;
+  });
+
+  vector<DNSRecord> ret;
+  auto res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), expectedValidationResult);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_secure_servfail_dnskey_insecure)
+{
+  dnssec_secure_servfail_dnskey_insecure(DNSSECMode::ValidateAll, vState::Insecure);
+  dnssec_secure_servfail_dnskey_insecure(DNSSECMode::Off, vState::Insecure);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
index c2c5d16a1d97f711397459d4925c80169dbfea21..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);
@@ -1879,4 +1850,94 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_broken_cname_ds)
   BOOST_CHECK_EQUAL(queriesCount, 8U);
 }
 
+BOOST_AUTO_TEST_CASE(test_dnssec_bogus_cname_for_ds)
+{
+  /* Test an Secure domain that responds with a CNAME on a DS query goes Bogus */
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+  primeHints();
+  const DNSName target("www.powerdns.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);
+
+  generateKeyMaterial(DNSName("powerdns.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 == DNSName("powerdns.com")) {
+        // Return a signed CNAME on a DS query for powerdns.com
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::CNAME, "some.name", DNSResourceRecord::ANSWER, 300);
+        addRRSIG(keys, res->d_records, domain, 300);
+        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")) {
+        if (domain == DNSName("com.")) {
+          setLWResult(res, 0, true, false, true);
+          addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
+          addRRSIG(keys, res->d_records, DNSName("com."), 300);
+          addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        }
+        else {
+          setLWResult(res, 0, false, false, true);
+          addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
+          addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+        }
+        return LWResult::Result::Success;
+      }
+      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);
+
+        return LWResult::Result::Success;
+      }
+    }
+
+    return LWResult::Result::Timeout;
+  });
+
+  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::BogusUnableToGetDSs);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(queriesCount, 6U);
+
+  /* again, to test the cache */
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusUnableToGetDSs);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(queriesCount, 6U);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
index 7d4484175ff5631628e58eb96c8ad02957473712..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,16 +1372,25 @@ 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 {
-
-      setLWResult(res, 0, true, false, true);
-      return LWResult::Result::Success;
+    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);
+        addRRSIG(keys, res->d_records, DNSName("com"), 300);
+        addNSECRecordToLW(DNSName("com"), DNSName("com."), {QType::SOA}, 600, res->d_records);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        return LWResult::Result::Success;
+      }
+      if (domain == target) {
+        setLWResult(res, 0, true, false, true);
+        return LWResult::Result::Success;
+      }
     }
 
     return LWResult::Result::Timeout;
@@ -1407,8 +1401,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nodata)
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusMissingNegativeIndication);
   BOOST_REQUIRE_EQUAL(ret.size(), 0U);
-  /* com|NS, powerdns.com|NS, powerdns.com|A */
-  BOOST_CHECK_EQUAL(queriesCount, 3U);
+  /* powerdns.com|A, com|A, com|DNSKEY, powerdns.com|DS */
+  BOOST_CHECK_EQUAL(queriesCount, 4U);
 
   /* again, to test the cache */
   ret.clear();
@@ -1417,10 +1411,10 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nodata)
   BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusMissingNegativeIndication);
   BOOST_REQUIRE_EQUAL(ret.size(), 0U);
   /* we don't store empty results */
-  BOOST_CHECK_EQUAL(queriesCount, 4U);
+  BOOST_CHECK_EQUAL(queriesCount, 5U);
 }
 
-BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nxdomain)
+BOOST_AUTO_TEST_CASE(test_dnssec_insecure_missing_soa_on_nodata)
 {
   std::unique_ptr<SyncRes> sr;
   initSR(sr, true);
@@ -1435,22 +1429,177 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nxdomain)
   luaconfsCopy.dsAnchors.clear();
   generateKeyMaterial(DNSName("."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
   generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
-  generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
   g_luaconfs.setState(luaconfsCopy);
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, LWResult* res, bool* chained) {
+  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 (domain.isPartOf(target)) {
+        /* proves cut */
+        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true);
+      }
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
+    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);
+      addRRSIG(keys, res->d_records, DNSName("com."), 300);
+      addRecordToLW(res, "ns1.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+      return LWResult::Result::Success;
+    }
     else {
+      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);
+        addRRSIG(keys, res->d_records, DNSName("com"), 300);
+        addNSECRecordToLW(DNSName("com"), DNSName("com."), {QType::SOA}, 600, res->d_records);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        return LWResult::Result::Success;
+      }
+      if (domain == target) {
+        setLWResult(res, 0, true, false, true);
+        return LWResult::Result::Success;
+      }
+    }
+
+    return LWResult::Result::Timeout;
+  });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 0U);
+  /* powerdns.com|A, com|A, com|DNSKEY, powerdns.com|DS */
+  BOOST_CHECK_EQUAL(queriesCount, 4U);
+
+  /* again, to test the cache */
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 0U);
+  /* we don't store empty results */
+  BOOST_CHECK_EQUAL(queriesCount, 5U);
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_insecure_missing_soa_on_nxd)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+  primeHints();
+  const DNSName target("powerdns.com.");
+  testkeysset_t keys;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  generateKeyMaterial(DNSName("."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+  g_luaconfs.setState(luaconfsCopy);
 
-      setLWResult(res, RCode::NXDomain, true, false, true);
+  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 (domain.isPartOf(target)) {
+        /* proves cut */
+        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true);
+      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+    }
+    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);
+      addRRSIG(keys, res->d_records, DNSName("com."), 300);
+      addRecordToLW(res, "ns1.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
+    else {
+      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);
+        addRRSIG(keys, res->d_records, DNSName("com"), 300);
+        addNSECRecordToLW(DNSName("com"), DNSName("com."), {QType::SOA}, 600, res->d_records);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        return LWResult::Result::Success;
+      }
+      if (domain == target) {
+        setLWResult(res, RCode::NXDomain, true, false, true);
+        return LWResult::Result::Success;
+      }
+    }
+
+    return LWResult::Result::Timeout;
+  });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 0U);
+  /* powerdns.com|A, com|A, com|DNSKEY, powerdns.com|DS */
+  BOOST_CHECK_EQUAL(queriesCount, 4U);
+
+  /* again, to test the cache */
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 0U);
+  /* we don't store empty results */
+  BOOST_CHECK_EQUAL(queriesCount, 5U);
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nxdomain)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+  primeHints();
+  const DNSName target("powerdns.com.");
+  testkeysset_t keys;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  generateKeyMaterial(DNSName("."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([&](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);
+    }
+    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);
+        addRRSIG(keys, res->d_records, DNSName("com"), 300);
+        addNSECRecordToLW(DNSName("com"), DNSName("com."), {QType::SOA}, 600, res->d_records);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        return LWResult::Result::Success;
+      }
+      if (domain == target) {
+        setLWResult(res, RCode::NXDomain, true, false, true);
+        return LWResult::Result::Success;
+      }
+    }
 
     return LWResult::Result::Timeout;
   });
@@ -1460,8 +1609,9 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nxdomain)
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
   BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusMissingNegativeIndication);
   BOOST_REQUIRE_EQUAL(ret.size(), 0U);
-  /* com|NS, powerdns.com|NS, powerdns.com|A */
-  BOOST_CHECK_EQUAL(queriesCount, 3U);
+
+  /* powerdns.com|A, com|A, powerdns.com|DS, com|DNSKEY */
+  BOOST_CHECK_EQUAL(queriesCount, 4U);
 
   /* again, to test the cache */
   ret.clear();
@@ -1470,7 +1620,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nxdomain)
   BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusMissingNegativeIndication);
   BOOST_REQUIRE_EQUAL(ret.size(), 0U);
   /* we don't store empty results */
-  BOOST_CHECK_EQUAL(queriesCount, 4U);
+  BOOST_CHECK_EQUAL(queriesCount, 5U);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cut_with_cname_at_apex)
@@ -1494,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);
@@ -1527,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);
@@ -1550,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) {
@@ -1633,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) {
@@ -1642,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);
@@ -1668,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 96a7330c59363047ca39208dc46b90afdde2e114..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();
@@ -15,14 +25,14 @@ BOOST_AUTO_TEST_CASE(test_nsec_denial_nowrap)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   /*
     No wrap test case:
     a.example.org. -> d.example.org. denies the existence of b.example.org.
    */
   addNSECRecordToLW(DNSName("a.example.org."), DNSName("d.example.org"), {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("example.org."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
   records.clear();
@@ -37,7 +47,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_denial_nowrap)
   recordContents.clear();
   signatureContents.clear();
   addNSECRecordToLW(DNSName("example.org."), DNSName("+.example.org"), {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("example.org."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
   records.clear();
@@ -64,14 +74,14 @@ BOOST_AUTO_TEST_CASE(test_nsec_denial_wrap_case_1)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   /*
     Wrap case 1 test case:
     z.example.org. -> b.example.org. denies the existence of a.example.org.
    */
   addNSECRecordToLW(DNSName("z.example.org."), DNSName("b.example.org"), {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("example.org."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
   records.clear();
@@ -100,14 +110,14 @@ BOOST_AUTO_TEST_CASE(test_nsec_denial_wrap_case_2)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   /*
     Wrap case 2 test case:
     y.example.org. -> a.example.org. denies the existence of z.example.org.
    */
   addNSECRecordToLW(DNSName("y.example.org."), DNSName("a.example.org"), {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("example.org."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
   records.clear();
@@ -136,14 +146,14 @@ BOOST_AUTO_TEST_CASE(test_nsec_denial_only_one_nsec)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   /*
     Only one NSEC in the whole zone test case:
     a.example.org. -> a.example.org. denies the existence of b.example.org.
    */
   addNSECRecordToLW(DNSName("a.example.org."), DNSName("a.example.org"), {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("example.org."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
   records.clear();
@@ -172,14 +182,14 @@ BOOST_AUTO_TEST_CASE(test_nsec_root_nxd_denial)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   /*
     The RRSIG from "." denies the existence of anything between a. and c.,
     including b.
   */
   addNSECRecordToLW(DNSName("a."), DNSName("c."), {QType::NS}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
   records.clear();
@@ -194,7 +204,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_root_nxd_denial)
   recordContents.clear();
   signatureContents.clear();
   addNSECRecordToLW(DNSName("."), DNSName("+"), {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
   records.clear();
@@ -217,7 +227,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_ancestor_nxqtype_denial)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   /*
     The RRSIG from "." denies the existence of any type except NS at a.
@@ -227,7 +237,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_ancestor_nxqtype_denial)
     or a DS.
   */
   addNSECRecordToLW(DNSName("a."), DNSName("b."), {QType::NS}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
   records.clear();
@@ -269,10 +279,10 @@ BOOST_AUTO_TEST_CASE(test_nsec_ds_denial_from_child)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   addNSECRecordToLW(DNSName("example.org."), DNSName("a.example.org"), {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("example.org."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
   records.clear();
@@ -284,10 +294,10 @@ BOOST_AUTO_TEST_CASE(test_nsec_ds_denial_from_child)
   denialMap[std::pair(DNSName("example.org."), QType::NSEC)] = pair;
 
   /* check that this NSEC from the child zone can deny a AAAA at the apex */
-  BOOST_CHECK_EQUAL(getDenial(denialMap, DNSName("example.org."), QType::AAAA, false, true, true), dState::NXQTYPE);
+  BOOST_CHECK_EQUAL(getDenial(denialMap, DNSName("example.org."), QType::AAAA, false, true, std::nullopt, true), dState::NXQTYPE);
 
   /* but not that the DS does not exist, since we need the parent for that */
-  BOOST_CHECK_EQUAL(getDenial(denialMap, DNSName("example.org."), QType::DS, false, true, true), dState::NODENIAL);
+  BOOST_CHECK_EQUAL(getDenial(denialMap, DNSName("example.org."), QType::DS, false, true, std::nullopt, true), dState::NODENIAL);
 }
 
 BOOST_AUTO_TEST_CASE(test_nsec_insecure_delegation_denial)
@@ -300,7 +310,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_insecure_delegation_denial)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   /*
    * RFC 5155 section 8.9:
@@ -315,7 +325,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_insecure_delegation_denial)
     we correctly detect that it's not.
   */
   addNSECRecordToLW(DNSName("a."), DNSName("b."), {}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
   records.clear();
@@ -342,7 +352,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_insecure_delegation_denial_soa)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   /*
    * RFC 5155 section 8.9:
@@ -356,7 +366,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_insecure_delegation_denial_soa)
     NS has to be set since it is proving an insecure delegation, but SOA should NOT!
   */
   addNSECRecordToLW(DNSName("a."), DNSName("b."), {QType::NS, QType::SOA}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
   records.clear();
@@ -382,10 +392,10 @@ BOOST_AUTO_TEST_CASE(test_nsec_nxqtype_cname)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   addNSECRecordToLW(DNSName("a.powerdns.com."), DNSName("a.c.powerdns.com."), {QType::CNAME}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("powerdns.com."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
   records.clear();
@@ -411,10 +421,10 @@ BOOST_AUTO_TEST_CASE(test_nsec3_nxqtype_ds)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
-
-  addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
+  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)
@@ -441,10 +456,10 @@ BOOST_AUTO_TEST_CASE(test_nsec3_nxqtype_cname)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   addNSEC3UnhashedRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::CNAME}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("powerdns.com."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
 
@@ -470,10 +485,10 @@ BOOST_AUTO_TEST_CASE(test_nsec_nxdomain_denial_missing_wildcard)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   addNSECRecordToLW(DNSName("a.powerdns.com."), DNSName("d.powerdns.com"), {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("powerdns.com."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
   records.clear();
@@ -498,10 +513,10 @@ BOOST_AUTO_TEST_CASE(test_nsec3_nxdomain_denial_missing_wildcard)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   addNSEC3NarrowRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, records, 10);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("powerdns.com."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
 
@@ -516,7 +531,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_nxdomain_denial_missing_wildcard)
   signatureContents.clear();
   records.clear();
   addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, records, 10);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("powerdns.com."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
 
@@ -538,11 +553,11 @@ BOOST_AUTO_TEST_CASE(test_nsec_expanded_wildcard_proof)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   /* proves that a.example.com does exist, and has been generated from a wildcard (see the RRSIG below) */
   addNSECRecordToLW(DNSName("a.example.org."), DNSName("d.example.org"), {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("example.org."), 300, false, boost::none, DNSName("example.org."));
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
   records.clear();
@@ -555,7 +570,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_expanded_wildcard_proof)
 
   /* This is an expanded wildcard proof, meaning that it does prove that the exact name
      does not exist so the wildcard can apply */
-  dState denialState = getDenial(denialMap, DNSName("a.example.org."), QType(0).getCode(), false, false, false, /* normally retrieved from the RRSIG's d_labels */ 2);
+  dState denialState = getDenial(denialMap, DNSName("a.example.org."), QType(0).getCode(), false, false, std::nullopt, false, /* normally retrieved from the RRSIG's d_labels */ 2);
   BOOST_CHECK_EQUAL(denialState, dState::NXDOMAIN);
 }
 
@@ -569,11 +584,11 @@ BOOST_AUTO_TEST_CASE(test_nsec_wildcard_with_cname)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   /* proves that b.example.com does not exist */
   addNSECRecordToLW(DNSName("a.example.org."), DNSName("d.example.org"), {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("example.org."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
   records.clear();
@@ -588,7 +603,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_wildcard_with_cname)
   recordContents.clear();
   signatureContents.clear();
   addNSECRecordToLW(DNSName("*.example.org."), DNSName("+.example.org"), {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("example.org."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
   records.clear();
@@ -608,7 +623,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_wildcard_with_cname)
   recordContents.clear();
   signatureContents.clear();
   addNSECRecordToLW(DNSName("*.example.org."), DNSName("+.example.org"), {QType::CNAME, QType::RRSIG, QType::NSEC}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("example.org."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
   records.clear();
@@ -635,11 +650,11 @@ BOOST_AUTO_TEST_CASE(test_nsec3_wildcard_with_cname)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   /* proves that b.example.com does not exist */
   addNSEC3NarrowRecordToLW(DNSName("b.example.org"), DNSName("example.org."), {QType::A, QType::TXT, QType::RRSIG, QType::NSEC3}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("example.org."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
 
@@ -654,7 +669,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_wildcard_with_cname)
   signatureContents.clear();
   records.clear();
   addNSEC3UnhashedRecordToLW(DNSName("example.org."), DNSName("example.org."), "whatever", {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("example.org."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
 
@@ -667,7 +682,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_wildcard_with_cname)
   signatureContents.clear();
   records.clear();
   addNSEC3UnhashedRecordToLW(DNSName("*.example.org."), DNSName("example.org"), "whatever", {QType::A, QType::TXT, QType::RRSIG, QType::NSEC3}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("example.org."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
 
@@ -687,7 +702,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_wildcard_with_cname)
   signatureContents.clear();
   records.clear();
   addNSEC3UnhashedRecordToLW(DNSName("*.example.org."), DNSName("example.org"), "whatever", {QType::CNAME, QType::RRSIG, QType::NSEC3}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("example.org."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
 
@@ -713,10 +728,10 @@ BOOST_AUTO_TEST_CASE(test_nsec_ent_denial)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   addNSECRecordToLW(DNSName("a.powerdns.com."), DNSName("a.c.powerdns.com."), {QType::A}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("powerdns.com."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
   records.clear();
@@ -745,7 +760,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_ent_denial)
   recordContents.clear();
   signatureContents.clear();
   addNSECRecordToLW(DNSName(").powerdns.com."), DNSName("+.powerdns.com."), {}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("powerdns.com."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
   records.clear();
@@ -772,7 +787,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_ancestor_nxqtype_denial)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   /*
     The RRSIG from "." denies the existence of any type except NS at a.
@@ -781,7 +796,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_ancestor_nxqtype_denial)
     be used to deny anything except the whole name or a DS.
   */
   addNSEC3UnhashedRecordToLW(DNSName("a."), DNSName("."), "whatever", {QType::NS}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
 
@@ -813,7 +828,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_ancestor_nxqtype_denial)
   signatureContents.clear();
   records.clear();
   addNSEC3NarrowRecordToLW(DNSName("sub.a."), DNSName("."), {QType::A, QType::TXT, QType::RRSIG, QType::NSEC3}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
 
@@ -826,7 +841,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_ancestor_nxqtype_denial)
   signatureContents.clear();
   records.clear();
   addNSEC3NarrowRecordToLW(DNSName("*.a."), DNSName("."), {QType::A, QType::TXT, QType::RRSIG, QType::NSEC3}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
 
@@ -852,11 +867,11 @@ BOOST_AUTO_TEST_CASE(test_nsec3_denial_too_many_iterations)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   /* adding a NSEC3 with more iterations that we support */
   addNSEC3UnhashedRecordToLW(DNSName("a."), DNSName("."), "whatever", {QType::AAAA}, 600, records, g_maxNSEC3Iterations + 100);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
 
@@ -882,7 +897,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_insecure_delegation_denial)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   /*
    * RFC 5155 section 8.9:
@@ -897,7 +912,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_insecure_delegation_denial)
     we correctly detect that it's not.
   */
   addNSEC3UnhashedRecordToLW(DNSName("a."), DNSName("."), "whatever", {}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
 
@@ -924,7 +939,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_insecure_delegation_denial_soa)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   /*
    * RFC 5155 section 8.9:
@@ -938,7 +953,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_insecure_delegation_denial_soa)
     NS has to be set since it is proving an insecure delegation, but SOA should NOT!
   */
   addNSEC3UnhashedRecordToLW(DNSName("a."), DNSName("."), "whatever", {QType::NS, QType::SOA}, 600, records);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
 
@@ -964,7 +979,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_ent_opt_out)
   vector<DNSRecord> records;
 
   sortedRecords_t recordContents;
-  vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+  vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
 
   /*
    * RFC 7129 section 5.1:
@@ -979,7 +994,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_ent_opt_out)
     a wildcard proof).
   */
   addNSEC3UnhashedRecordToLW(DNSName("was.here."), DNSName("."), "whatever", {}, 600, records, 10, true /* opt out */);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
 
@@ -995,7 +1010,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_ent_opt_out)
   signatureContents.clear();
   records.clear();
   addNSEC3NarrowRecordToLW(DNSName("ent.was.here."), DNSName("."), {QType::RRSIG, QType::NSEC3}, 600, records, 10, true /* opt-out */);
-  recordContents.insert(records.at(0).d_content);
+  recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
 
@@ -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);
@@ -1197,8 +1212,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_cache_validity)
   /* check that the entry has not been cached for longer than the RRSIG validity */
   const ComboAddress who;
   vector<DNSRecord> cached;
-  vector<std::shared_ptr<RRSIGRecordContent>> signatures;
-  BOOST_REQUIRE_EQUAL(g_recCache->get(tnow, target, QType(QType::A), true, &cached, who, 0, boost::none, &signatures), 1);
+  vector<std::shared_ptr<const RRSIGRecordContent>> signatures;
+  BOOST_REQUIRE_EQUAL(g_recCache->get(tnow, target, QType(QType::A), MemRecursorCache::RequireAuth, &cached, who, boost::none, &signatures), 1);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
   BOOST_REQUIRE_EQUAL(signatures.size(), 1U);
   BOOST_CHECK_EQUAL((cached[0].d_ttl - tnow), 1);
@@ -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 48094541e53cd2e1ce92b5658d88f3543da7c42d..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");
@@ -715,7 +718,7 @@ BOOST_AUTO_TEST_CASE(test_lowercase_outgoing)
   BOOST_CHECK_EQUAL(res, RCode::NoError);
 
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
-  BOOST_CHECK_EQUAL(ret[0].d_content->getZoneRepresentation(), cname.toString());
+  BOOST_CHECK_EQUAL(ret[0].getContent()->getZoneRepresentation(), cname.toString());
 
   BOOST_REQUIRE_EQUAL(sentOutQnames.size(), 4U);
   BOOST_CHECK_EQUAL(sentOutQnames[0].toString(), target.makeLowerCase().toString());
@@ -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) {
@@ -764,7 +767,7 @@ BOOST_AUTO_TEST_CASE(test_getDSRecords_multialgo)
   });
 
   dsmap_t ds;
-  auto state = sr->getDSRecords(target, ds, false, 0, false);
+  auto state = sr->getDSRecords(target, ds, false, 0, "", false);
   BOOST_CHECK_EQUAL(state, vState::Secure);
   BOOST_REQUIRE_EQUAL(ds.size(), 1U);
   for (const auto& i : ds) {
@@ -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) {
@@ -817,7 +820,7 @@ BOOST_AUTO_TEST_CASE(test_getDSRecords_multialgo_all_sha)
   });
 
   dsmap_t ds;
-  auto state = sr->getDSRecords(target, ds, false, 0, false);
+  auto state = sr->getDSRecords(target, ds, false, 0, "", false);
   BOOST_CHECK_EQUAL(state, vState::Secure);
   BOOST_REQUIRE_EQUAL(ds.size(), 2U);
   for (const auto& i : ds) {
@@ -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) {
@@ -870,7 +873,7 @@ BOOST_AUTO_TEST_CASE(test_getDSRecords_multialgo_two_highest)
   });
 
   dsmap_t ds;
-  auto state = sr->getDSRecords(target, ds, false, 0, false);
+  auto state = sr->getDSRecords(target, ds, false, 0, "", false);
   BOOST_CHECK_EQUAL(state, vState::Secure);
   BOOST_REQUIRE_EQUAL(ds.size(), 2U);
   for (const auto& i : ds) {
@@ -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");
       }
@@ -937,7 +940,7 @@ BOOST_AUTO_TEST_CASE(test_cname_plus_authority_ns_ttl)
   vector<DNSRecord> cached;
   bool wasAuth = false;
 
-  auto ttl = g_recCache->get(now, DNSName("powerdns.com."), QType(QType::NS), false, &cached, who, 0, boost::none, nullptr, nullptr, nullptr, nullptr, &wasAuth);
+  auto ttl = g_recCache->get(now, DNSName("powerdns.com."), QType(QType::NS), MemRecursorCache::None, &cached, who, boost::none, nullptr, nullptr, nullptr, nullptr, &wasAuth);
   BOOST_REQUIRE_GE(ttl, 1);
   BOOST_REQUIRE_LE(ttl, 42);
   BOOST_CHECK_EQUAL(cached.size(), 1U);
@@ -946,7 +949,7 @@ BOOST_AUTO_TEST_CASE(test_cname_plus_authority_ns_ttl)
   cached.clear();
 
   /* Also check that the the part in additional is still not auth */
-  BOOST_REQUIRE_GE(g_recCache->get(now, DNSName("a.gtld-servers.net."), QType(QType::A), false, &cached, who, 0, boost::none, nullptr, nullptr, nullptr, nullptr, &wasAuth), -1);
+  BOOST_REQUIRE_GE(g_recCache->get(now, DNSName("a.gtld-servers.net."), QType(QType::A), MemRecursorCache::None, &cached, who, boost::none, nullptr, nullptr, nullptr, nullptr, &wasAuth), -1);
   BOOST_CHECK_EQUAL(cached.size(), 1U);
   BOOST_CHECK_EQUAL(wasAuth, false);
 }
@@ -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");
@@ -1016,7 +1017,7 @@ BOOST_AUTO_TEST_CASE(test_bogus_does_not_replace_secure_in_the_cache)
   vector<DNSRecord> cached;
   bool wasAuth = false;
   vState retrievedState = vState::Insecure;
-  BOOST_CHECK_GT(g_recCache->get(now, DNSName("powerdns.com."), QType(QType::SOA), true, &cached, who, false, boost::none, nullptr, nullptr, nullptr, &retrievedState, &wasAuth), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("powerdns.com."), QType(QType::SOA), MemRecursorCache::RequireAuth, &cached, who, boost::none, nullptr, nullptr, nullptr, &retrievedState, &wasAuth), 0);
   BOOST_CHECK_EQUAL(vStateToString(retrievedState), vStateToString(vState::Secure));
   BOOST_CHECK_EQUAL(wasAuth, true);
 
@@ -1026,7 +1027,7 @@ BOOST_AUTO_TEST_CASE(test_bogus_does_not_replace_secure_in_the_cache)
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
 
   cached.clear();
-  BOOST_CHECK_GT(g_recCache->get(now, DNSName("powerdns.com."), QType(QType::SOA), true, &cached, who, false, boost::none, nullptr, nullptr, nullptr, &retrievedState, &wasAuth), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("powerdns.com."), QType(QType::SOA), MemRecursorCache::RequireAuth, &cached, who, boost::none, nullptr, nullptr, nullptr, &retrievedState, &wasAuth), 0);
   BOOST_CHECK_EQUAL(vStateToString(retrievedState), vStateToString(vState::Secure));
   BOOST_CHECK_EQUAL(wasAuth, true);
 }
@@ -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 */
@@ -1067,14 +1068,14 @@ BOOST_AUTO_TEST_CASE(test_records_sanitization_general)
 
   const ComboAddress who;
   vector<DNSRecord> cached;
-  BOOST_CHECK_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, target, QType(QType::A), MemRecursorCache::RequireAuth, &cached, who), 0);
   cached.clear();
-  BOOST_CHECK_LT(g_recCache->get(now, target, QType(QType::AAAA), true, &cached, who), 0);
-  BOOST_CHECK_EQUAL(g_recCache->get(now, DNSName("not-sanitization.powerdns.com."), QType(QType::DNAME), true, &cached, who), -1);
-  BOOST_CHECK_LT(g_recCache->get(now, target, QType(QType::MX), true, &cached, who), 0);
-  BOOST_CHECK_EQUAL(g_recCache->get(now, DNSName("not-sanitization.powerdns.com."), QType(QType::SOA), true, &cached, who), -1);
-  BOOST_CHECK_LT(g_recCache->get(now, target, QType(QType::TXT), false, &cached, who), 0);
-  BOOST_CHECK_EQUAL(g_recCache->get(now, DNSName("powerdns.com."), QType(QType::AAAA), false, &cached, who), -1);
+  BOOST_CHECK_LT(g_recCache->get(now, target, QType(QType::AAAA), MemRecursorCache::RequireAuth, &cached, who), 0);
+  BOOST_CHECK_EQUAL(g_recCache->get(now, DNSName("not-sanitization.powerdns.com."), QType(QType::DNAME), MemRecursorCache::RequireAuth, &cached, who), -1);
+  BOOST_CHECK_LT(g_recCache->get(now, target, QType(QType::MX), MemRecursorCache::RequireAuth, &cached, who), 0);
+  BOOST_CHECK_EQUAL(g_recCache->get(now, DNSName("not-sanitization.powerdns.com."), QType(QType::SOA), MemRecursorCache::RequireAuth, &cached, who), -1);
+  BOOST_CHECK_LT(g_recCache->get(now, target, QType(QType::TXT), MemRecursorCache::None, &cached, who), 0);
+  BOOST_CHECK_EQUAL(g_recCache->get(now, DNSName("powerdns.com."), QType(QType::AAAA), MemRecursorCache::None, &cached, who), -1);
 }
 
 BOOST_AUTO_TEST_CASE(test_records_sanitization_keep_relevant_additional_aaaa)
@@ -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);
@@ -1102,11 +1103,11 @@ BOOST_AUTO_TEST_CASE(test_records_sanitization_keep_relevant_additional_aaaa)
 
   const ComboAddress who;
   vector<DNSRecord> cached;
-  BOOST_CHECK_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, target, QType(QType::A), MemRecursorCache::RequireAuth, &cached, who), 0);
   cached.clear();
   /* not auth since it was in the additional section */
-  BOOST_CHECK_LT(g_recCache->get(now, target, QType(QType::AAAA), true, &cached, who), 0);
-  BOOST_CHECK_GT(g_recCache->get(now, target, QType(QType::AAAA), false, &cached, who), 0);
+  BOOST_CHECK_LT(g_recCache->get(now, target, QType(QType::AAAA), MemRecursorCache::RequireAuth, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, target, QType(QType::AAAA), MemRecursorCache::None, &cached, who), 0);
 }
 
 BOOST_AUTO_TEST_CASE(test_records_sanitization_keep_glue)
@@ -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;
@@ -1162,23 +1161,23 @@ BOOST_AUTO_TEST_CASE(test_records_sanitization_keep_glue)
 
   const ComboAddress who;
   vector<DNSRecord> cached;
-  BOOST_CHECK_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, target, QType(QType::A), MemRecursorCache::RequireAuth, &cached, who), 0);
   cached.clear();
 
-  BOOST_CHECK_GT(g_recCache->get(now, DNSName("com."), QType(QType::NS), false, &cached, who), 0);
-  BOOST_CHECK_GT(g_recCache->get(now, DNSName("a.gtld-servers.net."), QType(QType::A), false, &cached, who), 0);
-  BOOST_CHECK_GT(g_recCache->get(now, DNSName("a.gtld-servers.net."), QType(QType::AAAA), false, &cached, who), 0);
-  BOOST_CHECK_GT(g_recCache->get(now, DNSName("powerdns.com."), QType(QType::NS), false, &cached, who), 0);
-  BOOST_CHECK_GT(g_recCache->get(now, DNSName("pdns-public-ns1.powerdns.com."), QType(QType::A), false, &cached, who), 0);
-  BOOST_CHECK_GT(g_recCache->get(now, DNSName("pdns-public-ns1.powerdns.com."), QType(QType::AAAA), false, &cached, who), 0);
-  BOOST_CHECK_GT(g_recCache->get(now, DNSName("pdns-public-ns2.powerdns.com."), QType(QType::A), false, &cached, who), 0);
-  BOOST_CHECK_GT(g_recCache->get(now, DNSName("pdns-public-ns2.powerdns.com."), QType(QType::AAAA), false, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("com."), QType(QType::NS), MemRecursorCache::None, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("a.gtld-servers.net."), QType(QType::A), MemRecursorCache::None, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("a.gtld-servers.net."), QType(QType::AAAA), MemRecursorCache::None, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("powerdns.com."), QType(QType::NS), MemRecursorCache::None, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("pdns-public-ns1.powerdns.com."), QType(QType::A), MemRecursorCache::None, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("pdns-public-ns1.powerdns.com."), QType(QType::AAAA), MemRecursorCache::None, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("pdns-public-ns2.powerdns.com."), QType(QType::A), MemRecursorCache::None, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("pdns-public-ns2.powerdns.com."), QType(QType::AAAA), MemRecursorCache::None, &cached, who), 0);
 
   cached.clear();
   /* check that we accepted the DS from the parent, and not from the child zone */
-  BOOST_CHECK_GT(g_recCache->get(now, DNSName("powerdns.com."), QType(QType::DS), false, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("powerdns.com."), QType(QType::DS), MemRecursorCache::None, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
-  BOOST_CHECK_EQUAL(cached.at(0).d_content->getZoneRepresentation(), "1 8 2 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
+  BOOST_CHECK_EQUAL(cached.at(0).getContent()->getZoneRepresentation(), "1 8 2 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
 }
 
 BOOST_AUTO_TEST_CASE(test_records_sanitization_scrubs_ns_nxd)
@@ -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);
@@ -1208,12 +1207,12 @@ BOOST_AUTO_TEST_CASE(test_records_sanitization_scrubs_ns_nxd)
 
   const ComboAddress who;
   vector<DNSRecord> cached;
-  BOOST_CHECK_GT(g_recCache->get(now, DNSName("powerdns.com."), QType(QType::SOA), true, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("powerdns.com."), QType(QType::SOA), MemRecursorCache::RequireAuth, &cached, who), 0);
   cached.clear();
 
-  BOOST_CHECK_LT(g_recCache->get(now, DNSName("powerdns.com."), QType(QType::NS), false, &cached, who), 0);
-  BOOST_CHECK_LT(g_recCache->get(now, DNSName("spoofed.ns."), QType(QType::A), false, &cached, who), 0);
-  BOOST_CHECK_LT(g_recCache->get(now, DNSName("spoofed.ns."), QType(QType::AAAA), false, &cached, who), 0);
+  BOOST_CHECK_LT(g_recCache->get(now, DNSName("powerdns.com."), QType(QType::NS), MemRecursorCache::None, &cached, who), 0);
+  BOOST_CHECK_LT(g_recCache->get(now, DNSName("spoofed.ns."), QType(QType::A), MemRecursorCache::None, &cached, who), 0);
+  BOOST_CHECK_LT(g_recCache->get(now, DNSName("spoofed.ns."), QType(QType::AAAA), MemRecursorCache::None, &cached, who), 0);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_validation_referral_on_ds_query_insecure)
@@ -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);
diff --git a/pdns/recursordist/test-xpf_cc.cc b/pdns/recursordist/test-xpf_cc.cc
deleted file mode 100644 (file)
index 02abbad..0000000
+++ /dev/null
@@ -1,176 +0,0 @@
-#define BOOST_TEST_DYN_LINK
-#define BOOST_TEST_NO_MAIN
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-#include <boost/test/unit_test.hpp>
-
-#include "xpf.hh"
-
-BOOST_AUTO_TEST_SUITE(xpf_cc)
-
-BOOST_AUTO_TEST_CASE(test_generateXPFPayload)
-{
-
-  /* Mixing v4 with v6 should throw */
-  BOOST_CHECK_THROW(generateXPFPayload(false, ComboAddress("192.0.2.1"), ComboAddress("2001:db8::1")), std::runtime_error);
-  BOOST_CHECK_THROW(generateXPFPayload(false, ComboAddress("2001:db8::1"), ComboAddress("192.0.2.1")), std::runtime_error);
-
-  {
-    /* v4 payload over UDP */
-    ComboAddress source("192.0.2.1:53");
-    ComboAddress destination("192.0.2.2:65535");
-
-    auto payload = generateXPFPayload(false, source, destination);
-    BOOST_CHECK_EQUAL(payload.size(), 14U);
-    BOOST_CHECK_EQUAL(payload.at(0), 4);
-    BOOST_CHECK_EQUAL(payload.at(1), 17);
-
-    ComboAddress parsedSource;
-    ComboAddress parsedDestination;
-    BOOST_CHECK(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination));
-    BOOST_CHECK_EQUAL(parsedSource.toStringWithPort(), source.toStringWithPort());
-    BOOST_CHECK_EQUAL(parsedDestination.toStringWithPort(), destination.toStringWithPort());
-  }
-
-  {
-    /* v4 payload over TCP */
-    ComboAddress source("192.0.2.1:53");
-    ComboAddress destination("192.0.2.2:65535");
-
-    auto payload = generateXPFPayload(true, source, destination);
-    BOOST_CHECK_EQUAL(payload.size(), 14U);
-    BOOST_CHECK_EQUAL(payload.at(0), 4);
-    BOOST_CHECK_EQUAL(payload.at(1), 6);
-
-    ComboAddress parsedSource;
-    ComboAddress parsedDestination;
-    BOOST_CHECK(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination));
-    BOOST_CHECK_EQUAL(parsedSource.toStringWithPort(), source.toStringWithPort());
-    BOOST_CHECK_EQUAL(parsedDestination.toStringWithPort(), destination.toStringWithPort());
-  }
-
-  {
-    /* v6 payload over UDP */
-    ComboAddress source("[2001:db8::1]:42");
-    ComboAddress destination("[::1]:65535");
-
-    auto payload = generateXPFPayload(false, source, destination);
-    BOOST_CHECK_EQUAL(payload.size(), 38U);
-    BOOST_CHECK_EQUAL(payload.at(0), 6);
-    BOOST_CHECK_EQUAL(payload.at(1), 17);
-
-    ComboAddress parsedSource;
-    ComboAddress parsedDestination;
-    BOOST_CHECK(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination));
-    BOOST_CHECK_EQUAL(parsedSource.toStringWithPort(), source.toStringWithPort());
-    BOOST_CHECK_EQUAL(parsedDestination.toStringWithPort(), destination.toStringWithPort());
-  }
-
-  {
-    /* v6 payload over TCP */
-    ComboAddress source("[2001:db8::1]:42");
-    ComboAddress destination("[::1]:65535");
-
-    auto payload = generateXPFPayload(true, source, destination);
-    BOOST_CHECK_EQUAL(payload.size(), 38U);
-    BOOST_CHECK_EQUAL(payload.at(0), 6);
-    BOOST_CHECK_EQUAL(payload.at(1), 6);
-
-    ComboAddress parsedSource;
-    ComboAddress parsedDestination;
-    BOOST_CHECK(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination));
-    BOOST_CHECK_EQUAL(parsedSource.toStringWithPort(), source.toStringWithPort());
-    BOOST_CHECK_EQUAL(parsedDestination.toStringWithPort(), destination.toStringWithPort());
-  }
-}
-
-BOOST_AUTO_TEST_CASE(test_parseXPFPayload)
-{
-
-  /* invalid sizes */
-  {
-    ComboAddress source;
-    ComboAddress destination;
-
-    BOOST_CHECK_EQUAL(parseXPFPayload(nullptr, 0, source, &destination), false);
-    BOOST_CHECK_EQUAL(parseXPFPayload(nullptr, 13, source, &destination), false);
-    BOOST_CHECK_EQUAL(parseXPFPayload(nullptr, 15, source, &destination), false);
-    BOOST_CHECK_EQUAL(parseXPFPayload(nullptr, 37, source, &destination), false);
-    BOOST_CHECK_EQUAL(parseXPFPayload(nullptr, 39, source, &destination), false);
-  }
-
-  {
-    /* invalid protocol */
-    ComboAddress source("[2001:db8::1]:42");
-    ComboAddress destination("[::1]:65535");
-
-    auto payload = generateXPFPayload(true, source, destination);
-    /* set protocol to 0 */
-    payload.at(1) = 0;
-
-    ComboAddress parsedSource;
-    ComboAddress parsedDestination;
-    BOOST_CHECK_EQUAL(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination), false);
-  }
-
-  {
-    /* invalid version */
-    ComboAddress source("[2001:db8::1]:42");
-    ComboAddress destination("[::1]:65535");
-
-    auto payload = generateXPFPayload(true, source, destination);
-    /* set version to 0 */
-    payload.at(0) = 0;
-
-    ComboAddress parsedSource;
-    ComboAddress parsedDestination;
-    BOOST_CHECK_EQUAL(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination), false);
-  }
-
-  {
-    /* payload too short (v6 size with v4 payload) */
-    ComboAddress source("192.0.2.1:53");
-    ComboAddress destination("192.0.2.2:65535");
-
-    auto payload = generateXPFPayload(true, source, destination);
-    /* set version to 6 */
-    payload.at(0) = 6;
-
-    ComboAddress parsedSource;
-    ComboAddress parsedDestination;
-    BOOST_CHECK_EQUAL(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination), false);
-  }
-
-  {
-    /* payload too long (v6 size with v4 payload) */
-    ComboAddress source("[2001:db8::1]:42");
-    ComboAddress destination("[::1]:65535");
-
-    auto payload = generateXPFPayload(true, source, destination);
-    /* set version to 4 */
-    payload.at(0) = 4;
-
-    ComboAddress parsedSource;
-    ComboAddress parsedDestination;
-    BOOST_CHECK_EQUAL(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination), false);
-  }
-
-  {
-    /* v4 payload over UDP */
-    ComboAddress source("192.0.2.1:53");
-    ComboAddress destination("192.0.2.2:65535");
-
-    auto payload = generateXPFPayload(false, source, destination);
-    BOOST_CHECK_EQUAL(payload.size(), 14U);
-    BOOST_CHECK_EQUAL(payload.at(0), 4);
-    BOOST_CHECK_EQUAL(payload.at(1), 17);
-
-    ComboAddress parsedSource;
-    BOOST_CHECK(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, nullptr));
-    BOOST_CHECK_EQUAL(parsedSource.toStringWithPort(), source.toStringWithPort());
-  }
-}
-
-BOOST_AUTO_TEST_SUITE_END()
index 91af04f225f8944779659b6961f1bcc3eb3b2a59..71e99abd22a368fff530d6d3f430fd9dbe878c18 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-if [ $(ldd pdns_recursor | grep -c libcrypto) -gt 1 ]; then
+if [ $(ldd pdns_recursor | grep -Fc libcrypto.) -gt 1 ]; then
   echo "Error! pdns_recursor is linked against multiple OpenSSL versions!"
   echo "This happens when one dependency is linked to OpenSSL 1.0, while"
   echo "pdns_recursor is linked against OpenSSL 1.1. Please set --with-libcrypto"
index 6d9b09374625defd1a635c6986e694bc3255c02e..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"
@@ -31,6 +33,8 @@
 #include <iomanip>
 #include "logger.hh"
 #include "logging.hh"
+#include "arguments.hh"
+#include "dns_random.hh"
 
 static std::string s_timestampFormat = "%s";
 
@@ -76,6 +80,10 @@ static void loggerBackend(const Logging::Entry& entry)
 
 static bool init_unit_test()
 {
+  ::arg().set("rng") = "auto";
+  ::arg().set("entropy-source") = "/dev/urandom";
+  // Force init while we are still unthreaded
+  dns_random_uint16();
   g_slog = Logging::Logger::create(loggerBackend);
   reportAllTypes();
   return true;
deleted file mode 120000 (symlink)
index 495daa995583088cd4d0eceb4ab6698c42109791..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../validate-recursor.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..d7f2b99c909f983dcb3e1567e6141cd4d1a2bf35
--- /dev/null
@@ -0,0 +1,87 @@
+#include "validate.hh"
+#include "validate-recursor.hh"
+#include "syncres.hh"
+#include "logger.hh"
+#include "rec-lua-conf.hh"
+#include "dnssecinfra.hh"
+#include "dnsseckeeper.hh"
+#include "zoneparser-tng.hh"
+#include "rec-tcounters.hh"
+
+DNSSECMode g_dnssecmode{DNSSECMode::ProcessNoValidate};
+bool g_dnssecLogBogus;
+
+bool checkDNSSECDisabled()
+{
+  return g_dnssecmode == DNSSECMode::Off;
+}
+
+bool warnIfDNSSECDisabled(const string& msg)
+{
+  if (g_dnssecmode == DNSSECMode::Off) {
+    if (!msg.empty()) {
+      auto log = g_slog->withName("config");
+      SLOG(g_log << Logger::Warning << msg << endl,
+           log->info(Logr::Warning, msg));
+    }
+    return true;
+  }
+  return false;
+}
+
+vState increaseDNSSECStateCounter(const vState& state)
+{
+  t_Counters.at(rec::DNSSECHistogram::dnssec).at(state)++;
+  return state;
+}
+
+vState increaseXDNSSECStateCounter(const vState& state)
+{
+  t_Counters.at(rec::DNSSECHistogram::xdnssec).at(state)++;
+  return state;
+}
+
+// Returns true if dsAnchors were modified
+bool updateTrustAnchorsFromFile(const std::string& fname, map<DNSName, dsmap_t>& dsAnchors, Logr::log_t log)
+{
+  map<DNSName, dsmap_t> newDSAnchors;
+  try {
+    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 '" + resourceRecord.qname.toString() + " " + resourceRecord.getZoneRepresentation() + "'");
+        }
+        newDSAnchors[resourceRecord.qname].insert(*dsr);
+      }
+      if (resourceRecord.qtype == QType::DNSKEY) {
+        auto dnskeyr = getRR<DNSKEYRecordContent>(dnsrecord);
+        if (dnskeyr == nullptr) {
+          throw PDNSException("Unable to parse DNSKEY record '" + resourceRecord.qname.toString() + " " + resourceRecord.getZoneRepresentation() + "'");
+        }
+        auto dsr = makeDSFromDNSKey(resourceRecord.qname, *dnskeyr, DNSSECKeeper::DIGEST_SHA256);
+        newDSAnchors[resourceRecord.qname].insert(dsr);
+      }
+    }
+    if (dsAnchors == newDSAnchors) {
+      SLOG(g_log << Logger::Debug << "Read Trust Anchors from file, no changes detected" << endl,
+           log->info(Logr::Debug, "Read Trust Anchors from file, no changes detected"));
+      return false;
+    }
+    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 = std::move(newDSAnchors);
+    return true;
+  }
+  catch (const std::exception& e) {
+    throw PDNSException("Error while reading Trust Anchors from file '" + fname + "': " + e.what());
+  }
+  catch (...) {
+    throw PDNSException("Error while reading Trust Anchors from file '" + fname + "'");
+  }
+}
deleted file mode 120000 (symlink)
index bb3aad95b04c7463488e3f7a23b255dd168ca35a..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../validate-recursor.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..70621628324c8ece641fe6e5213cdbe9a7082d29
--- /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.
+ */
+#pragma once
+#include "namespaces.hh"
+#include "validate.hh"
+#include "logging.hh"
+
+/* Off: 3.x behaviour, we do no DNSSEC, no EDNS
+   ProcessNoValidate: we gather DNSSEC records on all queries, but we will never validate
+   Process: we gather DNSSEC records on all queries, if you do ad=1, we'll validate for you (unless you set cd=1)
+   ValidateForLog: Process + validate all answers, but only log failures
+   ValidateAll: DNSSEC issue -> servfail
+*/
+
+enum class DNSSECMode
+{
+  Off,
+  Process,
+  ProcessNoValidate,
+  ValidateForLog,
+  ValidateAll
+};
+extern DNSSECMode g_dnssecmode;
+extern bool g_dnssecLogBogus;
+
+bool checkDNSSECDisabled();
+bool warnIfDNSSECDisabled(const string& msg);
+vState increaseDNSSECStateCounter(const vState& state);
+vState increaseXDNSSECStateCounter(const vState& state);
+bool updateTrustAnchorsFromFile(const std::string& fname, map<DNSName, dsmap_t>& dsAnchors, Logr::log_t);
deleted file mode 120000 (symlink)
index c931bf2da5dadcd73f8e6f4d1e61c56bd0fd224c..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../ws-recursor.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..af2cbfee3ea25517e16594abd1100ffdb7e69f1b
--- /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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "ws-recursor.hh"
+#include "json.hh"
+
+#include <string>
+#include "namespaces.hh"
+#include <iostream>
+#include "iputils.hh"
+#include "rec_channel.hh"
+#include "rec_metrics.hh"
+#include "arguments.hh"
+#include "misc.hh"
+#include "syncres.hh"
+#include "dnsparser.hh"
+#include "json11.hpp"
+#include "webserver.hh"
+#include "ws-api.hh"
+#include "logger.hh"
+#include "logging.hh"
+#include "rec-lua-conf.hh"
+#include "rpzloader.hh"
+#include "uuid-utils.hh"
+#include "tcpiohandler.hh"
+#include "rec-main.hh"
+#include "settings/cxxsettings.hh"
+
+using json11::Json;
+
+void productServerStatisticsFetch(map<string, string>& out)
+{
+  auto stats = getAllStatsMap(StatComponent::API);
+  map<string, string> ret;
+  for (const auto& entry : stats) {
+    ret.emplace(entry.first, entry.second.d_value);
+  }
+  out.swap(ret);
+}
+
+std::optional<uint64_t> productServerStatisticsFetch(const std::string& name)
+{
+  return getStatByName(name);
+}
+
+static void apiWriteConfigFile(const string& filebasename, const string& content)
+{
+  if (::arg()["api-config-dir"].empty()) {
+    throw ApiException("Config Option \"api-config-dir\" must be set");
+  }
+
+  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());
+  }
+  ofconf << "# Generated by pdns-recursor REST API, DO NOT EDIT" << endl;
+  ofconf << content << endl;
+  ofconf.close();
+}
+
+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)
+{
+  const auto& document = req->json();
+
+  const auto& jlist = document["value"];
+
+  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 {
+        nmg.addMask(value.string_value());
+      }
+      catch (const NetmaskException& e) {
+        throw ApiException(e.reason);
+      }
+    }
+
+    ostringstream strStream;
+
+    // Clear <foo>-from-file if set, so our changes take effect
+    strStream << aclType << "-file=" << endl;
+
+    // Clear ACL setting, and provide a "parent" value
+    strStream << aclType << "=" << endl;
+    strStream << aclType << "+=" << nmg.toString() << endl;
+
+    apiWriteConfigFile(aclType, strStream.str());
+  }
+
+  parseACLs();
+
+  apiServerConfigACLGET(aclType, req, resp);
+}
+
+static void apiServerConfigAllowFromGET(HttpRequest* req, HttpResponse* resp)
+{
+  apiServerConfigACLGET("allow-from", req, resp);
+}
+
+static void apiServerConfigAllowNotifyFromGET(HttpRequest* req, HttpResponse* resp)
+{
+  apiServerConfigACLGET("allow-notify-from", req, resp);
+}
+
+static void apiServerConfigAllowFromPUT(HttpRequest* req, HttpResponse* resp)
+{
+  apiServerConfigACLPUT("allow-from", req, resp);
+}
+
+static void apiServerConfigAllowNotifyFromPUT(HttpRequest* req, HttpResponse* 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()) {
+    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.emplace_back(server.toStringWithPort());
+  }
+
+  Json::array records;
+  for (const SyncRes::AuthDomain::records_t::value_type& record : zone.d_records) {
+    records.push_back(Json::object{
+      {"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)
+  string zoneId = apiZoneNameToId(iter->first);
+  Json::object doc = {
+    {"id", zoneId},
+    {"url", "/api/v1/servers/localhost/zones/" + zoneId},
+    {"name", iter->first.toString()},
+    {"kind", zone.d_servers.empty() ? "Native" : "Forwarded"},
+    {"servers", servers},
+    {"recursion_desired", zone.d_servers.empty() ? false : zone.d_rdForward},
+    {"records", records}};
+
+  resp->setJsonBody(doc);
+}
+
+static void doCreateZone(const Json& document)
+{
+  if (::arg()["api-config-dir"].empty()) {
+    throw ApiException("Config Option \"api-config-dir\" must be set");
+  }
+
+  const DNSName zone = apiNameToDNSName(stringFromJson(document, "name"));
+  const string zonename = zone.toString();
+  apiCheckNameAllowedCharacters(zonename);
+
+  string singleIPTarget = document["single_target_ip"].string_value();
+  string kind = toUpper(stringFromJson(document, "kind"));
+  bool rdFlag = boolFromJson(document, "recursion_desired");
+  string confbasename = "zone-" + apiZoneNameToId(zone);
+
+  const string yamlAPiZonesFile = ::arg()["api-config-dir"] + "/apizones";
+
+  if (kind == "NATIVE") {
+    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) {
+          throw ApiException("");
+        }
+        singleIPTarget = rem.toString();
+      }
+      catch (...) {
+        throw ApiException("Single IP target '" + singleIPTarget + "' is invalid");
+      }
+    }
+    string zonefilename = ::arg()["api-config-dir"] + "/" + confbasename + ".zone";
+    ofstream ofzone(zonefilename.c_str());
+    if (!ofzone) {
+      throw ApiException("Could not open '" + zonefilename + "' for writing: " + stringerror());
+    }
+    ofzone << "; Generated by pdns-recursor REST API, DO NOT EDIT" << endl;
+    ofzone << zonename << "\tIN\tSOA\tlocal.zone.\thostmaster." << zonename << " 1 1 1 1 1" << endl;
+    if (!singleIPTarget.empty()) {
+      ofzone << zonename << "\t3600\tIN\tA\t" << singleIPTarget << endl;
+      ofzone << "*." << zonename << "\t3600\tIN\tA\t" << singleIPTarget << endl;
+    }
+    ofzone.close();
+
+    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") {
+    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());
+      }
+      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);
+        }
+      }
+      if (serverlist.empty()) {
+        throw ApiException("Need at least one upstream server when forwarding");
+      }
+
+      if (rdFlag) {
+        apiWriteConfigFile(confbasename, "forward-zones-recurse+=" + zonename + "=" + serverlist);
+      }
+      else {
+        apiWriteConfigFile(confbasename, "forward-zones+=" + zonename + "=" + serverlist);
+      }
+    }
+  }
+  else {
+    throw ApiException("invalid kind");
+  }
+}
+
+static bool doDeleteZone(const DNSName& zonename)
+{
+  if (::arg()["api-config-dir"].empty()) {
+    throw ApiException("Config Option \"api-config-dir\" must be set");
+  }
+
+  string filename;
+  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());
+
+  return true;
+}
+
+static void apiServerZonesPOST(HttpRequest* req, HttpResponse* resp)
+{
+  if (::arg()["api-config-dir"].empty()) {
+    throw ApiException("Config Option \"api-config-dir\" must be set");
+  }
+
+  Json document = req->json();
+
+  DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
+
+  const auto& iter = SyncRes::t_sstorage.domainmap->find(zonename);
+  if (iter != SyncRes::t_sstorage.domainmap->cend()) {
+    throw ApiException("Zone already exists");
+  }
+
+  doCreateZone(document);
+  reloadZoneConfiguration(g_yamlSettings);
+  fillZone(zonename, resp);
+  resp->status = 201;
+}
+
+static void apiServerZonesGET(HttpRequest* /* req */, HttpResponse* resp)
+{
+  Json::array doc;
+  for (const auto& val : *SyncRes::t_sstorage.domainmap) {
+    const SyncRes::AuthDomain& zone = val.second;
+    Json::array servers;
+    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);
+    doc.push_back(Json::object{
+      {"id", zoneId},
+      {"url", "/api/v1/servers/localhost/zones/" + zoneId},
+      {"name", val.first.toString()},
+      {"kind", zone.d_servers.empty() ? "Native" : "Forwarded"},
+      {"servers", servers},
+      {"recursion_desired", zone.d_servers.empty() ? false : zone.d_rdForward}});
+  }
+  resp->setJsonBody(doc);
+}
+
+static inline DNSName findZoneById(HttpRequest* req)
+{
+  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;
+}
+
+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
+}
+
+static void apiServerZoneDetailDELETE(HttpRequest* req, HttpResponse* resp)
+{
+  auto zonename = findZoneById(req);
+  if (!doDeleteZone(zonename)) {
+    throw ApiException("Deleting domain failed");
+  }
+
+  reloadZoneConfiguration(g_yamlSettings);
+  // empty body on success
+  resp->body = "";
+  resp->status = 204; // No Content: declare that the zone is gone now
+}
+
+static void apiServerZoneDetailGET(HttpRequest* req, HttpResponse* resp)
+{
+  auto zonename = findZoneById(req);
+  fillZone(zonename, resp);
+}
+
+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, qVar) != string::npos) {
+      doc.push_back(Json::object{
+        {"type", "zone"},
+        {"zone_id", zoneId},
+        {"name", zoneName}});
+    }
+
+    // if zone name is an exact match, don't bother with returning all records/comments in it
+    if (val.first == DNSName(qVar)) {
+      continue;
+    }
+
+    const SyncRes::AuthDomain& zone = val.second;
+
+    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", resourceRec.d_name.toString()},
+        {"content", resourceRec.getContent()->getZoneRepresentation()}});
+    }
+  }
+  resp->setJsonBody(doc);
+}
+
+static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp)
+{
+  DNSName canon = apiNameToDNSName(req->getvars["domain"]);
+  bool subtree = req->getvars.count("subtree") > 0 && req->getvars["subtree"] == "true";
+  uint16_t qtype = 0xffff;
+  if (req->getvars.count("type") != 0) {
+    qtype = QType::chartocode(req->getvars["type"].c_str());
+  }
+
+  struct WipeCacheResult res = wipeCaches(canon, subtree, qtype);
+  resp->setJsonBody(Json::object{
+    {"count", res.record_count + res.packet_count + res.negative_record_count},
+    {"result", "Flushed cache."}});
+}
+
+static void apiServerRPZStats(HttpRequest* /* req */, HttpResponse* resp)
+{
+  auto luaconf = g_luaconfs.getLocal();
+  auto numZones = luaconf->dfe.size();
+
+  Json::object ret;
+
+  for (size_t i = 0; i < numZones; i++) {
+    auto zone = luaconf->dfe.getZone(i);
+    if (zone == nullptr) {
+      continue;
+    }
+    const auto& name = zone->getName();
+    auto stats = getRPZZoneStats(name);
+    if (stats == nullptr) {
+      continue;
+    }
+    Json::object zoneInfo = {
+      {"transfers_failed", (double)stats->d_failedTransfers},
+      {"transfers_success", (double)stats->d_successfulTransfers},
+      {"transfers_full", (double)stats->d_fullTransfers},
+      {"records", (double)stats->d_numberOfRecords},
+      {"last_update", (double)stats->d_lastUpdate},
+      {"serial", (double)stats->d_serial},
+    };
+    ret[name] = zoneInfo;
+  }
+  resp->setJsonBody(ret);
+}
+
+static void prometheusMetrics(HttpRequest* /* req */, HttpResponse* resp)
+{
+  static MetricDefinitionStorage s_metricDefinitions;
+
+  std::ostringstream output;
+
+  // Argument controls disabling of any stats. So
+  // stats-api-disabled-list will be used to block returned stats.
+  auto varmap = getAllStatsMap(StatComponent::API);
+  for (const auto& tup : varmap) {
+    std::string metricName = tup.first;
+    std::string prometheusMetricName = tup.second.d_prometheusName;
+    std::string helpname = tup.second.d_prometheusName;
+    MetricDefinition metricDetails;
+
+    if (s_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
+      std::string prometheusTypeName = MetricDefinitionStorage::getPrometheusStringMetricType(
+        metricDetails.d_prometheusType);
+
+      if (prometheusTypeName.empty()) {
+        continue;
+      }
+      if (metricDetails.d_prometheusType == PrometheusMetricType::multicounter) {
+        helpname = prometheusMetricName.substr(0, prometheusMetricName.find('{'));
+      }
+      else if (metricDetails.d_prometheusType == PrometheusMetricType::histogram) {
+        helpname = prometheusMetricName.substr(0, prometheusMetricName.find('{'));
+        // name is XXX_count, strip the _count part
+        helpname = helpname.substr(0, helpname.length() - 6);
+      }
+      output << "# TYPE " << helpname << " " << prometheusTypeName << "\n";
+      output << "# HELP " << helpname << " " << metricDetails.d_description << "\n";
+    }
+    output << prometheusMetricName << " " << tup.second.d_value << "\n";
+  }
+
+  output << "# HELP pdns_recursor_info "
+         << "Info from pdns_recursor, value is always 1"
+         << "\n";
+  output << "# TYPE pdns_recursor_info "
+         << "gauge"
+         << "\n";
+  output << "pdns_recursor_info{version=\"" << VERSION << "\"} "
+         << "1"
+         << "\n";
+
+  resp->body = output.str();
+  resp->headers["Content-Type"] = "text/plain";
+  resp->status = 200;
+}
+
+#include "htmlfiles.h"
+
+static void serveStuff(HttpRequest* req, HttpResponse* resp)
+{
+  resp->headers["Cache-Control"] = "max-age=86400";
+
+  if (req->url.path == "/") {
+    req->url.path = "/index.html";
+  }
+
+  const string charset = "; charset=utf-8";
+  if (boost::ends_with(req->url.path, ".html")) {
+    resp->headers["Content-Type"] = "text/html" + charset;
+  }
+  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")) {
+    resp->headers["Content-Type"] = "application/javascript" + charset;
+  }
+  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";
+  resp->headers["X-Permitted-Cross-Domain-Policies"] = "none";
+
+  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.substr(1)) != 0)) {
+    resp->body = g_urlmap.at(req->url.path.substr(1));
+    resp->status = 200;
+  }
+  else {
+    resp->status = 404;
+  }
+}
+
+const std::map<std::string, MetricDefinition> MetricDefinitionStorage::d_metrics = {
+  {"all-outqueries",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of outgoing queries since starting")},
+
+  {"answers-slow",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries answered after 1 second")},
+  {"answers0-1",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries answered within 1 millisecond")},
+  {"answers1-10",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries answered within 10 milliseconds")},
+  {"answers10-100",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries answered within 100 milliseconds")},
+  {"answers100-1000",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries answered within 1 second")},
+
+  {"auth4-answers-slow",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries answered by authoritatives over IPv4 after 1 second")},
+  {"auth4-answers0-1",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries answered by authoritatives over IPv4within 1 millisecond")},
+  {"auth4-answers1-10",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries answered by authoritatives over IPv4 within 10 milliseconds")},
+  {"auth4-answers10-100",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries answered by authoritatives over IPv4 within 100 milliseconds")},
+  {"auth4-answers100-1000",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries answered by authoritatives over IPv4 within 1 second")},
+
+  {"auth6-answers-slow",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries answered by authoritatives over IPv6 after 1 second")},
+  {"auth6-answers0-1",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries answered by authoritatives over IPv6 within 1 millisecond")},
+  {"auth6-answers1-10",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries answered by authoritatives over IPv6 within 10 milliseconds")},
+  {"auth6-answers10-100",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries answered by authoritatives over IPv6 within 100 milliseconds")},
+  {"auth6-answers100-1000",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries answered by authoritatives over IPv6 within 1 second")},
+
+  {"auth-zone-queries",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries to locally hosted authoritative zones (`setting-auth-zones`) since starting")},
+  {"cache-bytes",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Size of the cache in bytes")},
+  {"cache-entries",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Number of entries in the cache")},
+  {"cache-hits",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of of cache hits since starting, this does **not** include hits that got answered from the packet-cache")},
+  {"cache-misses",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of cache misses since starting")},
+  {"case-mismatches",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of mismatches in character case since starting")},
+  {"chain-resends",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries chained to existing outstanding")},
+  {"client-parse-errors",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of client packets that could not be parsed")},
+  {"concurrent-queries",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Number of MThreads currently running")},
+
+  // For multicounters, state the first
+  {"cpu-msec-thread-0",
+   MetricDefinition(PrometheusMetricType::multicounter,
+                    "Number of milliseconds spent in thread n")},
+
+  {"zone-disallowed-notify",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of NOTIFY operations denied because of allow-notify-for restrictions")},
+  {"dnssec-authentic-data-queries",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries received with the AD bit set")},
+  {"dnssec-check-disabled-queries",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries received with the CD bit set")},
+  {"dnssec-queries",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries received with the DO bit set")},
+  {"dnssec-result-bogus",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state")},
+  {"dnssec-result-indeterminate",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Indeterminate state")},
+  {"dnssec-result-insecure",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Insecure state")},
+  {"dnssec-result-nta",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the (negative trust anchor) state")},
+  {"dnssec-result-secure",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Secure state")},
+  {"x-dnssec-result-bogus",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state")},
+  {"x-dnssec-result-indeterminate",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Indeterminate state")},
+  {"x-dnssec-result-insecure",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Insecure state")},
+  {"x-dnssec-result-nta",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the (negative trust anchor) state")},
+  {"x-dnssec-result-secure",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Secure state")},
+
+  {"dnssec-validations",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, for which a DNSSEC validation was requested by either the client or the configuration")},
+  {"dont-outqueries",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of outgoing queries dropped because of `setting-dont-query` setting")},
+  {"qname-min-fallback-success",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of successful queries due to fallback mechanism within 'qname-minimization' setting")},
+  {"ecs-queries",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of outgoing queries adorned with an EDNS Client Subnet option")},
+  {"ecs-responses",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses received from authoritative servers with an EDNS Client Subnet option we used")},
+  {"edns-ping-matches",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of servers that sent a valid EDNS PING response")},
+  {"edns-ping-mismatches",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of servers that sent an invalid EDNS PING response")},
+  {"failed-host-entries",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Number of entries in the failed NS cache")},
+  {"non-resolving-nameserver-entries",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Number of entries in the non-resolving NS name cache")},
+  {"ignored-packets",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of non-query packets received on server sockets that should only get query packets")},
+  {"ipv6-outqueries",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of outgoing queries over IPv6")},
+  {"ipv6-questions",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of end-user initiated queries with the RD bit set, received over IPv6 UDP")},
+  {"malloc-bytes",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of bytes allocated by the process (broken, always returns 0)")},
+  {"max-cache-entries",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Currently configured maximum number of cache entries")},
+  {"max-packetcache-entries",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Currently configured maximum number of packet cache entries")},
+  {"max-mthread-stack",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Maximum amount of thread stack ever used")},
+
+  {"negcache-entries",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Number of entries in the negative answer cache")},
+  {"no-packet-error",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of erroneous received packets")},
+  {"nod-lookups-dropped-oversize",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of NOD lookups dropped because they would exceed the maximum name length")},
+  {"noedns-outqueries",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries sent out without EDNS")},
+  {"noerror-answers",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of NOERROR answers since starting")},
+  {"noping-outqueries",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries sent out without ENDS PING")},
+  {"nsset-invalidations",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of times an nsset was dropped because it no longer worked")},
+  {"nsspeeds-entries",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Number of entries in the NS speeds map")},
+  {"nxdomain-answers",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of NXDOMAIN answers since starting")},
+  {"outgoing-timeouts",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of timeouts on outgoing UDP queries since starting")},
+  {"outgoing4-timeouts",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of timeouts on outgoing UDP IPv4 queries since starting")},
+  {"outgoing6-timeouts",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of timeouts on outgoing UDP IPv6 queries since starting")},
+  {"over-capacity-drops",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of questions dropped because over maximum concurrent query limit")},
+  {"packetcache-bytes",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Size of the packet cache in bytes")},
+  {"packetcache-entries",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Number of packet cache entries")},
+  {"packetcache-hits",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of packet cache hits")},
+  {"packetcache-misses",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of packet cache misses")},
+
+  {"policy-drops",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of packets dropped because of (Lua) policy decision")},
+  {"policy-result-noaction",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of packets that were not acted upon by the RPZ/filter engine")},
+  {"policy-result-drop",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of packets that were dropped by the RPZ/filter engine")},
+  {"policy-result-nxdomain",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of packets that were replied to with NXDOMAIN by the RPZ/filter engine")},
+  {"policy-result-nodata",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of packets that were replied to with no data by the RPZ/filter engine")},
+  {"policy-result-truncate",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of packets that were were forced to TCP by the RPZ/filter engine")},
+  {"policy-result-custom",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of packets that were sent a custom answer by the RPZ/filter engine")},
+
+  {"qa-latency",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Shows the current latency average, in microseconds, exponentially weighted over past 'latency-statistic-size' packets")},
+  {"query-pipe-full-drops",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of questions dropped because the query distribution pipe was full")},
+  {"questions",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Counts all end-user initiated queries with the RD bit set")},
+  {"rebalanced-queries",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries balanced to a different worker thread because the first selected one was above the target load configured with 'distribution-load-factor'")},
+  {"resource-limits",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of queries that could not be performed because of resource limits")},
+  {"security-status",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "security status based on `securitypolling`")},
+  {"server-parse-errors",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of server replied packets that could not be parsed")},
+  {"servfail-answers",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of SERVFAIL answers since starting")},
+  {"spoof-prevents",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of times PowerDNS considered itself spoofed, and dropped the data")},
+  {"sys-msec",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of CPU milliseconds spent in 'system' mode")},
+  {"tcp-client-overflow",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of times an IP address was denied TCP access because it already had too many connections")},
+  {"tcp-clients",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Number of currently active TCP/IP clients")},
+  {"tcp-outqueries",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of outgoing TCP queries since starting")},
+  {"tcp-questions",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of all incoming TCP queries since starting")},
+  {"throttle-entries",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Number of of entries in the throttle map")},
+  {"throttled-out",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of throttled outgoing UDP queries since starting")},
+  {"throttled-outqueries",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of throttled outgoing UDP queries since starting")},
+  {"too-old-drops",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of questions dropped that were too old")},
+  {"truncated-drops",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of questions dropped because they were larger than 512 bytes")},
+  {"empty-queries",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Questions dropped because they had a QD count of 0")},
+  {"unauthorized-tcp",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of TCP questions denied because of allow-from restrictions")},
+  {"unauthorized-udp",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of UDP questions denied because of allow-from restrictions")},
+  {"source-disallowed-notify",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of NOTIFY operations denied because of allow-notify-from restrictions")},
+  {"unexpected-packets",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of answers from remote servers that were unexpected (might point to spoofing)")},
+  {"unreachables",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of times nameservers were unreachable since starting")},
+  {"uptime",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of seconds process has been running")},
+  {"user-msec",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of CPU milliseconds spent in 'user' mode")},
+  {"variable-responses",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses that were marked as 'variable'")},
+
+  {"x-our-latency",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Shows the averaged time spent within PowerDNS, in microseconds, exponentially weighted over past 'latency-statistic-size' packets")},
+  {"x-ourtime0-1",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Counts responses where between 0 and 1 milliseconds was spent within the Recursor")},
+  {"x-ourtime1-2",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Counts responses where between 1 and 2 milliseconds was spent within the Recursor")},
+  {"x-ourtime2-4",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Counts responses where between 2 and 4 milliseconds was spent within the Recursor")},
+  {"x-ourtime4-8",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Counts responses where between 4 and 8 milliseconds was spent within the Recursor")},
+  {"x-ourtime8-16",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Counts responses where between 8 and 16 milliseconds was spent within the Recursor")},
+  {"x-ourtime16-32",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Counts responses where between 16 and 32 milliseconds was spent within the Recursor")},
+  {"x-ourtime-slow",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Counts responses where more than 32 milliseconds was spent within the Recursor")},
+
+  {"fd-usage",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Number of open file descriptors")},
+  {"real-memory-usage",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Number of bytes real process memory usage")},
+  {"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 InErrors")},
+  {"udp6-noport-errors",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "From /proc/net/snmp6 NoPorts")},
+  {"udp6-recvbuf-errors",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "From /proc/net/snmp6 RcvbufErrors")},
+  {"udp6-sndbuf-errors",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "From /proc/net/snmp6 SndbufErrors")},
+  {"udp6-in-csum-errors",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "From /proc/net/snmp6 InCsumErrors")},
+
+  {"cpu-iowait",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Time spent waiting for I/O to complete by the whole system, in units of USER_HZ")},
+  {"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")},
+
+  {"dnssec-result-bogus-invalid-denial",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because a valid denial of existence proof could not be found")},
+
+  {"dnssec-result-bogus-invalid-dnskey-protocol",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because all DNSKEYs had invalid protocols")},
+
+  {"dnssec-result-bogus-missing-negative-indication",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because a NODATA or NXDOMAIN answer lacked the required SOA and/or NSEC(3) records")},
+
+  {"dnssec-result-bogus-no-rrsig",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because required RRSIG records were not present in an answer")},
+
+  {"dnssec-result-bogus-no-valid-dnskey",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because a valid DNSKEY could not be found")},
+
+  {"dnssec-result-bogus-no-valid-rrsig",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because only invalid RRSIG records were present in an answer")},
+
+  {"dnssec-result-bogus-no-zone-key-bit-set",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because no DNSKEY with the Zone Key bit set was found")},
+
+  {"dnssec-result-bogus-revoked-dnskey",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because all DNSKEYs were revoked")},
+
+  {"dnssec-result-bogus-self-signed-ds",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because a DS record was signed by itself")},
+
+  {"dnssec-result-bogus-signature-expired",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because the signature expired time in the RRSIG was in the past")},
+
+  {"dnssec-result-bogus-signature-not-yet-valid",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because the signature inception time in the RRSIG was not yet valid")},
+
+  {"dnssec-result-bogus-unable-to-get-dnskeys",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because a valid DNSKEY could not be retrieved")},
+
+  {"dnssec-result-bogus-unable-to-get-dss",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because a valid DS could not be retrieved")},
+  {"dnssec-result-bogus-unsupported-dnskey-algo",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because a DNSKEY RRset contained only unsupported DNSSEC algorithms")},
+
+  {"dnssec-result-bogus-unsupported-ds-digest-type",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because a DS RRset contained only unsupported digest types")},
+  {"x-dnssec-result-bogus-invalid-denial",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because a valid denial of existence proof could not be found")},
+
+  {"x-dnssec-result-bogus-invalid-dnskey-protocol",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because all DNSKEYs had invalid protocols")},
+
+  {"x-dnssec-result-bogus-missing-negative-indication",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because a NODATA or NXDOMAIN answer lacked the required SOA and/or NSEC(3) records")},
+
+  {"x-dnssec-result-bogus-no-rrsig",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because required RRSIG records were not present in an answer")},
+
+  {"x-dnssec-result-bogus-no-valid-dnskey",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because a valid DNSKEY could not be found")},
+
+  {"x-dnssec-result-bogus-no-valid-rrsig",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because only invalid RRSIG records were present in an answer")},
+
+  {"x-dnssec-result-bogus-no-zone-key-bit-set",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because no DNSKEY with the Zone Key bit set was found")},
+
+  {"x-dnssec-result-bogus-revoked-dnskey",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because all DNSKEYs were revoked")},
+
+  {"x-dnssec-result-bogus-self-signed-ds",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because a DS record was signed by itself")},
+
+  {"x-dnssec-result-bogus-signature-expired",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because the signature expired time in the RRSIG was in the past")},
+
+  {"x-dnssec-result-bogus-signature-not-yet-valid",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because the signature inception time in the RRSIG was not yet valid")},
+
+  {"x-dnssec-result-bogus-unable-to-get-dnskeys",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because a valid DNSKEY could not be retrieved")},
+
+  {"x-dnssec-result-bogus-unable-to-get-dss",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because a valid DS could not be retrieved")},
+  {"x-dnssec-result-bogus-unsupported-dnskey-algo",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because a DNSKEY RRset contained only unsupported DNSSEC algorithms")},
+
+  {"x-dnssec-result-bogus-unsupported-ds-digest-type",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of responses sent, packet-cache hits excluded, that were in the Bogus state because a DS RRset contained only unsupported digest types")},
+
+  {"proxy-protocol-invalid",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of invalid proxy-protocol headers received")},
+
+  {"record-cache-acquired",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of record cache lock acquisitions")},
+
+  {"record-cache-contended",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of contended record cache lock acquisitions")},
+
+  {"packetcache-acquired",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of packet cache lock acquisitions")},
+
+  {"packetcache-contended",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of contended packet cache lock acquisitions")},
+
+  {"taskqueue-expired",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of tasks expired before they could be run")},
+
+  {"taskqueue-pushed",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of tasks pushed to the taskqueues")},
+
+  {"taskqueue-size",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Number of tasks currently in the taskqueue")},
+
+  {"dot-outqueries",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of outgoing DoT queries since starting")},
+
+  {"dns64-prefix-answers",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of AAAA and PTR generated by a matching dns64-prefix")},
+  {"aggressive-nsec-cache-entries",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of entries in the aggressive NSEC cache")},
+
+  {"aggressive-nsec-cache-nsec-hits",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of NSEC-related hits from the aggressive NSEC cache")},
+
+  {"aggressive-nsec-cache-nsec-wc-hits",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of answers synthesized from the NSEC aggressive cache")},
+
+  {"aggressive-nsec-cache-nsec3-hits",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of NSEC3-related hits from the aggressive NSEC cache")},
+
+  {"aggressive-nsec-cache-nsec3-wc-hits",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of answers synthesized from the NSEC3 aggressive cache")},
+
+  // For cumulative histogram, state the xxx_count name where xxx matches the name in rec_channel_rec
+  {"cumul-clientanswers-count",
+   MetricDefinition(PrometheusMetricType::histogram,
+                    "histogram of our answer times to clients")},
+  // For cumulative histogram, state the xxx_count name where xxx matches the name in rec_channel_rec
+  {"cumul-authanswers-count4",
+   MetricDefinition(PrometheusMetricType::histogram,
+                    "histogram of answer times of authoritative servers")},
+  {"almost-expired-pushed",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of almost-expired tasks pushed")},
+
+  {"almost-expired-run",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of almost-expired tasks run to completion")},
+
+  {"almost-expired-exceptions",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of almost-expired tasks that caused an exception")},
+
+  // For multicounters, state the first
+  {"policy-hits",
+   MetricDefinition(PrometheusMetricType::multicounter,
+                    "Number of filter or RPZ policy hits")},
+
+  {"idle-tcpout-connections",
+   MetricDefinition(PrometheusMetricType::gauge,
+                    "Number of connections in the TCP idle outgoing connections pool")},
+
+  {"maintenance-usec",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Time spent doing internal maintenance, including Lua maintenance")},
+
+  {"maintenance-calls",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Number of times internal maintenance has been called, including Lua maintenance")},
+
+  // For multicounters, state the first
+  {"proxy-mapping-total-n-0",
+   MetricDefinition(PrometheusMetricType::multicounter,
+                    "Number of queries matching proxyMappings")},
+
+  // For multicounters, state the first
+  {"auth-formerr-answers",
+   MetricDefinition(PrometheusMetricType::multicounter,
+                    "Count of RCodes returned by authoritative servers")},
+
+  // For multicounters, state the first
+  {"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")},
+};
+
+constexpr bool CHECK_PROMETHEUS_METRICS = false;
+
+static void validatePrometheusMetrics()
+{
+  MetricDefinitionStorage s_metricDefinitions;
+
+  auto varmap = getAllStatsMap(StatComponent::API);
+  for (const auto& tup : varmap) {
+    std::string metricName = tup.first;
+    if (metricName.find("cpu-msec-") == 0) {
+      continue;
+    }
+    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)) {
+      SLOG(g_log << Logger::Debug << "{ \"" << metricName << "\", MetricDefinition(PrometheusMetricType::counter, \"\")}," << endl,
+           g_slog->info(Logr::Debug, "{ \"" + metricName + "\", MetricDefinition(PrometheusMetricType::counter, \"\")},"));
+    }
+  }
+}
+
+RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm)
+{
+  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"));
+
+  d_ws->setApiKey(arg()["api-key"], arg().mustDo("webserver-hash-plaintext-credentials"));
+  d_ws->setPassword(arg()["webserver-password"], arg().mustDo("webserver-hash-plaintext-credentials"));
+  d_ws->setLogLevel(arg()["webserver-loglevel"]);
+
+  NetmaskGroup acl;
+  acl.toMasks(::arg()["webserver-allow-from"]);
+  d_ws->setACL(acl);
+
+  d_ws->bind();
+
+  // legacy dispatch
+  d_ws->registerApiHandler(
+    "/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, "GET");
+  d_ws->registerWebHandler("/metrics", prometheusMetrics, "GET");
+  d_ws->go();
+}
+
+void RecursorWebServer::jsonstat(HttpRequest* req, HttpResponse* resp)
+{
+  string command;
+
+  if (req->getvars.count("command") != 0) {
+    command = req->getvars["command"];
+    req->getvars.erase("command");
+  }
+
+  map<string, string> stats;
+  if (command == "get-query-ring") {
+    typedef pair<DNSName, uint16_t> query_t;
+    vector<query_t> queries;
+    bool filter = !req->getvars["public-filtered"].empty();
+
+    if (req->getvars["name"] == "servfail-queries") {
+      queries = broadcastAccFunction<vector<query_t>>(pleaseGetServfailQueryRing);
+    }
+    else if (req->getvars["name"] == "bogus-queries") {
+      queries = broadcastAccFunction<vector<query_t>>(pleaseGetBogusQueryRing);
+    }
+    else if (req->getvars["name"] == "queries") {
+      queries = broadcastAccFunction<vector<query_t>>(pleaseGetQueryRing);
+    }
+
+    typedef map<query_t, unsigned int> counts_t;
+    counts_t counts;
+    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 (const auto& count : counts) {
+      rcounts.emplace(-count.second, count.first);
+    }
+
+    Json::array entries;
+    unsigned int tot = 0;
+    unsigned int totIncluded = 0;
+    for (const rcounts_t::value_type& count : rcounts) {
+      totIncluded -= count.first;
+      entries.push_back(Json::array{
+        -count.first, count.second.first.toLogString(), DNSRecordContent::NumberToType(count.second.second)});
+      if (tot++ >= 100) {
+        break;
+      }
+    }
+    if (queries.size() != totIncluded) {
+      entries.push_back(Json::array{
+        (int)(queries.size() - totIncluded), "", ""});
+    }
+    resp->setJsonBody(Json::object{{"entries", entries}});
+    return;
+  }
+  if (command == "get-remote-ring") {
+    vector<ComboAddress> queries;
+    if (req->getvars["name"] == "remotes") {
+      queries = broadcastAccFunction<vector<ComboAddress>>(pleaseGetRemotes);
+    }
+    else if (req->getvars["name"] == "servfail-remotes") {
+      queries = broadcastAccFunction<vector<ComboAddress>>(pleaseGetServfailRemotes);
+    }
+    else if (req->getvars["name"] == "bogus-remotes") {
+      queries = broadcastAccFunction<vector<ComboAddress>>(pleaseGetBogusRemotes);
+    }
+    else if (req->getvars["name"] == "large-answer-remotes") {
+      queries = broadcastAccFunction<vector<ComboAddress>>(pleaseGetLargeAnswerRemotes);
+    }
+    else if (req->getvars["name"] == "timeouts") {
+      queries = broadcastAccFunction<vector<ComboAddress>>(pleaseGetTimeouts);
+    }
+    typedef map<ComboAddress, unsigned int, ComboAddress::addressOnlyLessThan> counts_t;
+    counts_t counts;
+    for (const ComboAddress& query : queries) {
+      counts[query]++;
+    }
+
+    typedef std::multimap<int, ComboAddress> rcounts_t;
+    rcounts_t rcounts;
+
+    for (const auto& count : counts) {
+      rcounts.emplace(-count.second, count.first);
+    }
+
+    Json::array entries;
+    unsigned int tot = 0;
+    unsigned int totIncluded = 0;
+    for (const rcounts_t::value_type& count : rcounts) {
+      totIncluded -= count.first;
+      entries.push_back(Json::array{
+        -count.first, count.second.toString()});
+      if (tot++ >= 100) {
+        break;
+      }
+    }
+    if (queries.size() != totIncluded) {
+      entries.push_back(Json::array{
+        (int)(queries.size() - totIncluded), ""});
+    }
+
+    resp->setJsonBody(Json::object{{"entries", entries}});
+    return;
+  }
+  resp->setErrorResult("Command '" + command + "' not found", 404);
+}
+
+void AsyncServerNewConnectionMT(void* arg)
+{
+  auto* server = static_cast<AsyncServer*>(arg);
+
+  try {
+    auto socket = server->accept(); // this is actually a shared_ptr
+    if (socket) {
+      server->d_asyncNewConnectionCallback(socket);
+    }
+  }
+  catch (NetworkError& e) {
+    // we're running in a shared process/thread, so can't just terminate/abort.
+    SLOG(g_log << Logger::Warning << "Network error in web thread: " << e.what() << endl,
+         g_slog->withName("webserver")->error(Logr::Warning, e.what(), "Exception in web tread", Logging::Loggable("NetworkError")));
+    return;
+  }
+  catch (...) {
+    SLOG(g_log << Logger::Warning << "Unknown error in web thread" << endl,
+         g_slog->withName("webserver")->info(Logr::Warning, "Exception in web tread"));
+
+    return;
+  }
+}
+
+void AsyncServer::asyncWaitForConnections(FDMultiplexer* fdm, const newconnectioncb_t& callback)
+{
+  d_asyncNewConnectionCallback = callback;
+  fdm->addReadFD(d_server_socket.getHandle(), [this](int, boost::any&) { newConnection(); });
+}
+
+void AsyncServer::newConnection()
+{
+  getMT()->makeThread(&AsyncServerNewConnectionMT, this);
+}
+
+// This is an entry point from FDM, so it needs to catch everything.
+void AsyncWebServer::serveConnection(const std::shared_ptr<Socket>& socket) const // NOLINT(readability-function-cognitive-complexity) #12791 Remove NOLINT(readability-function-cognitive-complexity) omoerbeek
+{
+  if (!socket->acl(d_acl)) {
+    return;
+  }
+
+  const auto unique = getUniqueID();
+  const string logprefix = d_logprefix + to_string(unique) + " ";
+
+  HttpRequest req(logprefix);
+  HttpResponse resp;
+#ifdef RECURSOR
+  auto log = d_slog->withValues("uniqueid", Logging::Loggable(to_string(unique)));
+  req.setSLog(log);
+  resp.setSLog(log);
+#endif
+
+  ComboAddress remote;
+  PacketBuffer reply;
+
+  try {
+    YaHTTP::AsyncRequestLoader yarl;
+    yarl.initialize(&req);
+    socket->setNonBlocking();
+
+    const struct timeval timeout
+    {
+      g_networkTimeoutMsec / 1000, static_cast<suseconds_t>(g_networkTimeoutMsec) % 1000 * 1000
+    };
+    std::shared_ptr<TLSCtx> tlsCtx{nullptr};
+    if (d_loglevel > WebServer::LogLevel::None) {
+      socket->getRemote(remote);
+    }
+    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()); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): safe cast, data.data() returns unsigned char *
+          req.complete = yarl.feed(str);
+        }
+        else {
+          // read error OR EOF
+          break;
+        }
+      }
+      yarl.finalize();
+    }
+    catch (YaHTTP::ParseError& e) {
+      // request stays incomplete
+      SLOG(g_log << Logger::Warning << logprefix << "Unable to parse request: " << e.what() << endl,
+           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 stringStream;
+    resp.write(stringStream);
+    const string& str = stringStream.str();
+    reply.insert(reply.end(), str.cbegin(), str.cend());
+
+    logResponse(resp, remote, logprefix);
+
+    // now send the reply
+    if (asendtcp(reply, handler) != LWResult::Result::Success || reply.empty()) {
+      SLOG(g_log << Logger::Error << logprefix << "Failed sending reply to HTTP client" << endl,
+           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") == 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"));
+  }
+}
+
+void AsyncWebServer::go()
+{
+  if (!d_server) {
+    return;
+  }
+  auto server = std::dynamic_pointer_cast<AsyncServer>(d_server);
+  if (!server) {
+    return;
+  }
+  server->asyncWaitForConnections(d_fdm, [this](const std::shared_ptr<Socket>& socket) { serveConnection(socket); });
+}
deleted file mode 120000 (symlink)
index f4fc7177fe1366095e53eb2d2e80d8806fa18d72..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../ws-recursor.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..18ab40b5b7fc7f10b79d7740469adc64eca2c339
--- /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 <boost/utility.hpp>
+#include "namespaces.hh"
+#include "mplexer.hh"
+#include "webserver.hh"
+
+class HttpRequest;
+class HttpResponse;
+
+class AsyncServer : public Server
+{
+public:
+  AsyncServer(const string& localaddress, int port) :
+    Server(localaddress, port)
+  {
+    d_server_socket.setNonBlocking();
+  };
+
+  friend void AsyncServerNewConnectionMT(void* arg);
+
+  using newconnectioncb_t = std::function<void(const std::shared_ptr<Socket>&)>;
+  void asyncWaitForConnections(FDMultiplexer* fdm, const newconnectioncb_t& callback);
+
+private:
+  void newConnection();
+
+  newconnectioncb_t d_asyncNewConnectionCallback;
+};
+
+class AsyncWebServer : public WebServer
+{
+public:
+  AsyncWebServer(FDMultiplexer* fdm, const string& listenaddress, int port) :
+    WebServer(listenaddress, port), d_fdm(fdm){};
+  void go();
+
+private:
+  FDMultiplexer* d_fdm;
+  void serveConnection(const std::shared_ptr<Socket>& socket) const;
+
+protected:
+  std::shared_ptr<Server> createServer() override
+  {
+    return std::make_shared<AsyncServer>(d_listenaddress, d_port);
+  };
+};
+
+class RecursorWebServer : public boost::noncopyable
+{
+public:
+  explicit RecursorWebServer(FDMultiplexer* fdm);
+  static void jsonstat(HttpRequest* req, HttpResponse* resp);
+
+private:
+  std::unique_ptr<AsyncWebServer> d_ws{nullptr};
+};
diff --git a/pdns/recursordist/xpf.cc b/pdns/recursordist/xpf.cc
deleted file mode 120000 (symlink)
index 98ab722..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../xpf.cc
\ No newline at end of file
diff --git a/pdns/recursordist/xpf.hh b/pdns/recursordist/xpf.hh
deleted file mode 120000 (symlink)
index 023c8c4..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../xpf.hh
\ No newline at end of file
diff --git a/pdns/reczones.cc b/pdns/reczones.cc
deleted file mode 100644 (file)
index c0770bf..0000000
+++ /dev/null
@@ -1,594 +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 "syncres.hh"
-#include "arguments.hh"
-#include "zoneparser-tng.hh"
-#include "logger.hh"
-#include "dnsrecords.hh"
-#include "root-addresses.hh"
-
-extern int g_argc;
-extern char** g_argv;
-
-static thread_local set<DNSName> t_rootNSZones;
-
-static void insertIntoRootNSZones(const DNSName& name)
-{
-  // do not insert dot, wiping dot's NS records from the cache in primeRootNSZones()
-  // will cause infinite recursion
-  if (!name.isRoot()) {
-    t_rootNSZones.insert(name);
-  }
-}
-
-bool primeHints(time_t ignored)
-{
-  // prime root cache
-  const vState validationState = vState::Insecure;
-  const ComboAddress from("255.255.255.255");
-  vector<DNSRecord> nsset;
-  t_rootNSZones.clear();
-
-  time_t now = time(nullptr);
-
-  const string hintfile = ::arg()["hint-file"];
-  if (hintfile == "no") {
-    g_log << Logger::Debug << "Priming root disabled by hint-file=no" << endl;
-    return true;
-  }
-  if (hintfile.empty()) {
-    DNSRecord arr, aaaarr, nsrr;
-    nsrr.d_name = g_rootdnsname;
-    arr.d_type = QType::A;
-    aaaarr.d_type = QType::AAAA;
-    nsrr.d_type = QType::NS;
-
-    arr.d_ttl = aaaarr.d_ttl = nsrr.d_ttl = now + 3600000;
-
-    for (char c = 'a'; c <= 'm'; ++c) {
-      char templ[40];
-      strncpy(templ, "a.root-servers.net.", sizeof(templ) - 1);
-      templ[sizeof(templ) - 1] = '\0';
-      *templ = c;
-      aaaarr.d_name = arr.d_name = DNSName(templ);
-      insertIntoRootNSZones(arr.d_name.getLastLabel());
-      nsrr.d_content = std::make_shared<NSRecordContent>(DNSName(templ));
-      arr.d_content = std::make_shared<ARecordContent>(ComboAddress(rootIps4[c - 'a']));
-      vector<DNSRecord> aset;
-      aset.push_back(arr);
-      /*
-       * Originally the hint records were inserted with the auth flag set, with the consequence that data from AUTHORITY and
-       * ADDITIONAL sections (as seen in a . NS response) were not used. This (together with the long ttl) caused outdated
-       * hint to be kept in cache. So insert as non-auth, and the extra sections in the . NS refreshing cause the cached
-       * records to be updated with up-to-date information received from a real root server.
-       *
-       * Note that if a user query is done for one of the root-server.net names, it will be inserted into the cache with the
-       * auth bit set. Further NS refreshes will not update that entry. If all root names are queried at the same time by a user,
-       * all root-server.net names will be marked auth and will expire at the same time. A re-prime is then triggered,
-       * as before, when the records were inserted with the auth bit set and the TTD comes.
-       */
-      g_recCache->replace(now, DNSName(templ), QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), false, g_rootdnsname, boost::none, boost::none, validationState, from); // auth, nuke it all
-      if (rootIps6[c - 'a'] != NULL) {
-        aaaarr.d_content = std::make_shared<AAAARecordContent>(ComboAddress(rootIps6[c - 'a']));
-
-        vector<DNSRecord> aaaaset;
-        aaaaset.push_back(aaaarr);
-        g_recCache->replace(now, DNSName(templ), QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), false, g_rootdnsname, boost::none, boost::none, validationState, from);
-      }
-
-      nsset.push_back(nsrr);
-    }
-  }
-  else {
-    ZoneParserTNG zpt(hintfile);
-    zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
-    zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
-    DNSResourceRecord rr;
-    set<DNSName> seenNS;
-    set<DNSName> seenA;
-    set<DNSName> seenAAAA;
-
-    while (zpt.get(rr)) {
-      rr.ttl += now;
-      if (rr.qtype.getCode() == QType::A) {
-        seenA.insert(rr.qname);
-        vector<DNSRecord> aset;
-        aset.push_back(DNSRecord(rr));
-        g_recCache->replace(now, rr.qname, QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true, g_rootdnsname, boost::none, boost::none, validationState, from); // auth, etc see above
-      }
-      else if (rr.qtype.getCode() == QType::AAAA) {
-        seenAAAA.insert(rr.qname);
-        vector<DNSRecord> aaaaset;
-        aaaaset.push_back(DNSRecord(rr));
-        g_recCache->replace(now, rr.qname, QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true, g_rootdnsname, boost::none, boost::none, validationState, from);
-      }
-      else if (rr.qtype.getCode() == QType::NS) {
-        seenNS.insert(DNSName(rr.content));
-        rr.content = toLower(rr.content);
-        nsset.push_back(DNSRecord(rr));
-      }
-      insertIntoRootNSZones(rr.qname.getLastLabel());
-    }
-
-    // Check reachability of A and AAAA records
-    bool reachableA = false, reachableAAAA = false;
-    for (auto const& r : seenA) {
-      if (seenNS.count(r)) {
-        reachableA = true;
-        break;
-      }
-    }
-    for (auto const& r : seenAAAA) {
-      if (seenNS.count(r)) {
-        reachableAAAA = true;
-        break;
-      }
-    }
-    if (SyncRes::s_doIPv4 && !SyncRes::s_doIPv6 && !reachableA) {
-      g_log << Logger::Error << "Running IPv4 only but no IPv4 root hints" << endl;
-      return false;
-    }
-    if (!SyncRes::s_doIPv4 && SyncRes::s_doIPv6 && !reachableAAAA) {
-      g_log << Logger::Error << "Running IPv6 only but no IPv6 root hints" << endl;
-      return false;
-    }
-    if (SyncRes::s_doIPv4 && SyncRes::s_doIPv6 && !reachableA && !reachableAAAA) {
-      g_log << Logger::Error << "No valid root hints" << endl;
-      return false;
-    }
-  }
-
-  g_recCache->doWipeCache(g_rootdnsname, false, QType::NS);
-  g_recCache->replace(now, g_rootdnsname, QType(QType::NS), nsset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), false, g_rootdnsname, boost::none, boost::none, validationState, from); // and stuff in the cache
-  return true;
-}
-
-// Do not only put the root hints into the cache, but also make sure
-// the NS records of the top level domains of the names of the root
-// servers are in the cache. We need these to correctly determine the
-// security status of that specific domain (normally
-// root-servers.net). This is caused by the accident that the root
-// servers are authoritative for root-servers.net, and some
-// implementations reply not with a delegation on a root-servers.net
-// DS query, but with a NODATA response (the domain is unsigned).
-void primeRootNSZones(DNSSECMode mode, unsigned int depth)
-{
-  struct timeval now;
-  gettimeofday(&now, 0);
-  SyncRes sr(now);
-
-  sr.setDoDNSSEC(mode != DNSSECMode::Off);
-  sr.setDNSSECValidationRequested(mode != DNSSECMode::Off && mode != DNSSECMode::ProcessNoValidate);
-
-  // beginResolve() can yield to another mthread that could trigger t_rootNSZones updates,
-  // so make a local copy
-  set<DNSName> copy(t_rootNSZones);
-  for (const auto& qname : copy) {
-    g_recCache->doWipeCache(qname, false, QType::NS);
-    vector<DNSRecord> ret;
-    sr.beginResolve(qname, QType(QType::NS), QClass::IN, ret, depth + 1);
-  }
-}
-
-static void makeNameToIPZone(std::shared_ptr<SyncRes::domainmap_t> newMap, const DNSName& hostname, const string& ip)
-{
-  SyncRes::AuthDomain ad;
-  ad.d_rdForward = false;
-
-  DNSRecord dr;
-  dr.d_name = hostname;
-  dr.d_place = DNSResourceRecord::ANSWER;
-  dr.d_ttl = 86400;
-  dr.d_type = QType::SOA;
-  dr.d_class = 1;
-  dr.d_content = DNSRecordContent::mastermake(QType::SOA, 1, "localhost. root 1 604800 86400 2419200 604800");
-
-  ad.d_records.insert(dr);
-
-  dr.d_type = QType::NS;
-  dr.d_content = std::make_shared<NSRecordContent>("localhost.");
-
-  ad.d_records.insert(dr);
-
-  dr.d_type = QType::A;
-  dr.d_content = DNSRecordContent::mastermake(QType::A, 1, ip);
-  ad.d_records.insert(dr);
-
-  if (newMap->count(dr.d_name)) {
-    g_log << Logger::Warning << "Hosts file will not overwrite zone '" << dr.d_name << "' already loaded" << endl;
-  }
-  else {
-    g_log << Logger::Warning << "Inserting forward zone '" << dr.d_name << "' based on hosts file" << endl;
-    ad.d_name = dr.d_name;
-    (*newMap)[ad.d_name] = ad;
-  }
-}
-
-//! parts[0] must be an IP address, the rest must be host names
-static void makeIPToNamesZone(std::shared_ptr<SyncRes::domainmap_t> newMap, const vector<string>& parts)
-{
-  string address = parts[0];
-  vector<string> ipparts;
-  stringtok(ipparts, address, ".");
-
-  SyncRes::AuthDomain ad;
-  ad.d_rdForward = false;
-
-  DNSRecord dr;
-  for (int n = ipparts.size() - 1; n >= 0; --n) {
-    dr.d_name.appendRawLabel(ipparts[n]);
-  }
-  dr.d_name.appendRawLabel("in-addr");
-  dr.d_name.appendRawLabel("arpa");
-  dr.d_class = 1;
-  dr.d_place = DNSResourceRecord::ANSWER;
-  dr.d_ttl = 86400;
-  dr.d_type = QType::SOA;
-  dr.d_content = DNSRecordContent::mastermake(QType::SOA, 1, "localhost. root 1 604800 86400 2419200 604800");
-
-  ad.d_records.insert(dr);
-
-  dr.d_type = QType::NS;
-  dr.d_content = std::make_shared<NSRecordContent>(DNSName("localhost."));
-
-  ad.d_records.insert(dr);
-  dr.d_type = QType::PTR;
-
-  if (ipparts.size() == 4) // otherwise this is a partial zone
-    for (unsigned int n = 1; n < parts.size(); ++n) {
-      dr.d_content = DNSRecordContent::mastermake(QType::PTR, 1, DNSName(parts[n]).toString()); // XXX FIXME DNSNAME PAIN CAN THIS BE RIGHT?
-      ad.d_records.insert(dr);
-    }
-
-  if (newMap->count(dr.d_name)) {
-    g_log << Logger::Warning << "Will not overwrite zone '" << dr.d_name << "' already loaded" << endl;
-  }
-  else {
-    if (ipparts.size() == 4)
-      g_log << Logger::Warning << "Inserting reverse zone '" << dr.d_name << "' based on hosts file" << endl;
-    ad.d_name = dr.d_name;
-    (*newMap)[ad.d_name] = ad;
-  }
-}
-
-static void convertServersForAD(const std::string& input, SyncRes::AuthDomain& ad, const char* sepa, bool verbose = true)
-{
-  vector<string> servers;
-  stringtok(servers, input, sepa);
-  ad.d_servers.clear();
-
-  for (vector<string>::const_iterator iter = servers.begin(); iter != servers.end(); ++iter) {
-    if (verbose && iter != servers.begin())
-      g_log << ", ";
-
-    ComboAddress addr = parseIPAndPort(*iter, 53);
-    if (verbose)
-      g_log << addr.toStringWithPort();
-    ad.d_servers.push_back(addr);
-  }
-  if (verbose)
-    g_log << endl;
-}
-
-static void* pleaseUseNewSDomainsMap(std::shared_ptr<SyncRes::domainmap_t> newmap)
-{
-  SyncRes::setDomainMap(newmap);
-  return 0;
-}
-
-string reloadZoneConfiguration()
-{
-  std::shared_ptr<SyncRes::domainmap_t> original = SyncRes::getDomainMap();
-
-  try {
-    g_log << Logger::Warning << "Reloading zones, purging data from cache" << endl;
-
-    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"]);
-    }
-
-    ::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");
-    ::arg().preParse(g_argc, g_argv, "auth-zones");
-    ::arg().preParse(g_argc, g_argv, "allow-notify-for");
-    ::arg().preParse(g_argc, g_argv, "allow-notify-for-file");
-    ::arg().preParse(g_argc, g_argv, "export-etc-hosts");
-    ::arg().preParse(g_argc, g_argv, "serve-rfc1918");
-
-    auto [newDomainMap, newNotifySet] = parseZoneConfiguration();
-
-    // purge both original and new names
-    std::set<DNSName> oldAndNewDomains;
-    for (const auto& i : *newDomainMap) {
-      oldAndNewDomains.insert(i.first);
-    }
-
-    if (original) {
-      for (const auto& i : *original) {
-        oldAndNewDomains.insert(i.first);
-      }
-    }
-
-    for (const auto& i : oldAndNewDomains) {
-      wipeCaches(i, true, 0xffff);
-    }
-
-    // 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); });
-    return "ok\n";
-  }
-  catch (std::exception& e) {
-    g_log << Logger::Error << "Encountered error reloading zones, keeping original data: " << e.what() << endl;
-  }
-  catch (PDNSException& ae) {
-    g_log << Logger::Error << "Encountered error reloading zones, keeping original data: " << ae.reason << endl;
-  }
-  catch (...) {
-    g_log << Logger::Error << "Encountered unknown error reloading zones, keeping original data" << endl;
-  }
-  return "reloading failed, see log\n";
-}
-
-std::tuple<std::shared_ptr<SyncRes::domainmap_t>, std::shared_ptr<notifyset_t>> parseZoneConfiguration()
-{
-  TXTRecordContent::report();
-  OPTRecordContent::report();
-
-  auto newMap = std::make_shared<SyncRes::domainmap_t>();
-  auto newSet = std::make_shared<notifyset_t>();
-
-  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, '=');
-      boost::trim(headers.first);
-      boost::trim(headers.second);
-      // headers.first=toCanonic("", headers.first);
-      if (n == 0) {
-        ad.d_rdForward = false;
-        g_log << Logger::Error << "Parsing authoritative data for zone '" << headers.first << "' from file '" << headers.second << "'" << endl;
-        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);
-        }
-      }
-      else {
-        g_log << Logger::Error << "Redirecting queries for zone '" << headers.first << "' ";
-        if (n == 2) {
-          g_log << "with recursion ";
-          ad.d_rdForward = true;
-        }
-        else
-          ad.d_rdForward = false;
-        g_log << "to: ";
-
-        convertServersForAD(headers.second, ad, ";");
-        if (n == 2) {
-          ad.d_rdForward = true;
-        }
-      }
-
-      ad.d_name = DNSName(headers.first);
-      (*newMap)[ad.d_name] = ad;
-    }
-  }
-
-  if (!::arg()["forward-zones-file"].empty()) {
-    g_log << Logger::Warning << "Reading zone forwarding information from '" << ::arg()["forward-zones-file"] << "'" << endl;
-    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());
-    }
-
-    string line;
-    int linenum = 0;
-    uint64_t before = newMap->size();
-    while (linenum++, stringfgets(fp.get(), line)) {
-      SyncRes::AuthDomain ad;
-      boost::trim(line);
-      if (line[0] == '#') // Comment line, skip to the next line
-        continue;
-      string domain, instructions;
-      std::tie(domain, instructions) = splitField(line, '=');
-      instructions = splitField(instructions, '#').first; // Remove EOL comments
-      boost::trim(domain);
-      boost::trim(instructions);
-      if (domain.empty()) {
-        if (instructions.empty()) { // empty line
-          continue;
-        }
-        throw PDNSException("Error parsing line " + std::to_string(linenum) + " of " + ::arg()["forward-zones-file"]);
-      }
-
-      bool allowNotifyFor = false;
-
-      for (; !domain.empty(); domain.erase(0, 1)) {
-        switch (domain[0]) {
-        case '+':
-          ad.d_rdForward = true;
-          continue;
-        case '^':
-          allowNotifyFor = true;
-          continue;
-        }
-        break;
-      }
-
-      if (domain.empty()) {
-        throw PDNSException("Error parsing line " + std::to_string(linenum) + " of " + ::arg()["forward-zones-file"]);
-      }
-
-      try {
-        convertServersForAD(instructions, ad, ",; ", false);
-      }
-      catch (...) {
-        throw PDNSException("Conversion error parsing line " + std::to_string(linenum) + " of " + ::arg()["forward-zones-file"]);
-      }
-
-      ad.d_name = DNSName(domain);
-      (*newMap)[ad.d_name] = ad;
-      if (allowNotifyFor) {
-        newSet->insert(ad.d_name);
-      }
-    }
-    g_log << Logger::Warning << "Done parsing " << newMap->size() - before << " forwarding instructions from file '" << ::arg()["forward-zones-file"] << "'" << endl;
-  }
-
-  if (::arg().mustDo("export-etc-hosts")) {
-    string line;
-    string fname = ::arg()["etc-hosts-file"];
-
-    ifstream ifs(fname.c_str());
-    if (!ifs) {
-      g_log << Logger::Warning << "Could not open " << fname << " for reading" << endl;
-    }
-    else {
-      string searchSuffix = ::arg()["export-etc-hosts-search-suffix"];
-      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;
-        parts.clear();
-        stringtok(parts, line, "\t\r\n ");
-        if (parts[0].find(':') != string::npos)
-          continue;
-
-        for (unsigned int n = 1; n < parts.size(); ++n) {
-          if (searchSuffix.empty() || parts[n].find('.') != string::npos)
-            makeNameToIPZone(newMap, DNSName(parts[n]), parts[0]);
-          else {
-            DNSName canonic = toCanonic(DNSName(searchSuffix), parts[n]); /// XXXX DNSName pain
-            if (canonic != DNSName(parts[n])) { // XXX further DNSName pain
-              makeNameToIPZone(newMap, canonic, parts[0]);
-            }
-          }
-        }
-        makeIPToNamesZone(newMap, parts);
-      }
-    }
-  }
-
-  if (::arg().mustDo("serve-rfc1918")) {
-    g_log << Logger::Warning << "Inserting rfc 1918 private space zones" << endl;
-    parts.clear();
-    parts.push_back("127");
-    makeIPToNamesZone(newMap, parts);
-    parts[0] = "10";
-    makeIPToNamesZone(newMap, parts);
-
-    parts[0] = "192.168";
-    makeIPToNamesZone(newMap, parts);
-    for (int n = 16; n < 32; n++) {
-      parts[0] = "172." + std::to_string(n);
-      makeIPToNamesZone(newMap, parts);
-    }
-  }
-
-  parts.clear();
-  stringtok(parts, ::arg()["allow-notify-for"], " ,\t\n\r");
-  for (parts_t::const_iterator iter = parts.begin(); iter != parts.end(); ++iter) {
-    newSet->insert(DNSName(*iter));
-  }
-
-  if (auto anff = ::arg()["allow-notify-for-file"]; !anff.empty()) {
-    g_log << Logger::Warning << "Reading NOTIFY-allowed zones from '" << anff << "'" << endl;
-    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());
-    }
-
-    string line;
-    uint64_t before = newSet->size();
-    while (stringfgets(fp.get(), line)) {
-      boost::trim(line);
-      if (line[0] == '#') // Comment line, skip to the next line
-        continue;
-      newSet->insert(DNSName(line));
-    }
-    g_log << Logger::Warning << "Done parsing " << newSet->size() - before << " NOTIFY-allowed zones from file '" << anff << "'" << endl;
-  }
-
-  return {newMap, newSet};
-}
index 96e596888c814991cd8f039160887a36fe8454df..d07a5aaf0013b7659d2dacf8c13e0140764ade7a 100644 (file)
@@ -11,6 +11,7 @@
 #else
 #include "dolog.hh"
 #endif
+#include "logging.hh"
 
 bool CircularWriteBuffer::hasRoomFor(const std::string& str) const
 {
@@ -94,6 +95,19 @@ bool CircularWriteBuffer::flush(int fd)
   return true;
 }
 
+const std::string& RemoteLoggerInterface::toErrorString(Result r)
+{
+  static const std::array<std::string,5> str = {
+    "Queued",
+    "Queue full, dropping",
+    "Not sending too large protobuf message",
+    "Submiting to queue failed",
+    "?"
+  };
+  auto i = static_cast<unsigned int>(r);
+  return str[std::min(i, 4U)];
+}
+
 RemoteLogger::RemoteLogger(const ComboAddress& remote, uint16_t timeout, uint64_t maxQueuedBytes, uint8_t reconnectWaitTime, bool asyncConnect): d_remote(remote), d_timeout(timeout), d_reconnectWaitTime(reconnectWaitTime), d_asyncConnect(asyncConnect), d_runtime({CircularWriteBuffer(maxQueuedBytes), nullptr})
 {
   if (!d_asyncConnect) {
@@ -119,7 +133,8 @@ bool RemoteLogger::reconnect()
   }
   catch (const std::exception& e) {
 #ifdef WE_ARE_RECURSOR
-    g_log<<Logger::Warning<<"Error connecting to remote logger "<<d_remote.toStringWithPort()<<": "<<e.what()<<std::endl;
+    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 connecting to remote logger", "address", Logging::Loggable(d_remote)));
 #else
     warnlog("Error connecting to remote logger %s: %s", d_remote.toStringWithPort(), e.what());
 #endif
@@ -129,52 +144,54 @@ bool RemoteLogger::reconnect()
   return true;
 }
 
-void RemoteLogger::queueData(const std::string& data)
+RemoteLoggerInterface::Result RemoteLogger::queueData(const std::string& data)
 {
+  auto runtime = d_runtime.lock();
+
   if (data.size() > std::numeric_limits<uint16_t>::max()) {
-    throw std::runtime_error("Got a request to write an object of size " + std::to_string(data.size()));
+    ++runtime->d_stats.d_tooLarge;
+    return Result::TooLarge;
   }
 
-  auto runtime = d_runtime.lock();
-
   if (!runtime->d_writer.hasRoomFor(data)) {
     /* not connected, queue is full, just drop */
     if (!runtime->d_socket) {
-      ++d_drops;
-      return;
+      ++runtime->d_stats.d_pipeFull;
+      return Result::PipeFull;
     }
     try {
       /* we try to flush some data */
       if (!runtime->d_writer.flush(runtime->d_socket->getHandle())) {
         /* but failed, let's just drop */
-        ++d_drops;
-        return;
+        ++runtime->d_stats.d_pipeFull;
+        return Result::PipeFull;
       }
 
       /* see if we freed enough data */
       if (!runtime->d_writer.hasRoomFor(data)) {
         /* we didn't */
-        ++d_drops;
-        return;
+        ++runtime->d_stats.d_pipeFull;
+        return Result::PipeFull;
       }
     }
     catch(const std::exception& e) {
       //      cout << "Got exception writing: "<<e.what()<<endl;
-      ++d_drops;
       runtime->d_socket.reset();
-      return;
+      ++runtime->d_stats.d_otherError;
+      return Result::OtherError;
     }
   }
 
   runtime->d_writer.write(data);
-  ++d_processed;
+  ++runtime->d_stats.d_queued;
+  return Result::Queued;
 }
 
 void RemoteLogger::maintenanceThread() 
 {
   try {
 #ifdef WE_ARE_RECURSOR
-    string threadName = "pdns-r/remLog";
+    string threadName = "rec/remlog";
 #else
     string threadName = "dnsdist/remLog";
 #endif
@@ -222,10 +239,12 @@ void RemoteLogger::maintenanceThread()
   }
   catch (const std::exception& e)
   {
-    cerr << "Remote Logger's maintenance thead died on: " << e.what() << endl;
+    SLOG(cerr << "Remote Logger's maintenance thread died on: " << e.what() << endl,
+         g_slog->withName("protobuf")->error(Logr::Error, e.what(), "Remote Logger's maintenance thread died"));
   }
   catch (...) {
-    cerr << "Remote Logger's maintenance thead died on unknown exception" << endl;
+    SLOG(cerr << "Remote Logger's maintenance thread died on unknown exception" << endl,
+         g_slog->withName("protobuf")->info(Logr::Error, "Remote Logger's maintenance thread died"));
   }
 }
 
@@ -235,3 +254,4 @@ RemoteLogger::~RemoteLogger()
 
   d_thread.join();
 }
+
index 04db3b53ad865d5e980fa099b514ba62e2e9f764..29013efef1847dd655c08c3e0d76ca6f1f21934e 100644 (file)
@@ -60,18 +60,48 @@ private:
 class RemoteLoggerInterface
 {
 public:
-  virtual ~RemoteLoggerInterface() {};
-  virtual void queueData(const std::string& data) = 0;
-  virtual std::string toString() const = 0;
+  enum class Result : uint8_t { Queued, PipeFull, TooLarge, OtherError };
+  static const std::string& toErrorString(Result r);
+
 
+  virtual ~RemoteLoggerInterface() {};
+  virtual Result queueData(const std::string& data) = 0;
+  [[nodiscard]] virtual std::string address() const = 0;
+  [[nodiscard]] virtual std::string toString() = 0;
+  [[nodiscard]] virtual std::string name() const = 0;
   bool logQueries(void) const { return d_logQueries; }
   bool logResponses(void) const { return d_logResponses; }
+  bool logNODs(void) const { return d_logNODs; }
+  bool logUDRs(void) const { return d_logUDRs; }
   void setLogQueries(bool flag) { d_logQueries = flag; }
   void setLogResponses(bool flag) { d_logResponses = flag; }
+  void setLogNODs(bool flag) { d_logNODs = flag; }
+  void setLogUDRs(bool flag) { d_logUDRs = flag; }
+
+  struct Stats
+  {
+    uint64_t d_queued{};
+    uint64_t d_pipeFull{};
+    uint64_t d_tooLarge{};
+    uint64_t d_otherError{};
+
+    Stats& operator += (const Stats& rhs)
+    {
+      d_queued += rhs.d_queued;
+      d_pipeFull += rhs.d_pipeFull;
+      d_tooLarge += rhs.d_tooLarge;
+      d_otherError += rhs.d_otherError;
+      return *this;
+    }
+  };
+
+  [[nodiscard]] virtual Stats getStats() = 0;
 
 private:
   bool d_logQueries{true};
   bool d_logResponses{true};
+  bool d_logNODs{true};
+  bool d_logUDRs{false};
 };
 
 /* Thread safe. Will connect asynchronously on request.
@@ -87,11 +117,28 @@ public:
                uint8_t reconnectWaitTime=1,
                bool asyncConnect=false);
   ~RemoteLogger();
-  void queueData(const std::string& data) override;
-  std::string toString() const override
+
+  std::string address() const override
   {
-    return d_remote.toStringWithPort() + " (" + std::to_string(d_processed) + " processed, " + std::to_string(d_drops) + " dropped)";
+    return d_remote.toStringWithPort();
   }
+
+  [[nodiscard]] Result queueData(const std::string& data) override;
+  [[nodiscard]] std::string name() const override
+  {
+    return "protobuf";
+  }
+  [[nodiscard]] std::string toString() override
+  {
+    auto runtime = d_runtime.lock();
+    return d_remote.toStringWithPort() + " (" + std::to_string(runtime->d_stats.d_queued) + " processed, " + std::to_string(runtime->d_stats.d_pipeFull + runtime->d_stats.d_tooLarge + runtime->d_stats.d_otherError) + " dropped)";
+  }
+
+  [[nodiscard]] RemoteLoggerInterface::Stats getStats() override
+  {
+    return d_runtime.lock()->d_stats;
+  }
+
   void stop()
   {
     d_exiting = true;
@@ -105,11 +152,10 @@ private:
   {
     CircularWriteBuffer d_writer;
     std::unique_ptr<Socket> d_socket{nullptr};
+    RemoteLoggerInterface::Stats d_stats{};
   };
 
   ComboAddress d_remote;
-  std::atomic<uint64_t> d_drops{0};
-  std::atomic<uint64_t> d_processed{0};
   uint16_t d_timeout;
   uint8_t d_reconnectWaitTime;
   std::atomic<bool> d_exiting{false};
@@ -118,3 +164,4 @@ private:
   LockGuarded<RuntimeData> d_runtime;
   std::thread d_thread;
 };
+
diff --git a/pdns/resolve-context.hh b/pdns/resolve-context.hh
deleted file mode 100644 (file)
index ccac550..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-#pragma once
-
-#include "config.h"
-
-#include <boost/uuid/uuid.hpp>
-#include <boost/optional.hpp>
-
-struct ResolveContext {
-  ResolveContext()
-  {
-  }
-
-  ResolveContext(const ResolveContext& ctx) = delete;
-  ResolveContext & operator=(const ResolveContext&) = delete;
-  
-  boost::optional<const boost::uuids::uuid&> d_initialRequestId;
-  DNSName d_nsName;
-#ifdef HAVE_FSTRM
-  boost::optional<const DNSName&> d_auth;
-#endif
-};
index d09976108d9fc6c1cf0b6e19f3fe3bdcb17f98de..28db4e65efd088cff3ed368c4a8afcb9d3ea17f1 100644 (file)
@@ -27,7 +27,7 @@
 #include <pthread.h>
 #include <semaphore.h>
 #include <iostream>
-#include <errno.h>
+#include <cerrno>
 #include "misc.hh"
 #include <algorithm>
 #include <sstream>
@@ -48,6 +48,7 @@
 
 #include "dns_random.hh"
 #include <poll.h>
+#include "gss_context.hh"
 #include "namespaces.hh"
 
 using pdns::resolver::parseResult;
@@ -175,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;
     }
@@ -185,14 +186,14 @@ 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;
 }
 
 namespace pdns {
   namespace resolver {
-    int parseResult(MOADNSParser& mdp, const DNSName& origQname, uint16_t origQtype, uint16_t id, Resolver::res_t* result)
+    int parseResult(MOADNSParser& mdp, const DNSName& origQname, uint16_t /* origQtype */, uint16_t id, Resolver::res_t* result)
     {
       result->clear();
 
@@ -203,7 +204,7 @@ namespace pdns {
         if(mdp.d_header.id != id)
           throw ResolverException("Remote nameserver replied with wrong id");
         if(mdp.d_header.qdcount != 1)
-          throw ResolverException("resolver: received answer with wrong number of questions ("+itoa(mdp.d_header.qdcount)+")");
+          throw ResolverException("resolver: received answer with wrong number of questions ("+std::to_string(mdp.d_header.qdcount)+")");
         if(mdp.d_qname != origQname)
           throw ResolverException(string("resolver: received an answer to another question (")+mdp.d_qname.toLogString()+"!="+ origQname.toLogString()+".)");
       }
@@ -216,7 +217,7 @@ namespace pdns {
         rr.qname = i.first.d_name;
         rr.qtype = i.first.d_type;
         rr.ttl = i.first.d_ttl;
-        rr.content = i.first.d_content->getZoneRepresentation(true);
+        rr.content = i.first.getContent()->getZoneRepresentation(true);
         result->push_back(rr);
       }
 
@@ -271,29 +272,29 @@ 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;
   for(const MOADNSParser::answers_t::value_type& drc :  mdp.d_answers) {
     if(drc.first.d_type == QType::SOA && drc.first.d_name == *domain) {
-      shared_ptr<SOARecordContent> src=getRR<SOARecordContent>(drc.first);
+      auto src = getRR<SOARecordContent>(drc.first);
       if (src) {
-        *theirSerial=src->d_st.serial;
+        *theirSerial = src->d_st.serial;
         gotSOA = true;
       }
     }
     if(drc.first.d_type == QType::RRSIG && drc.first.d_name == *domain) {
-      shared_ptr<RRSIGRecordContent> rrc=getRR<RRSIGRecordContent>(drc.first);
+      auto rrc = getRR<RRSIGRecordContent>(drc.first);
       if(rrc && rrc->d_type == QType::SOA) {
         *theirInception= std::max(*theirInception, rrc->d_siginception);
         *theirExpire = std::max(*theirExpire, rrc->d_sigexpire);
@@ -301,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;
 }
 
@@ -327,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 0acc334132d33bbedd355257e7466448d700b722..128776cde994d28b52edffd064c2f6e50fc64c29 100644 (file)
@@ -47,7 +47,7 @@ void ResponseStats::submitResponse(uint16_t qtype, uint16_t respsize, uint8_t rc
   submitResponse(qtype, respsize, udpOrTCP);
 }
 
-void ResponseStats::submitResponse(uint16_t qtype, uint16_t respsize, bool udpOrTCP) const
+void ResponseStats::submitResponse(uint16_t qtype, uint16_t respsize, bool /* udpOrTCP */) const
 {
   d_qtypecounters.at(qtype).value++;
   d_sizecounters(respsize);
index ea985b35c05ff8595935ff8fc4bf809f26fcfaea..0144456b29dcad592662422495c4bc295cbdf126 100644 (file)
@@ -1,3 +1,4 @@
+#include "dnswriter.hh"
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -17,6 +18,8 @@
 #include "backends/gsql/ssql.hh"
 #include "communicator.hh"
 #include "query-local-address.hh"
+#include "gss_context.hh"
+#include "auth-main.hh"
 
 extern StatBag S;
 extern CommunicatorClass Communicator;
@@ -65,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;
 }
@@ -122,7 +131,7 @@ uint PacketHandler::performUpdate(const string &msgPrefix, const DNSRecord *rr,
     if (rrType == QType::NSEC3PARAM) {
       g_log<<Logger::Notice<<msgPrefix<<"Adding/updating NSEC3PARAM for zone, resetting ordernames."<<endl;
 
-      *ns3pr = NSEC3PARAMRecordContent(rr->d_content->getZoneRepresentation(), di->zone);
+      *ns3pr = NSEC3PARAMRecordContent(rr->getContent()->getZoneRepresentation(), di->zone);
       *narrow = false; // adding a NSEC3 will cause narrow mode to be dropped, as you cannot specify that in a NSEC3PARAM record
       d_dk.setNSEC3PARAM(di->zone, *ns3pr, (*narrow));
       *haveNSEC3 = true;
@@ -150,7 +159,7 @@ uint PacketHandler::performUpdate(const string &msgPrefix, const DNSRecord *rr,
         SOAData sdOld, sdUpdate;
         DNSResourceRecord *oldRec = &rrset.front();
         fillSOAData(oldRec->content, sdOld);
-        oldRec->setContent(rr->d_content->getZoneRepresentation());
+        oldRec->setContent(rr->getContent()->getZoneRepresentation());
         fillSOAData(oldRec->content, sdUpdate);
         if (rfc1982LessThan(sdOld.serial, sdUpdate.serial)) {
           di->backend->replaceRRSet(di->id, oldRec->qname, oldRec->qtype, rrset);
@@ -165,9 +174,9 @@ uint PacketHandler::performUpdate(const string &msgPrefix, const DNSRecord *rr,
       } else if (rrType == QType::CNAME) {
         int changedCNames = 0;
         for (auto& i : rrset) {
-          if (i.ttl != rr->d_ttl || i.content != rr->d_content->getZoneRepresentation()) {
+          if (i.ttl != rr->d_ttl || i.content != rr->getContent()->getZoneRepresentation()) {
             i.ttl = rr->d_ttl;
-            i.setContent(rr->d_content->getZoneRepresentation());
+            i.setContent(rr->getContent()->getZoneRepresentation());
             changedCNames++;
           }
         }
@@ -189,7 +198,7 @@ uint PacketHandler::performUpdate(const string &msgPrefix, const DNSRecord *rr,
             rrType.getCode() == QType::SRV) {
           lowerCase = true;
         }
-        string content = rr->d_content->getZoneRepresentation();
+        string content = rr->getContent()->getZoneRepresentation();
         if (lowerCase) content = toLower(content);
         for (auto& i : rrset) {
           string icontent = i.getZoneRepresentation();
@@ -368,7 +377,7 @@ uint PacketHandler::performUpdate(const string &msgPrefix, const DNSRecord *rr,
       if (rr->d_class == QClass::ANY)
         d_dk.unsetNSEC3PARAM(rr->d_name);
       else if (rr->d_class == QClass::NONE) {
-        NSEC3PARAMRecordContent nsec3rr(rr->d_content->getZoneRepresentation(), di->zone);
+        NSEC3PARAMRecordContent nsec3rr(rr->getContent()->getZoneRepresentation(), di->zone);
         if (*haveNSEC3 && ns3pr->getZoneRepresentation() == nsec3rr.getZoneRepresentation())
           d_dk.unsetNSEC3PARAM(rr->d_name);
         else
@@ -398,7 +407,17 @@ uint PacketHandler::performUpdate(const string &msgPrefix, const DNSRecord *rr,
           recordsToDelete.push_back(rec);
       }
       if (rr->d_class == QClass::NONE) { // 3.4.2.4
-        if (rrType == rec.qtype && rec.getZoneRepresentation() == rr->d_content->getZoneRepresentation())
+        auto repr = rec.getZoneRepresentation();
+        if (rec.qtype == QType::TXT) {
+          DLOG(g_log<<msgPrefix<<"Adjusting TXT content from ["<<repr<<"]"<<endl);
+          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);
+          DLOG(g_log<<msgPrefix<<"Adjusted TXT content to ["<<repr<<"]"<<endl);
+        }
+        DLOG(g_log<<msgPrefix<<"Matching RR in RRset - (adjusted) representation from request=["<<repr<<"], rr->getContent()->getZoneRepresentation()=["<<rr->getContent()->getZoneRepresentation()<<"]"<<endl);
+        if (rrType == rec.qtype && repr == rr->getContent()->getZoneRepresentation())
           recordsToDelete.push_back(rec);
         else
           rrset.push_back(rec);
@@ -526,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;
@@ -549,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;
     }
@@ -566,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;
     }
@@ -597,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;
     }
@@ -611,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;
     }
@@ -624,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 {
@@ -633,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;
 
 }
@@ -646,7 +665,7 @@ int PacketHandler::processUpdate(DNSPacket& p) {
   if (! ::arg().mustDo("dnsupdate"))
     return RCode::Refused;
 
-  string msgPrefix="UPDATE (" + itoa(p.d.id) + ") from " + p.getRemoteString() + " for " + p.qdomain.toLogString() + ": ";
+  string msgPrefix="UPDATE (" + std::to_string(p.d.id) + ") from " + p.getRemoteString() + " for " + p.qdomain.toLogString() + ": ";
   g_log<<Logger::Info<<msgPrefix<<"Processing started."<<endl;
 
   // if there is policy, we delegate all checks to it
@@ -682,11 +701,24 @@ int PacketHandler::processUpdate(DNSPacket& p) {
         g_log<<Logger::Error<<msgPrefix<<"TSIG key required, but packet does not contain key. Sending REFUSED"<<endl;
         return RCode::Refused;
       }
-
-      for(const auto& key: tsigKeys) {
-        if (inputkey == DNSName(key)) { // because checkForCorrectTSIG has already been performed earlier on, if the names of the ky match with the domain given. THis is valid.
-          validKey=true;
-          break;
+#ifdef ENABLE_GSS_TSIG
+      if (g_doGssTSIG && p.d_tsig_algo == TSIG_GSS) {
+        GssName inputname(p.d_peer_principal); // match against principal since GSS requires that
+        for(const auto& key: tsigKeys) {
+          if (inputname.match(key)) {
+            validKey = true;
+            break;
+          }
+        }
+      }
+      else
+#endif
+        {
+        for(const auto& key: tsigKeys) {
+          if (inputkey == DNSName(key)) { // because checkForCorrectTSIG has already been performed earlier on, if the name of the key matches with the domain given it is valid.
+            validKey=true;
+            break;
+          }
         }
       }
 
@@ -727,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
@@ -930,7 +962,7 @@ int PacketHandler::processUpdate(DNSPacket& p) {
       if (nsRRInZone.size() > nsRRtoDelete.size()) { // only delete if the NS's we delete are less then what we have in the zone (3.4.2.4)
         for (auto& inZone: nsRRInZone) {
           for (auto& rr: nsRRtoDelete) {
-            if (inZone.getZoneRepresentation() == (rr)->d_content->getZoneRepresentation())
+            if (inZone.getZoneRepresentation() == (rr)->getContent()->getZoneRepresentation())
               changedRecords += performUpdate(msgPrefix, rr, &di, isPresigned, &narrow, &haveNSEC3, &ns3pr, &updatedSerial);
           }
         }
@@ -957,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") {
diff --git a/pdns/root-addresses.hh b/pdns/root-addresses.hh
deleted file mode 100644 (file)
index a0167b2..0000000
+++ /dev/null
@@ -1,54 +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
-
-static const char* const rootIps4[]={"198.41.0.4",             // a.root-servers.net.
-                                     "199.9.14.201",           // b.root-servers.net.
-                                     "192.33.4.12",            // c.root-servers.net.
-                                     "199.7.91.13",            // d.root-servers.net.
-                                     "192.203.230.10",         // e.root-servers.net.
-                                     "192.5.5.241",            // f.root-servers.net.
-                                     "192.112.36.4",           // g.root-servers.net.
-                                     "198.97.190.53",          // h.root-servers.net.
-                                     "192.36.148.17",          // i.root-servers.net.
-                                     "192.58.128.30",          // j.root-servers.net.
-                                     "193.0.14.129",           // k.root-servers.net.
-                                     "199.7.83.42",            // l.root-servers.net.
-                                     "202.12.27.33"            // m.root-servers.net.
-                                    };
-static size_t const rootIps4Count = sizeof(rootIps4) / sizeof(*rootIps4);
-
-static const char* const rootIps6[]={"2001:503:ba3e::2:30",    // a.root-servers.net.
-                                     "2001:500:200::b",        // b.root-servers.net.
-                                     "2001:500:2::c",          // c.root-servers.net.
-                                     "2001:500:2d::d",         // d.root-servers.net.
-                                     "2001:500:a8::e",         // e.root-servers.net.
-                                     "2001:500:2f::f",         // f.root-servers.net.
-                                     "2001:500:12::d0d",       // g.root-servers.net.
-                                     "2001:500:1::53",         // h.root-servers.net.
-                                     "2001:7fe::53",           // i.root-servers.net.
-                                     "2001:503:c27::2:30",     // j.root-servers.net.
-                                     "2001:7fd::1",            // k.root-servers.net.
-                                     "2001:500:9f::42",        // l.root-servers.net.
-                                     "2001:dc3::35"            // m.root-servers.net.
-                                    };
-static size_t const rootIps6Count = sizeof(rootIps6) / sizeof(*rootIps6);
diff --git a/pdns/rpzloader.cc b/pdns/rpzloader.cc
deleted file mode 100644 (file)
index 03d8228..0000000
+++ /dev/null
@@ -1,590 +0,0 @@
-#include "arguments.hh"
-#include "dnsparser.hh"
-#include "dnsrecords.hh"
-#include "ixfr.hh"
-#include "syncres.hh"
-#include "axfr-retriever.hh"
-#include "lock.hh"
-#include "logger.hh"
-#include "logging.hh"
-#include "rec-lua-conf.hh"
-#include "rpzloader.hh"
-#include "zoneparser-tng.hh"
-#include "threadname.hh"
-#include "query-local-address.hh"
-
-Netmask makeNetmaskFromRPZ(const DNSName& name)
-{
-  auto parts = name.getRawLabels();
-  /*
-   * why 2?, the minimally valid IPv6 address that can be encoded in an RPZ is
-   * $NETMASK.zz (::/$NETMASK)
-   * Terrible right?
-   */
-  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))
-        isV6 = true;
-
-    if (pdns_iequals(part,"zz")) {
-      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)
-    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[parts.size()-1] == "") {
-    v6 += ":";
-  }
-  for (uint8_t i = parts.size()-1 ; i > 0; i--) {
-    v6 += parts[i];
-    if (i > 1 || (i == 1 && parts[i] == "")) {
-      v6 += ":";
-    }
-  }
-  v6 += "/" + parts[0];
-
-  return Netmask(v6);
-}
-
-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)
-{
-  static const DNSName drop("rpz-drop."), truncate("rpz-tcp-only."), noaction("rpz-passthru.");
-  static const DNSName rpzClientIP("rpz-client-ip"), rpzIP("rpz-ip"),
-    rpzNSDname("rpz-nsdname"), rpzNSIP("rpz-nsip.");
-  static const std::string rpzPrefix("rpz-");
-
-  DNSFilterEngine::Policy pol;
-  bool defpolApplied = false;
-
-  if(dr.d_class != QClass::IN) {
-    return;
-  }
-
-  if(dr.d_type == QType::CNAME) {
-    auto crc = getRR<CNAMERecordContent>(dr);
-    if (!crc) {
-      return;
-    }
-    auto crcTarget=crc->getTarget();
-    if(defpol) {
-      pol=*defpol;
-      defpolApplied = true;
-    }
-    else if(crcTarget.isRoot()) {
-      // cerr<<"Wants NXDOMAIN for "<<dr.d_name<<": ";
-      pol.d_kind = DNSFilterEngine::PolicyKind::NXDOMAIN;
-    } else if(crcTarget==g_wildcarddnsname) {
-      // cerr<<"Wants NODATA for "<<dr.d_name<<": ";
-      pol.d_kind = DNSFilterEngine::PolicyKind::NODATA;
-    }
-    else if(crcTarget==drop) {
-      // cerr<<"Wants DROP for "<<dr.d_name<<": ";
-      pol.d_kind = DNSFilterEngine::PolicyKind::Drop;
-    }
-    else if(crcTarget==truncate) {
-      // cerr<<"Wants TRUNCATE for "<<dr.d_name<<": ";
-      pol.d_kind = DNSFilterEngine::PolicyKind::Truncate;
-    }
-    else if(crcTarget==noaction) {
-      // cerr<<"Wants NOACTION for "<<dr.d_name<<": ";
-      pol.d_kind = DNSFilterEngine::PolicyKind::NoAction;
-    }
-    /* "The special RPZ encodings which are not to be taken as Local Data are
-       CNAMEs with targets that are:
-       +  "."  (NXDOMAIN action),
-       +  "*." (NODATA action),
-       +  a top level domain starting with "rpz-",
-       +  a child of a top level domain starting with "rpz-".
-    */
-    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. */
-      g_log<<Logger::Info<<"Discarding unsupported RPZ entry "<<crcTarget<<" for "<<dr.d_name<<endl;
-      return;
-    }
-    else {
-      pol.d_kind = DNSFilterEngine::PolicyKind::Custom;
-      pol.d_custom.emplace_back(dr.d_content);
-      // cerr<<"Wants custom "<<crcTarget<<" for "<<dr.d_name<<": ";
-    }
-  }
-  else {
-    if (defpol && defpolOverrideLocal) {
-      pol=*defpol;
-      defpolApplied = true;
-    }
-    else {
-      pol.d_kind = DNSFilterEngine::PolicyKind::Custom;
-      pol.d_custom.emplace_back(dr.d_content);
-      // 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));
-  } else {
-    pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, static_cast<uint32_t>(pol.d_ttl)));
-  }
-
-  // now to DO something with that
-
-  if(dr.d_name.isPartOf(rpzNSDname)) {
-    DNSName filt=dr.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)) {
-    // 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));
-  } 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);
-    }
-    else {
-      zone->rmQNameTrigger(dr.d_name, std::move(pol));
-    }
-  }
-}
-
-static shared_ptr<SOARecordContent> loadRPZFromServer(const shared_ptr<Logr::Logger>& 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)
-{
-
-  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"));
-  logger = logger->v(1);
-  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)));
-  }
-
-  ComboAddress local(localAddress);
-  if (local == ComboAddress())
-    local = pdns::getQueryLocalAddress(primary.sin4.sin_family, 0);
-
-  AXFRRetriever axfr(primary, zoneName, tt, &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<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) {
-       continue;
-      }
-
-      dr.d_name.makeUsRelative(zoneName);
-      if(dr.d_type==QType::SOA) {
-       sr = getRR<SOARecordContent>(dr);
-       continue;
-      }
-
-      RPZRecordToPolicy(dr, zone, true, defpol, defpolOverrideLocal, maxTTL);
-      nrecords++;
-    } 
-    axfrNow = time(nullptr);
-    if (axfrNow < axfrStart || axfrNow - axfrStart > axfrTimeout) {
-      throw PDNSException("Total AXFR time exceeded!");
-    }
-    if(last != time(0)) {
-      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);
-    }
-  }
-  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;
-}
-
-static LockGuarded<std::unordered_map<std::string, shared_ptr<rpzStats> > > s_rpzStats;
-
-shared_ptr<rpzStats> getRPZZoneStats(const std::string& zone)
-{
-  auto stats = s_rpzStats.lock();
-  auto it = stats->find(zone);
-  if (it == stats->end()) {
-    auto stat = std::make_shared<rpzStats>();
-    (*stats)[zone] = stat;
-    return stat;
-  }
-  return it->second;
-}
-
-static void incRPZFailedTransfers(const std::string& zone)
-{
-  auto stats = getRPZZoneStats(zone);
-  if (stats != nullptr)
-    stats->d_failedTransfers++;
-}
-
-static void setRPZZoneNewState(const std::string& zone, uint32_t serial, uint64_t numberOfRecords, bool fromFile, bool wasAXFR)
-{
-  auto stats = getRPZZoneStats(zone);
-  if (stats == nullptr) {
-    return;
-  }
-  if (!fromFile) {
-    stats->d_successfulTransfers++;
-    if (wasAXFR) {
-      stats->d_fullTransfers++;
-    }
-  }
-  stats->d_lastUpdate = time(nullptr);
-  stats->d_serial = serial;
-  stats->d_numberOfRecords = numberOfRecords;
-}
-
-// this function is silent - you do the logging
-std::shared_ptr<SOARecordContent> loadRPZFromFile(const std::string& fname, std::shared_ptr<DNSFilterEngine::Zone> zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL)
-{
-  shared_ptr<SOARecordContent> sr = nullptr;
-  ZoneParserTNG zpt(fname);
-  zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
-  zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
-  DNSResourceRecord drr;
-  DNSName domain;
-  while(zpt.get(drr)) {
-    try {
-      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;
-        zone->setDomain(domain);
-      }
-      else if(dr.d_type == QType::NS) {
-       continue;
-      }
-      else {
-       dr.d_name=dr.d_name.makeRelative(domain);
-       RPZRecordToPolicy(dr, zone, true, defpol, defpolOverrideLocal, maxTTL);
-      }
-    }
-    catch(const PDNSException& pe) {
-      throw PDNSException("Issue parsing '"+drr.qname.toLogString()+"' '"+drr.content+"' at "+zpt.getLineOfFile()+": "+pe.reason);
-    }
-  }
-
-  if (sr != nullptr) {
-    zone->setRefresh(sr->d_st.refresh);
-    setRPZZoneNewState(zone->getName(), sr->d_st.serial, zone->size(), true, false);
-  }
-  return sr;
-}
-
-static bool dumpZoneToDisk(const shared_ptr<Logr::Logger>& plogger, const DNSName& zoneName, const std::shared_ptr<DNSFilterEngine::Zone>& newZone, const std::string& dumpZoneFileName)
-{
-  auto logger = plogger->v(1);
-  logger->info("Dumping zone to disk", "destination_file", Logging::Loggable(dumpZoneFileName));
-  std::string temp = dumpZoneFileName + "XXXXXX";
-  int fd = mkstemp(&temp.at(0));
-  if (fd < 0) {
-    SLOG(g_log<<Logger::Warning<<"Unable to open a file to dump the content of the RPZ zone "<<zoneName<<endl,
-         logger->error(Logr::Warning, errno, "Unable to create temporary file"));
-    return false;
-  }
-
-  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(fd, "w+"), fclose);
-  if (!fp) {
-    int err = errno;
-    close(fd);
-    SLOG(g_log<<Logger::Warning<<"Unable to open a file pointer to dump the content of the RPZ zone "<<zoneName<<endl,
-         logger->error(Logr::Warning, err, "Unable to open file pointer"));
-    return false;
-  }
-  fd = -1;
-
-  try {
-    newZone->dump(fp.get());
-  }
-  catch(const std::exception& e) {
-    SLOG(g_log<<Logger::Warning<<"Error while dumping the content of the RPZ zone "<<zoneName<<": "<<e.what()<<endl,
-         logger->error(Logr::Warning, e.what(), "Error while dumping the content of the RPZ"));
-    return false;
-  }
-
-  if (fflush(fp.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) {
-    SLOG(g_log<<Logger::Warning<<"Error while syncing the content of the RPZ zone "<<zoneName<<" to the dump file: "<<stringerror()<<endl,
-         logger->error(Logr::Warning, errno, "Error while syncing the content of the RPZ"));
-    return false;
-  }
-
-  if (fclose(fp.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::Warning, errno, "Error while writing the content of the RPZ"));
-    return false;
-  }
-
-  if (rename(temp.c_str(), dumpZoneFileName.c_str()) != 0) {
-    SLOG(g_log<<Logger::Warning<<"Error while moving the content of the RPZ zone "<<zoneName<<" to the dump file: "<<stringerror()<<endl,
-         logger->error(Logr::Warning, errno, "Error while moving the content of the RPZ", "destination_file", Logging::Loggable(dumpZoneFileName)));
-    return false;
-  }
-
-  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 axfrTimeout, const uint32_t refreshFromConf, std::shared_ptr<SOARecordContent> sr, const std::string& dumpZoneFileName, uint64_t configGeneration)
-{
-  setThreadName("pdns-r/RPZIXFR");
-  bool isPreloaded = sr != nullptr;
-  auto luaconfsLocal = g_luaconfs.getLocal();
-
-  auto logger = g_slog->withName("rpz")->v(1);
-
-  /* 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) {
-    /* 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) {
-      try {
-        sr = loadRPZFromServer(logger, primary, zoneName, newZone, defpol, defpolOverrideLocal, maxTTL, tt, maxReceivedBytes, localAddress, axfrTimeout);
-        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);
-
-        g_luaconfs.modify([zoneIdx, &newZone](LuaConfigItems& lci) {
-            lci.dfe.setZone(zoneIdx, newZone);
-          });
-
-        if (!dumpZoneFileName.empty()) {
-          dumpZoneToDisk(logger, zoneName, newZone, dumpZoneFileName);
-        }
-
-        /* no need to try another primary */
-        break;
-      }
-      catch(const std::exception& e) {
-        SLOG(g_log<<Logger::Warning<<"Unable to load RPZ zone '"<<zoneName<<"' from '"<<primary<<"': '"<<e.what()<<"'. (Will try again in "<<refresh<<" seconds...)"<<endl,
-             logger->info(Logr::Warning, "Unable to load RPZ zone, will retry", "from", Logging::Loggable(primary), "exception", Logging::Loggable(e.what()), "refresh", Logging::Loggable(refresh)));
-        incRPZFailedTransfers(polName);
-      }
-      catch(const PDNSException& e) {
-        SLOG(g_log<<Logger::Warning<<"Unable to load RPZ zone '"<<zoneName<<"' from '"<<primary<<"': '"<<e.reason<<"'. (Will try again in "<<refresh<<" seconds...)"<<endl,
-             logger->info(Logr::Warning, "Unable to load RPZ zone, will retry", "from", Logging::Loggable(primary), "exception", Logging::Loggable(e.reason), "refresh", Logging::Loggable(refresh)));
-        incRPZFailedTransfers(polName);
-      }
-    }
-
-    if (!sr) {
-      sleep(refresh);
-    }
-  }
-
-  bool skipRefreshDelay = isPreloaded;
-
-  for(;;) {
-    DNSRecord dr;
-    dr.d_content=sr;
-
-    if (skipRefreshDelay) {
-      skipRefreshDelay = false;
-    }
-    else {
-      sleep(refresh);
-    }
-
-    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.
-      */
-      g_log<<Logger::Info<<"A more recent configuration has been found, stopping the existing RPZ update thread for "<<zoneName<<endl;
-      return;
-    }
-
-    vector<pair<vector<DNSRecord>, vector<DNSRecord> > > deltas;
-    for (const auto& primary : primaries) {
-      g_log<<Logger::Info<<"Getting IXFR deltas for "<<zoneName<<" from "<<primary.toStringWithPort()<<", our serial: "<<getRR<SOARecordContent>(dr)->d_st.serial<<endl;
-
-      ComboAddress local(localAddress);
-      if (local == ComboAddress()) {
-        local = pdns::getQueryLocalAddress(primary.sin4.sin_family, 0);
-      }
-
-      try {
-        deltas = getIXFRDeltas(primary, zoneName, dr, tt, &local, maxReceivedBytes);
-
-        /* no need to try another primary */
-        break;
-      } catch(const std::runtime_error& e ){
-        g_log<<Logger::Warning<<e.what()<<endl;
-        incRPZFailedTransfers(polName);
-        continue;
-      }
-    }
-
-    if(deltas.empty()) {
-      continue;
-    }
-
-    try {
-      g_log<<Logger::Info<<"Processing "<<deltas.size()<<" delta"<<addS(deltas)<<" for RPZ "<<zoneName<<endl;
-
-      if (luaconfsLocal->generation != configGeneration) {
-        g_log<<Logger::Info<<"A more recent configuration has been found, stopping the existing RPZ update thread for "<<zoneName<<endl;
-        return;
-      }
-      oldZone = luaconfsLocal->dfe.getZone(zoneIdx);
-      if (!oldZone || oldZone->getDomain() != zoneName) {
-        g_log<<Logger::Info<<"This policy is no more, stopping the existing RPZ update thread for "<<zoneName << endl;
-        return;
-      }
-      /* we need to make a _full copy_ of the zone we are going to work on */
-      std::shared_ptr<DNSFilterEngine::Zone> newZone = std::make_shared<DNSFilterEngine::Zone>(*oldZone);
-      /* initialize the current serial to the last one */
-      std::shared_ptr<SOARecordContent> currentSR = sr;
-
-      int totremove=0, totadd=0;
-      bool fullUpdate = false;
-      for(const auto& delta : deltas) {
-        const auto& remove = delta.first;
-        const auto& add = delta.second;
-        if(remove.empty()) {
-          g_log<<Logger::Warning<<"IXFR update is a whole new zone"<<endl;
-          newZone->clear();
-          fullUpdate = true;
-        }
-        for(const auto& rr : remove) { // should always contain the SOA
-          if(rr.d_type == QType::NS)
-            continue;
-          if(rr.d_type == QType::SOA) {
-            auto oldsr = getRR<SOARecordContent>(rr);
-            if (oldsr && oldsr->d_st.serial == currentSR->d_st.serial) {
-              //           cout<<"Got good removal of SOA serial "<<oldsr->d_st.serial<<endl;
-            }
-            else {
-              if (!oldsr) {
-                throw std::runtime_error("Unable to extract serial from SOA record while processing the removal part of an update");
-              }
-              else {
-                throw std::runtime_error("Received an unexpected serial (" + std::to_string(oldsr->d_st.serial) + ", expecting " + std::to_string(currentSR->d_st.serial) + ") from SOA record while processing the removal part of an update");
-              }
-            }
-          }
-          else {
-            totremove++;
-            g_log<<(g_logRPZChanges ? Logger::Info : Logger::Debug)<<"Had removal of "<<rr.d_name<<" from RPZ zone "<<zoneName<<endl;
-            RPZRecordToPolicy(rr, newZone, false, defpol, defpolOverrideLocal, maxTTL);
-          }
-        }
-
-        for(const auto& rr : add) { // should always contain the new SOA
-          if(rr.d_type == QType::NS)
-            continue;
-          if(rr.d_type == QType::SOA) {
-            auto tempSR = getRR<SOARecordContent>(rr);
-            //   g_log<<Logger::Info<<"New SOA serial for "<<zoneName<<": "<<currentSR->d_st.serial<<endl;
-            if (tempSR) {
-              currentSR = tempSR;
-            }
-          }
-          else {
-            totadd++;
-            g_log<<(g_logRPZChanges ? Logger::Info : Logger::Debug)<<"Had addition of "<<rr.d_name<<" to RPZ zone "<<zoneName<<endl;
-            RPZRecordToPolicy(rr, newZone, true, defpol, defpolOverrideLocal, maxTTL);
-          }
-        }
-      }
-
-      /* only update sr now that all changes have been converted */
-      if (currentSR) {
-        sr = currentSR;
-      }
-      g_log<<Logger::Info<<"Had "<<totremove<<" RPZ removal"<<addS(totremove)<<", "<<totadd<<" addition"<<addS(totadd)<<" for "<<zoneName<<" New serial: "<<sr->d_st.serial<<endl;
-      newZone->setSerial(sr->d_st.serial);
-      newZone->setRefresh(sr->d_st.refresh);
-      setRPZZoneNewState(polName, sr->d_st.serial, newZone->size(), 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) {
-        g_log<<Logger::Info<<"A more recent configuration has been found, stopping the existing RPZ update thread for "<<zoneName<<endl;
-        return;
-      }
-      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);
-    }
-    catch (const std::exception& e) {
-      g_log << Logger::Error << "Error while applying the update received over XFR for "<<zoneName<<", skipping the update: "<< e.what() <<endl;
-    }
-  }
-}
diff --git a/pdns/rpzloader.hh b/pdns/rpzloader.hh
deleted file mode 100644 (file)
index 304ab5a..0000000
+++ /dev/null
@@ -1,43 +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 "filterpo.hh"
-#include <string>
-#include "dnsrecords.hh"
-
-extern bool g_logRPZChanges;
-
-std::shared_ptr<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 axfrTimeout, const uint32_t reloadFromConf, shared_ptr<SOARecordContent> sr, const std::string& dumpZoneFileName, uint64_t configGeneration);
-
-struct rpzStats
-{
-  std::atomic<uint64_t> d_failedTransfers;
-  std::atomic<uint64_t> d_successfulTransfers;
-  std::atomic<uint64_t> d_fullTransfers;
-  std::atomic<uint64_t> d_numberOfRecords;
-  std::atomic<time_t> d_lastUpdate;
-  std::atomic<uint32_t> d_serial;
-};
-
-Netmask makeNetmaskFromRPZ(const DNSName& name);
-shared_ptr<rpzStats> getRPZZoneStats(const std::string& zone);
index ef0761e5edd15012099ac62f223722483bd93048..bd333095e955e96860dd4cc64e9ee1b21ccfaf82 100644 (file)
@@ -12,6 +12,7 @@
 #include "dnssecinfra.hh"
 
 #include "dns_random.hh"
+#include "gss_context.hh"
 
 StatBag S;
 
@@ -19,13 +20,14 @@ int main(int argc, char** argv)
 try
 {
   if(argc < 4) {
-    cerr<<"Syntax: saxfr IP-address port zone [showdetails] [showflags] [unhash] [tsig:keyname:algo:secret]"<<endl;
+    cerr<<"Syntax: saxfr IP-address port zone [showdetails] [showflags] [unhash] [gss:remote-principal] [tsig:keyname:algo:secret]"<<endl;
     exit(EXIT_FAILURE);
   }
 
   bool showdetails=false;
   bool showflags=false;
   bool unhash=false;
+  bool gss=false;
   bool tsig=false;
   TSIGHashEnum tsig_algo;
   DNSName tsig_key;
@@ -41,6 +43,16 @@ try
         showflags=true;
       if (strcmp(argv[i], "unhash") == 0)
         unhash=true;
+      if (strncmp(argv[i], "gss:",4) == 0) {
+        gss=true;
+        tsig=true;
+        tsig_algo=TSIG_GSS;
+        remote_principal = string(argv[i]+4);
+        if (remote_principal.empty()) {
+          cerr<<"Remote principal is required"<<endl;
+          exit(EXIT_FAILURE);
+        }
+      }
       if (strncmp(argv[i], "tsig:",5) == 0) {
         vector<string> parts;
         tsig=true;
@@ -78,6 +90,76 @@ try
   Socket sock(dest.sin4.sin_family, SOCK_STREAM);
   sock.connect(dest);
 
+  if (gss) {
+#ifndef ENABLE_GSS_TSIG
+    cerr<<"No GSS support compiled in"<<endl;
+    exit(EXIT_FAILURE);
+#else
+    string input,output;
+    GssContext gssctx;
+    gssctx.generateLabel(argv[3]);
+    gssctx.setPeerPrincipal(remote_principal);
+
+    while(gssctx.init(input, output) && gssctx.valid() == false) {
+      input="";
+      DNSPacketWriter pwtkey(packet, gssctx.getLabel(), QType::TKEY, QClass::ANY);
+      TKEYRecordContent tkrc;
+      tkrc.d_algo = DNSName("gss-tsig.");
+      // coverity[store_truncates_time_t]
+      tkrc.d_inception = time((time_t*)NULL);
+      tkrc.d_expiration = tkrc.d_inception+15;
+      tkrc.d_mode = 3;
+      tkrc.d_error = 0;
+      tkrc.d_keysize = output.size();
+      tkrc.d_key = output;
+      tkrc.d_othersize = 0;
+      pwtkey.getHeader()->id = dns_random_uint16();
+      pwtkey.startRecord(gssctx.getLabel(), QType::TKEY, 3600, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
+      tkrc.toPacket(pwtkey);
+      pwtkey.commit();
+      for(const string& msg :  gssctx.getErrorStrings()) {
+        cerr<<msg<<endl;
+      }
+
+      len = htons(packet.size());
+      if(sock.write((char *) &len, 2) != 2)
+        throw PDNSException("tcp write failed");
+      sock.writen(string((char*)&packet[0], packet.size()));
+      if(sock.read((char *) &len, 2) != 2)
+        throw PDNSException("tcp read failed");
+
+      len=ntohs(len);
+      auto creply = std::make_unique<char[]>(len);
+      int n=0;
+      int numread;
+      while(n<len) {
+        numread=sock.read(creply.get()+n, len-n);
+        if(numread<0)
+          throw PDNSException("tcp read failed");
+        n+=numread;
+      }
+
+      MOADNSParser mdp(false, string(creply.get(), len));
+       if (mdp.d_header.rcode != 0) {
+         throw PDNSException(string("Remote server refused: ") + std::to_string(mdp.d_header.rcode));
+       }
+       for(MOADNSParser::answers_t::const_iterator i=mdp.d_answers.begin(); i!=mdp.d_answers.end(); ++i) {
+         if(i->first.d_type != QType::TKEY) continue;
+         // recover TKEY record
+         tkrc = TKEYRecordContent(i->first.getContent()->getZoneRepresentation());
+         input = tkrc.d_key;
+       }
+    }
+
+    if (gssctx.valid() == false) {
+      cerr<<"Could not create GSS context"<<endl;
+      exit(EXIT_FAILURE);
+    }
+
+    tsig_key = DNSName(gssctx.getLabel());
+#endif
+  }
+
   DNSPacketWriter pw(packet, DNSName(argv[3]), 252);
 
   pw.getHeader()->id = dns_random_uint16();
@@ -132,7 +214,7 @@ try
         if (!tsig) {
           std::cerr<<"Unexpected TSIG signature in data"<<endl;
         }
-        trc = TSIGRecordContent(i->first.d_content->getZoneRepresentation());
+        trc = TSIGRecordContent(i->first.getContent()->getZoneRepresentation());
         continue;
       }
       if(i->first.d_type == QType::SOA)
@@ -140,26 +222,26 @@ try
         ++soacount;
       }
       else if (i->first.d_type == QType::NSEC3PARAM) {
-          ns3pr = NSEC3PARAMRecordContent(i->first.d_content->getZoneRepresentation());
-          isNSEC3 = true;
+        ns3pr = NSEC3PARAMRecordContent(i->first.getContent()->getZoneRepresentation());
+        isNSEC3 = true;
       }
 
       ostringstream o;
       o<<"\t"<<i->first.d_ttl<<"\tIN\t"<<DNSRecordContent::NumberToType(i->first.d_type);
       if(showdetails)
       {
-        o<<"\t"<<i->first.d_content->getZoneRepresentation();
+        o<<"\t"<<i->first.getContent()->getZoneRepresentation();
       }
       else if(i->first.d_type == QType::RRSIG)
       {
-        string zoneRep = i->first.d_content->getZoneRepresentation();
+        string zoneRep = i->first.getContent()->getZoneRepresentation();
         vector<string> parts;
         stringtok(parts, zoneRep);
         o<<"\t"<<parts[0]<<" "<<parts[1]<<" "<<parts[2]<<" "<<parts[3]<<" [expiry] [inception] [keytag] "<<parts[7]<<" ...";
       }
       else if(i->first.d_type == QType::NSEC3)
       {
-        string zoneRep = i->first.d_content->getZoneRepresentation();
+        string zoneRep = i->first.getContent()->getZoneRepresentation();
         vector<string> parts;
         stringtok(parts, zoneRep);
         o<<"\t"<<parts[0]<<" ";
@@ -173,14 +255,14 @@ try
       }
       else if(i->first.d_type == QType::DNSKEY)
       {
-        string zoneRep = i->first.d_content->getZoneRepresentation();
+        string zoneRep = i->first.getContent()->getZoneRepresentation();
         vector<string> parts;
         stringtok(parts, zoneRep);
         o<<"\t"<<parts[0]<<" "<<parts[1]<<" "<<parts[2]<<" ...";
       }
       else
       {
-        o<<"\t"<<i->first.d_content->getZoneRepresentation();
+        o<<"\t"<<i->first.getContent()->getZoneRepresentation();
       }
 
       records.emplace_back(i->first.d_name, o.str());
index 917f75038eee1ddaed121bb09a4428bf97bc4296..036122317adf88ff5338090f7f977dbefb9d27d8 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;
 
@@ -57,7 +57,7 @@ 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 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)
@@ -121,27 +121,27 @@ static void printReply(const string& reply, bool showflags, bool hidesoadetails,
   for (MOADNSParser::answers_t::const_iterator i = mdp.d_answers.begin();
        i != mdp.d_answers.end(); ++i) {
     cout << i->first.d_place - 1 << "\t" << i->first.d_name.toString() << "\t"
-         << nameForClass(i->first.d_class, i->first.d_type) << "\t"
+         << ttl(i->first.d_ttl) << "\t" << nameForClass(i->first.d_class, i->first.d_type) << "\t"
          << DNSRecordContent::NumberToType(i->first.d_type);
     if (dumpluaraw) {
-      cout<<"\t"<< makeLuaString(i->first.d_content->serialize(DNSName(), true))<<endl;
+      cout<<"\t"<< makeLuaString(i->first.getContent()->serialize(DNSName(), true))<<endl;
       continue;
     }
     if (i->first.d_class == QClass::IN) {
       if (i->first.d_type == QType::RRSIG) {
-        string zoneRep = i->first.d_content->getZoneRepresentation();
+        string zoneRep = i->first.getContent()->getZoneRepresentation();
         vector<string> parts;
         stringtok(parts, zoneRep);
-        cout << "\t" << ttl(i->first.d_ttl) << "\t" << parts[0] << " "
+        cout << "\t" << parts[0] << " "
              << parts[1] << " " << parts[2] << " " << parts[3]
              << " [expiry] [inception] [keytag] " << parts[7] << " ...\n";
         continue;
       }
       if (!showflags && i->first.d_type == QType::NSEC3) {
-        string zoneRep = i->first.d_content->getZoneRepresentation();
+        string zoneRep = i->first.getContent()->getZoneRepresentation();
         vector<string> parts;
         stringtok(parts, zoneRep);
-        cout << "\t" << ttl(i->first.d_ttl) << "\t" << parts[0] << " [flags] "
+        cout << "\t" << parts[0] << " [flags] "
              << parts[2] << " " << parts[3] << " " << parts[4];
         for (vector<string>::iterator iter = parts.begin() + 5;
              iter != parts.end(); ++iter)
@@ -150,25 +150,24 @@ static void printReply(const string& reply, bool showflags, bool hidesoadetails,
         continue;
       }
       if (i->first.d_type == QType::DNSKEY) {
-        string zoneRep = i->first.d_content->getZoneRepresentation();
+        string zoneRep = i->first.getContent()->getZoneRepresentation();
         vector<string> parts;
         stringtok(parts, zoneRep);
-        cout << "\t" << ttl(i->first.d_ttl) << "\t" << parts[0] << " "
+        cout << "\t" << parts[0] << " "
              << parts[1] << " " << parts[2] << " ...\n";
         continue;
       }
       if (i->first.d_type == QType::SOA && hidesoadetails) {
-        string zoneRep = i->first.d_content->getZoneRepresentation();
+        string zoneRep = i->first.getContent()->getZoneRepresentation();
         vector<string> parts;
         stringtok(parts, zoneRep);
-        cout << "\t" << ttl(i->first.d_ttl) << "\t" << parts[0] << " "
+        cout << "\t" << parts[0] << " "
              << parts[1] << " [serial] " << parts[3] << " " << parts[4] << " "
              << parts[5] << " " << parts[6] << "\n";
         continue;
       }
     }
-    cout << "\t" << ttl(i->first.d_ttl) << "\t"
-         << i->first.d_content->getZoneRepresentation() << "\n";
+    cout << "\t" << i->first.getContent()->getZoneRepresentation() << "\n";
   }
 
   EDNSOpts edo;
@@ -186,6 +185,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;
       }
@@ -418,7 +422,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, time(nullptr));
+    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()) {
diff --git a/pdns/secpoll-recursor.cc b/pdns/secpoll-recursor.cc
deleted file mode 100644 (file)
index 6961803..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-#include "secpoll-recursor.hh"
-#include "syncres.hh"
-#include "logger.hh"
-#include "arguments.hh"
-#include "version.hh"
-#include "validate-recursor.hh"
-#include "secpoll.hh"
-
-#include <stdint.h>
-#ifndef PACKAGEVERSION
-#define PACKAGEVERSION getPDNSVersion()
-#endif
-
-uint32_t g_security_status;
-string g_security_message;
-
-void doSecPoll(time_t* last_secpoll)
-{
-  if (::arg()["security-poll-suffix"].empty())
-    return;
-
-  string pkgv(PACKAGEVERSION);
-  struct timeval now;
-  gettimeofday(&now, 0);
-
-  /* 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);
-  if (g_dnssecmode != DNSSECMode::Off) {
-    sr.setDoDNSSEC(true);
-    sr.setDNSSECValidationRequested(true);
-  }
-
-  vector<DNSRecord> ret;
-
-  string version = "recursor-" + pkgv;
-  string qstring(version.substr(0, 63) + ".security-status." + ::arg()["security-poll-suffix"]);
-
-  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);
-
-  if (g_dnssecmode != DNSSECMode::Off && res) {
-    state = sr.getValidationState();
-  }
-
-  if (vStateIsBogus(state)) {
-    g_log << Logger::Error << "Failed to retrieve security status update for '" + pkgv + "' on '" << query << "', DNSSEC validation result was Bogus!" << endl;
-    if (g_security_status == 1) // If we were OK, go to unknown
-      g_security_status = 0;
-    return;
-  }
-
-  if (res == RCode::NXDomain && !isReleaseVersion(pkgv)) {
-    g_log << Logger::Warning << "Not validating response for security status update, this is a non-release version" << endl;
-    return;
-  }
-
-  string security_message;
-  int security_status = g_security_status;
-
-  try {
-    processSecPoll(res, ret, security_status, security_message);
-  }
-  catch (const PDNSException& pe) {
-    g_security_status = security_status;
-    g_log << Logger::Warning << "Failed to retrieve security status update for '" << pkgv << "' on '" << query << "': " << pe.reason << endl;
-    return;
-  }
-
-  g_security_message = security_message;
-
-  if (g_security_status != 1 && security_status == 1) {
-    g_log << Logger::Warning << "Polled security status of version " << pkgv << ", no known issues reported: " << g_security_message << endl;
-  }
-  if (security_status == 2) {
-    g_log << Logger::Error << "PowerDNS Security Update Recommended: " << g_security_message << endl;
-  }
-  if (security_status == 3) {
-    g_log << Logger::Error << "PowerDNS Security Update Mandatory: " << g_security_message << endl;
-  }
-
-  g_security_status = security_status;
-}
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 3b85ae055b43abf2e915f1e761c90bf5aeb9d1ae..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")) {
@@ -170,7 +172,7 @@ DNSZoneRecord makeEditedDNSZRFromSOAData(DNSSECKeeper& dk, const SOAData& sd, DN
   soa.d_type = QType::SOA;
   soa.d_ttl = sd.ttl;
   soa.d_place = place;
-  soa.d_content = makeSOAContent(edited);
+  soa.setContent(makeSOAContent(edited));
 
   DNSZoneRecord dzr;
   dzr.domain_id = sd.domain_id;
index faef292f4705d92cbdb140ae9cf7cc848ec3065f..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:
+  SHADigest() :
+    SHADigest(256) {}
   SHADigest(unsigned int bits) :
 #if defined(HAVE_EVP_MD_CTX_NEW) && defined(HAVE_EVP_MD_CTX_FREE)
     mdctx(std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>(EVP_MD_CTX_new(), EVP_MD_CTX_free))
@@ -81,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)
   {
@@ -102,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 0f1bfd17b230af0227286957149f6cbea2ed1218..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.d_content->getZoneRepresentation(), a.dr.d_ttl) < std::make_tuple(b.dr.d_content->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.d_content->getZoneRepresentation(), a.dr.d_ttl) == std::make_tuple(b.dr.d_content->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
 }
 }
 
@@ -199,8 +199,10 @@ void ChunkedSigningPipe::sendRRSetToWorker() // it sounds so socialist!
   
   if(wantWrite && !rwVect.second.empty()) {
     shuffle(rwVect.second.begin(), rwVect.second.end(), pdns::dns_random_engine()); // pick random available worker
-    auto ptr = d_rrsetToSign.release();
+    auto ptr = d_rrsetToSign.get();
     writen2(*rwVect.second.begin(), &ptr, sizeof(ptr));
+    // coverity[leaked_storage]
+    static_cast<void>(d_rrsetToSign.release());
     d_rrsetToSign = make_unique<rrset_t>();
     d_outstandings[*rwVect.second.begin()]++;
     d_outstanding++;
@@ -248,8 +250,10 @@ void ChunkedSigningPipe::sendRRSetToWorker() // it sounds so socialist!
   if(wantWrite) {  // our optimization above failed, we now wait synchronously
     rwVect = waitForRW(false, wantWrite, -1); // wait for something to happen
     shuffle(rwVect.second.begin(), rwVect.second.end(), pdns::dns_random_engine()); // pick random available worker
-    auto ptr = d_rrsetToSign.release();
+    auto ptr = d_rrsetToSign.get();
     writen2(*rwVect.second.begin(), &ptr, sizeof(ptr));
+    // coverity[leaked_storage]
+    static_cast<void>(d_rrsetToSign.release());
     d_rrsetToSign = make_unique<rrset_t>();
     d_outstandings[*rwVect.second.begin()]++;
     d_outstanding++;
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 d1319a1a4227e88125de9a5b3301691864c85dc3..cb97acc8fe493373de5cc5bade5d39d869ae2415 100644 (file)
 #include <cstdlib>
 #include <sys/types.h>
 #include <string>
-#include <errno.h>
+#include <cerrno>
 #include "dnsrecords.hh"
 
-static unsigned int poweroften[10] = {1, 10, 100, 1000, 10000, 100000,
-                                 1000000,10000000,100000000,1000000000};
+const static unsigned int poweroften[10] = {1, 10, 100, 1000, 10000, 100000,
+  1000000,10000000,100000000,1000000000};
 
 /* converts ascii size/precision X * 10**Y(cm) to 0xXY. moves pointer.*/
 static uint8_t precsize_aton(const char **strptr)
@@ -69,22 +69,22 @@ latlon2ul(const char **latlonstrptr, int *which)
 
   while (isdigit(*cp))
     deg = deg * 10 + (*cp++ - '0');
-  
+
   while (isspace(*cp))
     cp++;
-  
+
   if (!(isdigit(*cp)))
     goto fndhemi;
-  
+
   while (isdigit(*cp))
     min = min * 10 + (*cp++ - '0');
-    
+
   while (isspace(*cp))
     cp++;
-  
+
   if (*cp && !(isdigit(*cp)))
     goto fndhemi;
-  
+
   while (isdigit(*cp))
     secs = secs * 10 + (*cp++ - '0');
 
@@ -100,13 +100,13 @@ latlon2ul(const char **latlonstrptr, int *which)
       }
     }
   }
-  
+
   while (*cp && !isspace(*cp))   /* if any trailing garbage */
     cp++;
-  
+
   while (isspace(*cp))
     cp++;
-  
+
  fndhemi:
   switch (*cp) {
   case 'N': case 'n':
@@ -125,7 +125,7 @@ latlon2ul(const char **latlonstrptr, int *which)
     retval = 0;     /* invalid value -- indicates error */
     break;
   }
-  
+
   switch (*cp) {
   case 'N': case 'n':
   case 'S': case 's':
@@ -144,15 +144,15 @@ latlon2ul(const char **latlonstrptr, int *which)
     return 0;
 
   cp++;                   /* skip the hemisphere */
-  
+
   while (*cp && !isspace(*cp))   /* if any trailing garbage */
     cp++;
-  
+
   while (isspace(*cp))    /* move to next field */
     cp++;
-  
+
   *latlonstrptr = cp;
-  
+
   return (retval);
 }
 
@@ -168,7 +168,7 @@ std::shared_ptr<DNSRecordContent> LOCRecordContent::make(const string& content)
 }
 
 
-void LOCRecordContent::toPacket(DNSPacketWriter& pw)
+void LOCRecordContent::toPacket(DNSPacketWriter& pw) const
 {
   pw.xfr8BitInt(d_version);
   pw.xfr8BitInt(d_size);
@@ -180,7 +180,7 @@ void LOCRecordContent::toPacket(DNSPacketWriter& pw)
   pw.xfr32BitInt(d_altitude);
 }
 
-std::shared_ptr<LOCRecordContent::DNSRecordContent> LOCRecordContent::make(const DNSRecord &dr, PacketReader& pr) 
+std::shared_ptr<LOCRecordContent::DNSRecordContent> LOCRecordContent::make(const DNSRecord& /* dr */, PacketReader& pr)
 {
   auto ret=std::make_shared<LOCRecordContent>();
   pr.xfr8BitInt(ret->d_version);
@@ -191,18 +191,18 @@ std::shared_ptr<LOCRecordContent::DNSRecordContent> LOCRecordContent::make(const
   pr.xfr32BitInt(ret->d_latitude);
   pr.xfr32BitInt(ret->d_longitude);
   pr.xfr32BitInt(ret->d_altitude);
-  
+
   return ret;
 }
 
-LOCRecordContent::LOCRecordContent(const string& content, const string& zone) 
+LOCRecordContent::LOCRecordContent(const string& content, const string& /* zone */)
 {
   // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
   // convert this to d_version, d_size, d_horiz/vertpre, d_latitude, d_longitude, d_altitude
   d_version = 0;
 
   const char *cp, *maxcp;
-  
+
   uint32_t lltemp1 = 0, lltemp2 = 0;
   int altmeters = 0, altfrac = 0, altsign = 1;
   d_horizpre = 0x16;    /* default = 1e6 cm = 10000.00m = 10km */
@@ -240,10 +240,10 @@ LOCRecordContent::LOCRecordContent(const string& content, const string& zone)
 
   if (*cp == '+')
     cp++;
-  
+
   while (isdigit(*cp))
     altmeters = altmeters * 10 + (*cp++ - '0');
-  
+
   if (*cp == '.') {               /* decimal meters */
     cp++;
     if (isdigit(*cp)) {
@@ -253,50 +253,50 @@ LOCRecordContent::LOCRecordContent(const string& content, const string& zone)
       }
     }
   }
-  
+
   d_altitude = (10000000 + (altsign * (altmeters * 100 + altfrac)));
-  
+
   while (!isspace(*cp) && (cp < maxcp))
     /* if trailing garbage or m */
     cp++;
-  
+
   while (isspace(*cp) && (cp < maxcp))
     cp++;
-  
-  
+
+
   if (cp >= maxcp)
     goto defaults;
-  
+
   d_size = precsize_aton(&cp);
-  
+
   while (!isspace(*cp) && (cp < maxcp))/*if trailing garbage or m*/
     cp++;
-  
+
   while (isspace(*cp) && (cp < maxcp))
     cp++;
-  
+
   if (cp >= maxcp)
     goto defaults;
-  
+
   d_horizpre = precsize_aton(&cp);
-  
+
   while (!isspace(*cp) && (cp < maxcp))/*if trailing garbage or m*/
     cp++;
-  
+
   while (isspace(*cp) && (cp < maxcp))
     cp++;
-  
+
   if (cp >= maxcp)
     goto defaults;
-  
+
   d_vertpre = precsize_aton(&cp);
-  
+
  defaults:
   ;
 }
 
 
-string LOCRecordContent::getZoneRepresentation(bool noDot) const
+string LOCRecordContent::getZoneRepresentation(bool /* noDot */) const
 {
   // convert d_version, d_size, d_horiz/vertpre, d_latitude, d_longitude, d_altitude to:
   // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
@@ -304,7 +304,7 @@ string LOCRecordContent::getZoneRepresentation(bool noDot) const
   double latitude= ((int32_t)((uint32_t)d_latitude  - ((uint32_t)1<<31)))/3600000.0;
   double longitude=((int32_t)((uint32_t)d_longitude - ((uint32_t)1<<31)))/3600000.0;
   double altitude= ((int32_t)d_altitude           )/100.0 - 100000;
-  
+
   double size=0.01*((d_size>>4)&0xf);
   int count=d_size & 0xf;
   while(count--)
diff --git a/pdns/slavecommunicator.cc b/pdns/slavecommunicator.cc
deleted file mode 100644 (file)
index 79b0d39..0000000
+++ /dev/null
@@ -1,1066 +0,0 @@
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include "utility.hh"
-#include "dnssecinfra.hh"
-#include "dnsseckeeper.hh"
-#include "base32.hh"
-#include <errno.h>
-#include "communicator.hh"
-#include <set>
-#include <boost/utility.hpp>
-#include "dnsbackend.hh"
-#include "ueberbackend.hh"
-#include "packethandler.hh"
-#include "axfr-retriever.hh"
-#include "logger.hh"
-#include "dns.hh"
-#include "arguments.hh"
-#include "auth-caches.hh"
-
-#include "base64.hh"
-#include "inflighter.cc"
-#include "namespaces.hh"
-#include "common_startup.hh"
-#include "query-local-address.hh"
-
-#include "ixfr.hh"
-
-void CommunicatorClass::addSuckRequest(const DNSName &domain, const ComboAddress& master, SuckRequest::RequestPriority priority, bool force)
-{
-  auto data = d_data.lock();
-  SuckRequest sr;
-  sr.domain = domain;
-  sr.master = master;
-  sr.force = force;
-  sr.priorityAndOrder.first = priority;
-  sr.priorityAndOrder.second = data->d_sorthelper++;
-  pair<UniQueue::iterator, bool>  res;
-
-  res = data->d_suckdomains.insert(sr);
-  if(res.second) {
-    d_suck_sem.post();
-  } else {
-    data->d_suckdomains.modify(res.first, [priorityAndOrder = sr.priorityAndOrder] (SuckRequest& so) {
-      if (priorityAndOrder.first < so.priorityAndOrder.first) {
-        so.priorityAndOrder = priorityAndOrder;
-      }
-    });
-  }
-}
-
-struct ZoneStatus
-{
-  bool isDnssecZone{false};
-  bool isPresigned{false};
-  bool isNSEC3 {false};
-  bool optOutFlag {false};
-  NSEC3PARAMRecordContent ns3pr;
-
-  bool isNarrow{false};
-  unsigned int soa_serial{0};
-  set<DNSName> nsset, qnames, secured;
-  uint32_t domain_id;
-  int numDeltas{0};
-};
-
-
-void CommunicatorClass::ixfrSuck(const DNSName &domain, const TSIGTriplet& tt, const ComboAddress& laddr, const ComboAddress& remote, unique_ptr<AuthLua4>& pdl,
-                                 ZoneStatus& zs, vector<DNSRecord>* axfr)
-{
-  string logPrefix="IXFR-in zone '"+domain.toLogString()+"', primary '"+remote.toString()+"', ";
-
-  UeberBackend B; // fresh UeberBackend
-
-  DomainInfo di;
-  di.backend=nullptr;
-  //  bool transaction=false;
-  try {
-    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;
-      else
-        g_log<<Logger::Warning<<logPrefix<<"can't determine backend"<<endl;
-      return;
-    }
-
-    soatimes st;
-    memset(&st, 0, sizeof(st));
-    st.serial=di.serial;
-
-    DNSRecord drsoa;
-    drsoa.d_content = std::make_shared<SOARecordContent>(g_rootdnsname, g_rootdnsname, st);
-    auto deltas = getIXFRDeltas(remote, domain, drsoa, 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) {
-      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!
-        *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;
-      
-      for(const auto& x: remove)
-        grouped[{x.d_name, x.d_type}].first.push_back(x);
-      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) {
-        vector<DNSRecord> rrset;
-        {
-          DNSZoneRecord 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);
-          }
-        }
-        // O(N^2)!
-        rrset.erase(remove_if(rrset.begin(), rrset.end(), 
-                              [&g](const DNSRecord& dr) {
-                                return count(g.second.first.cbegin(), 
-                                             g.second.first.cend(), dr);
-                              }), rrset.end());
-        // the DNSRecord== operator compares on name, type, class and lowercase content representation
-
-        for(const auto& x : g.second.second) {
-          rrset.push_back(x);
-        }
-
-        vector<DNSResourceRecord> replacement;
-        for(const auto& dr : rrset) {
-          auto rr = DNSResourceRecord::fromWire(dr);
-          rr.qname += domain;
-          rr.domain_id = di.id;
-          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;
-          }
-          
-          replacement.push_back(rr);
-        }
-
-        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;
-    throw;
-  }
-  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()) {
-  case QType::NSEC3PARAM: 
-    zs.ns3pr = NSEC3PARAMRecordContent(rr.content);
-    zs.isDnssecZone = zs.isNSEC3 = true;
-    zs.isNarrow = false;
-    return false;
-  case QType::NSEC3: {
-    NSEC3RecordContent ns3rc(rr.content);
-    if (firstNSEC3) {
-      zs.isDnssecZone = zs.isPresigned = true;
-      firstNSEC3 = false;
-    } 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)) {
-      DNSName hashPart = rr.qname.makeRelative(domain);
-      zs.secured.insert(hashPart);
-    }
-    return false;
-  }
-  
-  case QType::NSEC: 
-    zs.isDnssecZone = zs.isPresigned = true;
-    return false;
-  
-  case QType::NS: 
-    if(rr.qname!=domain)
-      zs.nsset.insert(rr.qname);
-    break;
-  }
-
-  zs.qnames.insert(rr.qname);
-
-  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
-      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
-   3) The code walks through the zone records do determine DNSSEC status (secured, nsec/nsec3, optout)
-   4) It inserts the zone into the database
-      With the right 'ordername' fields
-   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)
-{
-  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);
-  Resolver::res_t recs;
-  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;
-    }
-
-    for(auto & rec : recs) {
-      rec.qname.makeUsLowerCase();
-      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;
-        continue;
-      }
-
-      vector<DNSResourceRecord> 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;
-          continue;
-        }
-        if(!processRecordForZS(domain, firstNSEC3, rr, zs))
-          continue;
-        if(rr.qtype.getCode() == QType::SOA) {
-          if(soa_received)
-            continue; //skip the last SOA
-          SOAData 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)
-{
-  {
-    auto data = d_data.lock();
-    if (data->d_inprogress.count(domain)) {
-      return; 
-    }
-    data->d_inprogress.insert(domain);
-  }
-  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()+"', ";
-
-  g_log<<Logger::Notice<<logPrefix<<"initiating transfer"<<endl;
-  UeberBackend B; // fresh UeberBackend
-
-  DomainInfo di;
-  di.backend=nullptr;
-  bool transaction=false;
-  try {
-    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.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;
-      else
-        g_log<<Logger::Warning<<logPrefix<<"can't determine backend"<<endl;
-      return;
-    }
-    ZoneStatus zs;
-    zs.domain_id=di.id;
-
-    TSIGTriplet tt;
-    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;
-          return;
-        }
-      } else {
-        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()) {
-      if (pdns_iequals(scripts[0], "NONE")) {
-        script.clear();
-      } else {
-        script=scripts[0];
-      }
-    }
-    if(!script.empty()){
-      try {
-        pdl = make_unique<AuthLua4>();
-        pdl->loadFile(script);
-        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;
-        return;
-      }
-    }
-
-    vector<string> localaddr;
-    ComboAddress laddr;
-
-    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;
-      }
-      catch(std::exception& e) {
-        g_log<<Logger::Error<<logPrefix<<"failed to set xfr source '"<<localaddr[0]<<"': "<<e.what()<<endl;
-        return;
-      }
-    } 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;
-        return;
-      }
-      laddr = pdns::getQueryLocalAddress(remote.sin4.sin_family, 0);
-    }
-
-    bool hadDnssecZone = false;
-    bool hadPresigned = false;
-    bool hadNSEC3 = false;
-    NSEC3PARAMRecordContent hadNs3pr;
-    bool hadNarrow=false;
-
-
-    vector<DNSResourceRecord> rrs;
-    if(dk.isSecuredZone(domain)) {
-      hadDnssecZone=true;
-      hadPresigned=dk.isPresigned(domain);
-      if (dk.getNSEC3PARAM(domain, &zs.ns3pr, &zs.isNarrow)) {
-        hadNSEC3 = true;
-        hadNs3pr = zs.ns3pr;
-        hadNarrow = zs.isNarrow;
-      }
-    }
-    else if(di.serial) {
-      vector<string> meta;
-      B.getDomainMetadata(domain, "IXFR", meta);
-      if(!meta.empty() && meta[0]=="1") {
-        logPrefix = "I" + logPrefix; // XFR -> IXFR
-        vector<DNSRecord> axfr;
-        g_log<<Logger::Notice<<logPrefix<<"starting IXFR"<<endl;
-        ixfrSuck(domain, tt, laddr, remote, pdl, zs, &axfr);
-        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) {
-            auto rr = DNSResourceRecord::fromWire(dr);
-            (rr.qname += domain).makeUsLowerCase();
-            rr.domain_id = zs.domain_id;
-            if(!processRecordForZS(domain, firstNSEC3, rr, zs))
-              continue;
-            if(dr.d_type == QType::SOA) {
-              auto sd = getRR<SOARecordContent>(dr);
-              zs.soa_serial = sd->d_st.serial;
-            }
-            rrs.push_back(rr);
-          }
-        }
-        else {
-          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;
-      rrs = doAxfr(remote, domain, tt, laddr, pdl, zs);
-      logPrefix = "A" + logPrefix; // XFR -> AXFR
-      g_log<<Logger::Notice<<logPrefix<<"retrieval finished"<<endl;
-    }
-
-    if(zs.isNSEC3) {
-      zs.ns3pr.d_flags = zs.optOutFlag ? 1 : 0;
-    }
-
-    if(!zs.isPresigned) {
-      DNSSECKeeper::keyset_t keys = dk.getKeys(domain);
-      if(!keys.empty()) {
-        zs.isDnssecZone = true;
-        zs.isNSEC3 = hadNSEC3;
-        zs.ns3pr = hadNs3pr;
-        zs.optOutFlag = (hadNs3pr.d_flags & 1);
-        zs.isNarrow = hadNarrow;
-      }
-    }
-
-    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;
-    }
-
-
-    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) {
-      // update presigned if there was a change
-      if (zs.isPresigned && !hadPresigned) {
-        // zone is now presigned
-        dk.setPresigned(domain);
-      } 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)) {
-          dk.setNSEC3PARAM(domain, zs.ns3pr, zs.isNarrow);
-        }
-      } else if (hadNSEC3 ) {
-         // zone is no longer NSEC3
-         dk.unsetNSEC3PARAM(domain);
-      }
-    } else if (hadDnssecZone) {
-      // zone is no longer signed
-      if (hadPresigned) {
-        // remove presigned
-        dk.unsetPresigned(domain);
-      }
-      if (hadNSEC3) {
-        // unset NSEC3PARAM
-        dk.unsetNSEC3PARAM(domain);
-      }
-    }
-
-    bool doent=true;
-    uint32_t maxent = ::arg().asNum("max-ent-entries");
-    DNSName shorter, ordername;
-    set<DNSName> rrterm;
-    map<DNSName,bool> nonterm;
-
-
-    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"))
-          continue;
-      }
-
-      // Figure out auth and ents
-      rr.auth=true;
-      shorter=rr.qname;
-      rrterm.clear();
-      do {
-        if(doent) {
-          if (!zs.qnames.count(shorter))
-            rrterm.insert(shorter);
-        }
-        if(zs.nsset.count(shorter) && rr.qtype.getCode() != QType::DS)
-          rr.auth=false;
-
-        if (shorter==domain) // stop at apex
-          break;
-      }while(shorter.chopOff());
-
-      // Insert ents
-      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;
-
-        for(const auto &nt: rrterm){
-          if (!nonterm.count(nt))
-              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;
-          nonterm.clear();
-          doent=false;
-        }
-      }
-
-      // RRSIG is always auth, even inside a delegation
-      if (rr.qtype.getCode() == QType::RRSIG)
-        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))))) {
-            di.backend->feedRecord(rr, ordername, true);
-          } else
-            di.backend->feedRecord(rr, DNSName());
-        } else {
-          // NSEC
-          if (rr.auth || rr.qtype.getCode() == QType::NS) {
-            ordername=rr.qname.makeRelative(domain);
-            di.backend->feedRecord(rr, ordername);
-          } else
-            di.backend->feedRecord(rr, DNSName());
-        }
-      } else
-        di.backend->feedRecord(rr, DNSName());
-    }
-
-    // Insert empty non-terminals
-    if(doent && !nonterm.empty()) {
-      if (zs.isNSEC3) {
-        di.backend->feedEnts3(zs.domain_id, domain, nonterm, zs.ns3pr, zs.isNarrow);
-      } else
-        di.backend->feedEnts(zs.domain_id, nonterm);
-    }
-
-    di.backend->commitTransaction();
-    transaction = false;
-    di.backend->setFresh(zs.domain_id);
-    purgeAuthCaches(domain.toString()+"$");
-
-    g_log<<Logger::Warning<<logPrefix<<"zone committed with serial "<<zs.soa_serial<<endl;
-
-    // Send slave 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(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;
-      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;
-      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;
-      di.backend->abortTransaction();
-    }
-  }
-  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
-      // 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.
-      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;
-      }
-      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;
-    }
-    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;
-      di.backend->abortTransaction();
-    }
-  }
-}
-namespace {
-struct DomainNotificationInfo
-{
-  DomainInfo di;
-  bool dnssecOk;
-  ComboAddress localaddr;
-  DNSName tsigkeyname, tsigalgname;
-  string tsigsecret;
-};
-}
-
-
-struct SlaveSenderReceiver
-{
-  typedef std::tuple<DNSName, ComboAddress, uint16_t> Identifier;
-
-  struct Answer {
-    uint32_t theirSerial;
-    uint32_t theirInception;
-    uint32_t theirExpire;
-  };
-
-  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());
-    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)
-        );
-    }
-    catch(PDNSException& e) {
-      throw runtime_error("While attempting to query freshness of '"+dni.di.zone.toLogString()+"': "+e.reason);
-    }
-  }
-
-  bool receive(Identifier& id, Answer& a)
-  {
-    return d_resolver.tryGetSOASerial(&(std::get<0>(id)), &(std::get<1>(id)), &a.theirSerial, &a.theirInception, &a.theirExpire, &(std::get<2>(id)));
-  }
-
-  void deliverAnswer(const DomainNotificationInfo& dni, const Answer& a, unsigned int usec)
-  {
-    d_freshness[dni.di.id]=a;
-  }
-
-  Resolver d_resolver;
-};
-
-void CommunicatorClass::addSlaveCheckRequest(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
-  // query goes to that one.
-  for (const auto& master : di.masters) {
-    if (ComboAddress::addressOnlyEqual()(remote, master)) {
-      ours.masters.clear();
-      ours.masters.push_back(master);
-      break;
-    }
-  }
-  data->d_tocheck.erase(di);
-  data->d_tocheck.insert(ours);
-  d_any_sem.post(); // kick the loop!
-}
-
-void CommunicatorClass::addTrySuperMasterRequest(const DNSPacket& p)
-{
-  const DNSPacket& ours = p;
-  auto data = d_data.lock();
-  if (data->d_potentialsupermasters.insert(ours).second) {
-    d_any_sem.post(); // kick the loop!
-  }
-}
-
-void CommunicatorClass::slaveRefresh(PacketHandler *P)
-{
-  // not unless we are slave
-  if (!::arg().mustDo("secondary")) return;
-
-  UeberBackend *B=P->getBackend();
-  vector<DomainInfo> rdomains;
-  vector<DomainNotificationInfo> sdomains;
-  set<DNSPacket, Data::cmp> trysuperdomains;
-  {
-    auto data = d_data.lock();
-    set<DomainInfo> requeue;
-    rdomains.reserve(data->d_tocheck.size());
-    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;
-        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;
-        }
-        rdomains.push_back(di);
-      }
-    }
-    data->d_tocheck.swap(requeue);
-
-    trysuperdomains = std::move(data->d_potentialsupermasters);
-    data->d_potentialsupermasters.clear();
-  }
-
-  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
-  }
-  if(rdomains.empty()) { // if we have priority domains, check them first
-    B->getUnfreshSlaveInfos(&rdomains);
-  }
-  sdomains.reserve(rdomains.size());
-  DNSSECKeeper dk(B); // NOW HEAR THIS! This DK uses our B backend, so no interleaved access!
-  {
-    auto data = d_data.lock();
-    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 ) {
-        // 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;
-        continue;
-      }
-      std::vector<std::string> localaddr;
-      SuckRequest sr;
-      sr.domain=di.zone;
-      if(di.masters.empty()) // slave domains w/o masters 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!
-        continue;
-      }
-      if(data->d_inprogress.count(sr.domain)) { // this does
-        continue;
-      }
-
-      DomainNotificationInfo dni;
-      dni.di=di;
-      dni.dnssecOk = dk.doesDNSSEC();
-
-      if(dk.getTSIGForAccess(di.zone, sr.master, &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;
-          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;
-          continue;
-        }
-      }
-
-      localaddr.clear();
-      // check for AXFR-SOURCE
-      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;
-        }
-        catch(std::exception& e) {
-          g_log<<Logger::Error<<"Failed to load freshness check source '"<<localaddr[0]<<"' for '"<<di.zone<<"': "<<e.what()<<endl;
-          return;
-        }
-      } else {
-        dni.localaddr.sin4.sin_family = 0;
-      }
-
-      sdomains.push_back(std::move(dni));
-    }
-  }
-  if(sdomains.empty())
-  {
-    if (d_slaveschanged) {
-      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;
-    }
-    d_slaveschanged = !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;
-  }
-
-  SlaveSenderReceiver ssr;
-
-  Inflighter<vector<DomainNotificationInfo>, SlaveSenderReceiver> ifl(sdomains, ssr);
-
-  ifl.d_maxInFlight = 200;
-
-  for(;;) {
-    try {
-      ifl.run();
-      break;
-    }
-    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;
-    }
-  }
-
-  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;
-  }
-
-  time_t now = time(nullptr);
-  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) {
-      // Do not overwrite received DI just to make sure it exists in backend:
-      // di.masters should contain the picked master (as first entry)!
-      DomainInfo tempdi;
-      if (!B->getDomainInfo(di.zone, tempdi, false)) {
-        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
-      // SEGFAULT on the setFresh command later on
-      di.backend = tempdi.backend;
-    }
-
-    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;
-      time_t nextCheck = now + std::min(newCount * d_tickinterval, (uint64_t)::arg().asNum("default-ttl"));
-      data->d_failedSlaveRefresh[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;
-      }
-      // Make sure we recheck SOA for notifies
-      if (di.receivedNotify) {
-        di.backend->setStale(di.id);
-      }
-      continue;
-    }
-
-    {
-      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);
-    }
-
-    bool hasSOA = false;
-    SOAData sd;
-    try {
-      // Use UeberBackend cache for SOA. Cache gets cleared after AXFR/IXFR.
-      B->lookup(QType(QType::SOA), di.zone, di.id, nullptr);
-      DNSZoneRecord zr;
-      hasSOA = B->get(zr);
-      if (hasSOA) {
-        fillSOAData(zr, sd);
-        while(B->get(zr));
-      }
-    }
-    catch(...) {}
-
-    uint32_t theirserial = ssr.d_freshness[di.id].theirSerial;
-    uint32_t ourserial = sd.serial;
-    const ComboAddress remote = *di.masters.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;
-      di.backend->setFresh(di.id);
-    }
-    else if(hasSOA && theirserial == ourserial) {
-      uint32_t maxExpire=0, maxInception=0;
-      if(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)) {
-          auto rrsig = getRR<RRSIGRecordContent>(zr.dr);
-          if(rrsig->d_type == QType::SOA) {
-            maxInception = std::max(maxInception, rrsig->d_siginception);
-            maxExpire = std::max(maxExpire, rrsig->d_sigexpire);
-          }
-        }
-      }
-
-      SuckRequest::RequestPriority prio = SuckRequest::SignaturesRefresh;
-      if (di.receivedNotify) {
-        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;
-        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;
-        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;
-        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;
-        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;
-        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;
-        addSuckRequest(di.zone, remote, prio);
-      }
-    }
-    else {
-      SuckRequest::RequestPriority prio = SuckRequest::SerialRefresh;
-      if (di.receivedNotify) {
-        prio = SuckRequest::Notify;
-      }
-
-      if (hasSOA) {
-        g_log<<Logger::Notice<<"Domain '"<< di.zone << "' is stale, master " << 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;
-      }
-      addSuckRequest(di.zone, remote, prio);
-    }
-  }
-}
-
-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);
-  }
-  return ret;
-}
-
-size_t CommunicatorClass::getSuckRequestsWaiting() {
-  return d_data.lock()->d_suckdomains.size();
-}
index 4b2bc6b0278525cf89e5860010a0edc93c312fe6..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)
@@ -78,7 +76,7 @@ void SNMPAgent::handleSNMPQueryEvent(int fd)
   snmp_read2(&fdset);
 }
 
-void SNMPAgent::handleTrapsCB(int fd, FDMultiplexer::funcparam_t& var)
+void SNMPAgent::handleTrapsCB(int /* fd */, FDMultiplexer::funcparam_t& var)
 {
   SNMPAgent** agent = boost::any_cast<SNMPAgent*>(&var);
   if (!agent || !*agent)
@@ -107,7 +105,7 @@ void SNMPAgent::worker()
   }
 
 #ifdef RECURSOR
-  string threadName = "pdns-r/SNMP";
+  string threadName = "rec/snmp";
 #else
   string threadName = "dnsdist/SNMP";
 #endif
@@ -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 f50c02e7881d508b067269ba930a1ecf66c4ce94..8607e066f1e0dda6f48f32c7d6a33877e2be4956 100644 (file)
@@ -1,24 +1,58 @@
-extern "C" {
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+extern "C"
+{
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 #include <sodium.h>
 }
 #include "dnssecinfra.hh"
+#include "dnsseckeeper.hh"
 
 class SodiumED25519DNSCryptoKeyEngine : public DNSCryptoKeyEngine
 {
 public:
-  explicit SodiumED25519DNSCryptoKeyEngine(unsigned int algo) : DNSCryptoKeyEngine(algo)
+  explicit SodiumED25519DNSCryptoKeyEngine(unsigned int algo) :
+    DNSCryptoKeyEngine(algo)
   {}
   string getName() const override { return "Sodium ED25519"; }
   void create(unsigned int bits) override;
-  storvector_t convertToISCVector() const override;
-  std::string getPubKeyHash() const override;
-  std::string sign(const std::string& msg) const override;
-  bool verify(const std::string& msg, const std::string& signature) const override;
-  std::string getPublicKeyString() const override;
-  int getBits() const override;
+
+#if defined(HAVE_LIBCRYPTO_ED25519)
+  /**
+   * \brief Creates an ED25519 key engine from a PEM file.
+   *
+   * Receives an open file handle with PEM contents and creates an ED25519 key engine.
+   *
+   * \param[in] drc Key record contents to be populated.
+   *
+   * \param[in] inputFile An open file handle to a file containing ED25519 PEM contents.
+   *
+   * \param[in] filename Only used for providing filename information in error messages.
+   *
+   * \return An ED25519 key engine populated with the contents of the PEM file.
+   */
+  void createFromPEMFile(DNSKEYRecordContent& drc, std::FILE& inputFile, std::optional<std::reference_wrapper<const std::string>> filename = std::nullopt) override;
+
+  /**
+   * \brief Writes this key's contents to a file.
+   *
+   * Receives an open file handle and writes this key's contents to the
+   * file.
+   *
+   * \param[in] outputFile An open file handle for writing.
+   *
+   * \exception std::runtime_error In case of OpenSSL errors.
+   */
+  void convertToPEMFile(std::FILE& outputFile) const override;
+#endif
+
+  [[nodiscard]] storvector_t convertToISCVector() const override;
+  [[nodiscard]] std::string sign(const std::string& msg) const override;
+  [[nodiscard]] bool verify(const std::string& msg, const std::string& signature) const override;
+  [[nodiscard]] std::string getPublicKeyString() const override;
+  [[nodiscard]] int getBits() const override;
   void fromISCMap(DNSKEYRecordContent& drc, std::map<std::string, std::string>& stormap) override;
   void fromPublicKeyString(const std::string& content) override;
 
@@ -34,12 +68,66 @@ private:
 
 void SodiumED25519DNSCryptoKeyEngine::create(unsigned int bits)
 {
-  if(bits != crypto_sign_ed25519_SEEDBYTES * 8) {
-    throw runtime_error("Unsupported key length of "+std::to_string(bits)+" bits requested, SodiumED25519 class");
+  if (bits != crypto_sign_ed25519_SEEDBYTES * 8) {
+    throw runtime_error("Unsupported key length of " + std::to_string(bits) + " bits requested, SodiumED25519 class");
   }
   crypto_sign_ed25519_keypair(d_pubkey, d_seckey);
 }
 
+#if defined(HAVE_LIBCRYPTO_ED25519)
+void SodiumED25519DNSCryptoKeyEngine::createFromPEMFile(DNSKEYRecordContent& drc, std::FILE& inputFile, std::optional<std::reference_wrapper<const std::string>> filename)
+{
+  drc.d_algorithm = d_algorithm;
+  auto key = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>(PEM_read_PrivateKey(&inputFile, nullptr, nullptr, nullptr), &EVP_PKEY_free);
+  if (key == nullptr) {
+    if (filename.has_value()) {
+      throw runtime_error(getName() + ": Failed to read private key from PEM file `" + filename->get() + "`");
+    }
+
+    throw runtime_error(getName() + ": Failed to read private key from PEM contents");
+  }
+
+  // The secret key is 64 bytes according to libsodium. But OpenSSL returns 32 in
+  // secKeyLen. Perhaps secret key means private key + public key in libsodium terms.
+  std::size_t secKeyLen = crypto_sign_ed25519_SECRETKEYBYTES;
+  int ret = EVP_PKEY_get_raw_private_key(key.get(), d_seckey, &secKeyLen);
+  if (ret == 0) {
+    if (filename.has_value()) {
+      throw runtime_error(getName() + ": Failed to get private key from PEM file contents `" + filename->get() + "`");
+    }
+
+    throw runtime_error(getName() + ": Failed to get private key from PEM contents");
+  }
+
+  std::size_t pubKeyLen = crypto_sign_ed25519_PUBLICKEYBYTES;
+  ret = EVP_PKEY_get_raw_public_key(key.get(), d_pubkey, &pubKeyLen);
+  if (ret == 0) {
+    if (filename.has_value()) {
+      throw runtime_error(getName() + ": Failed to get public key from PEM file contents `" + filename->get() + "`");
+    }
+
+    throw runtime_error(getName() + ": Failed to get public key from PEM contents");
+  }
+
+  // It looks like libsodium expects the public key to be appended to the private key,
+  // creating the "secret key" mentioned above.
+  memcpy(d_seckey + secKeyLen, d_pubkey, pubKeyLen);
+}
+
+void SodiumED25519DNSCryptoKeyEngine::convertToPEMFile(std::FILE& outputFile) const
+{
+  auto key = std::unique_ptr<EVP_PKEY, void (*)(EVP_PKEY*)>(EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, nullptr, d_seckey, crypto_sign_ed25519_SEEDBYTES), EVP_PKEY_free);
+  if (key == nullptr) {
+    throw runtime_error(getName() + ": Could not create private key from buffer");
+  }
+
+  auto ret = PEM_write_PrivateKey(&outputFile, key.get(), nullptr, nullptr, 0, nullptr, nullptr);
+  if (ret == 0) {
+    throw runtime_error(getName() + ": Could not convert private key to PEM");
+  }
+}
+#endif
+
 int SodiumED25519DNSCryptoKeyEngine::getBits() const
 {
   return crypto_sign_ed25519_SEEDBYTES * 8;
@@ -58,12 +146,11 @@ DNSCryptoKeyEngine::storvector_t SodiumED25519DNSCryptoKeyEngine::convertToISCVe
 
   storvector.emplace_back("Algorithm", algorithm);
 
-  vector<unsigned char> buffer;
   storvector.emplace_back("PrivateKey", string((char*)d_seckey, crypto_sign_ed25519_SEEDBYTES));
   return storvector;
 }
 
-void SodiumED25519DNSCryptoKeyEngine::fromISCMap(DNSKEYRecordContent& drc, std::map<std::string, std::string>& stormap )
+void SodiumED25519DNSCryptoKeyEngine::fromISCMap(DNSKEYRecordContent& drc, std::map<std::string, std::string>& stormap)
 {
   /*
     Private-key-format: v1.2
@@ -83,11 +170,6 @@ void SodiumED25519DNSCryptoKeyEngine::fromISCMap(DNSKEYRecordContent& drc, std::
   crypto_sign_ed25519_seed_keypair(d_pubkey, d_seckey, seed.get());
 }
 
-std::string SodiumED25519DNSCryptoKeyEngine::getPubKeyHash() const
-{
-  return this->getPublicKeyString();
-}
-
 std::string SodiumED25519DNSCryptoKeyEngine::getPublicKeyString() const
 {
   return string((char*)d_pubkey, crypto_sign_ed25519_PUBLICKEYBYTES);
@@ -103,36 +185,32 @@ void SodiumED25519DNSCryptoKeyEngine::fromPublicKeyString(const std::string& inp
 
 std::string SodiumED25519DNSCryptoKeyEngine::sign(const std::string& msg) const
 {
-  unsigned long long smlen = msg.length() + crypto_sign_ed25519_BYTES;
-  auto sm = std::make_unique<unsigned char[]>(smlen);
+  unsigned char signature[crypto_sign_ed25519_BYTES];
 
-  crypto_sign_ed25519(sm.get(), &smlen, (const unsigned char*)msg.c_str(), msg.length(), d_seckey);
+  // https://doc.libsodium.org/public-key_cryptography/public-key_signatures#detached-mode:
+  // It is safe to ignore siglen and always consider a signature as crypto_sign_BYTES
+  // bytes long; shorter signatures will be transparently padded with zeros if necessary.
+  crypto_sign_ed25519_detached(signature, nullptr, (const unsigned char*)msg.c_str(), msg.length(), d_seckey);
 
-  return string((const char*)sm.get(), crypto_sign_ed25519_BYTES);
+  return {(const char*)signature, crypto_sign_ed25519_BYTES};
 }
 
 bool SodiumED25519DNSCryptoKeyEngine::verify(const std::string& msg, const std::string& signature) const
 {
-  if (signature.length() != crypto_sign_ed25519_BYTES)
+  if (signature.length() != crypto_sign_ed25519_BYTES) {
     return false;
+  }
 
-  unsigned long long smlen = msg.length() + crypto_sign_ed25519_BYTES;
-  auto sm = std::make_unique<unsigned char[]>(smlen);
-
-  memcpy(sm.get(), signature.c_str(), crypto_sign_ed25519_BYTES);
-  memcpy(sm.get() + crypto_sign_ed25519_BYTES, msg.c_str(), msg.length());
-
-  auto m = std::make_unique<unsigned char[]>(smlen);
-
-  return crypto_sign_ed25519_open(m.get(), &smlen, sm.get(), smlen, d_pubkey) == 0;
+  return crypto_sign_ed25519_verify_detached((const unsigned char*)signature.c_str(), (const unsigned char*)msg.c_str(), msg.length(), d_pubkey) == 0;
 }
 
-namespace {
-struct LoaderSodiumStruct
+namespace
+{
+const struct LoaderSodiumStruct
 {
   LoaderSodiumStruct()
   {
-    DNSCryptoKeyEngine::report(15, &SodiumED25519DNSCryptoKeyEngine::maker);
+    DNSCryptoKeyEngine::report(DNSSECKeeper::ED25519, &SodiumED25519DNSCryptoKeyEngine::maker);
   }
 } loadersodium;
 }
index 652b7ecf09822dc616e8f7206cedb28baf869961..f8188b0ab2aee6208c9c9c7028c16320020f81dc 100644 (file)
@@ -53,7 +53,7 @@ template<typename C> void doRun(const C& cmd, int mseconds=100)
     cmd();
   }
   double delta=dt.ndiff()/1000000000.0;
-  boost::format fmt("'%s' %.02f seconds: %.1f runs/s, %.02f usec/run");
+  boost::format fmt("'%s' %.02f seconds: %.1f runs/s, %.02f us/run");
 
   cerr<< (fmt % cmd.getName() % delta % (runs/delta) % (delta* 1000000.0/runs)) << endl;
   g_totalRuns += runs;
@@ -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);
     }
 
@@ -769,7 +769,33 @@ struct DNSNameRootTest
 
 };
 
+struct SuffixMatchNodeTest
+{
+  SuffixMatchNodeTest()
+  {
+    d_smn.add(d_exist);
+  }
+
+  string getName() const
+  {
+    return "SuffixMatchNode";
+  }
+
+  void operator()() const
+  {
+    if (!d_smn.check(d_exist)) {
+      throw std::runtime_error("Entry not found in SuffixMatchNodeTest");
+    }
+    if (d_smn.check(d_does_not_exist)) {
+      throw std::runtime_error("Non-existent entry found in SuffixMatchNodeTest");
+    }
+  }
 
+private:
+  const DNSName d_exist{"a.bb.ccc.ddd."};
+  const DNSName d_does_not_exist{"e.bb.ccc.ddd"};
+  SuffixMatchNode d_smn;
+};
 
 struct IEqualsTest
 {
@@ -913,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
   {
@@ -1058,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
   {
@@ -1106,7 +1131,24 @@ struct BurtleHashTest
   void operator()() const
   {
     burtle(reinterpret_cast<const unsigned char*>(d_name.data()), d_name.length(), 0);
+  }
+
+private:
+  const string d_name;
+};
 
+struct BurtleHashCITest
+{
+  explicit BurtleHashCITest(const string& str) : d_name(str) {}
+
+  string getName() const
+  {
+    return "BurtleHashCI";
+  }
+
+  void operator()() const
+  {
+    burtleCI(reinterpret_cast<const unsigned char*>(d_name.data()), d_name.length(), 0);
   }
 
 private:
@@ -1139,163 +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(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(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 c2c96ebea7b8bc23700726e832c099c21cf28d3c..2fecbba5bfa53169b92c4938647ea909d939c589 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()<<" usec 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()<<" total usec 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;
+  for (int tries = 0;; ++tries) {
+    int ret = sqlite3_close(m_pDB);
+    if (ret != SQLITE_OK) {
+      if (tries != 0 || 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
+    else {
       break;
+    }
   }
 }
 
-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 5b14e234e60cd768bb814ccb2da375ea67d4fc5e..88f680929934dd1363d6ce5dc577cee4b74836c3 100644 (file)
@@ -24,7 +24,7 @@
 #include <sstream>
 #include <iostream>
 #include "iputils.hh"
-#include <errno.h>
+#include <cerrno>
 #include <sys/types.h>
 #include <unistd.h>
 #include <sys/socket.h>
@@ -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,61 +174,68 @@ 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, ComboAddress &ep)
+  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);
   }
 
 
-  //! Write this data to the socket, taking care that all bytes are written out 
+  //! Write this data to the socket, taking care that all bytes are written out
   void writen(const string &data)
   {
     if(data.empty())
@@ -239,7 +247,7 @@ public:
 
     do {
       res=::send(d_socket, ptr, toWrite, 0);
-      if(res<0) 
+      if(res<0)
         throw NetworkError("Writing to a socket: "+stringerror());
       if(!res)
         throw NetworkError("EOF on socket");
@@ -266,7 +274,7 @@ public:
 
     if(errno==EAGAIN)
       return 0;
-    
+
     throw NetworkError("Writing to a socket: "+stringerror());
   }
 
@@ -310,7 +318,7 @@ public:
     }
   }
 
-  //! reads one character from the socket 
+  //! reads one character from the socket
   int getChar()
   {
     char c;
@@ -337,7 +345,7 @@ public:
   {
     d_buffer.resize(s_buflen);
     ssize_t res=::recv(d_socket, &d_buffer[0], s_buflen, 0);
-    if(res<0) 
+    if(res<0)
       throw NetworkError("Reading from a socket: "+stringerror());
     data.assign(d_buffer, 0, static_cast<size_t>(res));
   }
@@ -346,11 +354,16 @@ public:
   size_t read(char *buffer, size_t bytes)
   {
     ssize_t res=::recv(d_socket, buffer, bytes, 0);
-    if(res<0) 
+    if(res<0)
       throw NetworkError("Reading from a socket: "+stringerror());
     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);
@@ -363,7 +376,7 @@ public:
     return read(buffer, n);
   }
 
-  //! Sets the socket to listen with a default listen backlog of 10 pending connections 
+  //! Sets the socket to listen with a default listen backlog of 10 pending connections
   void listen(unsigned int length=10)
   {
     if(::listen(d_socket,length)<0)
index 40a9a4b9b9c26edb0e991d0b7de34565522055a9..0ca91064b6584d6c47c78797eee6319aab8d70d2 100644 (file)
@@ -1,4 +1,4 @@
-
+#include <cstdint>
 #include <fstream>
 #include <iostream>
 #include <stdexcept>
index d318c980b2599e67a16262446dfb729c908f8084..d339cec37d224a19b66619c38e490cd563509a71 100644 (file)
@@ -23,6 +23,7 @@
 
 #include <atomic>
 
+#ifndef DISABLE_FALSE_SHARING_PADDING
 #define CPU_LEVEL1_DCACHE_LINESIZE 64 // Until we know better via configure/getconf
 
 namespace pdns {
@@ -70,10 +71,19 @@ 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;
   typedef stat_t_trait<uint32_t> stat32_t;
   typedef stat_t_trait<uint16_t> stat16_t;
 }
+#else
+namespace pdns {
+  using stat_t = std::atomic<uint64_t>;
+  using stat32_t = std::atomic<uint32_t>;
+  using stat16_t = std::atomic<uint16_t>;
+  template <class T>
+  using stat_t_trait = std::atomic<T>;
+}
+#endif
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 acdc3832303b0d09d2a1bb5383b792924be6b909..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);
   }
@@ -73,7 +73,7 @@ class StatBag
   map<string, LockGuarded<StatRing<string, CIStringCompare> > > d_rings;
   map<string, LockGuarded<StatRing<SComboAddress> > > d_comboRings;
   map<string, LockGuarded<StatRing<std::tuple<DNSName, QType> > > > d_dnsnameqtyperings;
-  typedef boost::function<uint64_t(const std::string&)> func_t;
+  typedef std::function<uint64_t(const std::string&)> func_t;
   typedef map<string, func_t> funcstats_t;
   funcstats_t d_funcstats;
   bool d_doRings;
@@ -98,7 +98,7 @@ public:
     if (d_doRings)  {
       auto it = d_rings.find(name);
       if (it == d_rings.end()) {
-       throw runtime_error("Attempting to account to non-existent ring '"+std::string(name)+"'");
+       throw runtime_error("Attempting to account to nonexistent ring '"+std::string(name)+"'");
       }
 
       it->second.lock()->account(item);
@@ -109,7 +109,7 @@ public:
     if (d_doRings) {
       auto it = d_comboRings.find(name);
       if (it == d_comboRings.end()) {
-       throw runtime_error("Attempting to account to non-existent comboRing '"+std::string(name)+"'");
+       throw runtime_error("Attempting to account to nonexistent comboRing '"+std::string(name)+"'");
       }
       it->second.lock()->account(item);
     }
@@ -119,9 +119,9 @@ public:
     if (d_doRings) {
       auto it = d_dnsnameqtyperings.find(name);
       if (it == d_dnsnameqtyperings.end()) {
-       throw runtime_error("Attempting to account to non-existent dnsname+qtype ring '"+std::string(name)+"'");
+       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 e4c8f9edb50f669f79d6d6b25cf2cb2c77eac3cf..898c221094da6a07c9f4cec97e823645b3fc0ba5 100644 (file)
@@ -13,6 +13,7 @@ StatNode::Stat StatNode::print(unsigned int depth, Stat newstat, bool silent) co
   childstat.servfails += s.servfails;
   childstat.drops += s.drops;
   childstat.bytes += s.bytes;
+  childstat.hits += s.hits;
 
   if(children.size()>1024 && !silent) {
     cout<<string(depth, ' ')<<name<<": too many to print"<<endl;
@@ -26,15 +27,15 @@ StatNode::Stat StatNode::print(unsigned int depth, Stat newstat, bool silent) co
       childstat.nxdomains<<" nxdomains, "<< 
       childstat.servfails<<" servfails, "<< 
       childstat.drops<<" drops, "<<
-      childstat.bytes<<" bytes"<<endl;
+      childstat.bytes<<" bytes, "<<
+      childstat.hits<<" hits"<<endl;
 
   newstat+=childstat;
 
   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);
 
@@ -47,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, 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();
@@ -57,7 +57,7 @@ void StatNode::submit(const DNSName& domain, int rcode, unsigned int bytes, boos
   }
 
   auto last = tmp.end() - 1;
-  children[*last].submit(last, tmp.begin(), "", rcode, bytes, remote, 1);
+  children[*last].submit(last, tmp.begin(), "", rcode, bytes, remote, 1, hit);
 }
 
 /* www.powerdns.com. -> 
@@ -67,7 +67,7 @@ void StatNode::submit(const DNSName& domain, int rcode, unsigned int bytes, boos
    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)
+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) 
@@ -96,18 +96,26 @@ void StatNode::submit(std::vector<string>::const_iterator end, std::vector<strin
     //    cerr<<"Hit the end, set our fullname to '"<<fullname<<"'"<<endl<<endl;
     s.queries++;
     s.bytes += bytes;
-    if(rcode<0)
+    if (rcode < 0) {
       s.drops++;
-    else if(rcode==0)
+    }
+    else if (rcode == RCode::NoError) {
       s.noerrors++;
-    else if(rcode==2)
+    }
+    else if (rcode == RCode::ServFail) {
       s.servfails++;
-    else if(rcode==3)
+    }
+    else if (rcode == RCode::NXDomain) {
       s.nxdomains++;
+    }
 
     if (remote) {
       s.remotes[*remote]++;
     }
+
+    if (hit) {
+      ++s.hits;
+    }
   }
   else {
     if (fullname.empty()) {
@@ -122,6 +130,6 @@ void StatNode::submit(std::vector<string>::const_iterator end, std::vector<strin
     }
     //    cerr<<"Not yet end, set our fullname to '"<<fullname<<"', recursing"<<endl;
     --end;
-    children[*end].submit(end, begin, fullname, rcode, bytes, remote, count+1);
+    children[*end].submit(end, begin, fullname, rcode, bytes, remote, count+1, hit);
   }
 }
index 88b4a210aa7d1a63eb7fd7ee57208cc1e6da0a42..18c17c20640a9cfa5580285483883fc6c2c625cb 100644 (file)
@@ -30,45 +30,50 @@ public:
 
   struct Stat
   {
-    Stat(): queries(0), noerrors(0), nxdomains(0), servfails(0), drops(0), bytes(0)
-    {
-    }
-    uint64_t queries, noerrors, nxdomains, servfails, drops, bytes;
+    Stat() {};
+    uint64_t queries{0};
+    uint64_t noerrors{0};
+    uint64_t nxdomains{0};
+    uint64_t servfails{0};
+    uint64_t drops{0};
+    uint64_t bytes{0};
+    uint64_t hits{0};
+    using remotes_t = std::map<ComboAddress,int,ComboAddress::addressOnlyLessThan>;
+    remotes_t remotes;
 
     Stat& operator+=(const Stat& rhs) {
-      queries+=rhs.queries;
-      noerrors+=rhs.noerrors;
-      nxdomains+=rhs.nxdomains;
-      servfails+=rhs.servfails;
-      drops+=rhs.drops;
-      bytes+=rhs.bytes;
+      queries += rhs.queries;
+      noerrors += rhs.noerrors;
+      nxdomains += rhs.nxdomains;
+      servfails += rhs.servfails;
+      drops += rhs.drops;
+      bytes += rhs.bytes;
+      hits += rhs.hits;
 
-      for(const remotes_t::value_type& rem : rhs.remotes) {
-        remotes[rem.first]+=rem.second;
+      for (const remotes_t::value_type& rem : rhs.remotes) {
+        remotes[rem.first] += rem.second;
       }
       return *this;
     }
-    typedef std::map<ComboAddress,int,ComboAddress::addressOnlyLessThan> remotes_t;
-    remotes_t remotes;
   };
 
+  using visitor_t = std::function<void(const StatNode*, const Stat& selfstat, const Stat& childstat)>;
+  using children_t = std::map<std::string, StatNode, CIStringCompare>;
+
   Stat s;
   std::string name;
   std::string fullname;
   uint8_t labelsCount{0};
 
-  void submit(const DNSName& domain, int rcode, unsigned int bytes, 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;
-  typedef std::function<void(const StatNode*, const Stat& selfstat, const Stat& childstat)> visitor_t;
-  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();
   }
-  typedef std::map<std::string,StatNode, CIStringCompare> children_t;
   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);
+  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 27954eead18e5fcc63538d35a84beebb3daf15fe..ea17b7c12330455209130467317c1d9862206619 100644 (file)
@@ -56,7 +56,7 @@ try
 
   cout<<"res: "<<res<<endl;
   for(const auto& r : ret) {
-    cout<<r.dr.d_content->getZoneRepresentation()<<endl;
+    cout<<r.dr.getContent()->getZoneRepresentation()<<endl;
   }
 }
 catch(std::exception &e)
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);
diff --git a/pdns/syncres.cc b/pdns/syncres.cc
deleted file mode 100644 (file)
index 9db36d3..0000000
+++ /dev/null
@@ -1,4899 +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 "arguments.hh"
-#include "aggressive_nsec.hh"
-#include "cachecleaner.hh"
-#include "dns_random.hh"
-#include "dnsparser.hh"
-#include "dnsrecords.hh"
-#include "ednssubnet.hh"
-#include "logger.hh"
-#include "lua-recursor4.hh"
-#include "rec-lua-conf.hh"
-#include "syncres.hh"
-#include "dnsseckeeper.hh"
-#include "validate-recursor.hh"
-#include "rec-taskqueue.hh"
-
-thread_local SyncRes::ThreadLocalStorage SyncRes::t_sstorage;
-thread_local std::unique_ptr<addrringbuf_t> t_timeouts;
-
-std::unique_ptr<NetmaskGroup> SyncRes::s_dontQuery{nullptr};
-NetmaskGroup SyncRes::s_ednslocalsubnets;
-NetmaskGroup SyncRes::s_ednsremotesubnets;
-SuffixMatchNode SyncRes::s_ednsdomains;
-EDNSSubnetOpts SyncRes::s_ecsScopeZero;
-string SyncRes::s_serverID;
-SyncRes::LogMode SyncRes::s_lm;
-const std::unordered_set<QType> SyncRes::s_redirectionQTypes = {QType::CNAME, QType::DNAME};
-LockGuarded<fails_t<ComboAddress>> SyncRes::s_fails;
-LockGuarded<fails_t<DNSName>> SyncRes::s_nonresolving;
-
-unsigned int SyncRes::s_maxnegttl;
-unsigned int SyncRes::s_maxbogusttl;
-unsigned int SyncRes::s_maxcachettl;
-unsigned int SyncRes::s_maxqperq;
-unsigned int SyncRes::s_maxnsaddressqperq;
-unsigned int SyncRes::s_maxtotusec;
-unsigned int SyncRes::s_maxdepth;
-unsigned int SyncRes::s_minimumTTL;
-unsigned int SyncRes::s_minimumECSTTL;
-unsigned int SyncRes::s_packetcachettl;
-unsigned int SyncRes::s_packetcacheservfailttl;
-unsigned int SyncRes::s_serverdownmaxfails;
-unsigned int SyncRes::s_serverdownthrottletime;
-unsigned int SyncRes::s_nonresolvingnsmaxfails;
-unsigned int SyncRes::s_nonresolvingnsthrottletime;
-unsigned int SyncRes::s_ecscachelimitttl;
-pdns::stat_t SyncRes::s_authzonequeries;
-pdns::stat_t SyncRes::s_queries;
-pdns::stat_t SyncRes::s_outgoingtimeouts;
-pdns::stat_t SyncRes::s_outgoing4timeouts;
-pdns::stat_t SyncRes::s_outgoing6timeouts;
-pdns::stat_t SyncRes::s_outqueries;
-pdns::stat_t SyncRes::s_tcpoutqueries;
-pdns::stat_t SyncRes::s_dotoutqueries;
-pdns::stat_t SyncRes::s_throttledqueries;
-pdns::stat_t SyncRes::s_dontqueries;
-pdns::stat_t SyncRes::s_qnameminfallbacksuccess;
-pdns::stat_t SyncRes::s_unreachables;
-pdns::stat_t SyncRes::s_ecsqueries;
-pdns::stat_t SyncRes::s_ecsresponses;
-std::map<uint8_t, pdns::stat_t> SyncRes::s_ecsResponsesBySubnetSize4;
-std::map<uint8_t, pdns::stat_t> SyncRes::s_ecsResponsesBySubnetSize6;
-
-uint8_t SyncRes::s_ecsipv4limit;
-uint8_t SyncRes::s_ecsipv6limit;
-uint8_t SyncRes::s_ecsipv4cachelimit;
-uint8_t SyncRes::s_ecsipv6cachelimit;
-bool SyncRes::s_ecsipv4nevercache;
-bool SyncRes::s_ecsipv6nevercache;
-
-bool SyncRes::s_doIPv4;
-bool SyncRes::s_doIPv6;
-bool SyncRes::s_nopacketcache;
-bool SyncRes::s_rootNXTrust;
-bool SyncRes::s_noEDNS;
-bool SyncRes::s_qnameminimization;
-SyncRes::HardenNXD SyncRes::s_hardenNXD;
-unsigned int SyncRes::s_refresh_ttlperc;
-int SyncRes::s_tcp_fast_open;
-bool SyncRes::s_tcp_fast_open_connect;
-bool SyncRes::s_dot_to_port_853;
-int SyncRes::s_event_trace_enabled;
-
-#define LOG(x) if(d_lm == Log) { g_log <<Logger::Warning << x; } else if(d_lm == Store) { d_trace << x; }
-
-// 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(const char* fmt, double f)
-{
-  char buf[20];
-  int ret = snprintf(buf, sizeof(buf), fmt, f);
-  if (ret < 0 || ret >= static_cast<int>(sizeof(buf))) {
-    return "?";
-  }
-  return std::string(buf, ret);
-}
-
-static inline void accountAuthLatency(uint64_t usec, int family)
-{
-  if (family == AF_INET) {
-    g_stats.auth4Answers(usec);
-    g_stats.cumulativeAuth4Answers(usec);
-  } else  {
-    g_stats.auth6Answers(usec);
-    g_stats.cumulativeAuth6Answers(usec);
-  }
-}
-
-
-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_now(now),
-                                              d_cacheonly(false), d_doDNSSEC(false), d_doEDNS0(false), d_qNameMinimization(s_qnameminimization), d_lm(s_lm)
-
-{
-}
-
-static void allowAdditionalEntry(std::unordered_set<DNSName>& allowedAdditionals, const DNSRecord& rec);
-
-void SyncRes::resolveAdditionals(const DNSName& qname, QType qtype, AdditionalMode mode, std::vector<DNSRecord>& additionals, unsigned int depth)
-{
-  vector<DNSRecord> addRecords;
-
-  vState state = vState::Indeterminate;
-  switch (mode) {
-  case AdditionalMode::ResolveImmediately: {
-    set<GetBestNSAnswer> beenthere;
-    int res = doResolve(qname, qtype, addRecords, depth, beenthere, state);
-    if (res != 0) {
-      return;
-    }
-    // We're conservative here. We do not add Bogus records in any circumstance, we add Indeterminates only if no
-    // validation is required.
-    if (vStateIsBogus(state)) {
-      return;
-    }
-    if (shouldValidate() && state != vState::Secure && state != vState::Insecure) {
-      return;
-    }
-    for (auto& rec : addRecords) {
-      if (rec.d_place == DNSResourceRecord::ANSWER) {
-        additionals.push_back(std::move(rec));
-      }
-    }
-    break;
-  }
-  case AdditionalMode::CacheOnly:
-  case AdditionalMode::CacheOnlyRequireAuth: {
-    // Peek into cache
-    if (g_recCache->get(d_now.tv_sec, qname, qtype, mode == AdditionalMode::CacheOnlyRequireAuth, &addRecords, d_cacheRemote, false, d_routingTag, nullptr, nullptr, nullptr, &state) <= 0) {
-      return;
-    }
-    // See the comment for the ResolveImmediately case
-    if (vStateIsBogus(state)) {
-      return;
-    }
-    if (shouldValidate() && state != vState::Secure && state != vState::Insecure) {
-      return;
-    }
-    for (auto& rec : addRecords) {
-      if (rec.d_place == DNSResourceRecord::ANSWER) {
-        rec.d_ttl -= d_now.tv_sec ;
-        additionals.push_back(std::move(rec));
-      }
-    }
-    break;
-  }
-  case AdditionalMode::ResolveDeferred:
-    // FIXME: Not yet implemented
-    // Look in cache for authoritative answer, if available return it
-    // If not, look in nergache and submit if not there as well. The logic should be the same as
-    // #11294, which is in review atm.
-    break;
-  case AdditionalMode::Ignore:
-    break;
-  }
-}
-
-// The main (recursive) function to add additionals
-// qtype: the original query type to expand
-// start: records to start from
-// This function uses to state sets to avoid infinite recursion and allow depulication
-// depth is the main recursion depth
-// additionaldepth is the depth for addAdditionals itself
-void SyncRes::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 additionaldepth)
-{
-  if (additionaldepth >= 5 || start.empty()) {
-    return;
-  }
-
-  auto luaLocal = g_luaconfs.getLocal();
-  const auto it = luaLocal->allowAdditionalQTypes.find(qtype);
-  if (it == luaLocal->allowAdditionalQTypes.end()) {
-    return;
-  }
-  std::unordered_set<DNSName> addnames;
-  for (const auto& rec : start) {
-    if (rec.d_place == DNSResourceRecord::ANSWER) {
-      // currently, this function only knows about names, we could also take the target types that are dependent on
-      // record contents into account
-      // e.g. for NAPTR records, go only for SRV for flag value "s", or A/AAAA for flag value "a"
-      allowAdditionalEntry(addnames, rec);
-    }
-  }
-
-  // We maintain two sets for deduplication:
-  // - uniqueCalls makes sure we never resolve a qname/qtype twice
-  // - 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) {
-    for (const auto& addname : addnames) {
-      std::vector<DNSRecord> records;
-      bool inserted = uniqueCalls.emplace(addname, targettype).second;
-      if (inserted) {
-        resolveAdditionals(addname, targettype, mode, records, depth);
-      }
-      if (!records.empty()) {
-        for (auto r = records.begin(); r != records.end(); ) {
-          QType covered = QType::ENT;
-          if (r->d_type == QType::RRSIG) {
-            if (auto rsig = getRR<RRSIGRecordContent>(*r); rsig != nullptr) {
-              covered = rsig->d_type;
-            }
-          }
-          if (uniqueResults.count(std::tuple(r->d_name, QType(r->d_type), covered)) > 0) {
-            // A bit expensive for vectors, but they are small
-            r = records.erase(r);
-          } else {
-            ++r;
-          }
-        }
-        for (const auto& r : records) {
-          additionals.push_back(r);
-          QType covered = QType::ENT;
-          if (r.d_type == QType::RRSIG) {
-            if (auto rsig = getRR<RRSIGRecordContent>(r); rsig != nullptr) {
-              covered = rsig->d_type;
-            }
-          }
-          uniqueResults.emplace(r.d_name, r.d_type, covered);
-        }
-        addAdditionals(targettype, records, additionals, uniqueCalls, uniqueResults, depth, additionaldepth + 1);
-      }
-    }
-  }
-}
-
-// The entry point for other code
-void SyncRes::addAdditionals(QType qtype, vector<DNSRecord>&ret, unsigned int depth)
-{
-  // The additional records of interest
-  std::vector<DNSRecord> additionals;
-
-  // We only call resolve for a specific name/type combo once
-  std::set<std::pair<DNSName, QType>> uniqueCalls;
-
-  // Collect multiple name/qtype from a single resolve but do not add a new set from new resolve calls
-  // For RRSIGs, the type covered is stored in the second Qtype
-  std::set<std::tuple<DNSName, QType, QType>> uniqueResults;
-
-  addAdditionals(qtype, ret, additionals, uniqueCalls, uniqueResults, depth, 0);
-
-  for (auto& rec : additionals) {
-    rec.d_place = DNSResourceRecord::ADDITIONAL;
-    ret.push_back(std::move(rec));
-  }
-}
-
-/** everything begins here - this is the entry point just after receiving a packet */
-int SyncRes::beginResolve(const DNSName &qname, const QType qtype, QClass qclass, vector<DNSRecord>&ret, unsigned int depth)
-{
-  d_eventTrace.add(RecEventTrace::SyncRes);
-  vState state = vState::Indeterminate;
-  s_queries++;
-  d_wasVariable=false;
-  d_wasOutOfBand=false;
-  d_cutStates.clear();
-
-  if (doSpecialNamesResolve(qname, qtype, qclass, ret)) {
-    d_queryValidationState = vState::Insecure; // this could fool our stats into thinking a validation took place
-    return 0;                          // so do check before updating counters (we do now)
-  }
-
-  auto qtypeCode = qtype.getCode();
-  /* rfc6895 section 3.1 */
-  if (qtypeCode == 0 || (qtypeCode >= 128 && qtypeCode <= 254) || qtypeCode == QType::RRSIG || qtypeCode == QType::NSEC3 || qtypeCode == QType::OPT || qtypeCode == 65535) {
-    return -1;
-  }
-
-  if(qclass==QClass::ANY)
-    qclass=QClass::IN;
-  else if(qclass!=QClass::IN)
-    return -1;
-
-  if (qtype == QType::DS) {
-    d_externalDSQuery = qname;
-  }
-  else {
-    d_externalDSQuery.clear();
-  }
-
-  set<GetBestNSAnswer> beenthere;
-  int res=doResolve(qname, qtype, ret, depth, beenthere, state);
-  d_queryValidationState = state;
-
-  if (shouldValidate()) {
-    if (d_queryValidationState != vState::Indeterminate) {
-      g_stats.dnssecValidations++;
-    }
-    auto xdnssec = g_xdnssec.getLocal();
-    if (xdnssec->check(qname)) {
-      increaseXDNSSECStateCounter(d_queryValidationState);
-    } else {
-      increaseDNSSECStateCounter(d_queryValidationState);
-    }
-  }
-
-  // Avoid calling addAdditionals() if we know we won't find anything
-  auto luaLocal = g_luaconfs.getLocal();
-  if (res == 0 && qclass == QClass::IN && luaLocal->allowAdditionalQTypes.find(qtype) != luaLocal->allowAdditionalQTypes.end()) {
-    addAdditionals(qtype, ret, depth);
-  }
-  d_eventTrace.add(RecEventTrace::SyncRes, res, false);
-  return res;
-}
-
-/*! Handles all special, built-in names
- * Fills ret with an answer and returns true if it handled the query.
- *
- * Handles the following queries (and their ANY variants):
- *
- * - localhost. IN A
- * - localhost. IN AAAA
- * - 1.0.0.127.in-addr.arpa. IN PTR
- * - 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa. IN PTR
- * - version.bind. CH TXT
- * - version.pdns. CH TXT
- * - id.server. CH TXT
- * - trustanchor.server CH TXT
- * - negativetrustanchor.server CH TXT
- */
-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.");
-
-  bool handled = false;
-  vector<pair<QType::typeenum, string> > answers;
-
-  if ((qname == arpa || qname == ip6_arpa) &&
-      qclass == QClass::IN) {
-    handled = true;
-    if (qtype == QType::PTR || qtype == QType::ANY)
-      answers.emplace_back(QType::PTR, "localhost.");
-  }
-
-  if (qname.isPartOf(localhost) &&
-      qclass == QClass::IN) {
-    handled = true;
-    if (qtype == QType::A || qtype == QType::ANY)
-      answers.emplace_back(QType::A, "127.0.0.1");
-    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)
-        answers.emplace_back(QType::TXT, "\"" + ::arg()["version-string"] + "\"");
-      else if (s_serverID != "disabled")
-        answers.emplace_back(QType::TXT, "\"" + s_serverID + "\"");
-    }
-  }
-
-  if (qname == trustanchorserver && qclass == QClass::CHAOS &&
-      ::arg().mustDo("allow-trust-anchor-query")) {
-    handled = true;
-    if (qtype == QType::TXT || qtype == QType::ANY) {
-      auto luaLocal = g_luaconfs.getLocal();
-      for (auto const &dsAnchor : luaLocal->dsAnchors) {
-        ostringstream ans;
-        ans<<"\"";
-        ans<<dsAnchor.first.toString(); // Explicit toString to have a trailing dot
-        for (auto const &dsRecord : dsAnchor.second) {
-          ans<<" ";
-          ans<<dsRecord.d_tag;
-        }
-        ans << "\"";
-        answers.emplace_back(QType::TXT, ans.str());
-      }
-    }
-  }
-
-  if (qname == negativetrustanchorserver && qclass == QClass::CHAOS &&
-      ::arg().mustDo("allow-trust-anchor-query")) {
-    handled = true;
-    if (qtype == QType::TXT || qtype == QType::ANY) {
-      auto luaLocal = g_luaconfs.getLocal();
-      for (auto const &negAnchor : luaLocal->negAnchors) {
-        ostringstream ans;
-        ans<<"\"";
-        ans<<negAnchor.first.toString(); // Explicit toString to have a trailing dot
-        if (negAnchor.second.length())
-          ans<<" "<<negAnchor.second;
-        ans << "\"";
-        answers.emplace_back(QType::TXT, ans.str());
-      }
-    }
-  }
-
-  if (handled && !answers.empty()) {
-    ret.clear();
-    d_wasOutOfBand=true;
-
-    DNSRecord dr;
-    dr.d_name = qname;
-    dr.d_place = DNSResourceRecord::ANSWER;
-    dr.d_class = qclass;
-    dr.d_ttl = 86400;
-    for (const auto& ans : answers) {
-      dr.d_type = ans.first;
-      dr.d_content = DNSRecordContent::mastermake(ans.first, qclass, ans.second);
-      ret.push_back(dr);
-    }
-  }
-
-  return handled;
-}
-
-
-//! This is the 'out of band resolver', in other words, the authoritative server
-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));
-  if (ziter != d_records.end()) {
-    DNSRecord dr = *ziter;
-    dr.d_place = DNSResourceRecord::AUTHORITY;
-    records.push_back(dr);
-  }
-  else {
-    // cerr<<qname<<": can't find SOA record '"<<getName()<<"' in our zone!"<<endl;
-  }
-}
-
-int SyncRes::AuthDomain::getRecords(const DNSName& qname, const QType qtype, std::vector<DNSRecord>& records) const
-{
-  int result = RCode::NoError;
-  records.clear();
-
-  // partial lookup
-  std::pair<records_t::const_iterator,records_t::const_iterator> range = d_records.equal_range(std::tie(qname));
-
-  SyncRes::AuthDomain::records_t::const_iterator ziter;
-  bool somedata = false;
-
-  for(ziter = range.first; ziter != range.second; ++ziter) {
-    somedata = true;
-
-    if(qtype == QType::ANY || ziter->d_type == qtype || ziter->d_type == QType::CNAME) {
-      // let rest of nameserver do the legwork on this one
-      records.push_back(*ziter);
-    }
-    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);
-    }
-  }
-
-  if (!records.empty()) {
-    /* We have found an exact match, we're done */
-    // cerr<<qname<<": exact match in zone '"<<getName()<<"'"<<endl;
-    return result;
-  }
-
-  if (somedata) {
-    /* We have records for that name, but not of the wanted qtype */
-    // cerr<<qname<<": found record in '"<<getName()<<"', but nothing of the right type, sending SOA"<<endl;
-    addSOA(records);
-
-    return result;
-  }
-
-  // cerr<<qname<<": nothing found so far in '"<<getName()<<"', trying wildcards"<<endl;
-  DNSName wcarddomain(qname);
-  while(wcarddomain != getName() && wcarddomain.chopOff()) {
-    // cerr<<qname<<": trying '*."<<wcarddomain<<"' in "<<getName()<<endl;
-    range = d_records.equal_range(std::make_tuple(g_wildcarddnsname + wcarddomain));
-    if (range.first==range.second)
-      continue;
-
-    for(ziter = range.first; ziter != range.second; ++ziter) {
-      DNSRecord dr = *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 (records.empty()) {
-      addSOA(records);
-    }
-
-    // cerr<<qname<<": in '"<<getName()<<"', had wildcard match on '*."<<wcarddomain<<"'"<<endl;
-    return result;
-  }
-
-  /* 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)
-      continue;
-
-    for(ziter = range.first; ziter != range.second; ++ziter) {
-      DNSRecord dr = *ziter;
-      dr.d_place = DNSResourceRecord::AUTHORITY;
-      records.push_back(dr);
-    }
-  }
-
-  if(records.empty()) {
-    // cerr<<qname<<": no NS match in zone '"<<getName()<<"' either, handing out SOA"<<endl;
-    addSOA(records);
-    result = RCode::NXDomain;
-  }
-
-  return result;
-}
-
-bool SyncRes::doOOBResolve(const AuthDomain& domain, const DNSName &qname, const QType qtype, vector<DNSRecord>&ret, int& res)
-{
-  d_authzonequeries++;
-  s_authzonequeries++;
-
-  res = domain.getRecords(qname, qtype, ret);
-  return true;
-}
-
-bool SyncRes::doOOBResolve(const DNSName &qname, const QType qtype, vector<DNSRecord>&ret, unsigned int depth, int& res)
-{
-  string prefix;
-  if(doLog()) {
-    prefix=d_prefix;
-    prefix.append(depth, ' ');
-  }
-
-  DNSName authdomain(qname);
-  domainmap_t::const_iterator 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;
-  }
-
-  LOG(prefix<<qname<<": auth storage has data, zone='"<<authdomain<<"'"<<endl);
-  return doOOBResolve(iter->second, qname, qtype, ret, res);
-}
-
-bool SyncRes::isRecursiveForwardOrAuth(const DNSName &qname) const {
-  DNSName authname(qname);
-  domainmap_t::const_iterator iter = getBestAuthZone(&authname);
-  return iter != t_sstorage.domainmap->end() && (iter->second.isAuth() || iter->second.shouldRecurse());
-}
-
-bool SyncRes::isForwardOrAuth(const DNSName &qname) const {
-  DNSName authname(qname);
-  domainmap_t::const_iterator iter = getBestAuthZone(&authname);
-  return iter != t_sstorage.domainmap->end() && (iter->second.isAuth() || !iter->second.shouldRecurse());
-}
-
-uint64_t SyncRes::doEDNSDump(int fd)
-{
-  int newfd = dup(fd);
-  if (newfd == -1) {
-    return 0;
-  }
-  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(newfd, "w"), fclose);
-  if (!fp) {
-    close(newfd);
-    return 0;
-  }
-  uint64_t count = 0;
-
-  fprintf(fp.get(),"; edns from thread follows\n;\n");
-  for(const auto& eds : t_sstorage.ednsstatus) {
-    count++;
-    char tmp[26];
-    fprintf(fp.get(), "%s\t%d\t%s", eds.address.toString().c_str(), (int)eds.mode, ctime_r(&eds.modeSetAt, tmp));
-  }
-  return count;
-}
-
-uint64_t SyncRes::doDumpNSSpeeds(int fd)
-{
-  int newfd = dup(fd);
-  if (newfd == -1) {
-    return 0;
-  }
-  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(newfd, "w"), fclose);
-  if (!fp) {
-    close(newfd);
-    return 0;
-  }
-  fprintf(fp.get(), "; nsspeed dump from thread follows\n;\n");
-  uint64_t count=0;
-
-  for(const auto& i : t_sstorage.nsSpeeds)
-  {
-    count++;
-
-    // an <empty> can appear hear in case of authoritative (hosted) zones
-    fprintf(fp.get(), "%s -> ", i.first.toLogString().c_str());
-    for(const auto& j : i.second.d_collection)
-    {
-      // typedef vector<pair<ComboAddress, DecayingEwma> > collection_t;
-      fprintf(fp.get(), "%s/%f ", j.first.toString().c_str(), j.second.peek());
-    }
-    fprintf(fp.get(), "\n");
-  }
-  return count;
-}
-
-uint64_t SyncRes::doDumpThrottleMap(int fd)
-{
-  int newfd = dup(fd);
-  if (newfd == -1) {
-    return 0;
-  }
-  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(newfd, "w"), fclose);
-  if (!fp) {
-    close(newfd);
-    return 0;
-  }
-  fprintf(fp.get(), "; throttle map dump follows\n");
-  fprintf(fp.get(), "; remote IP\tqname\tqtype\tcount\tttd\n");
-  uint64_t count=0;
-
-  const auto& throttleMap = t_sstorage.throttle.getThrottleMap();
-  for(const auto& i : throttleMap)
-  {
-    count++;
-    char tmp[26];
-    // remote IP, dns name, qtype, count, ttd
-    fprintf(fp.get(), "%s\t%s\t%d\t%u\t%s", std::get<0>(i.thing).toString().c_str(), std::get<1>(i.thing).toLogString().c_str(), std::get<2>(i.thing), i.count, ctime_r(&i.ttd, tmp));
-  }
-
-  return count;
-}
-
-uint64_t SyncRes::doDumpFailedServers(int fd)
-{
-  int newfd = dup(fd);
-  if (newfd == -1) {
-    return 0;
-  }
-  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(newfd, "w"), fclose);
-  if (!fp) {
-    close(newfd);
-    return 0;
-  }
-  fprintf(fp.get(), "; failed servers dump follows\n");
-  fprintf(fp.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())
-  {
-    count++;
-    char tmp[26];
-    ctime_r(&i.last, tmp);
-    fprintf(fp.get(), "%s\t%" PRIu64 "\t%s", i.key.toString().c_str(), i.value, tmp);
-  }
-
-  return count;
-}
-
-uint64_t SyncRes::doDumpNonResolvingNS(int fd)
-{
-  int newfd = dup(fd);
-  if (newfd == -1) {
-    return 0;
-  }
-  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(newfd, "w"), fclose);
-  if (!fp) {
-    close(newfd);
-    return 0;
-  }
-  fprintf(fp.get(), "; non-resolving nameserver dump follows\n");
-  fprintf(fp.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())
-  {
-    count++;
-    char tmp[26];
-    ctime_r(&i.last, tmp);
-    fprintf(fp.get(), "%s\t%" PRIu64 "\t%s", i.key.toString().c_str(), i.value, tmp);
-  }
-
-  return count;
-}
-
-/* so here is the story. First we complete the full resolution process for a domain name. And only THEN do we decide
-   to also do DNSSEC validation, which leads to new queries. To make this simple, we *always* ask for DNSSEC records
-   so that if there are RRSIGs for a name, we'll have them.
-
-   However, some hosts simply can't answer questions which ask for DNSSEC. This can manifest itself as:
-   * No answer
-   * FormErr
-   * Nonsense answer
-
-   The cause of "No answer" may be fragmentation, and it is tempting to probe if smaller answers would get through.
-   Another cause of "No answer" may simply be a network condition.
-   Nonsense answers are a clearer indication this host won't be able to do DNSSEC evah.
-
-   Previous implementations have suffered from turning off DNSSEC questions for an authoritative server based on timeouts.
-   A clever idea is to only turn off DNSSEC if we know a domain isn't signed anyhow. The problem with that really
-   clever idea however is that at this point in PowerDNS, we may simply not know that yet. All the DNSSEC thinking happens
-   elsewhere. It may not have happened yet.
-
-   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
-{
-  /* what is your QUEST?
-     the goal is to get as many remotes as possible on the highest level of EDNS support
-     The levels are:
-
-     0) UNKNOWN Unknown state
-     1) EDNS: Honors EDNS0
-     2) EDNSIGNORANT: Ignores EDNS0, gives replies without EDNS0
-     3) NOEDNS: Generates FORMERR on EDNS queries
-
-     Everybody starts out assumed to be '0'.
-     If '0', send out EDNS0
-        If you FORMERR us, go to '3',
-        If no EDNS in response, go to '2'
-     If '1', send out EDNS0
-        If FORMERR, downgrade to 3
-     If '2', keep on including EDNS0, see what happens
-        Same behaviour as 0
-     If '3', send bare queries
-  */
-
-  auto ednsstatus = t_sstorage.ednsstatus.insert(ip).first; // does this include port? YES
-  auto &ind = t_sstorage.ednsstatus.get<ComboAddress>();
-  if (ednsstatus->modeSetAt && ednsstatus->modeSetAt + 3600 < d_now.tv_sec) {
-    t_sstorage.ednsstatus.reset(ind, ednsstatus);
-    //    cerr<<"Resetting EDNS Status for "<<ip.toString()<<endl);
-  }
-
-  const SyncRes::EDNSStatus::EDNSMode *mode = &ednsstatus->mode;
-  const SyncRes::EDNSStatus::EDNSMode oldmode = *mode;
-  int EDNSLevel = 0;
-  auto luaconfsLocal = g_luaconfs.getLocal();
-  ResolveContext ctx;
-  ctx.d_initialRequestId = d_initialRequestId;
-  ctx.d_nsName = nsName;
-#ifdef HAVE_FSTRM
-  ctx.d_auth = auth;
-#endif
-
-  LWResult::Result ret;
-  for(int tries = 0; tries < 3; ++tries) {
-    //    cerr<<"Remote '"<<ip.toString()<<"' currently in mode "<<mode<<endl;
-
-    if (*mode == EDNSStatus::NOEDNS) {
-      g_stats.noEdnsOutQueries++;
-      EDNSLevel = 0; // level != mode
-    }
-    else if (ednsMANDATORY || *mode == EDNSStatus::UNKNOWN || *mode == EDNSStatus::EDNSOK || *mode == EDNSStatus::EDNSIGNORANT)
-      EDNSLevel = 1;
-
-    DNSName sendQname(domain);
-    if (g_lowercaseOutgoing)
-      sendQname.makeUsLowerCase();
-
-    if (d_asyncResolve) {
-      ret = d_asyncResolve(ip, 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);
-    }
-    // ednsstatus might be cleared, so do a new lookup
-    ednsstatus = t_sstorage.ednsstatus.insert(ip).first;
-    mode = &ednsstatus->mode;
-    if (ret == LWResult::Result::PermanentError || ret == LWResult::Result::OSLimitError || ret == LWResult::Result::Spoofed) {
-      return ret; // transport error, nothing to learn here
-    }
-
-    if (ret == LWResult::Result::Timeout) { // timeout, not doing anything with it now
-      return ret;
-    }
-    else if (*mode == EDNSStatus::UNKNOWN || *mode == EDNSStatus::EDNSOK || *mode == EDNSStatus::EDNSIGNORANT ) {
-      if(res->d_validpacket && !res->d_haveEDNS && res->d_rcode == RCode::FormErr)  {
-       //      cerr<<"Downgrading to NOEDNS because of "<<RCode::to_s(res->d_rcode)<<" for query to "<<ip.toString()<<" for '"<<domain<<"'"<<endl;
-        t_sstorage.ednsstatus.setMode(ind, ednsstatus, EDNSStatus::NOEDNS);
-        continue;
-      }
-      else if(!res->d_haveEDNS) {
-        if (*mode != EDNSStatus::EDNSIGNORANT) {
-          t_sstorage.ednsstatus.setMode(ind, ednsstatus, EDNSStatus::EDNSIGNORANT);
-         //      cerr<<"We find that "<<ip.toString()<<" is an EDNS-ignorer for '"<<domain<<"', moving to mode 2"<<endl;
-       }
-      }
-      else {
-        t_sstorage.ednsstatus.setMode(ind, ednsstatus, EDNSStatus::EDNSOK);
-       //      cerr<<"We find that "<<ip.toString()<<" is EDNS OK!"<<endl;
-      }
-    }
-
-    if (oldmode != *mode || !ednsstatus->modeSetAt) {
-      t_sstorage.ednsstatus.setTS(ind, ednsstatus, d_now.tv_sec);
-    }
-    //    cerr<<"Result: ret="<<ret<<", EDNS-level: "<<EDNSLevel<<", haveEDNS: "<<res->d_haveEDNS<<", new mode: "<<mode<<endl;
-    return LWResult::Result::Success;
-  }
-  return ret;
-}
-
-#define QLOG(x) LOG(prefix << " child=" <<  child << ": " << x << endl)
-
-/* 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;
-
-static unsigned int qmStepLen(unsigned int labels, unsigned int qnamelen, unsigned int i)
-{
-    unsigned int step;
-
-    if (i < s_minimise_one_lab) {
-      step = 1;
-    } else if (i < s_max_minimise_count) {
-      step = std::max(1U, (qnamelen - labels) / (10 - i));
-    } else {
-      step = qnamelen - labels;
-    }
-    unsigned int targetlen = std::min(labels + step, qnamelen);
-    return targetlen;
-}
-
-int SyncRes::doResolve(const DNSName &qname, const QType qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state) {
-
-  string prefix = d_prefix;
-  prefix.append(depth, ' ');
-  auto luaconfsLocal = g_luaconfs.getLocal();
-
-  /* Apply qname (including CNAME chain) filtering policies */
-  if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
-    if (luaconfsLocal->dfe.getQueryPolicy(qname, d_discardedPolicies, d_appliedPolicy)) {
-      mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
-      bool done = false;
-      int rcode = RCode::NoError;
-      handlePolicyHit(prefix, qname, qtype, ret, done, rcode, depth);
-      if (done) {
-        return rcode;
-      }
-    }
-  }
-
-  initZoneCutsFromTA(qname);
-
-  // In the auth or recursive forward case, it does not make sense to do qname-minimization
-  if (!getQNameMinimization() || isRecursiveForwardOrAuth(qname)) {
-    return doResolveNoQNameMinimization(qname, qtype, ret, depth, beenthere, state);
-  }
-
-  // The qname minimization algorithm is a simplified version of the one in RFC 7816 (bis).
-  // It could be simplified because the cache maintenance (both positive and negative)
-  // is already done by doResolveNoQNameMinimization().
-  //
-  // Sketch of algorithm:
-  // Check cache
-  //  If result found: done
-  //  Otherwise determine closes ancestor from cache data
-  //    Repeat querying A, adding more labels of the original qname
-  //    If we get a delegation continue at ancestor determination
-  //    Until we have the full name.
-  //
-  // The algorithm starts with adding a single label per iteration, and
-  // moves to three labels per iteration after three iterations.
-
-  DNSName child;
-  prefix.append(string("QM ") + qname.toString() + "|" + qtype.toString());
-
-  QLOG("doResolve");
-
-  // Look in cache only
-  vector<DNSRecord> retq;
-  bool old = setCacheOnly(true);
-  bool fromCache = false;
-  // For cache peeking, we tell doResolveNoQNameMinimization not to consider the (non-recursive) forward case.
-  // Otherwise all queries in a forward domain will be forwarded, while we want to consult the cache.
-  // The out-of-band cases for doResolveNoQNameMinimization() should be reconsidered and redone some day.
-  int res = doResolveNoQNameMinimization(qname, qtype, retq, depth, beenthere, state, &fromCache, nullptr, false);
-  setCacheOnly(old);
-  if (fromCache) {
-    QLOG("Step0 Found in cache");
-    if (d_appliedPolicy.d_type != DNSFilterEngine::PolicyType::None && (d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NXDOMAIN || d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NODATA)) {
-      ret.clear();
-    }
-    ret.insert(ret.end(), retq.begin(), retq.end());
-
-    return res;
-  }
-  QLOG("Step0 Not cached");
-
-  const unsigned int qnamelen = qname.countLabels();
-
-  DNSName fwdomain(qname);
-  const bool forwarded = getBestAuthZone(&fwdomain) != t_sstorage.domainmap->end();
-  if (forwarded) {
-    QLOG("Step0 qname is in a forwarded domain " << fwdomain);
-  }
-
-  for (unsigned int i = 0; i <= qnamelen; ) {
-
-    // Step 1
-    vector<DNSRecord> bestns;
-    DNSName nsdomain(qname);
-    if (qtype == QType::DS) {
-      nsdomain.chopOff();
-    }
-    // the two retries allow getBestNSFromCache&co to reprime the root
-    // hints, in case they ever go missing
-    for (int tries = 0; tries < 2 && bestns.empty(); ++tries) {
-      bool flawedNSSet = false;
-      set<GetBestNSAnswer> beenthereIgnored;
-      getBestNSFromCache(nsdomain, qtype, bestns, &flawedNSSet, depth, beenthereIgnored, boost::make_optional(forwarded, fwdomain));
-      if (forwarded) {
-        break;
-      }
-    }
-
-    if (bestns.size() == 0) {
-      if (!forwarded) {
-        // Something terrible is wrong
-        QLOG("Step1 No ancestor found return ServFail");
-        return RCode::ServFail;
-      }
-      child = fwdomain;
-    } else {
-      QLOG("Step1 Ancestor from cache is " << bestns[0].d_name);
-      if (forwarded) {
-        child = bestns[0].d_name.isPartOf(fwdomain) ? bestns[0].d_name : fwdomain;
-        QLOG("Step1 Final Ancestor (using forwarding info) is " << child);
-      } else {
-        child = bestns[0].d_name;
-      }
-    }
-    for (; i <= qnamelen; i++) {
-      // Step 2
-      unsigned int labels = child.countLabels();
-      unsigned int targetlen = qmStepLen(labels, qnamelen, i);
-
-      while (labels < targetlen) {
-        child.prependRawLabel(qname.getRawLabel(qnamelen - labels - 1));
-        labels++;
-      }
-      // rfc9156 section-2.3, append labels if they start with an underscore
-      while (labels < qnamelen) {
-        auto prependLabel = qname.getRawLabel(qnamelen - labels - 1);
-        if (prependLabel.at(0) != '_') {
-          break;
-        }
-        child.prependRawLabel(prependLabel);
-        labels++;
-      }
-
-      QLOG("Step2 New child");
-
-      // Step 3 resolve
-      if (child == qname) {
-        QLOG("Step3 Going to do final resolve");
-        res = doResolveNoQNameMinimization(qname, qtype, ret, depth, beenthere, state);
-        QLOG("Step3 Final resolve: " << RCode::to_s(res) << "/" << ret.size());
-        return res;
-      }
-
-      // Step 4
-      QLOG("Step4 Resolve A for child");
-      bool oldFollowCNAME = d_followCNAME;
-      d_followCNAME = false;
-      retq.resize(0);
-      StopAtDelegation stopAtDelegation = Stop;
-      res = doResolveNoQNameMinimization(child, QType::A, retq, depth, beenthere, state, nullptr, &stopAtDelegation);
-      d_followCNAME = oldFollowCNAME;
-      QLOG("Step4 Resolve A result is " << RCode::to_s(res) << "/" << retq.size() << "/" << stopAtDelegation);
-      if (stopAtDelegation == Stopped) {
-        QLOG("Delegation seen, continue at step 1");
-        break;
-      }
-
-      if (res != RCode::NoError) {
-        // Case 5: unexpected answer
-        QLOG("Step5: other rcode, last effort final resolve");
-        setQNameMinimization(false);
-        // We might have hit a depth level check, but we still want to allow some recursion levels in the fallback
-        // no-qname-minimization case. This has the effect that a qname minimization fallback case might reach 150% of
-        // maxdepth.
-        res = doResolveNoQNameMinimization(qname, qtype, ret, depth/2, beenthere, state);
-
-        if(res == RCode::NoError) {
-          s_qnameminfallbacksuccess++;
-        }
-
-        QLOG("Step5 End resolve: " << RCode::to_s(res) << "/" << ret.size());
-        return res;
-      }
-    }
-  }
-
-  // Should not be reached
-  QLOG("Max iterations reached, return ServFail");
-  return RCode::ServFail;
-}
-
-/*! This function will check the cache and go out to the internet if the answer is not in cache
- *
- * \param qname The name we need an answer for
- * \param qtype
- * \param ret The vector of DNSRecords we need to fill with the answers
- * \param depth The recursion depth we are in
- * \param beenthere
- * \param fromCache tells the caller the result came from the cache, may be nullptr
- * \param stopAtDelegation if non-nullptr and pointed-to value is Stop requests the callee to stop at a delegation, if so pointed-to value is set to Stopped
- * \return DNS RCODE or -1 (Error)
- */
-int SyncRes::doResolveNoQNameMinimization(const DNSName &qname, const QType qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, bool *fromCache, StopAtDelegation *stopAtDelegation, bool considerforwards)
-{
-  string prefix;
-  if(doLog()) {
-    prefix=d_prefix;
-    prefix.append(depth, ' ');
-  }
-
-  LOG(prefix<<qname<<": Wants "<< (d_doDNSSEC ? "" : "NO ") << "DNSSEC processing, "<<(d_requireAuthData ? "" : "NO ")<<"auth data in query for "<<qtype<<endl);
-
-  if (s_maxdepth && depth > s_maxdepth) {
-    string msg = "More than " + std::to_string(s_maxdepth) + " (max-recursion-depth) levels of recursion needed while resolving " + qname.toLogString();
-    LOG(prefix << qname << ": " << msg << endl);
-    throw ImmediateServFailException(msg);
-  }
-  int res=0;
-
-  // This is a difficult way of expressing "this is a normal query", i.e. not getRootNS.
-  if(!(d_updatingRootNS && qtype.getCode()==QType::NS && qname.isRoot())) {
-    if(d_cacheonly) { // very limited OOB support
-      LWResult lwr;
-      LOG(prefix<<qname<<": Recursion not requested for '"<<qname<<"|"<<qtype<<"', peeking at auth/forward zones"<<endl);
-      DNSName authname(qname);
-      domainmap_t::const_iterator iter=getBestAuthZone(&authname);
-      if(iter != t_sstorage.domainmap->end()) {
-        if(iter->second.isAuth()) {
-          ret.clear();
-          d_wasOutOfBand = doOOBResolve(qname, qtype, ret, depth, res);
-          if (fromCache)
-            *fromCache = d_wasOutOfBand;
-          return res;
-        }
-        else if (considerforwards) {
-          const vector<ComboAddress>& servers = iter->second.d_servers;
-          const ComboAddress remoteIP = servers.front();
-          LOG(prefix<<qname<<": forwarding query to hardcoded nameserver '"<< remoteIP.toStringWithPort()<<"' for zone '"<<authname<<"'"<<endl);
-
-          boost::optional<Netmask> nm;
-          bool chained = false;
-          // forwardes are "anonymous", so plug in an empty name; some day we might have a fancier config language...
-          auto resolveRet = asyncresolveWrapper(remoteIP, d_doDNSSEC, qname, authname, qtype.getCode(), false, false, &d_now, nm, &lwr, &chained, DNSName());
-
-          d_totUsec += lwr.d_usec;
-          accountAuthLatency(lwr.d_usec, remoteIP.sin4.sin_family);
-          if (fromCache)
-            *fromCache = true;
-
-          // filter out the good stuff from lwr.result()
-          if (resolveRet == LWResult::Result::Success) {
-            for(const auto& rec : lwr.d_records) {
-              if(rec.d_place == DNSResourceRecord::ANSWER)
-                ret.push_back(rec);
-            }
-            return 0;
-          }
-          else {
-            return RCode::ServFail;
-          }
-        }
-      }
-    }
-
-    DNSName authname(qname);
-    bool wasForwardedOrAuthZone = false;
-    bool wasAuthZone = false;
-    bool wasForwardRecurse = false;
-    domainmap_t::const_iterator iter = getBestAuthZone(&authname);
-    if(iter != t_sstorage.domainmap->end()) {
-      const auto& domain = iter->second;
-      wasForwardedOrAuthZone = true;
-
-      if (domain.isAuth()) {
-        wasAuthZone = true;
-      } else if (domain.shouldRecurse()) {
-        wasForwardRecurse = true;
-      }
-    }
-
-    /* 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, res, state, wasAuthZone, wasForwardRecurse)) { // will reroute us if needed
-      d_wasOutOfBand = wasAuthZone;
-      // Here we have an issue. If we were prevented from going out to the network (cache-only was set, possibly because we
-      // are in QM Step0) we might have a CNAME but not the corresponding target.
-      // It means that we will sometimes go to the next steps when we are in fact done, but that's fine since
-      // we will get the records from the cache, resulting in a small overhead.
-      // This might be a real problem if we had a RPZ hit, though, because we do not want the processing to continue, since
-      // RPZ rules will not be evaluated anymore (we already matched).
-      const bool stoppedByPolicyHit = d_appliedPolicy.wasHit();
-
-      if (fromCache && (!d_cacheonly || stoppedByPolicyHit)) {
-        *fromCache = true;
-      }
-      /* Apply Post filtering policies */
-
-      if (d_wantsRPZ && !stoppedByPolicyHit) {
-        auto luaLocal = g_luaconfs.getLocal();
-        if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
-          mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
-          bool done = false;
-          handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
-          if (done && fromCache) {
-            *fromCache = true;
-          }
-        }
-      }
-
-      return res;
-    }
-
-    if (doCacheCheck(qname, authname, wasForwardedOrAuthZone, wasAuthZone, wasForwardRecurse, qtype, ret, depth, res, state)) {
-      // we done
-      d_wasOutOfBand = wasAuthZone;
-      if (fromCache) {
-        *fromCache = true;
-      }
-
-      if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
-        auto luaLocal = g_luaconfs.getLocal();
-        if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
-          mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
-          bool done = false;
-          handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
-        }
-      }
-
-      return res;
-    }
-
-    /* if 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, res, state, wasAuthZone, wasForwardRecurse)) { // will reroute us if needed
-      d_wasOutOfBand = wasAuthZone;
-      // Here we have an issue. If we were prevented from going out to the network (cache-only was set, possibly because we
-      // are in QM Step0) we might have a CNAME but not the corresponding target.
-      // It means that we will sometimes go to the next steps when we are in fact done, but that's fine since
-      // we will get the records from the cache, resulting in a small overhead.
-      // This might be a real problem if we had a RPZ hit, though, because we do not want the processing to continue, since
-      // RPZ rules will not be evaluated anymore (we already matched).
-      const bool stoppedByPolicyHit = d_appliedPolicy.wasHit();
-
-      if (fromCache && (!d_cacheonly || stoppedByPolicyHit)) {
-        *fromCache = true;
-      }
-      /* Apply Post filtering policies */
-
-      if (d_wantsRPZ && !stoppedByPolicyHit) {
-        auto luaLocal = g_luaconfs.getLocal();
-        if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
-          mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
-          bool done = false;
-          handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
-          if (done && fromCache) {
-            *fromCache = true;
-          }
-        }
-      }
-
-      return res;
-    }
-  }
-
-  if (d_cacheonly) {
-    return 0;
-  }
-
-  LOG(prefix<<qname<<": No cache hit for '"<<qname<<"|"<<qtype<<"', trying to find an appropriate NS record"<<endl);
-
-  DNSName subdomain(qname);
-  if (qtype == QType::DS) subdomain.chopOff();
-
-  NsSet nsset;
-  bool flawedNSSet=false;
-
-  // the two retries allow getBestNSNamesFromCache&co to reprime the root
-  // hints, in case they ever go missing
-  for(int tries=0;tries<2 && nsset.empty();++tries) {
-    subdomain=getBestNSNamesFromCache(subdomain, qtype, nsset, &flawedNSSet, depth, beenthere); //  pass beenthere to both occasions
-  }
-
-  res = doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state, stopAtDelegation);
-
-  /* Apply Post filtering policies */
-  if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
-    auto luaLocal = g_luaconfs.getLocal();
-    if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
-      mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
-      bool done = false;
-      handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
-    }
-  }
-
-  if (!res) {
-    return 0;
-  }
-
-  LOG(prefix<<qname<<": failed (res="<<res<<")"<<endl);
-
-  return res<0 ? RCode::ServFail : res;
-}
-
-#if 0
-// for testing purposes
-static bool ipv6First(const ComboAddress& a, const ComboAddress& b)
-{
-  return !(a.sin4.sin_family < a.sin4.sin_family);
-}
-#endif
-
-struct speedOrderCA
-{
-  speedOrderCA(std::map<ComboAddress,float>& speeds): d_speeds(speeds) {}
-  bool operator()(const ComboAddress& a, const ComboAddress& b) const
-  {
-    return d_speeds[a] < d_speeds[b];
-  }
-  std::map<ComboAddress, float>& d_speeds;
-};
-
-/** This function explicitly goes out for A or AAAA addresses
-*/
-vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth, set<GetBestNSAnswer>& beenthere, bool cacheOnly, unsigned int& addressQueriesForNS)
-{
-  typedef vector<DNSRecord> res_t;
-  typedef vector<ComboAddress> ret_t;
-  ret_t ret;
-
-  bool oldCacheOnly = setCacheOnly(cacheOnly);
-  bool oldRequireAuthData = d_requireAuthData;
-  bool oldValidationRequested = d_DNSSECValidationRequested;
-  bool oldFollowCNAME = d_followCNAME;
-  bool seenV6 = false;
-  const unsigned int startqueries = d_outqueries;
-  d_requireAuthData = false;
-  d_DNSSECValidationRequested = false;
-  d_followCNAME = true;
-
-  try {
-    // 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, false, &cset, d_cacheRemote, false, d_routingTag) > 0) {
-      for (const auto &i : cset) {
-        if (auto rec = getRR<ARecordContent>(i)) {
-          ret.push_back(rec->getCA(53));
-        }
-      }
-    }
-    if (s_doIPv6 && g_recCache->get(d_now.tv_sec, qname, QType::AAAA, false, &cset, d_cacheRemote, false, d_routingTag) > 0) {
-      for (const auto &i : cset) {
-        if (auto rec = getRR<AAAARecordContent>(i)) {
-          seenV6 = true;
-          ret.push_back(rec->getCA(53));
-        }
-      }
-    }
-    if (ret.empty()) {
-      // Neither A nor AAAA in the cache...
-      vState newState = vState::Indeterminate;
-      cset.clear();
-      // Go out to get A's
-      if (s_doIPv4 && doResolve(qname, QType::A, cset, depth+1, beenthere, newState) == 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_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.
-          newState = vState::Indeterminate;
-          cset.clear();
-          if (doResolve(qname, QType::AAAA, cset, depth+1, beenthere, newState) == 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));
-                }
-              }
-            }
-          }
-        } else {
-          // 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, false, &cset, d_cacheRemote, false, d_routingTag) > 0) {
-            for (const auto &i : cset) {
-              if (auto rec = getRR<AAAARecordContent>(i)) {
-                seenV6 = true;
-                ret.push_back(rec->getCA(53));
-              }
-            }
-          }
-        }
-      }
-    }
-    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, true);
-      if (!inNegCache) {
-        pushResolveTask(qname, QType::AAAA, d_now.tv_sec, d_now.tv_sec + 60);
-      }
-    }
-  }
-  catch (const PolicyHitException&) {
-    // We ignore a policy hit while trying to retrieve the addresses
-    // of a NS and keep processing the current query
-  }
-
-  if (ret.empty() && d_outqueries > startqueries) {
-    // We did 1 or more outgoing queries to resolve this NS name but returned empty handed
-    addressQueriesForNS++;
-  }
-  d_requireAuthData = oldRequireAuthData;
-  d_DNSSECValidationRequested = oldValidationRequested;
-  setCacheOnly(oldCacheOnly);
-  d_followCNAME = oldFollowCNAME;
-
-  /* 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& collection = t_sstorage.nsSpeeds[qname];
-  float factor = collection.getFactor(d_now);
-  for(const auto& val: ret) {
-    speeds[val] = collection.d_collection[val].get(factor);
-  }
-
-  t_sstorage.nsSpeeds[qname].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()) {
-    string prefix=d_prefix;
-    prefix.append(depth, ' ');
-    LOG(prefix<<"Nameserver "<<qname<<" IPs: ");
-    bool first = true;
-    for(const auto& addr : ret) {
-      if (first) {
-        first = false;
-      }
-      else {
-        LOG(", ");
-      }
-      LOG((addr.toString())<<"(" << fmtfloat("%0.2f", speeds[addr]/1000.0) <<"ms)");
-    }
-    LOG(endl);
-  }
-
-  return ret;
-}
-
-void SyncRes::getBestNSFromCache(const DNSName &qname, const QType qtype, vector<DNSRecord>& bestns, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>& beenthere, const boost::optional<DNSName>& cutOffDomain)
-{
-  string prefix;
-  DNSName subdomain(qname);
-  if(doLog()) {
-    prefix=d_prefix;
-    prefix.append(depth, ' ');
-  }
-  bestns.clear();
-  bool brokeloop;
-  do {
-    if (cutOffDomain && (subdomain == *cutOffDomain || !subdomain.isPartOf(*cutOffDomain))) {
-      break;
-    }
-    brokeloop=false;
-    LOG(prefix<<qname<<": Checking if we have NS in cache for '"<<subdomain<<"'"<<endl);
-    vector<DNSRecord> ns;
-    *flawedNSSet = false;
-
-    if(g_recCache->get(d_now.tv_sec, subdomain, QType::NS, false, &ns, d_cacheRemote, false, d_routingTag) > 0) {
-      bestns.reserve(ns.size());
-
-      for(auto k=ns.cbegin();k!=ns.cend(); ++k) {
-        if(k->d_ttl > (unsigned int)d_now.tv_sec ) {
-          vector<DNSRecord> aset;
-          QType nsqt{QType::ADDR};
-          if (s_doIPv4 && !s_doIPv6) {
-            nsqt = QType::A;
-          } else if (!s_doIPv4 && s_doIPv6) {
-            nsqt = QType::AAAA;
-          }
-
-          const DNSRecord& dr=*k;
-         auto nrr = getRR<NSRecordContent>(dr);
-          if(nrr && (!nrr->getNS().isPartOf(subdomain) || g_recCache->get(d_now.tv_sec, nrr->getNS(), nsqt,
-                                                                          false, doLog() ? &aset : 0, d_cacheRemote, false, d_routingTag) > 5)) {
-            bestns.push_back(dr);
-            LOG(prefix<<qname<<": NS (with ip, or non-glue) in cache for '"<<subdomain<<"' -> '"<<nrr->getNS()<<"'"<<endl);
-            LOG(prefix<<qname<<": within bailiwick: "<< nrr->getNS().isPartOf(subdomain));
-            if(!aset.empty()) {
-              LOG(",  in cache, ttl="<<(unsigned int)(((time_t)aset.begin()->d_ttl- d_now.tv_sec ))<<endl);
-            }
-            else {
-              LOG(", not in cache / did not look at cache"<<endl);
-            }
-          }
-          else {
-            *flawedNSSet=true;
-            LOG(prefix<<qname<<": NS in cache for '"<<subdomain<<"', but needs glue ("<<nrr->getNS()<<") which we miss or is expired"<<endl);
-          }
-        }
-      }
-
-      if(!bestns.empty()) {
-        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());
-          }
-        }
-
-        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);
-         ;
-          if(doLog())
-            for( set<GetBestNSAnswer>::const_iterator 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 {
-          LOG(prefix<<qname<<": We have NS in cache for '"<<subdomain<<"' (flawedNSSet="<<*flawedNSSet<<")"<<endl);
-          return;
-        }
-      }
-    }
-    LOG(prefix<<qname<<": no valid/useful NS in cache for '"<<subdomain<<"'"<<endl);
-
-    if(subdomain.isRoot() && !brokeloop) {
-      // We lost the root NS records
-      primeHints();
-      LOG(prefix<<qname<<": reprimed the root"<<endl);
-      /* let's prevent an infinite loop */
-      if (!d_updatingRootNS) {
-        primeRootNSZones(g_dnssecmode, depth);
-        getRootNS(d_now, d_asyncResolve, depth);
-      }
-    }
-  } while(subdomain.chopOff());
-}
-
-SyncRes::domainmap_t::const_iterator SyncRes::getBestAuthZone(DNSName* qname) const
-{
-  if (t_sstorage.domainmap->empty()) {
-    return t_sstorage.domainmap->end();
-  }
-
-  SyncRes::domainmap_t::const_iterator ret;
-  do {
-    ret=t_sstorage.domainmap->find(*qname);
-    if(ret!=t_sstorage.domainmap->end())
-      break;
-  }while(qname->chopOff());
-  return ret;
-}
-
-/** doesn't actually do the work, leaves that to getBestNSFromCache */
-DNSName SyncRes::getBestNSNamesFromCache(const DNSName &qname, const QType qtype, NsSet& nsset, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>&beenthere)
-{
-  string prefix;
-  if (doLog()) {
-    prefix = d_prefix;
-    prefix.append(depth, ' ');
-  }
-  DNSName authOrForwDomain(qname);
-
-  domainmap_t::const_iterator iter = getBestAuthZone(&authOrForwDomain);
-  // We have an auth, forwarder of forwarder-recurse
-  if (iter != t_sstorage.domainmap->end()) {
-    if (iter->second.isAuth()) {
-      // this gets picked up in doResolveAt, the empty DNSName, combined with the
-      // empty vector means 'we are auth for this zone'
-      nsset.insert({DNSName(), {{}, false}});
-      return authOrForwDomain;
-    }
-    else {
-      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;
-      }
-    }
-  }
-
-  // We might have a (non-recursive) forwarder, but maybe the cache already contains
-  // a better NS
-  vector<DNSRecord> bestns;
-  DNSName nsFromCacheDomain(g_rootdnsname);
-  getBestNSFromCache(qname, qtype, bestns, flawedNSSet, depth, beenthere);
-
-  // Pick up the auth domain
-  for (const auto& k : bestns) {
-    const auto nsContent = getRR<NSRecordContent>(k);
-    if (nsContent) {
-      nsFromCacheDomain = k.d_name;
-      break;
-    }
-  }
-
-  if (iter != t_sstorage.domainmap->end()) {
-    if (doLog()) {
-      LOG(prefix << qname << " authOrForwDomain: " << authOrForwDomain << " nsFromCacheDomain: " << nsFromCacheDomain << " isPartof: " << authOrForwDomain.isPartOf(nsFromCacheDomain) << endl);
-    }
-
-    // If the forwarder is better or equal to what's found in the cache, use forwarder. Note that name.isPartOf(name).
-    // So queries that get NS for authOrForwDomain itself go to the forwarder
-    if (authOrForwDomain.isPartOf(nsFromCacheDomain)) {
-      if (doLog()) {
-        LOG(prefix << qname << ": using forwarder as NS" << endl);
-      }
-      nsset.insert({DNSName(), {iter->second.d_servers, false }});
-      return authOrForwDomain;
-    } else {
-      if (doLog()) {
-        LOG(prefix << qname << ": using NS from cache" << endl);
-      }
-    }
-  }
-  for (auto k = bestns.cbegin(); k != bestns.cend(); ++k) {
-    // The actual resolver code will not even look at the ComboAddress or bool
-    const auto nsContent = getRR<NSRecordContent>(*k);
-    if (nsContent) {
-      nsset.insert({nsContent->getNS(), {{}, false}});
-    }
-  }
-  return nsFromCacheDomain;
-}
-
-void SyncRes::updateValidationStatusInCache(const DNSName &qname, const QType qt, bool aa, vState newState) const
-{
-  if (qt == QType::ANY || qt == 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);
-  }
-  else {
-    g_recCache->updateValidationStatus(d_now.tv_sec, qname, qt, d_cacheRemote, d_routingTag, aa, newState, boost::none);
-  }
-}
-
-static bool scanForCNAMELoop(const DNSName& name, const vector<DNSRecord>& records)
-{
-  for (const auto& record: records) {
-    if (record.d_type == QType::CNAME && record.d_place == DNSResourceRecord::ANSWER) {
-      if (name == record.d_name) {
-        return true;
-      }
-    }
-  }
-  return false;
-}
-
-bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, int &res, vState& state, bool wasAuthZone, bool wasForwardRecurse)
-{
-  string prefix;
-  if(doLog()) {
-    prefix=d_prefix;
-    prefix.append(depth, ' ');
-  }
-
-  if((depth>9 && d_outqueries>10 && d_throttledqueries>5) || depth > 15) {
-    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<RRSIGRecordContent>> signatures;
-  vector<std::shared_ptr<DNSRecord>> authorityRecs;
-  bool wasAuth;
-  uint32_t capTTL = std::numeric_limits<uint32_t>::max();
-  DNSName foundName;
-  DNSName authZone;
-  QType foundQT = QType::ENT;
-
-  /* we don't require auth data for forward-recurse lookups */
-  if (g_recCache->get(d_now.tv_sec, qname, QType::CNAME, !wasForwardRecurse && d_requireAuthData, &cset, d_cacheRemote, d_refresh, d_routingTag, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &state, &wasAuth, &authZone, &d_fromAuthIP) > 0) {
-    foundName = qname;
-    foundQT = QType::CNAME;
-  }
-
-  if (foundName.empty() && qname != g_rootdnsname) {
-    // look for a DNAME cache hit
-    auto labels = qname.getRawLabels();
-    DNSName dnameName(g_rootdnsname);
-
-    do {
-      dnameName.prependRawLabel(labels.back());
-      labels.pop_back();
-      if (dnameName == qname && qtype != QType::DNAME) { // The client does not want a DNAME, but we've reached the QNAME already. So there is no match
-        break;
-      }
-      if (g_recCache->get(d_now.tv_sec, dnameName, QType::DNAME, !wasForwardRecurse && d_requireAuthData, &cset, d_cacheRemote, d_refresh, d_routingTag, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &state, &wasAuth, &authZone, &d_fromAuthIP) > 0) {
-        foundName = dnameName;
-        foundQT = QType::DNAME;
-        break;
-      }
-    } while(!labels.empty());
-  }
-
-  if (foundName.empty()) {
-    return false;
-  }
-
-  if (qtype == QType::DS && authZone == qname) {
-    /* CNAME at APEX of the child zone, we can't use that to prove that
-       there is no DS */
-    LOG(prefix<<qname<<": Found a "<<foundQT.toString()<<" cache hit of '"<< qname <<"' from "<<authZone<<", but such a record at the apex of the child zone does not prove that there is no DS in the parent zone"<<endl);
-    return false;
-  }
-
-  for(auto const &record : cset) {
-    if (record.d_class != QClass::IN) {
-      continue;
-    }
-
-    if(record.d_ttl > (unsigned int) d_now.tv_sec) {
-
-      if (!wasAuthZone && shouldValidate() && (wasAuth || wasForwardRecurse) && state == vState::Indeterminate && d_requireAuthData) {
-        /* This means we couldn't figure out the state when this entry was cached */
-
-        vState recordState = getValidationStatus(foundName, !signatures.empty(), qtype == QType::DS, depth);
-        if (recordState == vState::Secure) {
-          LOG(prefix<<qname<<": got vState::Indeterminate state from the "<<foundQT.toString()<<" cache, validating.."<<endl);
-          state = SyncRes::validateRecordsWithSigs(depth, qname, qtype, foundName, foundQT, cset, signatures);
-          if (state != vState::Indeterminate) {
-            LOG(prefix<<qname<<": got vState::Indeterminate state from the "<<foundQT.toString()<<" cache, new validation result is "<<state<<endl);
-            if (vStateIsBogus(state)) {
-              capTTL = s_maxbogusttl;
-            }
-            updateValidationStatusInCache(foundName, foundQT, wasAuth, state);
-          }
-        }
-      }
-
-      LOG(prefix<<qname<<": Found cache "<<foundQT.toString()<<" hit for '"<< foundName << "|"<<foundQT.toString()<<"' to '"<<record.d_content->getZoneRepresentation()<<"', validation state is "<<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);
-
-      for(const auto& signature : signatures) {
-        DNSRecord sigdr;
-        sigdr.d_type=QType::RRSIG;
-        sigdr.d_name=foundName;
-        sigdr.d_ttl=ttl;
-        sigdr.d_content=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);
-      }
-
-      DNSName newTarget;
-      if (foundQT == QType::DNAME) {
-        if (qtype == QType::DNAME && qname == foundName) { // client wanted the DNAME, no need to synthesize a CNAME
-          res = RCode::NoError;
-          return true;
-        }
-        // Synthesize a CNAME
-        auto dnameRR = getRR<DNAMERecordContent>(record);
-        if (dnameRR == nullptr) {
-          throw ImmediateServFailException("Unable to get record content for "+foundName.toLogString()+"|DNAME cache entry");
-        }
-        const auto& dnameSuffix = dnameRR->getTarget();
-        DNSName targetPrefix = qname.makeRelative(foundName);
-        try {
-          dr.d_type = QType::CNAME;
-          dr.d_name = targetPrefix + foundName;
-          newTarget = targetPrefix + dnameSuffix;
-          dr.d_content = std::make_shared<CNAMERecordContent>(CNAMERecordContent(newTarget));
-          ret.push_back(dr);
-        } 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)
-          // But this is consistent with processRecords
-          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);
-      }
-
-      if(qtype == QType::CNAME) { // perhaps they really wanted a CNAME!
-        res = RCode::NoError;
-        return true;
-      }
-
-      if (qtype == QType::DS || qtype == QType::DNSKEY) {
-        res = RCode::NoError;
-        return true;
-      }
-
-      // We have a DNAME _or_ CNAME cache hit and the client wants something else than those two.
-      // Let's find the answer!
-      if (foundQT == QType::CNAME) {
-        const auto cnameContent = getRR<CNAMERecordContent>(record);
-        if (cnameContent == nullptr) {
-          throw ImmediateServFailException("Unable to get record content for "+foundName.toLogString()+"|CNAME cache entry");
-        }
-        newTarget = cnameContent->getTarget();
-      }
-
-      if (qname == newTarget) {
-        string msg = "got a CNAME referral (from cache) to self";
-        LOG(prefix<<qname<<": "<<msg<<endl);
-        throw ImmediateServFailException(msg);
-      }
-
-      if (newTarget.isPartOf(qname)) {
-        // a.b.c. CNAME x.a.b.c will go to great depths with QM on
-        string msg = "got a CNAME referral (from cache) to child, disabling QM";
-        LOG(prefix<<qname<<": "<<msg<<endl);
-        setQNameMinimization(false);
-      }
-
-      if (!d_followCNAME) {
-        res = RCode::NoError;
-        return true;
-      }
-
-      // Check to see if we already have seen the new target as a previous target
-      if (scanForCNAMELoop(newTarget, ret)) {
-        string msg = "got a CNAME referral (from cache) that causes a loop";
-        LOG(prefix<<qname<<": status="<<msg<<endl);
-        throw ImmediateServFailException(msg);
-      }
-
-      set<GetBestNSAnswer>beenthere;
-      vState cnameState = vState::Indeterminate;
-      // Be aware that going out on the network might be disabled (cache-only), for example because we are in QM Step0,
-      // so you can't trust that a real lookup will have been made.
-      res = doResolve(newTarget, qtype, ret, depth+1, beenthere, cnameState);
-      LOG(prefix<<qname<<": updating validation state for response to "<<qname<<" from "<<state<<" with the state from the DNAME/CNAME quest: "<<cnameState<<endl);
-      updateValidationState(state, cnameState);
-
-      return true;
-    }
-  }
-  throw ImmediateServFailException("Could not determine whether or not there was a CNAME or DNAME in cache for '" + qname.toLogString() + "'");
-}
-
-namespace {
-struct CacheEntry
-{
-  vector<DNSRecord> records;
-  vector<shared_ptr<RRSIGRecordContent>> signatures;
-  uint32_t signaturesTTL{std::numeric_limits<uint32_t>::max()};
-};
-struct CacheKey
-{
-  DNSName name;
-  QType type;
-  DNSResourceRecord::Place place;
-  bool operator<(const CacheKey& rhs) const {
-    return std::tie(type, place, name) < std::tie(rhs.type, rhs.place, rhs.name);
-  }
-};
-typedef map<CacheKey, CacheEntry> tcache_t;
-}
-
-static void reapRecordsFromNegCacheEntryForValidation(tcache_t& tcache, const vector<DNSRecord>& records)
-{
-  for (const auto& rec : records) {
-    if (rec.d_type == QType::RRSIG) {
-      auto rrsig = getRR<RRSIGRecordContent>(rec);
-      if (rrsig) {
-        tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signatures.push_back(rrsig);
-      }
-    } else {
-      tcache[{rec.d_name,rec.d_type,rec.d_place}].records.push_back(rec);
-    }
-  }
-}
-
-static bool negativeCacheEntryHasSOA(const NegCache::NegCacheEntry& ne)
-{
-  return !ne.authoritySOA.records.empty();
-}
-
-static void reapRecordsForValidation(std::map<QType, CacheEntry>& entries, const vector<DNSRecord>& records)
-{
-  for (const auto& rec : records) {
-    entries[rec.d_type].records.push_back(rec);
-  }
-}
-
-static void reapSignaturesForValidation(std::map<QType, CacheEntry>& entries, const vector<std::shared_ptr<RRSIGRecordContent>>& signatures)
-{
-  for (const auto& sig : signatures) {
-    entries[sig->d_type].signatures.push_back(sig);
-  }
-}
-
-/*!
- * Convenience function to push the records from records into ret with a new TTL
- *
- * \param records DNSRecords that need to go into ret
- * \param ttl     The new TTL for these records
- * \param ret     The vector of DNSRecords that should contain the records with the modified TTL
- */
-static void addTTLModifiedRecords(vector<DNSRecord>& records, const uint32_t ttl, vector<DNSRecord>& ret) {
-  for (auto& rec : records) {
-    rec.d_ttl = ttl;
-    ret.push_back(std::move(rec));
-  }
-}
-
-void SyncRes::computeNegCacheValidationStatus(const NegCache::NegCacheEntry& ne, const DNSName& qname, const QType qtype, const int res, vState& state, unsigned int depth)
-{
-  tcache_t tcache;
-  reapRecordsFromNegCacheEntryForValidation(tcache, ne.authoritySOA.records);
-  reapRecordsFromNegCacheEntryForValidation(tcache, ne.authoritySOA.signatures);
-  reapRecordsFromNegCacheEntryForValidation(tcache, ne.DNSSECRecords.records);
-  reapRecordsFromNegCacheEntryForValidation(tcache, ne.DNSSECRecords.signatures);
-
-  for (const auto& entry : tcache) {
-    // this happens when we did store signatures, but passed on the records themselves
-    if (entry.second.records.empty()) {
-      continue;
-    }
-
-    const DNSName& owner = entry.first.name;
-
-    vState recordState = getValidationStatus(owner, !entry.second.signatures.empty(), qtype == QType::DS, depth);
-    if (state == vState::Indeterminate) {
-      state = recordState;
-    }
-
-    if (recordState == vState::Secure) {
-      recordState = SyncRes::validateRecordsWithSigs(depth, qname, qtype, owner, QType(entry.first.type), entry.second.records, entry.second.signatures);
-    }
-
-    if (recordState != vState::Indeterminate && recordState != state) {
-      updateValidationState(state, recordState);
-      if (state != vState::Secure) {
-        break;
-      }
-    }
-  }
-
-  if (state == vState::Secure) {
-    vState neValidationState = ne.d_validationState;
-    dState expectedState = res == RCode::NXDomain ? dState::NXDOMAIN : dState::NXQTYPE;
-    dState denialState = getDenialValidationState(ne, expectedState, false);
-    updateDenialValidationState(neValidationState, ne.d_name, state, denialState, expectedState, qtype == QType::DS, depth);
-  }
-  if (state != vState::Indeterminate) {
-    /* validation succeeded, let's update the cache entry so we don't have to validate again */
-    boost::optional<time_t> capTTD = boost::none;
-    if (vStateIsBogus(state)) {
-      capTTD = d_now.tv_sec + s_maxbogusttl;
-    }
-    g_negCache->updateValidationStatus(ne.d_name, ne.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, int &res, vState& state)
-{
-  bool giveNegative=false;
-
-  string prefix;
-  if(doLog()) {
-    prefix=d_prefix;
-    prefix.append(depth, ' ');
-  }
-
-  // sqname and sqtype are used contain 'higher' names if we have them (e.g. powerdns.com|SOA when we find a negative entry for doesnotexist.powerdns.com|A)
-  DNSName sqname(qname);
-  QType sqt(qtype);
-  uint32_t sttl=0;
-  //  cout<<"Lookup for '"<<qname<<"|"<<qtype.toString()<<"' -> "<<getLastLabel(qname)<<endl;
-  vState cachedState;
-  NegCache::NegCacheEntry ne;
-
-  if(s_rootNXTrust &&
-      g_negCache->getRootNXTrust(qname, d_now, ne) &&
-      ne.d_auth.isRoot() &&
-      !(wasForwardedOrAuthZone && !authname.isRoot())) { // when forwarding, the root may only neg-cache if it was forwarded to.
-    sttl = ne.d_ttd - d_now.tv_sec;
-    LOG(prefix<<qname<<": Entire name '"<<qname<<"', is negatively cached via '"<<ne.d_auth<<"' & '"<<ne.d_name<<"' for another "<<sttl<<" seconds"<<endl);
-    res = RCode::NXDomain;
-    giveNegative = true;
-    cachedState = ne.d_validationState;
-  } else if (g_negCache->get(qname, qtype, d_now, ne)) {
-    /* If we are looking for a DS, discard NXD if auth == qname
-       and ask for a specific denial instead */
-    if (qtype != QType::DS || ne.d_qtype.getCode() || ne.d_auth != qname ||
-        g_negCache->get(qname, qtype, d_now, ne, true))
-    {
-      /* 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)) {
-        giveNegative = false;
-      }
-      else {
-        res = RCode::NXDomain;
-        sttl = ne.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);
-          res = RCode::NoError;
-        } else {
-          LOG(prefix<<qname<<": Entire name '"<<qname<<"' is negatively cached via '"<<ne.d_auth<<"' for another "<<sttl<<" seconds"<<endl);
-        }
-      }
-    }
-  } else if (s_hardenNXD != HardenNXD::No && !qname.isRoot() && !wasForwardedOrAuthZone) {
-    auto labels = qname.getRawLabels();
-    DNSName negCacheName(g_rootdnsname);
-    negCacheName.prependRawLabel(labels.back());
-    labels.pop_back();
-    while(!labels.empty()) {
-      if (g_negCache->get(negCacheName, QType::ENT, d_now, ne, true)) {
-        if (ne.d_validationState == vState::Indeterminate && validationEnabled()) {
-          // LOG(prefix << negCacheName <<  " negatively cached and vState::Indeterminate, trying to validate NXDOMAIN" << endl);
-          // ...
-          // And get the updated ne struct
-          //t_sstorage.negcache.get(negCacheName, QType(0), d_now, ne, true);
-        }
-        if ((s_hardenNXD == HardenNXD::Yes && !vStateIsBogus(ne.d_validationState)) || ne.d_validationState == vState::Secure) {
-          res = RCode::NXDomain;
-          sttl = ne.d_ttd - d_now.tv_sec;
-          giveNegative = true;
-          cachedState = ne.d_validationState;
-          LOG(prefix<<qname<<": Name '"<<negCacheName<<"' and below, is negatively cached via '"<<ne.d_auth<<"' for another "<<sttl<<" seconds"<<endl);
-          break;
-        }
-      }
-      negCacheName.prependRawLabel(labels.back());
-      labels.pop_back();
-    }
-  }
-
-  if (giveNegative) {
-
-    state = cachedState;
-
-    if (!wasAuthZone && shouldValidate() && state == vState::Indeterminate) {
-      LOG(prefix<<qname<<": got vState::Indeterminate state for records retrieved from the negative cache, validating.."<<endl);
-      computeNegCacheValidationStatus(ne, qname, qtype, res, state, depth);
-
-      if (state != cachedState && vStateIsBogus(state)) {
-        sttl = std::min(sttl, s_maxbogusttl);
-      }
-    }
-
-    // Transplant SOA to the returned packet
-    addTTLModifiedRecords(ne.authoritySOA.records, sttl, ret);
-    if(d_doDNSSEC) {
-      addTTLModifiedRecords(ne.authoritySOA.signatures, sttl, ret);
-      addTTLModifiedRecords(ne.DNSSECRecords.records, sttl, ret);
-      addTTLModifiedRecords(ne.DNSSECRecords.signatures, sttl, ret);
-    }
-
-    LOG(prefix<<qname<<": updating validation state with negative cache content for "<<qname<<" to "<<state<<endl);
-    return true;
-  }
-
-  vector<DNSRecord> cset;
-  bool found=false, expired=false;
-  vector<std::shared_ptr<RRSIGRecordContent>> signatures;
-  vector<std::shared_ptr<DNSRecord>> authorityRecs;
-  uint32_t ttl=0;
-  uint32_t capTTL = std::numeric_limits<uint32_t>::max();
-  bool wasCachedAuth;
-
-  if(g_recCache->get(d_now.tv_sec, sqname, sqt, !wasForwardRecurse && d_requireAuthData, &cset, d_cacheRemote, d_refresh, d_routingTag, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &cachedState, &wasCachedAuth, nullptr, &d_fromAuthIP) > 0) {
-
-    LOG(prefix<<sqname<<": Found cache hit for "<<sqt.toString()<<": ");
-
-    if (!wasAuthZone && shouldValidate() && (wasCachedAuth || wasForwardRecurse) && cachedState == vState::Indeterminate && d_requireAuthData) {
-
-      /* This means we couldn't figure out the state when this entry was cached */
-      vState recordState = getValidationStatus(qname, !signatures.empty(), qtype == QType::DS, depth);
-
-      if (recordState == vState::Secure) {
-        LOG(prefix<<sqname<<": got vState::Indeterminate state from the cache, validating.."<<endl);
-        if (sqt == QType::DNSKEY && sqname == getSigner(signatures)) {
-          cachedState = validateDNSKeys(sqname, cset, signatures, depth);
-        }
-        else {
-          if (sqt == QType::ANY) {
-            std::map<QType, CacheEntry> types;
-            reapRecordsForValidation(types, cset);
-            reapSignaturesForValidation(types, signatures);
-
-            for (const auto& type : types) {
-              vState cachedRecordState;
-              if (type.first == QType::DNSKEY && sqname == getSigner(type.second.signatures)) {
-                cachedRecordState = validateDNSKeys(sqname, type.second.records, type.second.signatures, depth);
-              }
-              else {
-                cachedRecordState = SyncRes::validateRecordsWithSigs(depth, qname, qtype, sqname, type.first, type.second.records, type.second.signatures);
-              }
-              updateDNSSECValidationState(cachedState, cachedRecordState);
-            }
-          }
-          else {
-            cachedState = SyncRes::validateRecordsWithSigs(depth, qname, qtype, sqname, sqt, cset, signatures);
-          }
-        }
-      }
-      else {
-        cachedState = recordState;
-      }
-
-      if (cachedState != vState::Indeterminate) {
-        LOG(prefix<<qname<<": got vState::Indeterminate state from the cache, validation result is "<<cachedState<<endl);
-        if (vStateIsBogus(cachedState)) {
-          capTTL = s_maxbogusttl;
-        }
-        if (sqt != QType::ANY && sqt != QType::ADDR) {
-          updateValidationStatusInCache(sqname, sqt, wasCachedAuth, cachedState);
-        }
-      }
-    }
-
-    for(auto j=cset.cbegin() ; j != cset.cend() ; ++j) {
-
-      LOG(j->d_content->getZoneRepresentation());
-
-      if (j->d_class != QClass::IN) {
-        continue;
-      }
-
-      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<<"] ");
-        found=true;
-      }
-      else {
-        LOG("[expired] ");
-        expired=true;
-      }
-    }
-
-    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.d_content=signature;
-      dr.d_place = DNSResourceRecord::ANSWER;
-      dr.d_class=QClass::IN;
-      ret.push_back(dr);
-    }
-
-    for(const auto& rec : authorityRecs) {
-      DNSRecord dr(*rec);
-      dr.d_ttl=ttl;
-      ret.push_back(dr);
-    }
-
-    LOG(endl);
-    if(found && !expired) {
-      if (!giveNegative)
-        res=0;
-      LOG(prefix<<qname<<": updating validation state with cache content for "<<qname<<" to "<<cachedState<<endl);
-      state = cachedState;
-      return true;
-    }
-    else
-      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)) {
-      state = vState::Secure;
-      return true;
-    }
-  }
-
-  return false;
-}
-
-bool SyncRes::moreSpecificThan(const DNSName& a, const DNSName &b) const
-{
-  return (a.isPartOf(b) && a.countLabels() > b.countLabels());
-}
-
-struct speedOrder
-{
-  bool operator()(const std::pair<DNSName, float> &a, const std::pair<DNSName, float> &b) const
-  {
-    return a.second < b.second;
-  }
-};
-
-inline std::vector<std::pair<DNSName, float>> SyncRes::shuffleInSpeedOrder(NsSet &tnameservers, const string &prefix)
-{
-  std::vector<std::pair<DNSName, float>> rnameservers;
-  rnameservers.reserve(tnameservers.size());
-  for(const auto& tns: tnameservers) {
-    float speed = t_sstorage.nsSpeeds[tns.first].get(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
-      return rnameservers;
-  }
-
-  shuffle(rnameservers.begin(),rnameservers.end(), pdns::dns_random_engine());
-  speedOrder so;
-  stable_sort(rnameservers.begin(),rnameservers.end(), so);
-
-  if(doLog()) {
-    LOG(prefix<<"Nameservers: ");
-    for(auto i=rnameservers.begin();i!=rnameservers.end();++i) {
-      if(i!=rnameservers.begin()) {
-        LOG(", ");
-        if(!((i-rnameservers.begin())%3)) {
-          LOG(endl<<prefix<<"             ");
-        }
-      }
-      LOG(i->first.toLogString()<<"(" << fmtfloat("%0.2f", i->second/1000.0) <<"ms)");
-    }
-    LOG(endl);
-  }
-  return rnameservers;
-}
-
-inline vector<ComboAddress> SyncRes::shuffleForwardSpeed(const vector<ComboAddress> &rnameservers, const string &prefix, const bool wasRd)
-{
-  vector<ComboAddress> nameservers = rnameservers;
-  map<ComboAddress, float> speeds;
-
-  for(const auto& val: nameservers) {
-    float speed;
-    DNSName nsName = DNSName(val.toStringWithPort());
-    speed=t_sstorage.nsSpeeds[nsName].get(d_now);
-    speeds[val]=speed;
-  }
-  shuffle(nameservers.begin(),nameservers.end(), pdns::dns_random_engine());
-  speedOrderCA so(speeds);
-  stable_sort(nameservers.begin(),nameservers.end(), so);
-
-  if(doLog()) {
-    LOG(prefix<<"Nameservers: ");
-    for(vector<ComboAddress>::const_iterator i=nameservers.cbegin();i!=nameservers.cend();++i) {
-      if(i!=nameservers.cbegin()) {
-        LOG(", ");
-        if(!((i-nameservers.cbegin())%3)) {
-          LOG(endl<<prefix<<"             ");
-        }
-      }
-      LOG((wasRd ? string("+") : string("-")) << i->toStringWithPort() <<"(" << fmtfloat("%0.2f", speeds[*i]/1000.0) <<"ms)");
-    }
-    LOG(endl);
-  }
-  return nameservers;
-}
-
-static uint32_t getRRSIGTTL(const time_t now, const std::shared_ptr<RRSIGRecordContent>& rrsig)
-{
-  uint32_t res = 0;
-  if (now < rrsig->d_sigexpire) {
-    res = static_cast<uint32_t>(rrsig->d_sigexpire) - now;
-  }
-  return res;
-}
-
-static const set<QType> nsecTypes = {QType::NSEC, QType::NSEC3};
-
-/* Fills the authoritySOA and DNSSECRecords fields from ne with those found in the records
- *
- * \param records The records to parse for the authority SOA and NSEC(3) records
- * \param ne      The NegCacheEntry to be filled out (will not be cleared, only appended to
- */
-static void harvestNXRecords(const vector<DNSRecord>& records, NegCache::NegCacheEntry& ne, const time_t now, uint32_t* lowestTTL) {
-  for (const auto& rec : records) {
-    if (rec.d_place != DNSResourceRecord::AUTHORITY) {
-      // RFC 4035 section 3.1.3. indicates that NSEC records MUST be placed in
-      // the AUTHORITY section. Section 3.1.1 indicates that that RRSIGs for
-      // records MUST be in the same section as the records they cover.
-      // Hence, we ignore all records outside of the AUTHORITY section.
-      continue;
-    }
-
-    if (rec.d_type == QType::RRSIG) {
-      auto rrsig = getRR<RRSIGRecordContent>(rec);
-      if (rrsig) {
-        if (rrsig->d_type == QType::SOA) {
-          ne.authoritySOA.signatures.push_back(rec);
-          if (lowestTTL && 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)) {
-            *lowestTTL = min(*lowestTTL, rec.d_ttl);
-            *lowestTTL = min(*lowestTTL, getRRSIGTTL(now, rrsig));
-          }
-        }
-      }
-      continue;
-    }
-    if (rec.d_type == QType::SOA) {
-      ne.authoritySOA.records.push_back(rec);
-      if (lowestTTL) {
-        *lowestTTL = min(*lowestTTL, rec.d_ttl);
-      }
-      continue;
-    }
-    if (nsecTypes.count(rec.d_type)) {
-      ne.DNSSECRecords.records.push_back(rec);
-      if (lowestTTL) {
-        *lowestTTL = min(*lowestTTL, rec.d_ttl);
-      }
-      continue;
-    }
-  }
-}
-
-static cspmap_t harvestCSPFromNE(const NegCache::NegCacheEntry& ne)
-{
-  cspmap_t cspmap;
-  for(const auto& rec : ne.DNSSECRecords.signatures) {
-    if(rec.d_type == QType::RRSIG) {
-      auto rrc = getRR<RRSIGRecordContent>(rec);
-      if (rrc) {
-        cspmap[{rec.d_name,rrc->d_type}].signatures.push_back(rrc);
-      }
-    }
-  }
-  for(const auto& rec : ne.DNSSECRecords.records) {
-    cspmap[{rec.d_name, rec.d_type}].records.insert(rec.d_content);
-  }
-  return cspmap;
-}
-
-// TODO remove after processRecords is fixed!
-// Adds the RRSIG for the SOA and the NSEC(3) + RRSIGs to ret
-static void addNXNSECS(vector<DNSRecord>&ret, const vector<DNSRecord>& records)
-{
-  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());
-}
-
-static bool rpzHitShouldReplaceContent(const DNSName& qname, const QType qtype, const std::vector<DNSRecord>& records)
-{
-  if (qtype == QType::CNAME) {
-    return true;
-  }
-
-  for (const auto& record : records) {
-    if (record.d_type == QType::CNAME) {
-      if (auto content = getRR<CNAMERecordContent>(record)) {
-        if (qname == content->getTarget()) {
-          /* we have a CNAME whose target matches the entry we are about to
-             generate, so it will complete the current records, not replace
-             them
-          */
-          return false;
-        }
-      }
-    }
-  }
-
-  return true;
-}
-
-static void removeConflictingRecord(std::vector<DNSRecord>& records, const DNSName& name, const QType dtype)
-{
-  for (auto it = records.begin(); it != records.end(); ) {
-    bool remove = false;
-
-    if (it->d_class == QClass::IN &&
-        (it->d_type == QType::CNAME || dtype == QType::CNAME || it->d_type == dtype) &&
-        it->d_name == name) {
-      remove = true;
-    }
-    else if (it->d_class == QClass::IN &&
-             it->d_type == QType::RRSIG &&
-             it->d_name == name) {
-      if (auto rrc = getRR<RRSIGRecordContent>(*it)) {
-        if (rrc->d_type == QType::CNAME || rrc->d_type == dtype) {
-          /* also remove any RRSIG that could conflict */
-          remove = true;
-        }
-      }
-    }
-
-    if (remove) {
-      it = records.erase(it);
-    }
-    else {
-      ++it;
-    }
-  }
-}
-
-void SyncRes::handlePolicyHit(const std::string& prefix, const DNSName& qname, const QType qtype, std::vector<DNSRecord>& ret, bool& done, int& rcode, unsigned int depth)
-{
-  if (d_pdl && d_pdl->policyHitEventFilter(d_requestor, qname, qtype, d_queryReceivedOverTCP, d_appliedPolicy, d_policyTags, d_discardedPolicies)) {
-    /* reset to no match */
-    d_appliedPolicy = DNSFilterEngine::Policy();
-    return;
-  }
-
-  /* don't account truncate actions for TCP queries, since they are not applied */
-  if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::Truncate || !d_queryReceivedOverTCP) {
-    ++g_stats.policyResults[d_appliedPolicy.d_kind];
-    ++(g_stats.policyHits.lock()->operator[](d_appliedPolicy.getName()));
-  }
-
-  if (d_appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) {
-    LOG(prefix << qname << "|" << qtype << d_appliedPolicy.getLogString() << endl);
-  }
-
-  switch (d_appliedPolicy.d_kind) {
-
-  case DNSFilterEngine::PolicyKind::NoAction:
-      return;
-
-  case DNSFilterEngine::PolicyKind::Drop:
-    ++g_stats.policyDrops;
-    throw ImmediateQueryDropException();
-
-  case DNSFilterEngine::PolicyKind::NXDOMAIN:
-    ret.clear();
-    rcode = RCode::NXDomain;
-    done = true;
-    return;
-
-  case DNSFilterEngine::PolicyKind::NODATA:
-    ret.clear();
-    rcode = RCode::NoError;
-    done = true;
-    return;
-
-  case DNSFilterEngine::PolicyKind::Truncate:
-    if (!d_queryReceivedOverTCP) {
-      ret.clear();
-      rcode = RCode::NoError;
-      throw SendTruncatedAnswerException();
-    }
-    return;
-
-  case DNSFilterEngine::PolicyKind::Custom:
-    {
-      if (rpzHitShouldReplaceContent(qname, qtype, ret)) {
-        ret.clear();
-      }
-
-      rcode = RCode::NoError;
-      done = true;
-      auto spoofed = d_appliedPolicy.getCustomRecords(qname, qtype.getCode());
-      for (auto& dr : spoofed) {
-        removeConflictingRecord(ret, dr.d_name, dr.d_type);
-      }
-
-      for (auto& dr : spoofed) {
-        ret.push_back(dr);
-
-        if (dr.d_name == qname && dr.d_type == QType::CNAME && qtype != QType::CNAME) {
-          if (auto content = getRR<CNAMERecordContent>(dr)) {
-            vState newTargetState = vState::Indeterminate;
-            handleNewTarget(prefix, qname, content->getTarget(), qtype.getCode(), ret, rcode, depth, {}, newTargetState);
-          }
-        }
-      }
-    }
-  }
-}
-
-bool SyncRes::nameserversBlockedByRPZ(const DNSFilterEngine& dfe, const NsSet& nameservers)
-{
-  /* we skip RPZ processing if:
-     - it was disabled (d_wantsRPZ is false) ;
-     - we already got a RPZ hit (d_appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) since
-     the only way we can get back here is that it was a 'pass-thru' (NoAction) meaning that we should not
-     process any further RPZ rules. 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);
-      if (match) {
-        mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
-        if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
-          LOG(", however nameserver "<<ns.first<<" was blocked by RPZ policy '"<<d_appliedPolicy.getName()<<"'"<<endl);
-          return true;
-        }
-      }
-
-      // Traverse all IP addresses for this NS to see if they have an RPN NSIP policy
-      for (auto const &address : ns.second.first) {
-        match = dfe.getProcessingPolicy(address, d_discardedPolicies, d_appliedPolicy);
-        if (match) {
-          mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
-          if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
-            LOG(", however nameserver "<<ns.first<<" IP address "<<address.toString()<<" was blocked by RPZ policy '"<<d_appliedPolicy.getName()<<"'"<<endl);
-            return true;
-          }
-        }
-      }
-    }
-  }
-  return false;
-}
-
-bool SyncRes::nameserverIPBlockedByRPZ(const DNSFilterEngine& dfe, const ComboAddress& remoteIP)
-{
-  /* we skip RPZ processing if:
-     - it was disabled (d_wantsRPZ is false) ;
-     - we already got a RPZ hit (d_appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) since
-     the only way we can get back here is that it was a 'pass-thru' (NoAction) meaning that we should not
-     process any further RPZ rules. Except that we need to process rules of higher priority..
-  */
-  if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
-    bool match = dfe.getProcessingPolicy(remoteIP, d_discardedPolicies, d_appliedPolicy);
-    if (match) {
-      mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
-      if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) {
-        LOG(" (blocked by RPZ policy '" + d_appliedPolicy.getName() + "')");
-        return true;
-      }
-    }
-  }
-  return false;
-}
-
-vector<ComboAddress> SyncRes::retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, std::vector<std::pair<DNSName, float>>::const_iterator& tns, const unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<std::pair<DNSName, float>>& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& flawedNSSet, bool cacheOnly, unsigned int &nretrieveAddressesForNS)
-{
-  vector<ComboAddress> result;
-
-  size_t nonresolvingfails = 0;
-  if (!tns->first.empty()) {
-    if (s_nonresolvingnsmaxfails > 0) {
-      nonresolvingfails = s_nonresolving.lock()->value(tns->first);
-      if (nonresolvingfails >= s_nonresolvingnsmaxfails) {
-        LOG(prefix<<qname<<": NS "<<tns->first<< " in non-resolving map, skipping"<<endl);
-        return result;
-      }
-    }
-
-    LOG(prefix<<qname<<": Trying to resolve NS '"<<tns->first<< "' ("<<1+tns-rnameservers.begin()<<"/"<<(unsigned int)rnameservers.size()<<")"<<endl);
-    const unsigned int oldOutQueries = d_outqueries;
-    try {
-      result = getAddrs(tns->first, depth, beenthere, cacheOnly, nretrieveAddressesForNS);
-    }
-    // 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)) {
-          s_nonresolving.lock()->incr(tns->first, d_now);
-        }
-      }
-      throw ex;
-    }
-    if (s_nonresolvingnsmaxfails > 0 && d_outqueries > oldOutQueries) {
-      if (result.empty()) {
-        auto dontThrottleNames = g_dontThrottleNames.getLocal();
-        if (!dontThrottleNames->check(tns->first)) {
-          s_nonresolving.lock()->incr(tns->first, d_now);
-        }
-      }
-      else if (nonresolvingfails > 0) {
-        // Succeeding resolve, clear memory of recent failures
-        s_nonresolving.lock()->clear(tns->first);
-      }
-    }
-    pierceDontQuery=false;
-  }
-  else {
-    LOG(prefix<<qname<<": Domain has hardcoded nameserver");
-
-    if(nameservers[tns->first].first.size() > 1) {
-      LOG("s");
-    }
-    LOG(endl);
-
-    sendRDQuery = nameservers[tns->first].second;
-    result = shuffleForwardSpeed(nameservers[tns->first].first, doLog() ? (prefix+qname.toString()+": ") : string(), sendRDQuery);
-    pierceDontQuery=true;
-  }
-  return result;
-}
-
-bool SyncRes::throttledOrBlocked(const std::string& prefix, const ComboAddress& remoteIP, const DNSName& qname, const QType qtype, bool pierceDontQuery)
-{
-  if(t_sstorage.throttle.shouldThrottle(d_now.tv_sec, std::make_tuple(remoteIP, g_rootdnsname, 0))) {
-    LOG(prefix<<qname<<": server throttled "<<endl);
-    s_throttledqueries++; d_throttledqueries++;
-    return true;
-  }
-  else if(t_sstorage.throttle.shouldThrottle(d_now.tv_sec, std::make_tuple(remoteIP, qname, qtype.getCode()))) {
-    LOG(prefix<<qname<<": query throttled "<<remoteIP.toString()<<", "<<qname<<"; "<<qtype<<endl);
-    s_throttledqueries++; d_throttledqueries++;
-    return true;
-  }
-  else 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()) {
-      LOG(prefix<<qname<<": not sending query to " << remoteIP.toString() << ", blocked by 'dont-query' setting" << endl);
-      s_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);
-        s_dontqueries++;
-        return true;
-      } else {
-        LOG(prefix<<qname<<": sending query to " << remoteIP.toString() << ", blocked by 'dont-query' but a forwarding/auth case" << endl);
-      }
-    }
-  }
-  return false;
-}
-
-bool SyncRes::validationEnabled() const
-{
-  return g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate;
-}
-
-uint32_t SyncRes::computeLowestTTD(const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures, uint32_t signaturesTTL, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs) const
-{
-  uint32_t lowestTTD = std::numeric_limits<uint32_t>::max();
-  for (const auto& record : records) {
-    lowestTTD = min(lowestTTD, record.d_ttl);
-  }
-
-  /* even if it was not requested for that request (Process, and neither AD nor DO set),
-     it might be requested at a later time so we need to be careful with the TTL. */
-  if (validationEnabled() && !signatures.empty()) {
-    /* if we are validating, we don't want to cache records after their signatures expire. */
-    /* records TTL are now TTD, let's add 'now' to the signatures lowest TTL */
-    lowestTTD = min(lowestTTD, static_cast<uint32_t>(signaturesTTL + d_now.tv_sec));
-
-    for(const auto& sig : signatures) {
-      if (isRRSIGNotExpired(d_now.tv_sec, sig)) {
-        // we don't decrement d_sigexpire by 'now' because we actually want a TTD, not a TTL */
-        lowestTTD = min(lowestTTD, static_cast<uint32_t>(sig->d_sigexpire));
-      }
-    }
-  }
-
-  for (const auto& entry : authorityRecs) {
-    /* be careful, this is still a TTL here */
-    lowestTTD = min(lowestTTD, static_cast<uint32_t>(entry->d_ttl + d_now.tv_sec));
-
-    if (entry->d_type == QType::RRSIG && validationEnabled()) {
-      auto rrsig = getRR<RRSIGRecordContent>(*entry);
-      if (rrsig) {
-        if (isRRSIGNotExpired(d_now.tv_sec, rrsig)) {
-          // we don't decrement d_sigexpire by 'now' because we actually want a TTD, not a TTL */
-          lowestTTD = min(lowestTTD, static_cast<uint32_t>(rrsig->d_sigexpire));
-        }
-      }
-    }
-  }
-
-  return lowestTTD;
-}
-
-void SyncRes::updateValidationState(vState& state, const vState stateUpdate)
-{
-  LOG(d_prefix<<"validation state was "<<state<<", state update is "<<stateUpdate);
-  updateDNSSECValidationState(state, stateUpdate);
-  LOG(", validation state is now "<<state<<endl);
-}
-
-vState SyncRes::getTA(const DNSName& zone, dsmap_t& ds)
-{
-  auto luaLocal = g_luaconfs.getLocal();
-
-  if (luaLocal->dsAnchors.empty()) {
-    LOG(d_prefix<<": No trust anchors configured, everything is Insecure"<<endl);
-    /* We have no TA, everything is insecure */
-    return vState::Insecure;
-  }
-
-  std::string reason;
-  if (haveNegativeTrustAnchor(luaLocal->negAnchors, zone, reason)) {
-    LOG(d_prefix<<": got NTA for '"<<zone<<"'"<<endl);
-    return vState::NTA;
-  }
-
-  if (getTrustAnchor(luaLocal->dsAnchors, zone, ds)) {
-    LOG(d_prefix<<": got TA for '"<<zone<<"'"<<endl);
-    return vState::TA;
-  }
-  else {
-    LOG(d_prefix<<": no TA found for '"<<zone<<"' among "<< luaLocal->dsAnchors.size()<<endl);
-  }
-
-  if (zone.isRoot()) {
-    /* No TA for the root */
-    return vState::Insecure;
-  }
-
-  return vState::Indeterminate;
-}
-
-static size_t countSupportedDS(const dsmap_t& dsmap)
-{
-  size_t count = 0;
-
-  for (const auto& ds : dsmap) {
-    if (isSupportedDS(ds)) {
-      count++;
-    }
-  }
-
-  return count;
-}
-
-void SyncRes::initZoneCutsFromTA(const DNSName& from)
-{
-  DNSName zone(from);
-  do {
-    dsmap_t ds;
-    vState result = getTA(zone, ds);
-    if (result != vState::Indeterminate) {
-      if (result == vState::TA) {
-        if (countSupportedDS(ds) == 0) {
-          ds.clear();
-          result = vState::Insecure;
-        }
-        else {
-          result = vState::Secure;
-        }
-      }
-      else if (result == vState::NTA) {
-        result = vState::Insecure;
-      }
-
-      d_cutStates[zone] = result;
-    }
-  }
-  while (zone.chopOff());
-}
-
-vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsigned int depth, bool bogusOnNXD, bool* foundCut)
-{
-  vState result = getTA(zone, ds);
-
-  if (result != vState::Indeterminate || taOnly) {
-    if (foundCut) {
-      *foundCut = (result != vState::Indeterminate);
-    }
-
-    if (result == vState::TA) {
-      if (countSupportedDS(ds) == 0) {
-        ds.clear();
-        result = vState::Insecure;
-      }
-      else {
-        result = vState::Secure;
-      }
-    }
-    else if (result == vState::NTA) {
-      result = vState::Insecure;
-    }
-
-    return result;
-  }
-
-  std::set<GetBestNSAnswer> beenthere;
-  std::vector<DNSRecord> dsrecords;
-
-  vState state = vState::Indeterminate;
-  const bool oldCacheOnly = setCacheOnly(false);
-  int rcode = doResolve(zone, QType::DS, dsrecords, depth + 1, beenthere, state);
-  setCacheOnly(oldCacheOnly);
-
-  if (rcode == RCode::ServFail) {
-    throw ImmediateServFailException("Server Failure while retrieving DS records for " + zone.toLogString());
-  }
-
-  if (rcode == RCode::NoError || (rcode == RCode::NXDomain && !bogusOnNXD)) {
-    uint8_t bestDigestType = 0;
-
-    bool gotCNAME = false;
-    for (const auto& record : dsrecords) {
-      if (record.d_type == QType::DS) {
-        const auto dscontent = getRR<DSRecordContent>(record);
-        if (dscontent && isSupportedDS(*dscontent)) {
-          // 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);
-        }
-      }
-      else if (record.d_type == QType::CNAME && record.d_name == zone) {
-        gotCNAME = true;
-      }
-    }
-
-    /* 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;
-      }
-    }
-
-    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 */
-
-          if (foundCut) {
-            *foundCut = false;
-          }
-          return state;
-        }
-
-        d_cutStates[zone] = state == vState::Secure ? vState::Insecure : state;
-        /* delegation with no DS, might be Secure -> Insecure */
-        if (foundCut) {
-          *foundCut = true;
-        }
-
-        /* 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 state == vState::Secure ? vState::Insecure : state;
-      } else {
-        /* we have a DS */
-        d_cutStates[zone] = state;
-        if (foundCut) {
-          *foundCut = true;
-        }
-      }
-    }
-
-    return state;
-  }
-
-  LOG(d_prefix<<": returning Bogus state from "<<__func__<<"("<<zone<<")"<<endl);
-  return vState::BogusUnableToGetDSs;
-}
-
-vState SyncRes::getValidationStatus(const DNSName& name, bool wouldBeValid, bool typeIsDS, unsigned int depth)
-{
-  vState result = vState::Indeterminate;
-
-  if (!shouldValidate()) {
-    return result;
-  }
-
-  DNSName subdomain(name);
-  if (typeIsDS) {
-    subdomain.chopOff();
-  }
-
-  {
-    const auto& it = d_cutStates.find(subdomain);
-    if (it != d_cutStates.cend()) {
-      LOG(d_prefix<<": got status "<<it->second<<" for name "<<subdomain<<endl);
-      return it->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;
-      if (vStateIsBogus(result) || result == vState::Insecure) {
-        LOG(d_prefix<<": got status "<<result<<" for name "<<best<<endl);
-        return result;
-      }
-      break;
-    }
-  }
-
-  /* by now we have the best match, it's likely Secure (otherwise we would not be there)
-     but we don't know if we missed a cut (or several).
-     We could see if we have DS (or denial of) in cache but let's not worry for now,
-     we will if we don't have a signature, or if the signer doesn't match what we expect */
-  if (!wouldBeValid && best != subdomain) {
-    /* no signatures or Bogus, we likely missed a cut, let's try to find it */
-    LOG(d_prefix<<": 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();
-
-    while (!labelsToAdd.empty()) {
-
-      ds.prependRawLabel(labelsToAdd.back());
-      labelsToAdd.pop_back();
-      LOG(d_prefix<<": - Looking for a DS at "<<ds<<endl);
-
-      bool foundCut = false;
-      dsmap_t results;
-      vState dsState = getDSRecords(ds, results, false, depth, false, &foundCut);
-
-      if (foundCut) {
-        LOG(d_prefix<<": - Found cut at "<<ds<<endl);
-        LOG(d_prefix<<": New state for "<<ds<<" is "<<dsState<<endl);
-        d_cutStates[ds] = dsState;
-
-        if (dsState != vState::Secure) {
-          return dsState;
-        }
-      }
-    }
-
-    /* we did not miss a cut, good luck */
-    return result;
-  }
-
-#if 0
-  /* we don't need this, we actually do the right thing later */
-  DNSName signer = getSigner(signatures);
-
-  if (!signer.empty() && name.isPartOf(signer)) {
-    if (signer == best) {
-      return result;
-    }
-    /* the zone cut is not the one we expected,
-       this is fine because we will retrieve the needed DNSKEYs and DSs
-       later, and even go Insecure if we missed a cut to Insecure (no DS)
-       and the signatures do not validate (we should not go Bogus in that
-       case) */
-  }
-  /* something is not right, but let's not worry about that for now.. */
-#endif
-
-  return result;
-}
-
-vState SyncRes::validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord>& dnskeys, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures, unsigned int depth)
-{
-  dsmap_t ds;
-  if (signatures.empty()) {
-    LOG(d_prefix<<": we have "<<std::to_string(dnskeys.size())<<" DNSKEYs but no signature, going Bogus!"<<endl);
-    return vState::BogusNoRRSIG;
-  }
-
-  DNSName signer = getSigner(signatures);
-
-  if (!signer.empty() && zone.isPartOf(signer)) {
-    vState state = getDSRecords(signer, ds, false, depth);
-
-    if (state != vState::Secure) {
-      return state;
-    }
-  }
-  else {
-    LOG(d_prefix<<": we have "<<std::to_string(dnskeys.size())<<" DNSKEYs but the zone ("<<zone<<") is not part of the signer ("<<signer<<"), check that we did not miss a zone cut"<<endl);
-    /* try again to get the missed cuts, harder this time */
-    auto zState = getValidationStatus(zone, false, false, depth);
-    if (zState == vState::Secure) {
-      /* too bad */
-      LOG(d_prefix<<": 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;
-    }
-  }
-
-  skeyset_t tentativeKeys;
-  sortedRecords_t toSign;
-
-  for (const auto& dnskey : dnskeys) {
-    if (dnskey.d_type == QType::DNSKEY) {
-      auto content = getRR<DNSKEYRecordContent>(dnskey);
-      if (content) {
-        tentativeKeys.insert(content);
-        toSign.insert(content);
-      }
-    }
-  }
-
-  LOG(d_prefix<<": trying to validate "<<std::to_string(tentativeKeys.size())<<" DNSKEYs with "<<std::to_string(ds.size())<<" DS"<<endl);
-  skeyset_t validatedKeys;
-  auto state = validateDNSKeysAgainstDS(d_now.tv_sec, zone, ds, tentativeKeys, toSign, signatures, validatedKeys);
-
-  LOG(d_prefix<<": we now have "<<std::to_string(validatedKeys.size())<<" DNSKEYs"<<endl);
-
-  /* if we found at least one valid RRSIG covering the set,
-     all tentative keys are validated keys. Otherwise it means
-     we haven't found at least one DNSKEY and a matching RRSIG
-     covering this set, this looks Bogus. */
-  if (validatedKeys.size() != tentativeKeys.size()) {
-    LOG(d_prefix<<": let's check whether we missed a zone cut before returning a Bogus state from "<<__func__<<"("<<zone<<")"<<endl);
-    /* try again to get the missed cuts, harder this time */
-    auto zState = getValidationStatus(zone, false, false, depth);
-    if (zState == vState::Secure) {
-      /* too bad */
-      LOG(d_prefix<<": after checking the zone cuts we are still in a Secure zone, returning Bogus state from "<<__func__<<"("<<zone<<")"<<endl);
-      return state;
-    }
-    else {
-      return zState;
-    }
-  }
-
-  return state;
-}
-
-vState SyncRes::getDNSKeys(const DNSName& signer, skeyset_t& keys, unsigned int depth)
-{
-  std::vector<DNSRecord> records;
-  std::set<GetBestNSAnswer> beenthere;
-  LOG(d_prefix<<"Retrieving DNSKeys for "<<signer<<endl);
-
-  vState state = vState::Indeterminate;
-  const bool oldCacheOnly = setCacheOnly(false);
-  int rcode = doResolve(signer, QType::DNSKEY, records, depth + 1, beenthere, state);
-  setCacheOnly(oldCacheOnly);
-
-  if (rcode == RCode::ServFail) {
-    throw ImmediateServFailException("Server Failure while retrieving DNSKEY records for " + signer.toLogString());
-  }
-
-  if (rcode == RCode::NoError) {
-    if (state == vState::Secure) {
-      for (const auto& key : records) {
-        if (key.d_type == QType::DNSKEY) {
-          auto content = getRR<DNSKEYRecordContent>(key);
-          if (content) {
-            keys.insert(content);
-          }
-        }
-      }
-    }
-    LOG(d_prefix<<"Retrieved "<<keys.size()<<" DNSKeys for "<<signer<<", state is "<<state<<endl);
-    return state;
-  }
-
-  if (state == vState::Insecure) {
-    return state;
-  }
-
-  LOG(d_prefix<<"Returning Bogus state from "<<__func__<<"("<<signer<<")"<<endl);
-  return vState::BogusUnableToGetDNSKEYs;
-}
-
-vState SyncRes::validateRecordsWithSigs(unsigned int depth, const DNSName& qname, const QType qtype, const DNSName& name, const QType type, const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures)
-{
-  skeyset_t keys;
-  if (signatures.empty()) {
-    LOG(d_prefix<<"Bogus!"<<endl);
-    return vState::BogusNoRRSIG;
-  }
-
-  const DNSName signer = getSigner(signatures);
-  bool dsFailed = false;
-  if (!signer.empty() && name.isPartOf(signer)) {
-    vState state = vState::Secure;
-
-    if ((qtype == QType::DNSKEY || qtype == QType::DS) && signer == qname) {
-      /* we are already retrieving those keys, sorry */
-      if (type == QType::DS && signer == name && !signer.isRoot()) {
-        /* Unless we are getting the DS of the root zone, we should never see a
-           DS (or a denial of a DS) signed by the DS itself, since we should be
-           requesting it from the parent zone. Something is very wrong */
-        LOG(d_prefix<<"The DS for "<<qname<<" is signed by itself"<<endl);
-        state = vState::BogusSelfSignedDS;
-        dsFailed = true;
-      }
-      else if (qtype == QType::DS && signer == qname && !signer.isRoot()) {
-        if (type == QType::SOA || type == QType::NSEC || type == QType::NSEC3) {
-        /* if we are trying to validate the DS or more likely NSEC(3)s proving that it does not exist, we have a problem.
-           In that case let's go Bogus (we will check later if we missed a cut)
-        */
-          state = vState::BogusSelfSignedDS;
-          dsFailed = true;
-        }
-        else if (type == QType::CNAME) {
-          state = vState::BogusUnableToGetDSs;
-          dsFailed = true;
-        }
-      }
-      else if (qtype == QType::DNSKEY && signer == qname) {
-        /* that actually does happen when a server returns NS records in authority
-           along with the DNSKEY, leading us to trying to validate the RRSIGs for
-           the NS with the DNSKEY that we are about to process. */
-        if ((name == signer && type == QType::NSEC) || type == QType::NSEC3) {
-          /* if we are trying to validate the DNSKEY (should not happen here),
-             or more likely NSEC(3)s proving that it does not exist, we have a problem.
-             In that case let's see if the DS does exist, and if it does let's go Bogus
-          */
-          dsmap_t results;
-          vState dsState = getDSRecords(signer, results, false, depth, true);
-          if (vStateIsBogus(dsState) || dsState == vState::Insecure) {
-            state = dsState;
-            if (vStateIsBogus(dsState)) {
-              dsFailed = true;
-            }
-          }
-          else {
-            LOG(d_prefix<<"Unable to get the DS for "<<signer<<endl);
-            state = vState::BogusUnableToGetDNSKEYs;
-            dsFailed = true;
-          }
-        }
-        else {
-          /* return immediately since looking at the cuts is not going to change the
-             fact that we are looking at a signature done with the key we are trying to
-             obtain */
-          LOG(d_prefix<<"we are looking at a signature done with the key we are trying to obtain "<<signer<<endl);
-          return vState::Indeterminate;
-        }
-      }
-    }
-    if (state == vState::Secure) {
-      LOG(d_prefix<<"retrieving the DNSKEYs for "<<signer<<endl);
-      state = getDNSKeys(signer, keys, depth);
-    }
-
-    if (state != vState::Secure) {
-      if (!vStateIsBogus(state)) {
-        return state;
-      }
-      /* try again to get the missed cuts, harder this time */
-      LOG(d_prefix<<"checking whether we missed a zone cut for "<<signer<<" before returning a Bogus state for "<<name<<"|"<<type.toString()<<endl);
-      auto zState = getValidationStatus(signer, false, dsFailed, depth);
-      if (zState == vState::Secure) {
-        /* too bad */
-        LOG(d_prefix<<"we are still in a Secure zone, returning "<<vStateToString(state)<<endl);
-        return state;
-      }
-      else {
-        return zState;
-      }
-    }
-  }
-
-  sortedRecords_t recordcontents;
-  for (const auto& record : records) {
-    recordcontents.insert(record.d_content);
-  }
-
-  LOG(d_prefix<<"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, false);
-  if (state == vState::Secure) {
-    LOG(d_prefix<<"Secure!"<<endl);
-    return vState::Secure;
-  }
-
-  LOG(d_prefix<<vStateToString(state)<<"!"<<endl);
-  /* try again to get the missed cuts, harder this time */
-  auto zState = getValidationStatus(name, false, type == QType::DS, depth);
-  LOG(d_prefix<<"checking whether we missed a zone cut before returning a Bogus state"<<endl);
-  if (zState == vState::Secure) {
-    /* too bad */
-    LOG(d_prefix<<"we are still in a Secure zone, returning "<<vStateToString(state)<<endl);
-    return state;
-  }
-  else {
-    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.
-   This is unfortunately needed to deal with very crappy so-called DNS servers */
-void SyncRes::fixupAnswer(const std::string& prefix, LWResult& lwr, const DNSName& qname, const QType qtype, const DNSName& auth, bool wasForwarded, bool rdQuery)
-{
-  const bool wasForwardRecurse = wasForwarded && rdQuery;
-
-  if (wasForwardRecurse || lwr.d_aabit) {
-    /* easy */
-    return;
-  }
-
-  for (const auto& rec : lwr.d_records) {
-
-    if (rec.d_type == QType::OPT) {
-      continue;
-    }
-
-    if (rec.d_class != QClass::IN) {
-      continue;
-    }
-
-    if (rec.d_type == QType::ANY) {
-      continue;
-    }
-
-    if (rec.d_place == DNSResourceRecord::ANSWER && (rec.d_type == qtype || rec.d_type == QType::CNAME || qtype == QType::ANY) && rec.d_name == qname && rec.d_name.isPartOf(auth)) {
-      /* This is clearly an answer to the question we were asking, from an authoritative server that is allowed to send it.
-         We are going to assume this server is broken and does not know it should set the AA bit, even though it is DNS 101 */
-      LOG(prefix<<"Received a record for "<<rec.d_name<<"|"<<DNSRecordContent::NumberToType(rec.d_type)<<" in the answer section from "<<auth<<", without the AA bit set. Assuming this server is clueless and setting the AA bit."<<endl);
-      lwr.d_aabit = true;
-      return;
-    }
-
-    if (rec.d_place != DNSResourceRecord::ANSWER) {
-      /* we have scanned all the records in the answer section, if any, we are done */
-      return;
-    }
-  }
-}
-
-static void allowAdditionalEntry(std::unordered_set<DNSName>& allowedAdditionals, const DNSRecord& rec)
-{
-  switch(rec.d_type) {
-  case QType::MX:
-    if (auto mxContent = getRR<MXRecordContent>(rec)) {
-      allowedAdditionals.insert(mxContent->d_mxname);
-    }
-    break;
-  case QType::NS:
-    if (auto nsContent = getRR<NSRecordContent>(rec)) {
-      allowedAdditionals.insert(nsContent->getNS());
-    }
-    break;
-  case QType::SRV:
-    if (auto srvContent = getRR<SRVRecordContent>(rec)) {
-      allowedAdditionals.insert(srvContent->d_target);
-    }
-    break;
-  case QType::SVCB: /* fall-through */
-  case QType::HTTPS:
-    if (auto svcbContent = getRR<SVCBBaseRecordContent>(rec)) {
-      if (svcbContent->getPriority() > 0) {
-        DNSName target = svcbContent->getTarget();
-        if (target.isRoot()) {
-          target = rec.d_name;
-        }
-        allowedAdditionals.insert(target);
-      }
-      else {
-        // FIXME: Alias mode not implemented yet
-      }
-    }
-    break;
-  case QType::NAPTR:
-    if (auto naptrContent = getRR<NAPTRRecordContent>(rec)) {
-      auto flags = naptrContent->getFlags();
-      toLowerInPlace(flags);
-      if (flags.find('a') != string::npos || flags.find('s') != string::npos) {
-        allowedAdditionals.insert(naptrContent->getReplacement());
-      }
-    }
-    break;
-  default:
-    break;
-  }
-}
-
-void SyncRes::sanitizeRecords(const std::string& prefix, LWResult& lwr, const DNSName& qname, const QType qtype, const DNSName& auth, bool wasForwarded, bool rdQuery)
-{
-  const bool wasForwardRecurse = wasForwarded && rdQuery;
-  /* list of names for which we will allow A and AAAA records in the additional section
-     to remain */
-  std::unordered_set<DNSName> allowedAdditionals = { qname };
-  bool haveAnswers = false;
-  bool isNXDomain = false;
-  bool isNXQType = false;
-
-  for(auto rec = lwr.d_records.begin(); rec != lwr.d_records.end(); ) {
-
-    if (rec->d_type == QType::OPT) {
-      ++rec;
-      continue;
-    }
-
-    if (rec->d_class != QClass::IN) {
-      LOG(prefix<<"Removing non internet-classed data received from "<<auth<<endl);
-      rec = lwr.d_records.erase(rec);
-      continue;
-    }
-
-    if (rec->d_type == QType::ANY) {
-      LOG(prefix<<"Removing 'ANY'-typed data received from "<<auth<<endl);
-      rec = lwr.d_records.erase(rec);
-      continue;
-    }
-
-    if (!rec->d_name.isPartOf(auth)) {
-      LOG(prefix<<"Removing record '"<<rec->d_name<<"|"<<DNSRecordContent::NumberToType(rec->d_type)<<"|"<<rec->d_content->getZoneRepresentation()<<"' in the "<<(int)rec->d_place<<" section received from "<<auth<<endl);
-      rec = lwr.d_records.erase(rec);
-      continue;
-    }
-
-    /* dealing with the records in answer */
-    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)) {
-        LOG(prefix<<"Removing record '"<<rec->d_name<<"|"<<DNSRecordContent::NumberToType(rec->d_type)<<"|"<<rec->d_content->getZoneRepresentation()<<"' in the answer section without the AA bit set received from "<<auth<<endl);
-        rec = lwr.d_records.erase(rec);
-        continue;
-      }
-    }
-
-    if (rec->d_type == QType::DNAME && (rec->d_place != DNSResourceRecord::ANSWER || !qname.isPartOf(rec->d_name))) {
-      LOG(prefix<<"Removing invalid DNAME record '"<<rec->d_name<<"|"<<DNSRecordContent::NumberToType(rec->d_type)<<"|"<<rec->d_content->getZoneRepresentation()<<"' in the "<<(int)rec->d_place<<" section received from "<<auth<<endl);
-      rec = lwr.d_records.erase(rec);
-      continue;
-    }
-
-    if (rec->d_place == DNSResourceRecord::ANSWER && (qtype != QType::ANY && rec->d_type != qtype.getCode() && s_redirectionQTypes.count(rec->d_type) == 0 && rec->d_type != QType::SOA && rec->d_type != QType::RRSIG)) {
-      LOG(prefix<<"Removing irrelevant record '"<<rec->d_name<<"|"<<DNSRecordContent::NumberToType(rec->d_type)<<"|"<<rec->d_content->getZoneRepresentation()<<"' in the ANSWER section received from "<<auth<<endl);
-      rec = lwr.d_records.erase(rec);
-      continue;
-    }
-
-    if (rec->d_place == DNSResourceRecord::ANSWER && !haveAnswers) {
-      haveAnswers = true;
-    }
-
-    if (rec->d_place == DNSResourceRecord::ANSWER) {
-      allowAdditionalEntry(allowedAdditionals, *rec);
-    }
-
-    /* dealing with the records in authority */
-    if (rec->d_place == DNSResourceRecord::AUTHORITY && rec->d_type != QType::NS && rec->d_type != QType::DS && rec->d_type != QType::SOA && rec->d_type != QType::RRSIG && rec->d_type != QType::NSEC && rec->d_type != QType::NSEC3) {
-      LOG(prefix<<"Removing irrelevant record '"<<rec->d_name<<"|"<<DNSRecordContent::NumberToType(rec->d_type)<<"|"<<rec->d_content->getZoneRepresentation()<<"' in the AUTHORITY section received from "<<auth<<endl);
-      rec = lwr.d_records.erase(rec);
-      continue;
-    }
-
-    if (rec->d_place == DNSResourceRecord::AUTHORITY && rec->d_type == QType::SOA) {
-      if (!qname.isPartOf(rec->d_name)) {
-        LOG(prefix<<"Removing irrelevant SOA record '"<<rec->d_name<<"|"<<rec->d_content->getZoneRepresentation()<<"' in the AUTHORITY section received from "<<auth<<endl);
-        rec = lwr.d_records.erase(rec);
-        continue;
-      }
-
-      if (!(lwr.d_aabit || wasForwardRecurse)) {
-        LOG(prefix<<"Removing irrelevant record '"<<rec->d_name<<"|"<<DNSRecordContent::NumberToType(rec->d_type)<<"|"<<rec->d_content->getZoneRepresentation()<<"' in the AUTHORITY section received from "<<auth<<endl);
-        rec = lwr.d_records.erase(rec);
-        continue;
-      }
-
-      if (!haveAnswers) {
-        if (lwr.d_rcode == RCode::NXDomain) {
-          isNXDomain = true;
-        }
-        else if (lwr.d_rcode == RCode::NoError) {
-          isNXQType = true;
-        }
-      }
-    }
-
-    if (rec->d_place == DNSResourceRecord::AUTHORITY && rec->d_type == QType::NS && (isNXDomain || isNXQType)) {
-      /*
-       * We don't want to pick up NS records in AUTHORITY and their ADDITIONAL sections of NXDomain answers
-       * because they are somewhat easy to insert into a large, fragmented UDP response
-       * for an off-path attacker by injecting spoofed UDP fragments. So do not add these to allowedAdditionals.
-       */
-      LOG(prefix<<"Removing NS record '"<<rec->d_name<<"|"<<DNSRecordContent::NumberToType(rec->d_type)<<"|"<<rec->d_content->getZoneRepresentation()<<"' in the "<<(int)rec->d_place<<" section of a "<<(isNXDomain ? "NXD" : "NXQTYPE")<<" response received from "<<auth<<endl);
-      rec = lwr.d_records.erase(rec);
-      continue;
-    }
-
-    if (rec->d_place == DNSResourceRecord::AUTHORITY && rec->d_type == QType::NS && !d_updatingRootNS && rec->d_name == g_rootdnsname) {
-      /*
-       * We don't want to pick up root NS records in AUTHORITY and their associated ADDITIONAL sections of random queries.
-       * So don't add them to allowedAdditionals.
-       */
-      LOG(prefix<<"Removing NS record '"<<rec->d_name<<"|"<<DNSRecordContent::NumberToType(rec->d_type)<<"|"<<rec->d_content->getZoneRepresentation()<<"' in the "<<(int)rec->d_place<<" section of a response received from "<<auth<<endl);
-      rec = lwr.d_records.erase(rec);
-      continue;
-    }
-
-    if (rec->d_place == DNSResourceRecord::AUTHORITY && rec->d_type == QType::NS) {
-      allowAdditionalEntry(allowedAdditionals, *rec);
-    }
-
-    /* dealing with the records in additional */
-    if (rec->d_place == DNSResourceRecord::ADDITIONAL && rec->d_type != QType::A && rec->d_type != QType::AAAA && rec->d_type != QType::RRSIG) {
-      LOG(prefix<<"Removing irrelevant record '"<<rec->d_name<<"|"<<DNSRecordContent::NumberToType(rec->d_type)<<"|"<<rec->d_content->getZoneRepresentation()<<"' in the ADDITIONAL section received from "<<auth<<endl);
-      rec = lwr.d_records.erase(rec);
-      continue;
-    }
-
-    if (rec->d_place == DNSResourceRecord::ADDITIONAL && allowedAdditionals.count(rec->d_name) == 0) {
-      LOG(prefix<<"Removing irrelevant additional record '"<<rec->d_name<<"|"<<DNSRecordContent::NumberToType(rec->d_type)<<"|"<<rec->d_content->getZoneRepresentation()<<"' in the ADDITIONAL section received from "<<auth<<endl);
-      rec = lwr.d_records.erase(rec);
-      continue;
-    }
-
-    ++rec;
-  }
-}
-
-RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, 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)
-{
-  bool wasForwardRecurse = wasForwarded && rdQuery;
-  tcache_t tcache;
-
-  string prefix;
-  if(doLog()) {
-    prefix=d_prefix;
-    prefix.append(depth, ' ');
-  }
-
-  fixupAnswer(prefix, lwr, qname, qtype, auth, wasForwarded, rdQuery);
-  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;
-
-  for (auto& rec : lwr.d_records) {
-    if (rec.d_type == QType::OPT || rec.d_class != QClass::IN) {
-      continue;
-    }
-
-    rec.d_ttl = min(s_maxcachettl, rec.d_ttl);
-
-    if (!isCNAMEAnswer && rec.d_place == DNSResourceRecord::ANSWER && rec.d_type == QType::CNAME && (!(qtype==QType::CNAME)) && rec.d_name == qname && !isDNAMEAnswer) {
-      isCNAMEAnswer = true;
-    }
-    if (!isDNAMEAnswer && rec.d_place == DNSResourceRecord::ANSWER && rec.d_type == QType::DNAME && qtype != QType::DNAME && qname.isPartOf(rec.d_name)) {
-      isDNAMEAnswer = true;
-      isCNAMEAnswer = false;
-    }
-
-    if (rec.d_type == QType::SOA && rec.d_place == DNSResourceRecord::AUTHORITY && qname.isPartOf(rec.d_name)) {
-      seenAuth = rec.d_name;
-    }
-
-    if (rec.d_type == QType::RRSIG) {
-      auto rrsig = getRR<RRSIGRecordContent>(rec);
-      if (rrsig) {
-        /* As illustrated in rfc4035's Appendix B.6, the RRSIG label
-           count can be lower than the name's label count if it was
-           synthesized from the wildcard. Note that the difference might
-           be > 1. */
-        if (rec.d_name == qname && isWildcardExpanded(labelCount, rrsig)) {
-          gatherWildcardProof = true;
-          if (!isWildcardExpandedOntoItself(rec.d_name, labelCount, rrsig)) {
-            /* if we have a wildcard expanded onto itself, we don't need to prove
-               that the exact name doesn't exist because it actually does.
-               We still want to gather the corresponding NSEC/NSEC3 records
-               to pass them to our client in case it wants to validate by itself.
-            */
-            LOG(prefix<<qname<<": RRSIG indicates the name was synthesized from a wildcard, we need a wildcard proof"<<endl);
-            needWildcardProof = true;
-          }
-          else {
-            LOG(prefix<<qname<<": RRSIG indicates the name was synthesized from a wildcard expanded onto itself, we need to gather wildcard proof"<<endl);
-          }
-          wildcardLabelsCount = rrsig->d_labels;
-        }
-
-        // cerr<<"Got an RRSIG for "<<DNSRecordContent::NumberToType(rrsig->d_type)<<" with name '"<<rec.d_name<<"' and place "<<rec.d_place<<endl;
-        tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signatures.push_back(rrsig);
-        tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signaturesTTL = std::min(tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signaturesTTL, rec.d_ttl);
-      }
-    }
-  }
-
-  /* if we have a positive answer synthesized from a wildcard,
-     we need to store the corresponding NSEC/NSEC3 records proving
-     that the exact name did not exist in the negative cache */
-  if (gatherWildcardProof) {
-    for (const auto& rec : lwr.d_records) {
-      if (rec.d_type == QType::OPT || rec.d_class != QClass::IN) {
-        continue;
-      }
-
-      if (nsecTypes.count(rec.d_type)) {
-        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)) {
-          authorityRecs.push_back(std::make_shared<DNSRecord>(rec));
-        }
-      }
-    }
-  }
-
-  // reap all answers from this packet that are acceptable
-  for (auto& rec : lwr.d_records) {
-    if(rec.d_type == QType::OPT) {
-      LOG(prefix<<qname<<": OPT answer '"<<rec.d_name<<"' from '"<<auth<<"' nameservers" <<endl);
-      continue;
-    }
-
-    LOG(prefix<<qname<<": accept answer '"<<rec.d_name<<"|"<<DNSRecordContent::NumberToType(rec.d_type)<<"|"<<rec.d_content->getZoneRepresentation()<<"' from '"<<auth<<"' nameservers? ttl="<<rec.d_ttl<<", place="<<(int)rec.d_place<<" ");
-
-    // We called sanitizeRecords before, so all ANY, non-IN and non-aa/non-forwardrecurse answer records are already removed
-
-    if(rec.d_name.isPartOf(auth)) {
-      if (rec.d_type == QType::RRSIG) {
-        LOG("RRSIG - separate"<<endl);
-      }
-      else if (rec.d_type == QType::DS && rec.d_name == auth) {
-        LOG("NO - DS provided by child zone"<<endl);
-      }
-      else {
-        bool haveLogged = false;
-        if (isDNAMEAnswer && rec.d_type == QType::CNAME) {
-          LOG("NO - we already have a DNAME answer for this domain"<<endl);
-          continue;
-        }
-        if (!t_sstorage.domainmap->empty()) {
-          // Check if we are authoritative for a zone in this answer
-          DNSName tmp_qname(rec.d_name);
-          // We may be auth for domain example.com, but the DS record needs to come from the parent (.com) nameserver
-          if (rec.d_type == QType::DS) {
-            tmp_qname.chopOff();
-          }
-          auto auth_domain_iter=getBestAuthZone(&tmp_qname);
-          if(auth_domain_iter!=t_sstorage.domainmap->end() &&
-             auth.countLabels() <= auth_domain_iter->first.countLabels()) {
-            if (auth_domain_iter->first != auth) {
-              LOG("NO! - we are authoritative for the zone "<<auth_domain_iter->first<<endl);
-              continue;
-            } 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);
-            }
-          }
-        }
-        if (!haveLogged) {
-          LOG("YES!"<<endl);
-        }
-
-        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);
-      }
-    }
-    else
-      LOG("NO!"<<endl);
-  }
-
-  // supplant
-  for (auto& entry : tcache) {
-    if ((entry.second.records.size() + entry.second.signatures.size() + authorityRecs.size()) > 1) {  // need to group the ttl to be the minimum of the RRSET (RFC 2181, 5.2)
-      uint32_t lowestTTD = computeLowestTTD(entry.second.records, entry.second.signatures, entry.second.signaturesTTL, authorityRecs);
-
-      for (auto& record : entry.second.records) {
-        record.d_ttl = lowestTTD; // boom
-      }
-    }
-
-//             cout<<"Have "<<i->second.records.size()<<" records and "<<i->second.signatures.size()<<" signatures for "<<i->first.name;
-//             cout<<'|'<<DNSRecordContent::NumberToType(i->first.type)<<endl;
-  }
-
-  for(tcache_t::iterator i = tcache.begin(); i != tcache.end(); ++i) {
-
-    if (i->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
-       because keeping records in the additional section is allowed even
-       if the corresponding RRSIGs are not included, without setting the TC
-       bit, as stated in rfc4035's section 3.1.1.  Including RRSIG RRs in a Response:
-       "When placing a signed RRset in the Additional section, the name
-       server MUST also place its RRSIG RRs in the Additional section.
-       If space does not permit inclusion of both the RRset and its
-       associated RRSIG RRs, the name server MAY retain the RRset while
-       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;
-    /* 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);
-    /* 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) {
-      expectSignature = true;
-    }
-
-    if (isCNAMEAnswer && (i->first.place != DNSResourceRecord::ANSWER || i->first.type != QType::CNAME || i->first.name != qname)) {
-      /*
-        rfc2181 states:
-        Note that the answer section of an authoritative answer normally
-        contains only authoritative data.  However when the name sought is an
-        alias (see section 10.1.1) only the record describing that alias is
-        necessarily authoritative.  Clients should assume that other records
-        may have come from the server's cache.  Where authoritative answers
-        are required, the client should query again, using the canonical name
-        associated with the alias.
-      */
-      isAA = false;
-      expectSignature = false;
-    }
-    else if (isDNAMEAnswer && (i->first.place != DNSResourceRecord::ANSWER || i->first.type != QType::DNAME || !qname.isPartOf(i->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) {
-      /* 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
-         of non-authoritative NS in the cache, they might be able to keep a "ghost" zone alive forever,
-         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(d_prefix<<": skipping authority NS from '"<<auth<<"' nameservers in CNAME/DNAME answer "<<i->first.name<<"|"<<DNSRecordContent::NumberToType(i->first.type)<<endl);
-      continue;
-    }
-
-    /*
-     * RFC 6672 section 5.3.1
-     *  In any response, a signed DNAME RR indicates a non-terminal
-     *  redirection of the query.  There might or might not be a server-
-     *  synthesized CNAME in the answer section; if there is, the CNAME will
-     *  never be signed.  For a DNSSEC validator, verification of the DNAME
-     *  RR and then that the CNAME was properly synthesized is sufficient
-     *  proof.
-     *
-     * We do the synthesis check in processRecords, here we make sure we
-     * don't validate the CNAME.
-     */
-    if (isDNAMEAnswer && i->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);
-      LOG(d_prefix<<": got initial zone status "<<initialState<<" for record "<<i->first.name<<"|"<<DNSRecordContent::NumberToType(i->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(d_prefix<<"Validating DNSKEY for "<<i->first.name<<endl);
-          recordState = validateDNSKeys(i->first.name, i->second.records, i->second.signatures, depth);
-        }
-        else {
-          LOG(d_prefix<<"Validating non-additional "<<QType(i->first.type).toString()<<" record for "<<i->first.name<<endl);
-          recordState = validateRecordsWithSigs(depth, qname, qtype, i->first.name, QType(i->first.type), i->second.records, i->second.signatures);
-        }
-      }
-      else {
-        recordState = initialState;
-        LOG(d_prefix<<"Skipping validation because the current state is "<<recordState<<endl);
-      }
-
-      LOG(d_prefix<<"Validation result is "<<recordState<<", current state is "<<state<<endl);
-      if (state != recordState) {
-        updateValidationState(state, recordState);
-      }
-    }
-
-    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));
-      }
-    }
-
-    /* We don't need to store NSEC3 records in the positive cache because:
-       - we don't allow direct NSEC3 queries
-       - denial of existence proofs in wildcard expanded positive responses are stored in authorityRecs
-       - denial of existence proofs for negative responses are stored in the negative cache
-       We also don't want to cache non-authoritative data except for:
-       - records coming from non forward-recurse servers (those will never be AA)
-       - 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)) {
-
-      bool doCache = true;
-      if (i->first.place == DNSResourceRecord::ANSWER && ednsmask) {
-        const bool isv4 = ednsmask->isIPv4();
-        if ((isv4 && s_ecsipv4nevercache) || (!isv4 && s_ecsipv6nevercache)) {
-          doCache = false;
-        }
-        // If ednsmask is relevant, we do not want to cache if the scope prefix length is large and TTL is small
-        if (doCache && s_ecscachelimitttl > 0) {
-          bool manyMaskBits = (isv4 && ednsmask->getBits() > s_ecsipv4cachelimit) ||
-            (!isv4 && ednsmask->getBits() > s_ecsipv6cachelimit);
-
-          if (manyMaskBits) {
-            uint32_t minttl = UINT32_MAX;
-            for (const auto &it : i->second.records) {
-              if (it.d_ttl < minttl)
-                minttl = it.d_ttl;
-            }
-            bool ttlIsSmall = minttl < s_ecscachelimitttl + d_now.tv_sec;
-            if (ttlIsSmall) {
-              // Case: many bits and ttlIsSmall
-              doCache = false;
-            }
-          }
-        }
-      }
-
-      d_fromAuthIP = remoteIP;
-
-      if (doCache) {
-        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);
-
-        if (g_aggressiveNSECCache && needWildcardProof && recordState == vState::Secure && i->first.place == DNSResourceRecord::ANSWER && i->first.name == qname && !i->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);
-
-          if (isWildcardExpanded(labelCount, rrsig) && !isWildcardExpandedOntoItself(i->first.name, labelCount, rrsig)) {
-            DNSName realOwner = getNSECOwnerName(i->first.name, i->second.signatures);
-
-            std::vector<DNSRecord> content;
-            content.reserve(i->second.records.size());
-            for (const auto& record : i->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);
-          }
-        }
-      }
-    }
-
-    if (seenAuth.empty() && !i->second.signatures.empty()) {
-      seenAuth = getSigner(i->second.signatures);
-    }
-
-    if (g_aggressiveNSECCache && (i->first.type == QType::NSEC || i->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);
-    }
-
-    if (i->first.place == DNSResourceRecord::ANSWER && ednsmask) {
-      d_wasVariable=true;
-    }
-  }
-
-  return RCode::NoError;
-}
-
-void SyncRes::updateDenialValidationState(vState& neValidationState, const DNSName& neName, vState& state, const dState denialState, const dState expectedState, bool isDS, unsigned int depth)
-{
-  if (denialState == expectedState) {
-    neValidationState = vState::Secure;
-  }
-  else {
-    if (denialState == dState::OPTOUT) {
-      LOG(d_prefix<<"OPT-out denial found for "<<neName<<endl);
-      /* rfc5155 states:
-         "The AD bit, as defined by [RFC4035], MUST NOT be set when returning a
-         response containing a closest (provable) encloser proof in which the
-         NSEC3 RR that covers the "next closer" name has the Opt-Out bit set.
-
-         This rule is based on what this closest encloser proof actually
-         proves: names that would be covered by the Opt-Out NSEC3 RR may or
-         may not exist as insecure delegations.  As such, not all the data in
-         responses containing such closest encloser proofs will have been
-         cryptographically verified, so the AD bit cannot be set."
-
-         At best the Opt-Out NSEC3 RR proves that there is no signed DS (so no
-         secure delegation).
-      */
-      neValidationState = vState::Insecure;
-    }
-    else if (denialState == dState::INSECURE) {
-      LOG(d_prefix<<"Insecure denial found for "<<neName<<", returning Insecure"<<endl);
-      neValidationState = vState::Insecure;
-    }
-    else {
-      LOG(d_prefix<<"Invalid denial found for "<<neName<<", res="<<denialState<<", expectedState="<<expectedState<<", checking whether we have missed a zone cut before returning a Bogus state"<<endl);
-      /* try again to get the missed cuts, harder this time */
-      auto zState = getValidationStatus(neName, false, isDS, depth);
-      if (zState != vState::Secure) {
-        neValidationState = zState;
-      }
-      else {
-        LOG(d_prefix<<"Still in a secure zone with an invalid denial for "<<neName<<", returning "<<vStateToString(vState::BogusInvalidDenial)<<endl);
-        neValidationState = vState::BogusInvalidDenial;
-      }
-    }
-  }
-  updateValidationState(state, neValidationState);
-}
-
-dState SyncRes::getDenialValidationState(const NegCache::NegCacheEntry& ne, const dState expectedState, bool referralToUnsigned)
-{
-  cspmap_t csp = harvestCSPFromNE(ne);
-  return getDenial(csp, ne.d_name, ne.d_qtype.getCode(), referralToUnsigned, expectedState == dState::NXQTYPE);
-}
-
-bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, const QType qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state, const bool needWildcardProof, const bool gatherWildcardProof, const unsigned int wildcardLabelsCount, int& rcode, bool& negIndicHasSignatures, unsigned int depth)
-{
-  bool done = false;
-  DNSName dnameTarget, dnameOwner;
-  uint32_t dnameTTL = 0;
-  bool referralOnDS = false;
-
-  for (auto& rec : lwr.d_records) {
-    if (rec.d_type != QType::OPT && rec.d_class != QClass::IN) {
-      continue;
-    }
-
-    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)) {
-        continue;
-      }
-    }
-    const bool negCacheIndiction = rec.d_place == DNSResourceRecord::AUTHORITY && rec.d_type == QType::SOA &&
-      lwr.d_rcode == RCode::NXDomain && qname.isPartOf(rec.d_name) && rec.d_name.isPartOf(auth);
-
-    bool putInNegCache = true;
-    if (negCacheIndiction && qtype == QType::DS && isForwardOrAuth(qname)) {
-      // #10189, a NXDOMAIN to a DS query for a forwarded or auth domain should not NXDOMAIN the whole domain
-      putInNegCache = false;
-    }
-
-    if (negCacheIndiction) {
-      LOG(prefix<<qname<<": got negative caching indication for name '"<<qname<<"' (accept="<<rec.d_name.isPartOf(auth)<<"), newtarget='"<<newtarget<<"'"<<endl);
-
-      rec.d_ttl = min(rec.d_ttl, s_maxnegttl);
-      // only add a SOA if we're not going anywhere after this
-      if (newtarget.empty()) {
-        ret.push_back(rec);
-      }
-
-      NegCache::NegCacheEntry ne;
-
-      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);
-
-      if (vStateIsBogus(state)) {
-        ne.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);
-        if (recordState == vState::Secure) {
-          dState denialState = getDenialValidationState(ne, dState::NXDOMAIN, false);
-          updateDenialValidationState(ne.d_validationState, ne.d_name, state, denialState, dState::NXDOMAIN, false, depth);
-        }
-        else {
-          ne.d_validationState = recordState;
-          updateValidationState(state, ne.d_validationState);
-        }
-      }
-
-      if (vStateIsBogus(ne.d_validationState)) {
-        lowestTTL = min(lowestTTL, s_maxbogusttl);
-      }
-
-      ne.d_ttd = d_now.tv_sec + 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);
-        if (s_rootNXTrust && ne.d_auth.isRoot() && auth.isRoot() && lwr.d_aabit) {
-          ne.d_name = ne.d_name.getLastLabel();
-          g_negCache->add(ne);
-        }
-      }
-
-      negIndicHasSignatures = !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty();
-      negindic = true;
-    }
-    else if (rec.d_place == DNSResourceRecord::ANSWER && s_redirectionQTypes.count(rec.d_type) > 0 && // CNAME or DNAME answer
-             s_redirectionQTypes.count(qtype.getCode()) == 0) { // But not in response to a CNAME or DNAME query
-      if (rec.d_type == QType::CNAME && rec.d_name == qname) {
-        if (!dnameOwner.empty()) { // We synthesize ourselves
-          continue;
-        }
-        ret.push_back(rec);
-        if (auto content = getRR<CNAMERecordContent>(rec)) {
-          newtarget = DNSName(content->getTarget());
-        }
-      } else if (rec.d_type == QType::DNAME && qname.isPartOf(rec.d_name)) { // DNAME
-        ret.push_back(rec);
-        if (auto content = getRR<DNAMERecordContent>(rec)) {
-          dnameOwner = rec.d_name;
-          dnameTarget = content->getTarget();
-          dnameTTL = rec.d_ttl;
-          if (!newtarget.empty()) { // We had a CNAME before, remove it from ret so we don't cache it
-            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);
-                  }),
-                  ret.end());
-          }
-          try {
-            newtarget = qname.makeRelative(dnameOwner) + dnameTarget;
-          } 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)
-            // But there is no way to set the RCODE from this function
-            throw ImmediateServFailException("Unable to perform DNAME substitution(DNAME owner: '" + dnameOwner.toLogString() +
-                                             "', DNAME target: '" + dnameTarget.toLogString() + "', substituted name: '" +
-                                             qname.makeRelative(dnameOwner).toLogString() + "." + dnameTarget.toLogString() +
-                                             "' : " + e.what());
-          }
-        }
-      }
-    }
-    /* if we have a positive answer synthesized from a wildcard, we need to
-       return the corresponding NSEC/NSEC3 records from the AUTHORITY section
-       proving that the exact name did not exist.
-       Except if this is a NODATA answer because then we will gather the NXNSEC records later */
-    else if (gatherWildcardProof && !negindic && (rec.d_type == QType::RRSIG || rec.d_type == QType::NSEC || rec.d_type == QType::NSEC3) && rec.d_place == DNSResourceRecord::AUTHORITY) {
-      ret.push_back(rec); // enjoy your DNSSEC
-    }
-    // for ANY answers we *must* have an authoritative answer, unless we are forwarding recursively
-    else if (rec.d_place == DNSResourceRecord::ANSWER && rec.d_name == qname &&
-            (
-              rec.d_type == qtype.getCode() || ((lwr.d_aabit || sendRDQuery) && qtype == QType::ANY)
-              )
-      )
-    {
-      LOG(prefix<<qname<<": answer is in: resolved to '"<< rec.d_content->getZoneRepresentation()<<"|"<<DNSRecordContent::NumberToType(rec.d_type)<<"'"<<endl);
-
-      done = true;
-      rcode = RCode::NoError;
-
-      if (needWildcardProof) {
-        /* positive answer synthesized from a wildcard */
-        NegCache::NegCacheEntry ne;
-        ne.d_name = qname;
-        ne.d_qtype = QType::ENT; // this encodes 'whole record'
-        uint32_t lowestTTL = rec.d_ttl;
-        harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
-
-        if (vStateIsBogus(state)) {
-          ne.d_validationState = state;
-        }
-        else {
-          auto recordState = getValidationStatus(qname, !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty(), false, depth);
-
-          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, false, wildcardLabelsCount);
-            if (res != dState::NXDOMAIN) {
-              vState st = 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;
-                LOG(d_prefix<<"Unable to validate denial in wildcard expanded positive response found for "<<qname<<", returning Insecure, res="<<res<<endl);
-              }
-              else {
-                LOG(d_prefix<<"Invalid denial in wildcard expanded positive response found for "<<qname<<", returning Bogus, res="<<res<<endl);
-                rec.d_ttl = std::min(rec.d_ttl, s_maxbogusttl);
-              }
-
-              updateValidationState(state, st);
-              /* we already stored the record with a different validation status, let's fix it */
-              updateValidationStatusInCache(qname, qtype, lwr.d_aabit, st);
-            }
-          }
-        }
-      }
-
-      ret.push_back(rec);
-    }
-    else if ((rec.d_type == QType::RRSIG || rec.d_type == QType::NSEC || rec.d_type == QType::NSEC3) && rec.d_place == DNSResourceRecord::ANSWER) {
-      if (rec.d_type != QType::RRSIG || rec.d_name == qname) {
-        ret.push_back(rec); // enjoy your DNSSEC
-      } else if (rec.d_type == QType::RRSIG && qname.isPartOf(rec.d_name)) {
-        auto rrsig = getRR<RRSIGRecordContent>(rec);
-        if (rrsig != nullptr && rrsig->d_type == QType::DNAME) {
-          ret.push_back(rec);
-        }
-      }
-    }
-    else if (rec.d_place == DNSResourceRecord::AUTHORITY && rec.d_type == QType::NS && qname.isPartOf(rec.d_name)) {
-      if (moreSpecificThan(rec.d_name,auth)) {
-        newauth = rec.d_name;
-        LOG(prefix<<qname<<": got NS record '"<<rec.d_name<<"' -> '"<<rec.d_content->getZoneRepresentation()<<"'"<<endl);
-
-        /* check if we have a referral from the parent zone to a child zone for a DS query, which is not right */
-        if (qtype == QType::DS && (newauth.isPartOf(qname) || qname == newauth)) {
-          /* just got a referral from the parent zone when asking for a DS, looks like this server did not get the DNSSEC memo.. */
-          referralOnDS = true;
-        }
-        else {
-          realreferral = true;
-          if (auto content = getRR<NSRecordContent>(rec)) {
-            nsset.insert(content->getNS());
-          }
-        }
-      }
-      else {
-        LOG(prefix<<qname<<": got upwards/level NS record '"<<rec.d_name<<"' -> '"<<rec.d_content->getZoneRepresentation()<<"', had '"<<auth<<"'"<<endl);
-        if (auto content = getRR<NSRecordContent>(rec)) {
-          nsset.insert(content->getNS());
-        }
-      }
-    }
-    else if (rec.d_place==DNSResourceRecord::AUTHORITY && rec.d_type==QType::DS && qname.isPartOf(rec.d_name)) {
-      LOG(prefix<<qname<<": got DS record '"<<rec.d_name<<"' -> '"<<rec.d_content->getZoneRepresentation()<<"'"<<endl);
-    }
-    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;
-      uint32_t lowestTTL = rec.d_ttl;
-      harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
-
-      if (!vStateIsBogus(state)) {
-        auto recordState = getValidationStatus(newauth, !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty(), true, depth);
-
-        if (recordState == vState::Secure) {
-          ne.d_auth = auth;
-          ne.d_name = newauth;
-          ne.d_qtype = QType::DS;
-          rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
-
-          dState denialState = getDenialValidationState(ne, dState::NXQTYPE, true);
-
-          if (denialState == dState::NXQTYPE || denialState == dState::OPTOUT || denialState == dState::INSECURE) {
-            ne.d_ttd = lowestTTL + d_now.tv_sec;
-            ne.d_validationState = vState::Secure;
-            if (denialState == dState::OPTOUT) {
-              ne.d_validationState = vState::Insecure;
-            }
-            LOG(prefix<<qname<<": got negative indication of DS record for '"<<newauth<<"'"<<endl);
-
-            g_negCache->add(ne);
-
-            /* 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 && qname == newauth && (d_externalDSQuery.empty() || qname != d_externalDSQuery)) {
-              /* we are actually done! */
-              negindic = true;
-              negIndicHasSignatures = !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty();
-              nsset.clear();
-            }
-          }
-        }
-      }
-    }
-    else if (!done && rec.d_place == DNSResourceRecord::AUTHORITY && rec.d_type == QType::SOA &&
-            lwr.d_rcode == RCode::NoError && qname.isPartOf(rec.d_name)) {
-      LOG(prefix<<qname<<": got negative caching indication for '"<< qname<<"|"<<qtype<<"'"<<endl);
-
-      if (!newtarget.empty()) {
-        LOG(prefix<<qname<<": Hang on! Got a redirect to '"<<newtarget<<"' already"<<endl);
-      }
-      else {
-        rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
-
-        NegCache::NegCacheEntry ne;
-        ne.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);
-
-        if (vStateIsBogus(state)) {
-          ne.d_validationState = state;
-        }
-        else {
-          auto recordState = getValidationStatus(qname, !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty(), qtype == QType::DS, depth);
-          if (recordState == vState::Secure) {
-            dState denialState = getDenialValidationState(ne, dState::NXQTYPE, false);
-            updateDenialValidationState(ne.d_validationState, ne.d_name, state, denialState, dState::NXQTYPE, qtype == QType::DS, depth);
-          } else {
-            ne.d_validationState = recordState;
-            updateValidationState(state, ne.d_validationState);
-          }
-        }
-
-        if (vStateIsBogus(ne.d_validationState)) {
-          lowestTTL = min(lowestTTL, s_maxbogusttl);
-          rec.d_ttl = min(rec.d_ttl, s_maxbogusttl);
-        }
-        ne.d_ttd = d_now.tv_sec + lowestTTL;
-
-        if (qtype.getCode()) {  // prevents us from NXDOMAIN'ing a whole domain
-          g_negCache->add(ne);
-        }
-
-        ret.push_back(rec);
-        negindic = true;
-        negIndicHasSignatures = !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty();
-      }
-    }
-  }
-
-  if (!dnameTarget.empty()) {
-    // Synthesize a CNAME
-    auto cnamerec = DNSRecord();
-    cnamerec.d_name = qname;
-    cnamerec.d_type = QType::CNAME;
-    cnamerec.d_ttl = dnameTTL;
-    cnamerec.d_content = std::make_shared<CNAMERecordContent>(CNAMERecordContent(newtarget));
-    ret.push_back(std::move(cnamerec));
-  }
-
-  /* If we have seen a proper denial, let's forget that we also had a referral for a DS query.
-     Otherwise we need to deal with it. */
-  if (referralOnDS && !negindic) {
-    LOG(prefix<<qname<<": got a referral to the child zone for a DS query without a negative indication (missing SOA in authority), treating that as a NODATA"<<endl);
-    if (!vStateIsBogus(state)) {
-      auto recordState = getValidationStatus(qname, false, true, depth);
-      if (recordState == vState::Secure) {
-        /* we are in a secure zone, got a referral to the child zone on a DS query, no denial, that's wrong */
-        LOG(prefix<<qname<<": NODATA without a negative indication (missing SOA in authority) in a DNSSEC secure zone, going Bogus"<<endl);
-        updateValidationState(state, vState::BogusMissingNegativeIndication);
-      }
-    }
-    negindic = true;
-    negIndicHasSignatures = false;
-  }
-
-  return done;
-}
-
-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)
-{
-  bool chained = false;
-  LWResult::Result resolveret = LWResult::Result::Success;
-  s_outqueries++;
-  d_outqueries++;
-
-  if(d_outqueries + d_throttledqueries > s_maxqperq) {
-    throw ImmediateServFailException("more than "+std::to_string(s_maxqperq)+" (max-qperq) queries sent while resolving "+qname.toLogString());
-  }
-
-  if(s_maxtotusec && d_totUsec > s_maxtotusec) {
-    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)+"msec");
-  }
-
-  if(doTCP) {
-    if (doDoT) {
-      LOG(prefix<<qname<<": using DoT with "<< remoteIP.toStringWithPort() <<endl);
-      s_dotoutqueries++;
-      d_dotoutqueries++;
-    } else {
-      LOG(prefix<<qname<<": using TCP with "<< remoteIP.toStringWithPort() <<endl);
-      s_tcpoutqueries++;
-      d_tcpoutqueries++;
-    }
-  }
-
-  int preOutQueryRet = RCode::NoError;
-  if(d_pdl && d_pdl->preoutquery(remoteIP, d_requestor, qname, qtype, doTCP, lwr.d_records, preOutQueryRet, d_eventTrace)) {
-    LOG(prefix<<qname<<": query handled by Lua"<<endl);
-  }
-  else {
-    ednsmask=getEDNSSubnetMask(qname, remoteIP);
-    if(ednsmask) {
-      LOG(prefix<<qname<<": Adding EDNS Client Subnet Mask "<<ednsmask->toString()<<" to query"<<endl);
-      s_ecsqueries++;
-    }
-    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);
-        }
-      }
-    }
-  }
-
-  /* preoutquery killed the query by setting dq.rcode to -3 */
-  if (preOutQueryRet == -3) {
-    throw ImmediateServFailException("Query killed by policy");
-  }
-
-  d_totUsec += lwr.d_usec;
-  accountAuthLatency(lwr.d_usec, remoteIP.sin4.sin_family);
-
-  bool dontThrottle = false;
-  {
-    auto dontThrottleNames = g_dontThrottleNames.getLocal();
-    auto dontThrottleNetmasks = g_dontThrottleNetmasks.getLocal();
-    dontThrottle = dontThrottleNames->check(nsName) || dontThrottleNetmasks->match(remoteIP);
-  }
-
-  if (resolveret != LWResult::Result::Success) {
-    /* Error while resolving */
-    if (resolveret == LWResult::Result::Timeout) {
-      /* Time out */
-
-      LOG(prefix<<qname<<": timeout resolving after "<<lwr.d_usec/1000.0<<"msec "<< (doTCP ? "over TCP" : "")<<endl);
-      d_timeouts++;
-      s_outgoingtimeouts++;
-
-      if(remoteIP.sin4.sin_family == AF_INET)
-        s_outgoing4timeouts++;
-      else
-        s_outgoing6timeouts++;
-
-      if(t_timeouts)
-        t_timeouts->push_back(remoteIP);
-    }
-    else if (resolveret == LWResult::Result::OSLimitError) {
-      /* OS resource limit reached */
-      LOG(prefix<<qname<<": hit a local resource limit resolving"<< (doTCP ? " over TCP" : "")<<", probable error: "<<stringerror()<<endl);
-      g_stats.resourceLimits++;
-    }
-    else if (resolveret == LWResult::Result::Spoofed) {
-      spoofed = true;
-    }
-    else {
-      /* LWResult::Result::PermanentError */
-      s_unreachables++;
-      d_unreachables++;
-      // XXX questionable use of errno
-      LOG(prefix<<qname<<": error resolving from "<<remoteIP.toString()<< (doTCP ? " over TCP" : "") <<", possible error: "<<stringerror()<< endl);
-    }
-
-    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
-      t_sstorage.nsSpeeds[nsName.empty()? DNSName(remoteIP.toStringWithPort()) : nsName].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) {
-        LOG(prefix<<qname<<": Max fails reached resolving on "<< remoteIP.toString() <<". Going full throttle for "<< s_serverdownthrottletime <<" seconds" <<endl);
-        // mark server as down
-        t_sstorage.throttle.throttle(d_now.tv_sec, std::make_tuple(remoteIP, g_rootdnsname, 0), s_serverdownthrottletime, 10000);
-      }
-      else if (resolveret == LWResult::Result::PermanentError) {
-        // unreachable, 1 minute or 100 queries
-        t_sstorage.throttle.throttle(d_now.tv_sec, std::make_tuple(remoteIP, qname, qtype.getCode()), 60, 100);
-      }
-      else {
-        // timeout, 10 seconds or 5 queries
-        t_sstorage.throttle.throttle(d_now.tv_sec, std::make_tuple(remoteIP, qname, qtype.getCode()), 10, 5);
-      }
-    }
-
-    return false;
-  }
-
-  if (lwr.d_validpacket == false) {
-    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) {
-
-      // let's make sure we prefer a different server for some time, if there is one available
-      t_sstorage.nsSpeeds[nsName.empty()? DNSName(remoteIP.toStringWithPort()) : nsName].submit(remoteIP, 1000000, d_now); // 1 sec
-
-      if (doTCP) {
-        // we can be more heavy-handed over TCP
-        t_sstorage.throttle.throttle(d_now.tv_sec, std::make_tuple(remoteIP, qname, qtype.getCode()), 60, 10);
-      }
-      else {
-        t_sstorage.throttle.throttle(d_now.tv_sec, std::make_tuple(remoteIP, qname, qtype.getCode()), 10, 2);
-      }
-    }
-    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
-          t_sstorage.nsSpeeds[nsName.empty()? DNSName(remoteIP.toStringWithPort()) : nsName].submit(remoteIP, 1000000, d_now); // 1 sec
-        }
-        else {
-          t_sstorage.throttle.throttle(d_now.tv_sec, std::make_tuple(remoteIP, qname, qtype.getCode()), 60, 3);
-        }
-      }
-      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);
-  }
-
-  if (lwr.d_tcbit) {
-    truncated = true;
-
-    if (doTCP) {
-      LOG(prefix<<qname<<": truncated bit set, over TCP?"<<endl);
-      if (!dontThrottle) {
-        /* let's treat that as a ServFail answer from this server */
-        t_sstorage.throttle.throttle(d_now.tv_sec, std::make_tuple(remoteIP, qname, qtype.getCode()), 60, 3);
-      }
-      return false;
-    }
-    LOG(prefix<<qname<<": truncated bit set, over UDP"<<endl);
-
-    return true;
-  }
-
-  return true;
-}
-
-void SyncRes::handleNewTarget(const std::string& prefix, const DNSName& qname, const DNSName& newtarget, const QType qtype, std::vector<DNSRecord>& ret, int& rcode, int depth, const std::vector<DNSRecord>& recordsFromAnswer, vState& state)
-{
-  if (newtarget == qname) {
-    LOG(prefix<<qname<<": status=got a CNAME referral to self, returning SERVFAIL"<<endl);
-    ret.clear();
-    rcode = RCode::ServFail;
-    return;
-  }
-  if (newtarget.isPartOf(qname)) {
-    // a.b.c. CNAME x.a.b.c will go to great depths with QM on
-    LOG(prefix<<qname<<": status=got a CNAME referral to child, disabling QM"<<endl);
-    setQNameMinimization(false);
-  }
-
-  if (depth > 10) {
-    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)) {
-    LOG(prefix<<qname<<": status=got a CNAME referral that causes a loop, returning SERVFAIL"<<endl);
-    ret.clear();
-    rcode = RCode::ServFail;
-    return;
-  }
-
-  if (qtype == QType::DS || qtype == QType::DNSKEY) {
-    LOG(prefix<<qname<<": status=got a CNAME referral, but we are looking for a DS or DNSKEY"<<endl);
-
-    if (d_doDNSSEC) {
-      addNXNSECS(ret, recordsFromAnswer);
-    }
-
-    rcode = RCode::NoError;
-    return;
-  }
-
-  LOG(prefix<<qname<<": status=got a CNAME referral, starting over with "<<newtarget<<endl);
-
-  set<GetBestNSAnswer> beenthere;
-  vState cnameState = vState::Indeterminate;
-  rcode = doResolve(newtarget, qtype, ret, depth + 1, beenthere, cnameState);
-  LOG(prefix<<qname<<": updating validation state for response to "<<qname<<" from "<<state<<" with the state from the CNAME quest: "<<cnameState<<endl);
-  updateValidationState(state, cnameState);
-}
-
-bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, bool sendRDQuery, NsSet &nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode, vState& state, const ComboAddress& remoteIP)
-{
-  string prefix;
-  if(doLog()) {
-    prefix=d_prefix;
-    prefix.append(depth, ' ');
-  }
-
-  if(s_minimumTTL) {
-    for(auto& rec : lwr.d_records) {
-      rec.d_ttl = max(rec.d_ttl, s_minimumTTL);
-    }
-  }
-
-  /* if the answer is ECS-specific, a minimum TTL is set for this kind of answers
-     and it's higher than the global minimum TTL */
-  if (ednsmask && s_minimumECSTTL > 0 && (s_minimumTTL == 0 || s_minimumECSTTL > s_minimumTTL)) {
-    for(auto& rec : lwr.d_records) {
-      if (rec.d_place == DNSResourceRecord::ANSWER) {
-        rec.d_ttl = max(rec.d_ttl, s_minimumECSTTL);
-      }
-    }
-  }
-
-  bool needWildcardProof = false;
-  bool gatherWildcardProof = false;
-  unsigned int wildcardLabelsCount;
-  *rcode = updateCacheFromRecords(depth, lwr, qname, qtype, auth, wasForwarded, ednsmask, state, needWildcardProof, gatherWildcardProof, wildcardLabelsCount, sendRDQuery, remoteIP);
-  if (*rcode != RCode::NoError) {
-    return true;
-  }
-
-  LOG(prefix<<qname<<": determining status after receiving this packet"<<endl);
-
-  set<DNSName> nsset;
-  bool realreferral = false;
-  bool negindic = false;
-  bool negIndicHasSignatures = false;
-  DNSName newauth;
-  DNSName newtarget;
-
-  bool done = processRecords(prefix, qname, qtype, auth, lwr, sendRDQuery, ret, nsset, newtarget, newauth, realreferral, negindic, state, needWildcardProof, gatherWildcardProof, wildcardLabelsCount, *rcode, negIndicHasSignatures, depth);
-
-  if (done){
-    LOG(prefix<<qname<<": status=got results, this level of recursion done"<<endl);
-    LOG(prefix<<qname<<": validation status is "<<state<<endl);
-    return true;
-  }
-
-  if (!newtarget.empty()) {
-    handleNewTarget(prefix, qname, newtarget, qtype.getCode(), ret, *rcode, depth, lwr.d_records, state);
-    return true;
-  }
-
-  if (lwr.d_rcode == RCode::NXDomain) {
-    LOG(prefix<<qname<<": status=NXDOMAIN, we are done "<<(negindic ? "(have negative SOA)" : "")<<endl);
-
-    auto tempState = getValidationStatus(qname, negIndicHasSignatures, qtype == QType::DS, depth);
-    if (tempState == vState::Secure && (lwr.d_aabit || sendRDQuery) && !negindic) {
-      LOG(prefix<<qname<<": NXDOMAIN without a negative indication (missing SOA in authority) in a DNSSEC secure zone, going Bogus"<<endl);
-      updateValidationState(state, vState::BogusMissingNegativeIndication);
-    }
-    else if (state == vState::Indeterminate) {
-      /* we might not have validated any record, because we did get a NXDOMAIN without any SOA
-         from an insecure zone, for example */
-      updateValidationState(state, tempState);
-    }
-
-    if (d_doDNSSEC) {
-      addNXNSECS(ret, lwr.d_records);
-    }
-
-    *rcode = RCode::NXDomain;
-    return true;
-  }
-
-  if (nsset.empty() && !lwr.d_rcode && (negindic || lwr.d_aabit || sendRDQuery)) {
-    LOG(prefix<<qname<<": status=noerror, other types may exist, but we are done "<<(negindic ? "(have negative SOA) " : "")<<(lwr.d_aabit ? "(have aa bit) " : "")<<endl);
-
-    auto tempState = getValidationStatus(qname, negIndicHasSignatures, qtype == QType::DS, depth);
-    if (tempState == vState::Secure && (lwr.d_aabit || sendRDQuery) && !negindic) {
-      LOG(prefix<<qname<<": NODATA without a negative indication (missing SOA in authority) in a DNSSEC secure zone, going Bogus"<<endl);
-      updateValidationState(state, vState::BogusMissingNegativeIndication);
-    }
-    else if (state == vState::Indeterminate) {
-      /* we might not have validated any record, because we did get a NODATA without any SOA
-         from an insecure zone, for example */
-      updateValidationState(state, tempState);
-    }
-
-    if (d_doDNSSEC) {
-      addNXNSECS(ret, lwr.d_records);
-    }
-
-    *rcode = RCode::NoError;
-    return true;
-  }
-
-  if(realreferral) {
-    LOG(prefix<<qname<<": status=did not resolve, got "<<(unsigned int)nsset.size()<<" NS, ");
-
-    nameservers.clear();
-    for (auto const &nameserver : nsset) {
-      if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
-        bool match = dfe.getProcessingPolicy(nameserver, 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
-            if (d_pdl && d_pdl->policyHitEventFilter(d_requestor, qname, qtype, d_queryReceivedOverTCP, d_appliedPolicy, d_policyTags, d_discardedPolicies)) {
-              /* reset to no match */
-              d_appliedPolicy = DNSFilterEngine::Policy();
-            }
-            else {
-              LOG("however "<<nameserver<<" was blocked by RPZ policy '"<<d_appliedPolicy.getName()<<"'"<<endl);
-              throw PolicyHitException();
-            }
-          }
-        }
-      }
-      nameservers.insert({nameserver, {{}, false}});
-    }
-    LOG("looping to them"<<endl);
-    *gotNewServers = true;
-    auth=newauth;
-
-    return false;
-  }
-
-  return false;
-}
-
-bool SyncRes::doDoTtoAuth(const DNSName& ns) const
-{
-  return g_DoTToAuthNames.getLocal()->check(ns);
-}
-
-/** returns:
- *  -1 in case of no results
- *  rcode otherwise
- */
-int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, const DNSName &qname, const QType qtype,
-                         vector<DNSRecord>&ret,
-                         unsigned int depth, set<GetBestNSAnswer>&beenthere, vState& state, StopAtDelegation* stopAtDelegation)
-{
-  auto luaconfsLocal = g_luaconfs.getLocal();
-  string prefix;
-  if(doLog()) {
-    prefix=d_prefix;
-    prefix.append(depth, ' ');
-  }
-
-  LOG(prefix<<qname<<": Cache consultations done, have "<<(unsigned int)nameservers.size()<<" NS to contact");
-
-  if (nameserversBlockedByRPZ(luaconfsLocal->dfe, nameservers)) {
-    /* 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(endl);
-
-  unsigned int addressQueriesForNS = 0;
-  for(;;) { // we may get more specific nameservers
-    auto rnameservers = shuffleInSpeedOrder(nameservers, doLog() ? (prefix+qname.toString()+": ") : string() );
-
-    // We allow s_maxnsaddressqperq (default 10) queries with empty responses when resolving NS names.
-    // If a zone publishes many (more than s_maxnsaddressqperq) NS records, we allow less.
-    // This is to "punish" zones that publish many non-resolving NS names.
-    // We always allow 5 NS name resolving attempts with empty results.
-    unsigned int nsLimit = s_maxnsaddressqperq;
-    if (rnameservers.size() > nsLimit) {
-      int newLimit = static_cast<int>(nsLimit) - (rnameservers.size() - nsLimit);
-      nsLimit = std::max(5, newLimit);
-    }
-
-    for(auto tns=rnameservers.cbegin();;++tns) {
-      if (addressQueriesForNS >= nsLimit) {
-        throw ImmediateServFailException(std::to_string(nsLimit)+" (adjusted max-ns-address-qperq) or more queries with empty results for NS addresses sent resolving "+qname.toLogString());
-      }
-      if(tns==rnameservers.cend()) {
-        LOG(prefix<<qname<<": Failed to resolve via any of the "<<(unsigned int)rnameservers.size()<<" offered NS at level '"<<auth<<"'"<<endl);
-        if(!auth.isRoot() && flawedNSSet) {
-          LOG(prefix<<qname<<": Ageing nameservers for level '"<<auth<<"', next query might succeed"<<endl);
-
-          if(g_recCache->doAgeCache(d_now.tv_sec, auth, QType::NS, 10))
-            g_stats.nsSetInvalidations++;
-        }
-        return -1;
-      }
-
-      bool cacheOnly = false;
-      // this line needs to identify the 'self-resolving' behaviour
-      if(qname == tns->first && (qtype.getCode() == QType::A || qtype.getCode() == QType::AAAA)) {
-        /* we might have a glue entry in cache so let's try this NS
-           but only if we have enough in the cache to know how to reach it */
-        LOG(prefix<<qname<<": Using NS to resolve itself, but only using what we have in cache ("<<(1+tns-rnameservers.cbegin())<<"/"<<rnameservers.size()<<")"<<endl);
-        cacheOnly = true;
-      }
-
-      typedef vector<ComboAddress> remoteIPs_t;
-      remoteIPs_t remoteIPs;
-      remoteIPs_t::iterator remoteIP;
-      bool pierceDontQuery=false;
-      bool sendRDQuery=false;
-      boost::optional<Netmask> ednsmask;
-      LWResult lwr;
-      const bool wasForwarded = tns->first.empty() && (!nameservers[tns->first].first.empty());
-      int rcode = RCode::NoError;
-      bool gotNewServers = false;
-
-      if (tns->first.empty() && !wasForwarded) {
-        static ComboAddress const s_oobRemote("255.255.255.255");
-        LOG(prefix<<qname<<": Domain is out-of-band"<<endl);
-        /* setting state to indeterminate since validation is disabled for local auth zone,
-           and Insecure would be misleading. */
-        state = vState::Indeterminate;
-        d_wasOutOfBand = doOOBResolve(qname, qtype, lwr.d_records, depth, lwr.d_rcode);
-        lwr.d_tcbit=false;
-        lwr.d_aabit=true;
-
-        /* we have received an answer, are we done ? */
-        bool done = processAnswer(depth, lwr, qname, qtype, auth, false, ednsmask, sendRDQuery, nameservers, ret, luaconfsLocal->dfe, &gotNewServers, &rcode, state, s_oobRemote);
-        if (done) {
-          return rcode;
-        }
-        if (gotNewServers) {
-          if (stopAtDelegation && *stopAtDelegation == Stop) {
-            *stopAtDelegation = Stopped;
-            return rcode;
-          }
-          break;
-        }
-      }
-      else {
-        /* if tns is empty, retrieveAddressesForNS() knows we have hardcoded servers (i.e. "forwards") */
-        remoteIPs = retrieveAddressesForNS(prefix, qname, tns, depth, beenthere, rnameservers, nameservers, sendRDQuery, pierceDontQuery, flawedNSSet, cacheOnly, addressQueriesForNS);
-
-        if(remoteIPs.empty()) {
-          LOG(prefix<<qname<<": Failed to get IP for NS "<<tns->first<<", trying next if available"<<endl);
-          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;
-            }
-          }
-          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();
-            }
-          }
-        }
-
-        for(remoteIP = remoteIPs.begin(); remoteIP != remoteIPs.end(); ++remoteIP) {
-          LOG(prefix<<qname<<": Trying IP "<< remoteIP->toStringWithPort() <<", asking '"<<qname<<"|"<<qtype<<"'"<<endl);
-
-          if (throttledOrBlocked(prefix, *remoteIP, qname, qtype, pierceDontQuery)) {
-            continue;
-          }
-
-          bool truncated = false;
-          bool spoofed = false;
-          bool gotAnswer = false;
-          bool doDoT = false;
-
-          if (doDoTtoAuth(tns->first)) {
-            remoteIP->setPort(853);
-            doDoT = true;
-          }
-          if (SyncRes::s_dot_to_port_853 && remoteIP->getPort() == 853) {
-            doDoT = true;
-          }
-          bool forceTCP = doDoT;
-
-          if (!forceTCP) {
-            gotAnswer = doResolveAtThisIP(prefix, qname, qtype, lwr, ednsmask, auth, sendRDQuery, wasForwarded,
-                                          tns->first, *remoteIP, false, false, truncated, spoofed);
-          }
-          if (forceTCP || (spoofed || (gotAnswer && truncated))) {
-            /* retry, over TCP this time */
-            gotAnswer = doResolveAtThisIP(prefix, qname, qtype, lwr, ednsmask, auth, sendRDQuery, wasForwarded,
-                                          tns->first, *remoteIP, true, doDoT, truncated, spoofed);
-          }
-
-          if (!gotAnswer) {
-            continue;
-          }
-
-          LOG(prefix<<qname<<": Got "<<(unsigned int)lwr.d_records.size()<<" answers from "<<tns->first<<" ("<< remoteIP->toString() <<"), rcode="<<lwr.d_rcode<<" ("<<RCode::to_s(lwr.d_rcode)<<"), aa="<<lwr.d_aabit<<", in "<<lwr.d_usec/1000<<"ms"<<endl);
-
-          /*  // for you IPv6 fanatics :-)
-              if(remoteIP->sin4.sin_family==AF_INET6)
-              lwr.d_usec/=3;
-          */
-          //        cout<<"msec: "<<lwr.d_usec/1000.0<<", "<<g_avgLatency/1000.0<<'\n';
-
-          t_sstorage.nsSpeeds[tns->first.empty()? DNSName(remoteIP->toStringWithPort()) : tns->first].submit(*remoteIP, lwr.d_usec, d_now);
-
-          /* we have received an answer, are we done ? */
-          bool done = processAnswer(depth, lwr, qname, qtype, auth, wasForwarded, ednsmask, sendRDQuery, nameservers, ret, luaconfsLocal->dfe, &gotNewServers, &rcode, state, *remoteIP);
-          if (done) {
-            return rcode;
-          }
-          if (gotNewServers) {
-            if (stopAtDelegation && *stopAtDelegation == Stop) {
-              *stopAtDelegation = Stopped;
-              return rcode;
-            }
-            break;
-          }
-          /* was lame */
-          t_sstorage.throttle.throttle(d_now.tv_sec, std::make_tuple(*remoteIP, qname, qtype.getCode()), 60, 100);
-        }
-
-        if (gotNewServers) {
-          break;
-        }
-
-        if(remoteIP == remoteIPs.cend())  // we tried all IP addresses, none worked
-          continue;
-
-      }
-    }
-  }
-  return -1;
-}
-
-void SyncRes::setQuerySource(const ComboAddress& requestor, boost::optional<const EDNSSubnetOpts&> incomingECS)
-{
-  d_requestor = requestor;
-
-  if (incomingECS && incomingECS->source.getBits() > 0) {
-    d_cacheRemote = incomingECS->source.getMaskedNetwork();
-    uint8_t bits = std::min(incomingECS->source.getBits(), (incomingECS->source.isIPv4() ? s_ecsipv4limit : s_ecsipv6limit));
-    ComboAddress trunc = incomingECS->source.getNetwork();
-    trunc.truncate(bits);
-    d_outgoingECSNetwork = boost::optional<Netmask>(Netmask(trunc, bits));
-  } else {
-    d_cacheRemote = d_requestor;
-    if(!incomingECS && s_ednslocalsubnets.match(d_requestor)) {
-      ComboAddress trunc = d_requestor;
-      uint8_t bits = d_requestor.isIPv4() ? 32 : 128;
-      bits = std::min(bits, (trunc.isIPv4() ? s_ecsipv4limit : s_ecsipv6limit));
-      trunc.truncate(bits);
-      d_outgoingECSNetwork = boost::optional<Netmask>(Netmask(trunc, bits));
-    } else if (s_ecsScopeZero.source.getBits() > 0) {
-      /* RFC7871 says we MUST NOT send any ECS if the source scope is 0.
-         But using an empty ECS in that case would mean inserting
-         a non ECS-specific entry into the cache, preventing any further
-         ECS-specific query to be sent.
-         So instead we use the trick described in section 7.1.2:
-         "The subsequent Recursive Resolver query to the Authoritative Nameserver
-         will then either not include an ECS option or MAY optionally include
-         its own address information, which is what the Authoritative
-         Nameserver will almost certainly use to generate any Tailored
-         Response in lieu of an option.  This allows the answer to be handled
-         by the same caching mechanism as other queries, with an explicit
-         indicator of the applicable scope.  Subsequent Stub Resolver queries
-         for /0 can then be answered from this cached response.
-      */
-      d_outgoingECSNetwork = boost::optional<Netmask>(s_ecsScopeZero.source.getMaskedNetwork());
-      d_cacheRemote = s_ecsScopeZero.source.getNetwork();
-    } else {
-      // ECS disabled because no scope-zero address could be derived.
-      d_outgoingECSNetwork = boost::none;
-    }
-  }
-}
-
-boost::optional<Netmask> SyncRes::getEDNSSubnetMask(const DNSName& dn, const ComboAddress& rem)
-{
-  if(d_outgoingECSNetwork && (s_ednsdomains.check(dn) || s_ednsremotesubnets.match(rem))) {
-    return d_outgoingECSNetwork;
-  }
-  return boost::none;
-}
-
-void SyncRes::parseEDNSSubnetAllowlist(const std::string& alist)
-{
-  vector<string> parts;
-  stringtok(parts, alist, ",; ");
-  for(const auto& a : parts) {
-    try {
-      s_ednsremotesubnets.addMask(Netmask(a));
-    }
-    catch(...) {
-      s_ednsdomains.add(DNSName(a));
-    }
-  }
-}
-
-void SyncRes::parseEDNSSubnetAddFor(const std::string& subnetlist)
-{
-  vector<string> parts;
-  stringtok(parts, subnetlist, ",; ");
-  for(const auto& a : parts) {
-    s_ednslocalsubnets.addMask(a);
-  }
-}
-
-// 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)
-{
-  return directResolve(qname, qtype, qclass, ret, pdl, SyncRes::s_qnameminimization);
-}
-
-int directResolve(const DNSName& qname, const QType qtype, const QClass qclass, vector<DNSRecord>& ret, shared_ptr<RecursorLua4> pdl, bool qm)
-{
-  struct timeval now;
-  gettimeofday(&now, 0);
-
-  SyncRes sr(now);
-  sr.setQNameMinimization(qm);
-  if (pdl) {
-    sr.setLuaEngine(pdl);
-  }
-
-  int res = -1;
-  try {
-    res = sr.beginResolve(qname, qtype, qclass, ret, 0);
-  }
-  catch(const PDNSException& e) {
-    g_log<<Logger::Error<<"Failed to resolve "<<qname<<", got pdns exception: "<<e.reason<<endl;
-    ret.clear();
-  }
-  catch(const ImmediateServFailException& e) {
-    g_log<<Logger::Error<<"Failed to resolve "<<qname<<", got ImmediateServFailException: "<<e.reason<<endl;
-    ret.clear();
-  }
-  catch(const PolicyHitException& e) {
-    g_log<<Logger::Error<<"Failed to resolve "<<qname<<", got a policy hit"<<endl;
-    ret.clear();
-  }
-  catch(const std::exception& e) {
-    g_log<<Logger::Error<<"Failed to resolve "<<qname<<", got STL error: "<<e.what()<<endl;
-    ret.clear();
-  }
-  catch(...) {
-    g_log<<Logger::Error<<"Failed to resolve "<<qname<<", got an exception"<<endl;
-    ret.clear();
-  }
-
-  return res;
-}
-
-int SyncRes::getRootNS(struct timeval now, asyncresolve_t asyncCallback, unsigned int depth) {
-  SyncRes sr(now);
-  sr.setDoEDNS0(true);
-  sr.setUpdatingRootNS();
-  sr.setDoDNSSEC(g_dnssecmode != DNSSECMode::Off);
-  sr.setDNSSECValidationRequested(g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate);
-  sr.setAsyncCallback(asyncCallback);
-
-  vector<DNSRecord> ret;
-  int res = -1;
-  try {
-    res = sr.beginResolve(g_rootdnsname, QType::NS, 1, ret, depth + 1);
-    if (g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate) {
-      auto state = sr.getValidationState();
-      if (vStateIsBogus(state)) {
-        throw PDNSException("Got Bogus validation result for .|NS");
-      }
-    }
-  }
-  catch(const PDNSException& e) {
-    g_log<<Logger::Error<<"Failed to update . records, got an exception: "<<e.reason<<endl;
-  }
-  catch(const ImmediateServFailException& e) {
-    g_log<<Logger::Error<<"Failed to update . records, got an exception: "<<e.reason<<endl;
-  }
-  catch(const PolicyHitException& e) {
-    g_log<<Logger::Error<<"Failed to update . records, got a policy hit"<<endl;
-    ret.clear();
-  }
-  catch(const std::exception& e) {
-    g_log<<Logger::Error<<"Failed to update . records, got an exception: "<<e.what()<<endl;
-  }
-  catch(...) {
-    g_log<<Logger::Error<<"Failed to update . records, got an exception"<<endl;
-  }
-
-  if (res == 0) {
-    g_log<<Logger::Debug<<"Refreshed . records"<<endl;
-  }
-  else {
-    g_log<<Logger::Warning<<"Failed to update root NS records, RCODE="<<res<<endl;
-  }
-  return res;
-}
diff --git a/pdns/syncres.hh b/pdns/syncres.hh
deleted file mode 100644 (file)
index db4dc45..0000000
+++ /dev/null
@@ -1,1243 +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 <atomic>
-#include "utility.hh"
-#include "dns.hh"
-#include "qtype.hh"
-#include <vector>
-#include <set>
-#include <unordered_set>
-#include <map>
-#include <cmath>
-#include <iostream>
-#include <utility>
-#include "misc.hh"
-#include "lwres.hh"
-#include <boost/optional.hpp>
-#include <boost/utility.hpp>
-#include "circular_buffer.hh"
-#include "sstuff.hh"
-#include "recursor_cache.hh"
-#include "recpacketcache.hh"
-#include <boost/optional.hpp>
-#include "mtasker.hh"
-#include "iputils.hh"
-#include "validate-recursor.hh"
-#include "ednssubnet.hh"
-#include "filterpo.hh"
-#include "negcache.hh"
-#include "proxy-protocol.hh"
-#include "sholder.hh"
-#include "histogram.hh"
-#include "stat_t.hh"
-#include "tcpiohandler.hh"
-#include "rec-eventtrace.hh"
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include <boost/uuid/uuid.hpp>
-#ifdef HAVE_FSTRM
-#include "fstrm_logger.hh"
-#endif /* HAVE_FSTRM */
-
-extern GlobalStateHolder<SuffixMatchNode> g_xdnssec;
-extern GlobalStateHolder<SuffixMatchNode> g_dontThrottleNames;
-extern GlobalStateHolder<NetmaskGroup> g_dontThrottleNetmasks;
-extern GlobalStateHolder<SuffixMatchNode> g_DoTToAuthNames;
-
-enum class AdditionalMode : uint8_t; // defined in rec-lua-conf.hh
-
-class RecursorLua4;
-
-typedef std::unordered_map<
-  DNSName,
-  pair<
-    vector<ComboAddress>,
-    bool
-  >
-> NsSet;
-
-template<class Thing> class Throttle : public boost::noncopyable
-{
-public:
-
-  struct entry_t
-  {
-    Thing thing;
-    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;
-
-  bool shouldThrottle(time_t now, const Thing &t)
-  {
-    auto i = d_cont.find(t);
-    if (i == d_cont.end()) {
-      return false;
-    }
-    if (now > i->ttd || i->count == 0) {
-      d_cont.erase(i);
-      return false;
-    }
-    i->count--;
-
-    return true; // still listed, still blocked
-  }
-
-  void throttle(time_t now, const Thing &t, time_t ttl, unsigned int count)
-  {
-    auto i = d_cont.find(t);
-    time_t ttd = now + ttl;
-    if (i == d_cont.end()) {
-      entry_t e = { t, ttd, count };
-      d_cont.insert(e);
-    } else if (ttd > i->ttd || count > i->count) {
-      ttd = std::max(i->ttd, ttd);
-      count = std::max(i->count, count);
-      auto &ind = d_cont.template get<Thing>();
-      ind.modify(i, [ttd,count](entry_t &e) { e.ttd = ttd; e.count = count; });
-    }
-  }
-
-  size_t size() const
-  {
-    return d_cont.size();
-  }
-
-  const cont_t &getThrottleMap() const
-  {
-    return d_cont;
-  }
-
-  void clear()
-  {
-    d_cont.clear();
-  }
-
-  void prune() {
-    time_t now = time(nullptr);
-    auto &ind = d_cont.template get<time_t>();
-    ind.erase(ind.begin(), ind.upper_bound(now));
-  }
-
-private:
-  cont_t d_cont;
-};
-
-
-/** Class that implements a decaying EWMA.
-    This class keeps an exponentially weighted moving average which, additionally, decays over time.
-    The decaying is only done on get.
-*/
-class DecayingEwma
-{
-public:
-  DecayingEwma() {}
-  DecayingEwma(const DecayingEwma& orig) = delete;
-  DecayingEwma & operator=(const DecayingEwma& orig) = delete;
-  
-  void submit(int val, const struct timeval& now)
-  {
-    if (d_last.tv_sec == 0 && d_last.tv_usec == 0) {
-      d_last = now;
-      d_val = val;
-    }
-    else {
-      float diff = makeFloat(d_last - now);
-      d_last = now;
-      float factor = expf(diff)/2.0f; // might be '0.5', or 0.0001
-      d_val = (1-factor)*val + factor*d_val;
-    }
-  }
-
-  float get(float factor)
-  {
-    return d_val *= factor;
-  }
-
-  float peek(void) const
-  {
-    return d_val;
-  }
-
-private:
-  struct timeval d_last{0, 0};          // stores time
-  float d_val{0};
-};
-
-template<class T>
-class fails_t : public boost::noncopyable
-{
-public:
-  typedef uint64_t counter_t;
-  struct value_t {
-    value_t(const T &a) : key(a) {}
-    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;
-
-  cont_t getMapCopy() const {
-    return d_cont;
-  }
-
-  counter_t value(const T& t) const
-  {
-    auto i = d_cont.find(t);
-
-    if (i == d_cont.end()) {
-      return 0;
-    }
-    return i->value;
-  }
-
-  counter_t incr(const T& key, const struct timeval& now)
-  {
-    auto i = d_cont.insert(key).first;
-
-    if (i->value < std::numeric_limits<counter_t>::max()) {
-      i->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;
-  }
-
-  void clear(const T& a)
-  {
-    d_cont.erase(a);
-  }
-
-  void clear()
-  {
-    d_cont.clear();
-  }
-
-  size_t size() const
-  {
-    return d_cont.size();
-  }
-
-  void prune(time_t cutoff) {
-    auto &ind = d_cont.template get<time_t>();
-    ind.erase(ind.begin(), ind.upper_bound(cutoff));
-  }
-
-private:
-  cont_t d_cont;
-};
-
-extern std::unique_ptr<NegCache> g_negCache;
-
-class SyncRes : public boost::noncopyable
-{
-public:
-  enum LogMode { LogNone, 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;
-
-  enum class HardenNXD { No, DNSSEC, Yes };
-
-  //! This represents a number of decaying Ewmas, used to store performance per nameserver-name.
-  /** Modelled to work mostly like the underlying DecayingEwma */
-  struct DecayingEwmaCollection
-  {
-    void submit(const ComboAddress& remote, int usecs, const struct timeval& now)
-    {
-      d_collection[remote].submit(usecs, now);
-    }
-
-    float getFactor(const struct timeval &now) {
-      float diff = makeFloat(d_lastget - now);
-      return expf(diff / 60.0f); // is 1.0 or less
-    }
-    
-    float get(const struct timeval& now)
-    {
-      if (d_collection.empty()) {
-        return 0;
-      }
-      if (d_lastget.tv_sec == 0 && d_lastget.tv_usec == 0) {
-        d_lastget = now;
-      }
-
-      float ret = std::numeric_limits<float>::max();
-      float factor = getFactor(now);
-      float tmp;
-      for (auto& entry : d_collection) {
-        if ((tmp = entry.second.get(factor)) < ret) {
-          ret = tmp;
-        }
-      }
-      d_lastget = now;
-      return ret;
-    }
-
-    bool stale(time_t limit) const
-    {
-      return limit > d_lastget.tv_sec;
-    }
-
-    void purge(const std::map<ComboAddress, float>& keep)
-    {
-      for (auto iter = d_collection.begin(); iter != d_collection.end(); ) {
-        if (keep.find(iter->first) != keep.end()) {
-          ++iter;
-        }
-        else {
-          iter = d_collection.erase(iter);
-        }
-      }
-    }
-
-    typedef std::map<ComboAddress, DecayingEwma> collection_t;
-    collection_t d_collection;
-    struct timeval d_lastget{0, 0};       // stores time
-  };
-
-  typedef std::unordered_map<DNSName, DecayingEwmaCollection> nsspeeds_t;
-
-  vState getDSRecords(const DNSName& zone, dsmap_t& ds, bool onlyTA, unsigned int depth, bool bogusOnNXD=true, bool* foundCut=nullptr);
-
-  class AuthDomain
-  {
-  public:
-    typedef 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;
-
-    records_t d_records;
-    vector<ComboAddress> d_servers;
-    DNSName d_name;
-    bool d_rdForward{false};
-
-    int getRecords(const DNSName& qname, QType qtype, std::vector<DNSRecord>& records) const;
-    bool isAuth() const
-    {
-      return d_servers.empty();
-    }
-    bool isForward() const
-    {
-      return !isAuth();
-    }
-    bool shouldRecurse() const
-    {
-      return d_rdForward;
-    }
-    const DNSName& getName() const
-    {
-      return d_name;
-    }
-
-  private:
-    void addSOA(std::vector<DNSRecord>& records) const;
-  };
-
-  typedef std::unordered_map<DNSName, AuthDomain> domainmap_t;
-  typedef Throttle<std::tuple<ComboAddress,DNSName,uint16_t> > throttle_t;
-
-  struct EDNSStatus {
-    EDNSStatus(const ComboAddress &arg) : address(arg) {}
-    ComboAddress address;
-    time_t modeSetAt{0};
-    mutable enum EDNSMode { UNKNOWN=0, EDNSOK=1, EDNSIGNORANT=2, NOEDNS=3 } mode{UNKNOWN};
-  };
-
-  struct ednsstatus_t : public multi_index_container<EDNSStatus,
-                                                     indexed_by<
-                                                       ordered_unique<tag<ComboAddress>, member<EDNSStatus, ComboAddress, &EDNSStatus::address>>,
-                                                       ordered_non_unique<tag<time_t>, member<EDNSStatus, time_t, &EDNSStatus::modeSetAt>>
-                                  >> {
-    void reset(index<ComboAddress>::type &ind, iterator it) {
-      ind.modify(it, [](EDNSStatus &s) { s.mode = EDNSStatus::EDNSMode::UNKNOWN; s.modeSetAt = 0; }); 
-    }
-    void setMode(index<ComboAddress>::type &ind, iterator it, EDNSStatus::EDNSMode mode) {
-      it->mode = mode;
-    }
-    void setTS(index<ComboAddress>::type &ind, iterator it, time_t ts) {
-      ind.modify(it, [ts](EDNSStatus &s) { s.modeSetAt = ts; });
-    }
-
-    void prune(time_t cutoff) {
-      auto &ind = get<time_t>();
-      ind.erase(ind.begin(), ind.upper_bound(cutoff));
-    }
-
-  };
-
-  static LockGuarded<fails_t<ComboAddress>> s_fails;
-  static LockGuarded<fails_t<DNSName>> s_nonresolving;
-
-  struct ThreadLocalStorage {
-    nsspeeds_t nsSpeeds;
-    throttle_t throttle;
-    ednsstatus_t ednsstatus;
-    std::shared_ptr<domainmap_t> domainmap;
-  };
-
-  static void setDefaultLogMode(LogMode lm)
-  {
-    s_lm = lm;
-  }
-  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 int getRootNS(struct timeval now, asyncresolve_t asyncCallback, unsigned int depth);
-  static void addDontQuery(const std::string& mask)
-  {
-    if (!s_dontQuery)
-      s_dontQuery = std::make_unique<NetmaskGroup>();
-
-    s_dontQuery->addMask(mask);
-  }
-  static void addDontQuery(const Netmask& mask)
-  {
-    if (!s_dontQuery)
-      s_dontQuery = std::make_unique<NetmaskGroup>();
-
-    s_dontQuery->addMask(mask);
-  }
-  static void clearDontQuery()
-  {
-    s_dontQuery = nullptr;
-  }
-  static void parseEDNSSubnetAllowlist(const std::string& alist);
-  static void parseEDNSSubnetAddFor(const std::string& subnetlist);
-  static void addEDNSLocalSubnet(const std::string& subnet)
-  {
-    s_ednslocalsubnets.addMask(subnet);
-  }
-  static void addEDNSRemoteSubnet(const std::string& subnet)
-  {
-    s_ednsremotesubnets.addMask(subnet);
-  }
-  static void addEDNSDomain(const DNSName& domain)
-  {
-    s_ednsdomains.add(domain);
-  }
-  static void clearEDNSLocalSubnets()
-  {
-    s_ednslocalsubnets.clear();
-  }
-  static void clearEDNSRemoteSubnets()
-  {
-    s_ednsremotesubnets.clear();
-  }
-  static void clearEDNSDomains()
-  {
-    s_ednsdomains = SuffixMatchNode();
-  }
-  static void pruneNSSpeeds(time_t limit)
-  {
-    for(auto i = t_sstorage.nsSpeeds.begin(), end = t_sstorage.nsSpeeds.end(); i != end; ) {
-      if(i->second.stale(limit)) {
-        i = t_sstorage.nsSpeeds.erase(i);
-      }
-      else {
-        ++i;
-      }
-    }
-  }
-  static uint64_t getNSSpeedsSize()
-  {
-    return t_sstorage.nsSpeeds.size();
-  }
-  static void submitNSSpeed(const DNSName& server, const ComboAddress& ca, uint32_t usec, const struct timeval& now)
-  {
-    t_sstorage.nsSpeeds[server].submit(ca, usec, now);
-  }
-  static void clearNSSpeeds()
-  {
-    t_sstorage.nsSpeeds.clear();
-  }
-  static float getNSSpeed(const DNSName& server, const ComboAddress& ca)
-  {
-    return t_sstorage.nsSpeeds[server].d_collection[ca].peek();
-  }
-  static EDNSStatus::EDNSMode getEDNSStatus(const ComboAddress& server)
-  {
-    const auto& it = t_sstorage.ednsstatus.find(server);
-    if (it == t_sstorage.ednsstatus.end())
-      return EDNSStatus::UNKNOWN;
-
-    return it->mode;
-  }
-  static uint64_t getEDNSStatusesSize()
-  {
-    return t_sstorage.ednsstatus.size();
-  }
-  static void clearEDNSStatuses()
-  {
-    t_sstorage.ednsstatus.clear();
-  }
-  static void pruneEDNSStatuses(time_t cutoff)
-  {
-    t_sstorage.ednsstatus.prune(cutoff);
-  }
-  static uint64_t getThrottledServersSize()
-  {
-    return t_sstorage.throttle.size();
-  }
-  static void pruneThrottledServers()
-  {
-    t_sstorage.throttle.prune();
-  }
-  static void clearThrottle()
-  {
-    t_sstorage.throttle.clear();
-  }
-  static bool isThrottled(time_t now, const ComboAddress& server, const DNSName& target, uint16_t qtype)
-  {
-    return t_sstorage.throttle.shouldThrottle(now, std::make_tuple(server, target, qtype));
-  }
-  static bool isThrottled(time_t now, const ComboAddress& server)
-  {
-    return t_sstorage.throttle.shouldThrottle(now, std::make_tuple(server, g_rootdnsname, 0));
-  }
-  static void doThrottle(time_t now, const ComboAddress& server, time_t duration, unsigned int tries)
-  {
-    t_sstorage.throttle.throttle(now, std::make_tuple(server, g_rootdnsname, 0), duration, tries);
-  }
-  static uint64_t getFailedServersSize()
-  {
-    return s_fails.lock()->size();
-  }
-  static uint64_t getNonResolvingNSSize()
-  {
-    return s_nonresolving.lock()->size();
-  }
-  static void clearFailedServers()
-  {
-    s_fails.lock()->clear();
-  }
-  static void clearNonResolvingNS()
-  {
-    s_nonresolving.lock()->clear();
-  }
-  static void pruneFailedServers(time_t cutoff)
-  {
-    s_fails.lock()->prune(cutoff);
-  }
-  static unsigned long getServerFailsCount(const ComboAddress& server)
-  {
-    return s_fails.lock()->value(server);
-  }
-  static void pruneNonResolving(time_t cutoff)
-  {
-    s_nonresolving.lock()->prune(cutoff);
-  }
-  static void setDomainMap(std::shared_ptr<domainmap_t> newMap)
-  {
-    t_sstorage.domainmap = newMap;
-  }
-  static const std::shared_ptr<domainmap_t> getDomainMap()
-  {
-    return t_sstorage.domainmap;
-  }
-
-  static void setECSScopeZeroAddress(const Netmask& scopeZeroMask)
-  {
-    s_ecsScopeZero.source = scopeZeroMask;
-  }
-
-  static void clearECSStats()
-  {
-    s_ecsqueries.store(0);
-    s_ecsresponses.store(0);
-
-    for (size_t idx = 0; idx < 32; idx++) {
-      SyncRes::s_ecsResponsesBySubnetSize4[idx].store(0);
-    }
-
-    for (size_t idx = 0; idx < 128; idx++) {
-      SyncRes::s_ecsResponsesBySubnetSize6[idx].store(0);
-    }
-  }
-
-  explicit SyncRes(const struct timeval& now);
-
-  int beginResolve(const DNSName &qname, QType qtype, QClass qclass, vector<DNSRecord>&ret, unsigned int depth = 0);
-
-  void setId(int id)
-  {
-    if(doLog())
-      d_prefix="["+itoa(id)+"] ";
-  }
-
-  void setLogMode(LogMode lm)
-  {
-    d_lm = lm;
-  }
-
-  bool doLog() const
-  {
-    return d_lm != LogNone;
-  }
-
-  bool setCacheOnly(bool state = true)
-  {
-    bool old = d_cacheonly;
-    d_cacheonly = state;
-    return old;
-  }
-
-  bool setRefreshAlmostExpired(bool doit)
-  {
-    auto old = d_refresh;
-    d_refresh = doit;
-    return old;
-  }
-
-  void setQNameMinimization(bool state=true)
-  {
-    d_qNameMinimization=state;
-  }
-
-  void setDoEDNS0(bool state=true)
-  {
-    d_doEDNS0=state;
-  }
-
-  void setDoDNSSEC(bool state=true)
-  {
-    d_doDNSSEC=state;
-  }
-
-  void setDNSSECValidationRequested(bool requested=true)
-  {
-    d_DNSSECValidationRequested = requested;
-  }
-
-  bool isDNSSECValidationRequested() const
-  {
-    return d_DNSSECValidationRequested;
-  }
-
-  bool shouldValidate() const
-  {
-    return d_DNSSECValidationRequested && !d_wasOutOfBand;
-  }
-
-  void setWantsRPZ(bool state=true)
-  {
-    d_wantsRPZ=state;
-  }
-
-  bool getWantsRPZ() const
-  {
-    return d_wantsRPZ;
-  }
-
-  string getTrace() const
-  {
-    return d_trace.str();
-  }
-
-  bool getQNameMinimization() const
-  {
-    return d_qNameMinimization;
-  }
-
-  void setLuaEngine(shared_ptr<RecursorLua4> pdl)
-  {
-    d_pdl = pdl;
-  }
-
-  bool wasVariable() const
-  {
-    return d_wasVariable;
-  }
-
-  bool wasOutOfBand() const
-  {
-    return d_wasOutOfBand;
-  }
-
-  struct timeval getNow() const
-  {
-    return d_now;
-  }
-
-  void setQuerySource(const ComboAddress& requestor, boost::optional<const EDNSSubnetOpts&> incomingECS);
-
-  void setInitialRequestId(boost::optional<const boost::uuids::uuid&> initialRequestId)
-  {
-    d_initialRequestId = initialRequestId;
-  }
-
-  void setOutgoingProtobufServers(std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& servers)
-  {
-    d_outgoingProtobufServers = servers;
-  }
-
-#ifdef HAVE_FSTRM
-  void setFrameStreamServers(std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& servers)
-  {
-    d_frameStreamServers = servers;
-  }
-#endif /* HAVE_FSTRM */
-
-  void setAsyncCallback(asyncresolve_t func)
-  {
-    d_asyncResolve = func;
-  }
-
-  vState getValidationState() const
-  {
-    return d_queryValidationState;
-  }
-
-  void setQueryReceivedOverTCP(bool tcp)
-  {
-    d_queryReceivedOverTCP = tcp;
-  }
-
-  static bool isUnsupported(QType qtype)
-  {
-    switch (qtype.getCode()) {
-      // Internal types
-    case QType::ENT:
-    case QType::ADDR:
-      return true;
-    }
-    return false;
-  }
-
-  static thread_local ThreadLocalStorage t_sstorage;
-
-  static pdns::stat_t s_queries;
-  static pdns::stat_t s_outgoingtimeouts;
-  static pdns::stat_t s_outgoing4timeouts;
-  static pdns::stat_t s_outgoing6timeouts;
-  static pdns::stat_t s_throttledqueries;
-  static pdns::stat_t s_dontqueries;
-  static pdns::stat_t s_qnameminfallbacksuccess;
-  static pdns::stat_t s_authzonequeries;
-  static pdns::stat_t s_outqueries;
-  static pdns::stat_t s_tcpoutqueries;
-  static pdns::stat_t s_dotoutqueries;
-  static pdns::stat_t s_unreachables;
-  static pdns::stat_t s_ecsqueries;
-  static pdns::stat_t s_ecsresponses;
-  static std::map<uint8_t, pdns::stat_t> s_ecsResponsesBySubnetSize4;
-  static std::map<uint8_t, pdns::stat_t> s_ecsResponsesBySubnetSize6;
-
-  static string s_serverID;
-  static unsigned int s_minimumTTL;
-  static unsigned int s_minimumECSTTL;
-  static unsigned int s_maxqperq;
-  static unsigned int s_maxnsaddressqperq;
-  static unsigned int s_maxtotusec;
-  static unsigned int s_maxdepth;
-  static unsigned int s_maxnegttl;
-  static unsigned int s_maxbogusttl;
-  static unsigned int s_maxcachettl;
-  static unsigned int s_packetcachettl;
-  static unsigned int s_packetcacheservfailttl;
-  static unsigned int s_serverdownmaxfails;
-  static unsigned int s_serverdownthrottletime;
-  static unsigned int s_nonresolvingnsmaxfails;
-  static unsigned int s_nonresolvingnsthrottletime;
-
-  static unsigned int s_ecscachelimitttl;
-  static uint8_t s_ecsipv4limit;
-  static uint8_t s_ecsipv6limit;
-  static uint8_t s_ecsipv4cachelimit;
-  static uint8_t s_ecsipv6cachelimit;
-  static bool s_ecsipv4nevercache;
-  static bool s_ecsipv6nevercache;
-
-  static bool s_doIPv4;
-  static bool s_doIPv6;
-  static bool s_noEDNSPing;
-  static bool s_noEDNS;
-  static bool s_rootNXTrust;
-  static bool s_nopacketcache;
-  static bool s_qnameminimization;
-  static HardenNXD s_hardenNXD;
-  static unsigned int s_refresh_ttlperc;
-  static int s_tcp_fast_open;
-  static bool s_tcp_fast_open_connect;
-  static bool s_dot_to_port_853;
-
-  static const int event_trace_to_pb = 1;
-  static const int event_trace_to_log = 2;
-  static int s_event_trace_enabled;
-
-  std::unordered_map<std::string,bool> d_discardedPolicies;
-  DNSFilterEngine::Policy d_appliedPolicy;
-  std::unordered_set<std::string> d_policyTags;
-  boost::optional<string> d_routingTag;
-  ComboAddress d_fromAuthIP;
-  RecEventTrace d_eventTrace;
-
-  unsigned int d_authzonequeries;
-  unsigned int d_outqueries;
-  unsigned int d_tcpoutqueries;
-  unsigned int d_dotoutqueries;
-  unsigned int d_throttledqueries;
-  unsigned int d_timeouts;
-  unsigned int d_unreachables;
-  unsigned int d_totUsec;
-
-private:
-  ComboAddress d_requestor;
-  ComboAddress d_cacheRemote;
-
-  static NetmaskGroup s_ednslocalsubnets;
-  static NetmaskGroup s_ednsremotesubnets;
-  static SuffixMatchNode s_ednsdomains;
-  static EDNSSubnetOpts s_ecsScopeZero;
-  static LogMode s_lm;
-  static std::unique_ptr<NetmaskGroup> s_dontQuery;
-  const static std::unordered_set<QType> s_redirectionQTypes;
-
-  struct GetBestNSAnswer
-  {
-    DNSName qname;
-    set<pair<DNSName,DNSName> > bestns;
-    uint8_t qtype;
-    bool operator<(const GetBestNSAnswer &b) const
-    {
-      return std::tie(qtype, qname, bestns) <
-       std::tie(b.qtype, b.qname, b.bestns);
-    }
-  };
-
-  typedef std::map<DNSName,vState> zonesStates_t;
-  enum StopAtDelegation { DontStop, Stop, Stopped };
-
-  void resolveAdditionals(const DNSName& qname, QType qtype, AdditionalMode, std::vector<DNSRecord>& additionals, unsigned int depth);
-  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);
-  void addAdditionals(QType qtype, vector<DNSRecord>&ret, unsigned int depth);
-
-  bool doDoTtoAuth(const DNSName& ns) const;
-  int doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, const DNSName &qname, QType qtype, vector<DNSRecord>&ret,
-                  unsigned int depth, set<GetBestNSAnswer>&beenthere, vState& state, StopAtDelegation* stopAtDelegation);
-  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);
-  bool processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, bool sendRDQuery, NsSet &nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode, vState& state, const ComboAddress& remoteIP);
-
-  int doResolve(const DNSName &qname, QType qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state);
-  int doResolveNoQNameMinimization(const DNSName &qname, QType qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, bool* fromCache = NULL, StopAtDelegation* stopAtDelegation = NULL, bool considerforwards = true);
-  bool doOOBResolve(const AuthDomain& domain, const DNSName &qname, QType qtype, vector<DNSRecord>&ret, int& res);
-  bool doOOBResolve(const DNSName &qname, QType qtype, vector<DNSRecord>&ret, unsigned int depth, 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, int &res, vState& state, bool wasAuthZone, bool wasForwardRecurse);
-  bool doCacheCheck(const DNSName &qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state);
-  void getBestNSFromCache(const DNSName &qname, QType qtype, vector<DNSRecord>&bestns, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>& beenthere, const boost::optional<DNSName>& cutOffDomain = boost::none);
-  DNSName getBestNSNamesFromCache(const DNSName &qname, QType qtype, NsSet& nsset, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>&beenthere);
-
-  inline vector<std::pair<DNSName, float>> shuffleInSpeedOrder(NsSet &nameservers, const string &prefix);
-  inline vector<ComboAddress> shuffleForwardSpeed(const vector<ComboAddress> &rnameservers, const string &prefix, const bool wasRd);
-  bool moreSpecificThan(const DNSName& a, const DNSName &b) const;
-  vector<ComboAddress> getAddrs(const DNSName &qname, unsigned int depth, set<GetBestNSAnswer>& beenthere, bool cacheOnly, unsigned int& addressQueriesForNS);
-
-  bool nameserversBlockedByRPZ(const DNSFilterEngine& dfe, const NsSet& nameservers);
-  bool nameserverIPBlockedByRPZ(const DNSFilterEngine& dfe, const ComboAddress&);
-  bool throttledOrBlocked(const std::string& prefix, const ComboAddress& remoteIP, const DNSName& qname, 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);
-
-  void sanitizeRecords(const std::string& prefix, LWResult& lwr, const DNSName& qname, const 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);
-  RCode::rcodes_ updateCacheFromRecords(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType qtype, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask>, vState& state, bool& needWildcardProof, bool& gatherWildcardProof, unsigned int& wildcardLabelsCount, bool sendRDQuery, 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);
-
-  bool doSpecialNamesResolve(const DNSName &qname, QType qtype, const 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;
-
-  boost::optional<Netmask> getEDNSSubnetMask(const DNSName&dn, const ComboAddress& rem);
-
-  bool validationEnabled() const;
-  uint32_t computeLowestTTD(const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures, uint32_t signaturesTTL, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs) const;
-  void updateValidationState(vState& state, const vState stateUpdate);
-  vState validateRecordsWithSigs(unsigned int depth, const DNSName& qname, const QType qtype, const DNSName& name, const QType type, const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures);
-  vState validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord>& dnskeys, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures, unsigned int depth);
-  vState getDNSKeys(const DNSName& signer, skeyset_t& keys, unsigned int depth);
-  dState getDenialValidationState(const NegCache::NegCacheEntry& ne, const dState expectedState, bool referralToUnsigned);
-  void updateDenialValidationState(vState& neValidationState, const DNSName& neName, vState& state, const dState denialState, const dState expectedState, bool isDS, unsigned int depth);
-  void computeNegCacheValidationStatus(const NegCache::NegCacheEntry& ne, const DNSName& qname, QType qtype, const int res, vState& state, unsigned int depth);
-  vState getTA(const DNSName& zone, dsmap_t& ds);
-  vState getValidationStatus(const DNSName& subdomain, bool wouldBeValid, bool typeIsDS, unsigned int depth);
-  void updateValidationStatusInCache(const DNSName &qname, QType qt, bool aa, vState newState) const;
-  void initZoneCutsFromTA(const DNSName& from);
-
-  void handleNewTarget(const std::string& prefix, const DNSName& qname, const DNSName& newtarget, QType qtype, std::vector<DNSRecord>& ret, int& rcode, int depth, const std::vector<DNSRecord>& recordsFromAnswer, vState& state);
-
-  void handlePolicyHit(const std::string& prefix, const DNSName& qname, QType qtype, vector<DNSRecord>& ret, bool& done, int& rcode, unsigned int depth);
-
-  void setUpdatingRootNS()
-  {
-    d_updatingRootNS = true;
-  }
-
-  zonesStates_t d_cutStates;
-  ostringstream d_trace;
-  shared_ptr<RecursorLua4> d_pdl;
-  boost::optional<Netmask> d_outgoingECSNetwork;
-  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;
-  asyncresolve_t d_asyncResolve{nullptr};
-  struct timeval d_now;
-  /* 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 */
-  DNSName d_externalDSQuery;
-  string d_prefix;
-  vState d_queryValidationState{vState::Indeterminate};
-
-  /* When d_cacheonly is set to true, we will only check the cache.
-   * This is set when the RD bit is unset in the incoming query
-   */
-  bool d_cacheonly;
-  bool d_doDNSSEC;
-  bool d_DNSSECValidationRequested{false};
-  bool d_doEDNS0{true};
-  bool d_requireAuthData{true};
-  bool d_updatingRootNS{false};
-  bool d_wantsRPZ{true};
-  bool d_wasOutOfBand{false};
-  bool d_wasVariable{false};
-  bool d_qNameMinimization{false};
-  bool d_queryReceivedOverTCP{false};
-  bool d_followCNAME{true};
-  bool d_refresh{false};
-
-  LogMode d_lm;
-};
-
-/* external functions, opaque to us */
-LWResult::Result asendtcp(const PacketBuffer& data, shared_ptr<TCPIOHandler>&);
-LWResult::Result arecvtcp(PacketBuffer& data, size_t len, shared_ptr<TCPIOHandler>&, bool incompleteOkay);
-
-enum TCPAction : uint8_t { DoingRead, DoingWrite };
-
-struct PacketID
-{
-  PacketID()
-  {
-    remote.reset();
-  }
-
-  ComboAddress remote;  // this is the remote
-  DNSName domain;             // this is the question
-
-  PacketBuffer inMSG; // they'll go here
-  PacketBuffer outMSG; // the outgoing message that needs to be sent
-
-  typedef set<uint16_t > chain_t;
-  mutable chain_t chain;
-  shared_ptr<TCPIOHandler> tcphandler{nullptr};
-  string::size_type inPos{0};   // how far are we along in the inMSG
-  size_t inWanted{0}; // if this is set, we'll read until inWanted bytes are read
-  string::size_type outPos{0};    // how far we are along in the outMSG
-  mutable uint32_t nearMisses{0}; // number of near misses - host correct, id wrong
-  int fd{-1};
-  int tcpsock{0};  // or wait for an event on a TCP fd
-  mutable bool closed{false}; // Processing already started, don't accept new chained ids
-  bool inIncompleteOkay{false};
-  uint16_t id{0};  // wait for a specific id/remote pair
-  uint16_t type{0};             // and this is its type
-  TCPAction highState;
-  IOState lowState;
-
-  bool operator<(const PacketID& b) const
-  {
-    // We don't want explicit PacketID compare here, but always via predicate classes below
-    assert(0);
-  }
-};
-
-inline ostream& operator<<(ostream & os, const PacketID& pid)
-{
-  return os << "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)
-{
-  return os << *pid;
-}
-
-/*
- * The two compare predicates below must be consistent!
- * PacketIDBirthdayCompare can omit minor fields, but not change the or skip fields
- * order! See boost docs on CompatibleCompare.
- */
-struct PacketIDCompare
-{
-  bool operator()(const std::shared_ptr<PacketID>& a, const std::shared_ptr<PacketID>& b) const
-  {
-    if (std::tie(a->remote, a->tcpsock, a->type) < std::tie(b->remote, b->tcpsock, b->type)) {
-      return true;
-    }
-    if (std::tie(a->remote, a->tcpsock, a->type) > std::tie(b->remote, b->tcpsock, b->type)) {
-      return false;
-    }
-
-    return std::tie(a->domain, a->fd, a->id) < std::tie(b->domain, b->fd, b->id);
-  }
-};
-
-struct PacketIDBirthdayCompare
-{
-  bool operator()(const std::shared_ptr<PacketID>& a, const std::shared_ptr<PacketID>& b) const
-  {
-    if (std::tie(a->remote, a->tcpsock, a->type) < std::tie(b->remote, b->tcpsock, b->type)) {
-      return true;
-    }
-    if (std::tie(a->remote, a->tcpsock, a->type) > std::tie(b->remote, b->tcpsock, b->type)) {
-      return false;
-    }
-    return a->domain < b->domain;
-  }
-};
-extern std::unique_ptr<MemRecursorCache> g_recCache;
-extern thread_local std::unique_ptr<RecursorPacketCache> t_packetCache;
-
-struct RecursorStats
-{
-  pdns::stat_t servFails;
-  pdns::stat_t nxDomains;
-  pdns::stat_t noErrors;
-  pdns::AtomicHistogram answers;
-  pdns::AtomicHistogram auth4Answers;
-  pdns::AtomicHistogram auth6Answers;
-  pdns::AtomicHistogram ourtime;
-  pdns::AtomicHistogram cumulativeAnswers;
-  pdns::AtomicHistogram cumulativeAuth4Answers;
-  pdns::AtomicHistogram cumulativeAuth6Answers;
-  pdns::stat_t_trait<double> avgLatencyUsec;
-  pdns::stat_t_trait<double> avgLatencyOursUsec;
-  pdns::stat_t qcounter;     // not increased for unauth packets
-  pdns::stat_t ipv6qcounter;
-  pdns::stat_t tcpqcounter;
-  pdns::stat_t unauthorizedUDP;  // when this is increased, qcounter isn't
-  pdns::stat_t unauthorizedTCP;  // when this is increased, qcounter isn't
-  pdns::stat_t sourceDisallowedNotify;  // when this is increased, qcounter is also
-  pdns::stat_t zoneDisallowedNotify;  // when this is increased, qcounter is also
-  pdns::stat_t policyDrops;
-  pdns::stat_t tcpClientOverflow;
-  pdns::stat_t clientParseError;
-  pdns::stat_t serverParseError;
-  pdns::stat_t tooOldDrops;
-  pdns::stat_t truncatedDrops;
-  pdns::stat_t queryPipeFullDrops;
-  pdns::stat_t unexpectedCount;
-  pdns::stat_t caseMismatchCount;
-  pdns::stat_t spoofCount;
-  pdns::stat_t resourceLimits;
-  pdns::stat_t overCapacityDrops;
-  pdns::stat_t ipv6queries;
-  pdns::stat_t chainResends;
-  pdns::stat_t nsSetInvalidations;
-  pdns::stat_t ednsPingMatches;
-  pdns::stat_t ednsPingMismatches;
-  pdns::stat_t noPingOutQueries, noEdnsOutQueries;
-  pdns::stat_t packetCacheHits;
-  pdns::stat_t noPacketError;
-  pdns::stat_t ignoredCount;
-  pdns::stat_t emptyQueriesCount;
-  time_t startupTime;
-  pdns::stat_t dnssecQueries;
-  pdns::stat_t dnssecAuthenticDataQueries;
-  pdns::stat_t dnssecCheckDisabledQueries;
-  pdns::stat_t variableResponses;
-  pdns::stat_t maxMThreadStackUsage;
-  pdns::stat_t dnssecValidations; // should be the sum of all dnssecResult* stats
-  std::map<vState, pdns::stat_t > dnssecResults;
-  std::map<vState, pdns::stat_t > xdnssecResults;
-  std::map<DNSFilterEngine::PolicyKind, pdns::stat_t > policyResults;
-  LockGuarded<std::unordered_map<std::string, pdns::stat_t>> policyHits;
-  pdns::stat_t rebalancedQueries{0};
-  pdns::stat_t proxyProtocolInvalidCount{0};
-  pdns::stat_t nodLookupsDroppedOversize{0};
-  pdns::stat_t dns64prefixanswers{0};
-
-  RecursorStats() :
-    answers("answers", { 1000, 10000, 100000, 1000000 }),
-    auth4Answers("auth4answers", { 1000, 10000, 100000, 1000000 }),
-    auth6Answers("auth6answers", { 1000, 10000, 100000, 1000000 }),
-    ourtime("ourtime", { 1000, 2000, 4000, 8000, 16000, 32000 }),
-    cumulativeAnswers("cumul-clientanswers-", 10, 19),
-    // These two will be merged when outputting
-    cumulativeAuth4Answers("cumul-authanswers-", 1000, 13),
-    cumulativeAuth6Answers("cumul-authanswers-", 1000, 13)
-  {
-  }
-};
-
-//! represents a running TCP/IP client session
-class TCPConnection : public boost::noncopyable
-{
-public:
-  TCPConnection(int fd, const ComboAddress& addr);
-  ~TCPConnection();
-
-  int getFD() const
-  {
-    return d_fd;
-  }
-  void setDropOnIdle()
-  {
-    d_dropOnIdle = true;
-  }
-  bool isDropOnIdle() const
-  {
-    return d_dropOnIdle;
-  }
-  std::vector<ProxyProtocolValue> proxyProtocolValues;
-  std::string data;
-  const ComboAddress d_remote;
-  ComboAddress d_source;
-  ComboAddress d_destination;
-  size_t queriesCount{0};
-  size_t proxyProtocolGot{0};
-  ssize_t proxyProtocolNeed{0};
-  enum stateenum {PROXYPROTOCOLHEADER, BYTE0, BYTE1, GETQUESTION, DONE} state{BYTE0};
-  uint16_t qlen{0};
-  uint16_t bytesread{0};
-  uint16_t d_requestsInFlight{0}; // number of mthreads spawned for this connection
-  // 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;
-  static std::atomic<uint32_t> s_currentConnections; //!< total number of current TCP connections
-  bool d_dropOnIdle{false};
-};
-
-class ImmediateServFailException
-{
-public:
-  ImmediateServFailException(string r) : reason(r) {};
-
-  string reason; //! Print this to tell the user what went wrong
-};
-
-class PolicyHitException
-{
-};
-
-class ImmediateQueryDropException
-{
-};
-
-class SendTruncatedAnswerException
-{
-};
-
-typedef boost::circular_buffer<ComboAddress> addrringbuf_t;
-extern thread_local std::unique_ptr<addrringbuf_t> t_servfailremotes, t_largeanswerremotes, t_remotes, t_bogusremotes, t_timeouts;
-
-extern thread_local std::unique_ptr<boost::circular_buffer<pair<DNSName,uint16_t> > > t_queryring, t_servfailqueryring, t_bogusqueryring;
-extern thread_local std::shared_ptr<NetmaskGroup> t_allowFrom;
-extern thread_local std::shared_ptr<NetmaskGroup> t_allowNotifyFrom;
-string doTraceRegex(vector<string>::const_iterator begin, vector<string>::const_iterator end);
-void parseACLs();
-extern RecursorStats g_stats;
-extern unsigned int g_networkTimeoutMsec;
-extern uint16_t g_outgoingEDNSBufsize;
-extern std::atomic<uint32_t> g_maxCacheEntries, g_maxPacketCacheEntries;
-extern bool g_lowercaseOutgoing;
-
-
-std::string reloadZoneConfiguration();
-typedef boost::function<void*(void)> pipefunc_t;
-void broadcastFunction(const pipefunc_t& func);
-void distributeAsyncFunction(const std::string& question, const pipefunc_t& func);
-
-int directResolve(const DNSName& qname, const QType qtype, const QClass qclass, vector<DNSRecord>& ret, shared_ptr<RecursorLua4> pdl);
-int directResolve(const DNSName& qname, const QType qtype, const QClass qclass, vector<DNSRecord>& ret, shared_ptr<RecursorLua4> pdl, bool qm);
-int followCNAMERecords(std::vector<DNSRecord>& ret, const QType qtype, int oldret);
-int getFakeAAAARecords(const DNSName& qname, ComboAddress prefix, vector<DNSRecord>& ret);
-int getFakePTRRecords(const DNSName& qname, vector<DNSRecord>& ret);
-
-template<class T> T broadcastAccFunction(const boost::function<T*()>& func);
-
-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);
-
-uint64_t* pleaseGetNsSpeedsSize();
-uint64_t* pleaseGetFailedServersSize();
-uint64_t* pleaseGetEDNSStatusesSize();
-uint64_t* pleaseGetConcurrentQueries();
-uint64_t* pleaseGetThrottleSize();
-uint64_t* pleaseGetPacketCacheHits();
-uint64_t* pleaseGetPacketCacheSize();
-void doCarbonDump(void*);
-bool primeHints(time_t now = time(nullptr));
-void primeRootNSZones(DNSSECMode, unsigned int depth);
-
-struct WipeCacheResult
-{
-  int record_count = 0;
-  int negative_record_count = 0;
-  int packet_count = 0;
-};
-
-struct WipeCacheResult wipeCaches(const DNSName& canon, bool subtree, uint16_t qtype);
-
-extern __thread struct timeval g_now;
-
-struct ThreadTimes
-{
-  uint64_t msec;
-  vector<uint64_t> times;
-  ThreadTimes& operator+=(const ThreadTimes& rhs)
-  {
-    times.push_back(rhs.msec);
-    return *this;
-  }
-};
-
diff --git a/pdns/tcounters.hh b/pdns/tcounters.hh
new file mode 100644 (file)
index 0000000..733e1df
--- /dev/null
@@ -0,0 +1,261 @@
+/*
+ * 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 <sys/time.h>
+#include <array>
+#include <set>
+#include <unistd.h>
+
+#include "lock.hh"
+
+namespace pdns
+{
+// We keep three sets of related counters:
+//
+// 1. The current counters (thread local, updated individually by thread code very often)
+// 2. The snapshot counters (thread local, updated by thread code in one single mutex protected copy)
+// 3. The history counters (global) to keep track of the counters of deleted threads
+
+// We have two main classes: one that holds the thread local counters
+// (both current and snapshot ones) and one that aggregates the
+// values for all threads and keeps the history counters.
+
+// The thread local current counters are the ones updated by
+// performance critical code.  Every once in a while, all values in the
+// current counters are copied to the snapshot thread local copies in
+// a thread safe way.
+
+// The snapshot counters are aggregated by the GlobalCounters
+// class, as these can be accessed safely from multiple threads.
+
+// Make sure to call the thread local tlocal.updatesAtomics() once
+// in a while. This will fill the snapshot values for that thread if some
+// time has passed since the last snap update.
+
+// To fetch aggregate values, call globals.sum(counter1) or
+// globals.avg(counter2), or any aggreggation function.  If multiple
+// counters need to be collected in a consistent way:
+// auto data = globals.aggregatedSnap();
+//
+// Note that the aggregate values can mix somewhat older thread-local
+// with newer thread-local info from another thread. So it is possible
+// to see the following:
+//
+// If thread T1 increments "received" and then passes the packet to
+// thread T2 that increments "processed", it may happen that the value
+// of "processed" observed by sum() is higher than "received", as T1
+// might not have called updateSnap() yet while T2 did.  To avoid this
+// inconsistency, be careful to update related counters in a single
+// thread only.
+
+// For an example of the use of these templates, see rec-tcounters.hh
+
+template <typename Counters>
+class TLocalCounters;
+
+template <typename Counters>
+class GlobalCounters
+{
+public:
+  // Register a thread local set of values
+  void subscribe(TLocalCounters<Counters>* ptr)
+  {
+    auto lock = d_guarded.lock();
+    lock->d_instances.emplace(ptr);
+  }
+
+  // Unregister, typically done when a thread exits
+  void unsubscribe(TLocalCounters<Counters>* ptr, const Counters& data)
+  {
+    auto lock = d_guarded.lock();
+    lock->d_instances.erase(ptr);
+    lock->d_history.merge(data);
+  }
+
+  // Two ways of computing aggregated values for a specific counter: simple additions of all thread data, or taking weighted averages into account
+  template <typename Enum>
+  auto sum(Enum index);
+  template <typename Enum>
+  auto avg(Enum index);
+  template <typename Enum>
+  auto max(Enum index);
+
+  // Aggregate all counter data for all threads
+  Counters aggregatedSnap();
+
+  // Reset history
+  void reset()
+  {
+    auto lock = d_guarded.lock();
+    lock->d_history = Counters();
+  }
+
+private:
+  struct Guarded
+  {
+    // We have x instances, normally one per thread
+    std::set<TLocalCounters<Counters>*> d_instances;
+    // If an instance gets deleted because its thread is cleaned up, the values
+    // are accumulated in d_history
+    Counters d_history;
+  };
+  LockGuarded<Guarded> d_guarded;
+};
+
+template <typename Counters>
+class TLocalCounters
+{
+public:
+  static constexpr suseconds_t defaultSnapUpdatePeriodus = 100000;
+  TLocalCounters(GlobalCounters<Counters>& collector, timeval interval = timeval{0, defaultSnapUpdatePeriodus}) :
+    d_collector(collector), d_interval(interval)
+  {
+    collector.subscribe(this);
+  }
+
+  ~TLocalCounters()
+  {
+    d_collector.unsubscribe(this, d_current);
+  }
+
+  TLocalCounters(const TLocalCounters&) = delete;
+  TLocalCounters(TLocalCounters&&) = delete;
+  TLocalCounters& operator=(const TLocalCounters&) = delete;
+  TLocalCounters& operator=(TLocalCounters&&) = delete;
+
+  template <typename Enum>
+  auto& at(Enum index)
+  {
+    return d_current.at(index);
+  }
+
+  template <typename Enum>
+  // coverity[auto_causes_copy]
+  auto snapAt(Enum index)
+  {
+    return d_snapshot.lock()->at(index);
+  }
+
+  [[nodiscard]] Counters getSnap()
+  {
+    return *(d_snapshot.lock());
+  }
+
+  bool updateSnap(const timeval& tv_now, bool force = false)
+  {
+    timeval tv_diff{};
+
+    if (!force) {
+      timersub(&tv_now, &d_last, &tv_diff);
+    }
+    if (force || timercmp(&tv_diff, &d_interval, >=)) {
+      // It's a copy
+      *(d_snapshot.lock()) = d_current;
+      d_last = tv_now;
+      return true;
+    }
+    return false;
+  }
+
+  bool updateSnap(bool force = false)
+  {
+    timeval tv_now{};
+
+    if (!force) {
+      gettimeofday(&tv_now, nullptr);
+    }
+    return updateSnap(tv_now, force);
+  }
+
+private:
+  GlobalCounters<Counters>& d_collector;
+  Counters d_current;
+  LockGuarded<Counters> d_snapshot;
+  timeval d_last{0, 0};
+  const timeval d_interval;
+};
+
+// Sum for a specific index
+// In the future we might want to move the specifics of computing an aggregated value to the
+// app specific Counters class
+template <typename Counters>
+template <typename Enum>
+auto GlobalCounters<Counters>::sum(Enum index)
+{
+  auto lock = d_guarded.lock();
+  auto sum = lock->d_history.at(index);
+  for (const auto& instance : lock->d_instances) {
+    sum += instance->snapAt(index);
+  }
+  return sum;
+}
+
+// Average for a specific index
+// In the future we might want to move the specifics of computing an aggregated value to the
+// app specific Counters class
+template <typename Counters>
+template <typename Enum>
+auto GlobalCounters<Counters>::avg(Enum index)
+{
+  auto lock = d_guarded.lock();
+  auto wavg = lock->d_history.at(index);
+  auto sum = wavg.avg * wavg.weight;
+  auto count = wavg.weight;
+  for (const auto& instance : lock->d_instances) {
+    auto val = instance->snapAt(index);
+    count += val.weight;
+    sum += val.avg * val.weight;
+  }
+  return count > 0 ? sum / count : 0;
+}
+
+// Max for a specific  index
+// In the future we might want to move the specifics of computing an aggregated value to the
+// app specific Counters class
+template <typename Counters>
+template <typename Enum>
+auto GlobalCounters<Counters>::max(Enum index)
+{
+  auto lock = d_guarded.lock();
+  uint64_t max = 0; // ignore history
+  for (const auto& instance : lock->d_instances) {
+    max = std::max(instance->snapAt(index), max);
+  }
+  return max;
+}
+
+// Get a consistent snap of *all* aggregated values
+template <typename Counters>
+Counters GlobalCounters<Counters>::aggregatedSnap()
+{
+  auto lock = d_guarded.lock();
+  Counters ret = lock->d_history;
+  for (const auto& instance : lock->d_instances) {
+    auto snap = instance->getSnap();
+    ret.merge(snap);
+  }
+  return ret;
+}
+
+}
index 84ac3aef76d56465e88e79d7ca5568aec4a7e21a..841d9e3217e314f487ca8312862b59b9c80f310e 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>
@@ -30,7 +30,12 @@ public:
   {
     registerOpenSSLUser();
 
-    d_tlsCtx = libssl_init_server_context(tlsConfig, d_ocspResponses);
+    auto [ctx, warnings] = libssl_init_server_context(tlsConfig, d_ocspResponses);
+    for (const auto& warning : warnings) {
+      warnlog("%s", warning);
+    }
+    d_tlsCtx = std::move(ctx);
+
     if (!d_tlsCtx) {
       ERR_print_errors_fp(stderr);
       throw std::runtime_error("Error creating TLS context on " + addr.toStringWithPort());
@@ -57,10 +62,6 @@ public:
   {
   }
 
-  virtual ~OpenSSLSession()
-  {
-  }
-
   std::unique_ptr<SSL_SESSION, void(*)(SSL_SESSION*)> getNative()
   {
     return std::move(d_sess);
@@ -74,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) {
@@ -98,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 */
@@ -106,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) {
@@ -144,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");
@@ -161,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
@@ -346,6 +331,13 @@ public:
 
   IOState tryWrite(const PacketBuffer& buffer, size_t& pos, size_t toWrite) override
   {
+    if (!d_feContext && !d_connected) {
+      if (d_ktls) {
+        /* work-around to get kTLS to be started, as we cannot do that until after the socket has been connected */
+        SSL_set_fd(d_conn.get(), SSL_get_fd(d_conn.get()));
+      }
+    }
+
     do {
       int res = SSL_write(d_conn.get(), reinterpret_cast<const char *>(&buffer.at(pos)), static_cast<int>(toWrite - pos));
       if (res <= 0) {
@@ -356,6 +348,11 @@ public:
       }
     }
     while (pos < toWrite);
+
+    if (!d_connected) {
+      d_connected = true;
+    }
+
     return IOState::Done;
   }
 
@@ -431,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) {
@@ -489,14 +477,16 @@ public:
 
     const unsigned char* alpn = nullptr;
     unsigned int alpnLen  = 0;
+#ifndef DISABLE_NPN
 #ifdef HAVE_SSL_GET0_NEXT_PROTO_NEGOTIATED
     SSL_get0_next_proto_negotiated(d_conn.get(), &alpn, &alpnLen);
-#endif
+#endif /* HAVE_SSL_GET0_NEXT_PROTO_NEGOTIATED */
+#endif /* DISABLE_NPN */
 #ifdef HAVE_SSL_GET0_ALPN_SELECTED
     if (alpn == nullptr) {
       SSL_get0_alpn_selected(d_conn.get(), &alpn, &alpnLen);
     }
-#endif
+#endif /* HAVE_SSL_GET0_ALPN_SELECTED */
     if (alpn != nullptr && alpnLen > 0) {
       result.insert(result.end(), alpn, alpn + alpnLen);
     }
@@ -555,11 +545,35 @@ public:
     d_tlsSessions.push_back(std::make_unique<OpenSSLSession>(std::unique_ptr<SSL_SESSION, void (*)(SSL_SESSION*)>(session, SSL_SESSION_free)));
   }
 
-  static int s_tlsConnIndex;
+  void enableKTLS()
+  {
+    d_ktls = true;
+  }
 
-private:
-  static std::atomic_flag s_initTLSConnIndex;
+  static void generateConnectionIndexIfNeeded()
+  {
+    auto init = s_initTLSConnIndex.lock();
+    if (*init == true) {
+      return;
+    }
+
+    /* 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;
@@ -568,10 +582,12 @@ private:
   std::unique_ptr<SSL, void(*)(SSL*)> d_conn;
   std::string d_hostname;
   struct timeval d_timeout;
+  bool d_connected{false};
+  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
 {
@@ -579,18 +595,30 @@ 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) {
       /* use our own ticket keys handler so we can rotate them */
+#if OPENSSL_VERSION_MAJOR >= 3
+      SSL_CTX_set_tlsext_ticket_key_evp_cb(d_feContext->d_tlsCtx.get(), &OpenSSLTLSIOCtx::ticketKeyCb);
+#else
       SSL_CTX_set_tlsext_ticket_key_cb(d_feContext->d_tlsCtx.get(), &OpenSSLTLSIOCtx::ticketKeyCb);
+#endif
       libssl_set_ticket_key_callback_data(d_feContext->d_tlsCtx.get(), d_feContext.get());
     }
 
+#ifndef DISABLE_OCSP_STAPLING
     if (!d_feContext->d_ocspResponses.empty()) {
       SSL_CTX_set_tlsext_status_cb(d_feContext->d_tlsCtx.get(), &OpenSSLTLSIOCtx::ocspStaplingCb);
       SSL_CTX_set_tlsext_status_arg(d_feContext->d_tlsCtx.get(), &d_feContext->d_ocspResponses);
     }
+#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);
 
@@ -621,6 +649,9 @@ public:
       SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
       SSL_OP_SINGLE_DH_USE |
       SSL_OP_SINGLE_ECDH_USE |
+#ifdef SSL_OP_IGNORE_UNEXPECTED_EOF
+      SSL_OP_IGNORE_UNEXPECTED_EOF |
+#endif
       SSL_OP_CIPHER_SERVER_PREFERENCE;
     if (!params.d_enableRenegotiation) {
 #ifdef SSL_OP_NO_RENEGOTIATION
@@ -630,8 +661,17 @@ public:
 #endif
     }
 
+    if (params.d_ktls) {
+#ifdef SSL_OP_ENABLE_KTLS
+      sslOptions |= SSL_OP_ENABLE_KTLS;
+      d_ktls = true;
+#endif /* SSL_OP_ENABLE_KTLS */
+    }
+
     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
@@ -697,9 +737,13 @@ public:
     unregisterOpenSSLUser();
   }
 
-  static int ticketKeyCb(SSL *s, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx, int enc)
+#if OPENSSL_VERSION_MAJOR >= 3
+  static int ticketKeyCb(SSL* s, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* iv, EVP_CIPHER_CTX* ectx, EVP_MAC_CTX* hctx, int enc)
+#else
+  static int ticketKeyCb(SSL* s, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* iv, EVP_CIPHER_CTX* ectx, HMAC_CTX* hctx, int enc)
+#endif
   {
-    OpenSSLFrontendContext* ctx = reinterpret_cast<OpenSSLFrontendContext*>(libssl_get_ticket_key_callback_data(s));
+    auto* ctx = reinterpret_cast<OpenSSLFrontendContext*>(libssl_get_ticket_key_callback_data(s));
     if (ctx == nullptr) {
       return -1;
     }
@@ -707,8 +751,8 @@ public:
     int ret = libssl_ticket_key_callback(s, ctx->d_ticketKeys, keyName, iv, ectx, hctx, enc);
     if (enc == 0) {
       if (ret == 0 || ret == 2) {
-        OpenSSLTLSConnection* conn = reinterpret_cast<OpenSSLTLSConnection*>(SSL_get_ex_data(s, OpenSSLTLSConnection::s_tlsConnIndex));
-        if (conn) {
+        auto* conn = reinterpret_cast<OpenSSLTLSConnection*>(SSL_get_ex_data(s, OpenSSLTLSConnection::getConnectionIndex()));
+        if (conn != nullptr) {
           if (ret == 0) {
             conn->setUnknownTicketKey();
           }
@@ -722,6 +766,7 @@ public:
     return ret;
   }
 
+#ifndef DISABLE_OCSP_STAPLING
   static int ocspStaplingCb(SSL* ssl, void* arg)
   {
     if (ssl == nullptr || arg == nullptr) {
@@ -730,10 +775,11 @@ public:
     const auto ocspMap = reinterpret_cast<std::map<int, std::string>*>(arg);
     return libssl_ocsp_stapling_callback(ssl, *ocspMap);
   }
+#endif /* DISABLE_OCSP_STAPLING */
 
   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;
     }
@@ -751,7 +797,11 @@ public:
 
   std::unique_ptr<TLSConnection> getClientConnection(const std::string& host, bool hostIsAddr, int socket, const struct timeval& timeout) override
   {
-    return std::make_unique<OpenSSLTLSConnection>(host, hostIsAddr, socket, timeout, d_tlsCtx);
+    auto conn = std::make_unique<OpenSSLTLSConnection>(host, hostIsAddr, socket, timeout, d_tlsCtx);
+    if (d_ktls) {
+      conn->enableKTLS();
+    }
+    return conn;
   }
 
   void rotateTicketsKey(time_t now) override
@@ -763,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);
 
@@ -795,16 +845,19 @@ public:
     return false;
   }
 
+#ifndef DISABLE_NPN
   bool setNextProtocolSelectCallback(bool(*cb)(unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen)) override
   {
     d_nextProtocolSelectCallback = cb;
     libssl_set_npn_select_callback(d_tlsCtx.get(), npnSelectCallback, this);
     return true;
   }
+#endif /* DISABLE_NPN */
 
 private:
   /* 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. */
-  static int npnSelectCallback(SSL* s, unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen, void* arg)
+#ifndef DISABLE_NPN
+  static int npnSelectCallback(SSL* /* s */, unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen, void* arg)
   {
     if (!arg) {
       return SSL_TLSEXT_ERR_ALERT_WARNING;
@@ -816,6 +869,7 @@ private:
 
     return SSL_TLSEXT_ERR_OK;
   }
+#endif /* NPN */
 
   static int alpnServerSelectCallback(SSL*, const unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen, void* arg)
   {
@@ -850,6 +904,7 @@ private:
   std::shared_ptr<OpenSSLFrontendContext> d_feContext{nullptr};
   std::shared_ptr<SSL_CTX> d_tlsCtx{nullptr}; // client context, on a server-side the context is stored in d_feContext->d_tlsCtx
   bool (*d_nextProtocolSelectCallback)(unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen){nullptr};
+  bool d_ktls{false};
 };
 
 #endif /* HAVE_LIBSSL */
@@ -956,7 +1011,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);
@@ -1051,7 +1106,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());
@@ -1070,9 +1125,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) {
@@ -1094,7 +1149,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;
 
@@ -1199,7 +1254,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) {
@@ -1250,7 +1305,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);
@@ -1286,7 +1341,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);
@@ -1376,15 +1431,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) {
@@ -1539,14 +1585,16 @@ public:
       }
     }
 
+#ifndef DISABLE_OCSP_STAPLING
     size_t count = 0;
     for (const auto& file : fe.d_tlsConfig.d_ocspFiles) {
       rc = gnutls_certificate_set_ocsp_status_request_file(d_creds.get(), file.c_str(), count);
       if (rc != GNUTLS_E_SUCCESS) {
-        throw std::runtime_error("Error loading OCSP response from file '" + file + "' for certificate ('" + fe.d_tlsConfig.d_certKeyPairs.at(count).d_cert + "') and key ('" + fe.d_tlsConfig.d_certKeyPairs.at(count).d_key.value() + "') for TLS context on " + fe.d_addr.toStringWithPort() + ": " + gnutls_strerror(rc));
+        warnlog("Error loading OCSP response from file '%s' for certificate ('%s') and key ('%s') for TLS context on %s: %s", file, fe.d_tlsConfig.d_certKeyPairs.at(count).d_cert, fe.d_tlsConfig.d_certKeyPairs.at(count).d_key.value(), fe.d_addr.toStringWithPort(), gnutls_strerror(rc));
       }
       ++count;
     }
+#endif /* DISABLE_OCSP_STAPLING */
 
 #if GNUTLS_VERSION_NUMBER >= 0x030600
     rc = gnutls_certificate_set_known_dh_params(d_creds.get(), GNUTLS_SEC_PARAM_HIGH);
@@ -1612,7 +1660,7 @@ public:
     }
   }
 
-  virtual ~GnuTLSIOCtx() override
+  ~GnuTLSIOCtx() override
   {
     d_creds.reset();
 
@@ -1688,7 +1736,7 @@ public:
     auto newKey = std::make_shared<GnuTLSTicketsKey>();
 
     {
-      *(d_ticketsKey.write_lock()) = newKey;
+      *(d_ticketsKey.write_lock()) = std::move(newKey);
     }
 
     if (d_ticketsKeyRotationDelay > 0) {
@@ -1696,7 +1744,7 @@ public:
     }
   }
 
-  void loadTicketsKeys(const std::string& file) override final
+  void loadTicketsKeys(const std::string& file) final
   {
     if (!d_enableTickets) {
       return;
@@ -1704,7 +1752,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) {
@@ -1745,7 +1793,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)
 {
@@ -1758,67 +1806,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 92faa6909a55423fba4e74f601ecadc0874d92e6..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,11 +73,11 @@ 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;
-  virtual void loadTicketsKeys(const std::string& file)
+  virtual void loadTicketsKeys(const std::string& /* file */)
   {
     throw std::runtime_error("This TLS backend does not have the capability to load a tickets key from a file");
   }
@@ -116,7 +113,7 @@ public:
   virtual std::string getName() const = 0;
 
   /* set the advertised ALPN protocols, in client or server context */
-  virtual bool setALPNProtos(const std::vector<std::vector<uint8_t>>& protos)
+  virtual bool setALPNProtos(const std::vector<std::vector<uint8_t>>& /* protos */)
   {
     return false;
   }
@@ -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, time_t now): 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) {
@@ -577,7 +570,9 @@ struct TLSContextParameters
   bool d_validateCertificates{true};
   bool d_releaseBuffers{true};
   bool d_enableRenegotiation{false};
+  bool d_ktls{false};
 };
 
 std::shared_ptr<TLSCtx> getTLSContext(const TLSContextParameters& params);
 bool setupDoTProtocolNegotiation(std::shared_ptr<TLSCtx>& ctx);
+bool setupDoHProtocolNegotiation(std::shared_ptr<TLSCtx>& ctx);
index 30cc461096555a16a7d7664574bba03dafc771f1..b84a6b9b4b4f2e8b08af67c74439dec30dc75d35 100644 (file)
@@ -40,8 +40,8 @@
 #include "tcpreceiver.hh"
 #include "sstuff.hh"
 
-#include <errno.h>
-#include <signal.h>
+#include <cerrno>
+#include <csignal>
 #include "base64.hh"
 #include "ueberbackend.hh"
 #include "dnspacket.hh"
@@ -51,7 +51,7 @@
 #include "logger.hh"
 #include "arguments.hh"
 
-#include "common_startup.hh"
+#include "auth-main.hh"
 #include "packethandler.hh"
 #include "statbag.hh"
 #include "communicator.hh"
@@ -60,6 +60,8 @@
 #include "stubresolver.hh"
 #include "proxy-protocol.hh"
 #include "noinitvector.hh"
+#include "gss_context.hh"
+#include "pdnsexception.hh"
 extern AuthPacketCache PC;
 extern StatBag S;
 
@@ -129,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;
@@ -171,9 +175,11 @@ static void writenWithTimeout(int fd, const void *buffer, unsigned int n, unsign
 
 void TCPNameserver::sendPacket(std::unique_ptr<DNSPacket>& p, int outsock, bool last)
 {
+  uint16_t len=htons(p->getString(true).length());
+
+  // this also calls p->getString; call it after our explicit call so throwsOnTruncation=true is honoured
   g_rs.submitResponse(*p, false, last);
 
-  uint16_t len=htons(p->getString().length());
   string buffer((const char*)&len, 2);
   buffer.append(p->getString());
   writenWithTimeout(outsock, buffer.c_str(), buffer.length(), d_idleTimeout);
@@ -196,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;
 }
@@ -409,24 +417,29 @@ void TCPNameserver::doConnection(int fd)
         break;
 
       sendPacket(reply, fd);
+#ifdef ENABLE_GSS_TSIG
+      if (g_doGssTSIG) {
+        packet->cleanupGSS(reply->d.rcode);
+      }
+#endif
     }
   }
   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();
 
@@ -434,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);
 }
@@ -455,9 +468,31 @@ bool TCPNameserver::canDoAXFR(std::unique_ptr<DNSPacket>& q, bool isAXFR, std::u
       return false;
     } else {
       getTSIGHashEnum(trc.d_algoName, q->d_tsig_algo);
+#ifdef ENABLE_GSS_TSIG
+      if (g_doGssTSIG && q->d_tsig_algo == TSIG_GSS) {
+        GssContext gssctx(keyname);
+        if (!gssctx.getPeerPrincipal(q->d_peer_principal)) {
+          g_log<<Logger::Warning<<"Failed to extract peer principal from GSS context with keyname '"<<keyname<<"'"<<endl;
+        }
+      }
+#endif
     }
 
     DNSSECKeeper dk(packetHandler->getBackend());
+#ifdef ENABLE_GSS_TSIG
+    if (g_doGssTSIG && q->d_tsig_algo == TSIG_GSS) {
+      vector<string> princs;
+      packetHandler->getBackend()->getDomainMetadata(q->qdomain, "GSS-ALLOW-AXFR-PRINCIPAL", princs);
+      for(const std::string& princ :  princs) {
+        if (q->d_peer_principal == princ) {
+          g_log<<Logger::Warning<<"AXFR of domain '"<<q->qdomain<<"' allowed: TSIG signed request with authorized principal '"<<q->d_peer_principal<<"' and algorithm 'gss-tsig'"<<endl;
+          return true;
+        }
+      }
+      g_log<<Logger::Warning<<"AXFR of domain '"<<q->qdomain<<"' denied: TSIG signed request with principal '"<<q->d_peer_principal<<"' and algorithm 'gss-tsig' is not permitted"<<endl;
+      return false;
+    }
+#endif
     if(!dk.TSIGGrantsAccess(q->qdomain, keyname)) {
       g_log<<Logger::Warning<<logPrefix<<"denied: key with name '"<<keyname<<"' and algorithm '"<<getTSIGAlgoName(q->d_tsig_algo)<<"' does not grant access"<<endl;
       return false;
@@ -551,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)
@@ -579,7 +614,7 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
       return 0;
     }
 
-    if(!(*packetHandler)->getBackend()->getSOAUncached(target, sd)) {
+    if (!(*packetHandler)->getBackend()->getSOAUncached(target, sd)) {
       g_log<<Logger::Warning<<logPrefix<<"failed: not authoritative"<<endl;
       outpacket->setRcode(RCode::NotAuth);
       sendPacket(outpacket,outsock);
@@ -595,14 +630,23 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
     return 0;
   }
 
+  bool securedZone = false;
+  bool presignedZone = false;
+  bool NSEC3Zone = false;
+  bool narrow = false;
+
+  DomainInfo di;
+  bool isCatalogZone = sd.db->getDomainInfo(target, di, false) && di.isCatalogType();
+
+  NSEC3PARAMRecordContent ns3pr;
+
   DNSSECKeeper dk(&db);
   DNSSECKeeper::clearCaches(target);
-  bool securedZone = dk.isSecuredZone(target);
-  bool presignedZone = dk.isPresigned(target);
+  if (!isCatalogZone) {
+    securedZone = dk.isSecuredZone(target);
+    presignedZone = dk.isPresigned(target);
+  }
 
-  NSEC3PARAMRecordContent ns3pr;
-  bool narrow;
-  bool NSEC3Zone=false;
   if(securedZone && dk.getNSEC3PARAM(target, &ns3pr, &narrow)) {
     NSEC3Zone=true;
     if(narrow) {
@@ -621,17 +665,18 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
 
   if(haveTSIGDetails && !tsigkeyname.empty()) {
     string tsig64;
-    DNSName algorithm=trc.d_algoName; // FIXME400: check
+    DNSName algorithm=trc.d_algoName;
     if (algorithm == DNSName("hmac-md5.sig-alg.reg.int"))
       algorithm = DNSName("hmac-md5");
-
-    if(!db.getTSIGKey(tsigkeyname, &algorithm, &tsig64)) {
-      g_log<<Logger::Warning<<logPrefix<<"TSIG key not found"<<endl;
-      return 0;
-    }
-    if (B64Decode(tsig64, tsigsecret) == -1) {
-      g_log<<Logger::Error<<logPrefix<<"unable to Base-64 decode TSIG key '"<<tsigkeyname<<"'"<<endl;
-      return 0;
+    if (algorithm != DNSName("gss-tsig")) {
+      if(!db.getTSIGKey(tsigkeyname, algorithm, tsig64)) {
+        g_log<<Logger::Warning<<logPrefix<<"TSIG key not found"<<endl;
+        return 0;
+      }
+      if (B64Decode(tsig64, tsigsecret) == -1) {
+        g_log<<Logger::Error<<logPrefix<<"unable to Base-64 decode TSIG key '"<<tsigkeyname<<"'"<<endl;
+        return 0;
+      }
     }
   }
 
@@ -679,7 +724,7 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
         continue;
       }
       zrr.dr.d_type = QType::DNSKEY;
-      zrr.dr.d_content = std::make_shared<DNSKEYRecordContent>(value.first.getDNSKEY());
+      zrr.dr.setContent(std::make_shared<DNSKEYRecordContent>(value.first.getDNSKEY()));
       DNSName keyname = NSEC3Zone ? DNSName(toBase32Hex(hashQNameWithSalt(ns3pr, zrr.dr.d_name))) : zrr.dr.d_name;
       zrrs.push_back(zrr);
 
@@ -689,10 +734,10 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
           zrr.dr.d_type=QType::CDNSKEY;
           if (publishCDNSKEY == "0") {
             doCDNSKEY = false;
-            zrr.dr.d_content=PacketHandler::s_deleteCDNSKEYContent;
+            zrr.dr.setContent(PacketHandler::s_deleteCDNSKEYContent);
             zrrs.push_back(zrr);
           } else {
-            zrr.dr.d_content = std::make_shared<DNSKEYRecordContent>(value.first.getDNSKEY());
+            zrr.dr.setContent(std::make_shared<DNSKEYRecordContent>(value.first.getDNSKEY()));
             zrrs.push_back(zrr);
           }
         }
@@ -703,11 +748,11 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
           stringtok(digestAlgos, publishCDS, ", ");
           if(std::find(digestAlgos.begin(), digestAlgos.end(), "0") != digestAlgos.end()) {
             doCDS = false;
-            zrr.dr.d_content=PacketHandler::s_deleteCDSContent;
+            zrr.dr.setContent(PacketHandler::s_deleteCDSContent);
             zrrs.push_back(zrr);
           } else {
             for(auto const &digestAlgo : digestAlgos) {
-              zrr.dr.d_content=std::make_shared<DSRecordContent>(makeDSFromDNSKey(target, value.first.getDNSKEY(), pdns::checked_stoi<uint8_t>(digestAlgo)));
+              zrr.dr.setContent(std::make_shared<DSRecordContent>(makeDSFromDNSKey(target, value.first.getDNSKEY(), pdns::checked_stoi<uint8_t>(digestAlgo))));
               zrrs.push_back(zrr);
             }
           }
@@ -721,24 +766,52 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
     uint8_t flags = ns3pr.d_flags;
     zrr.dr.d_type = QType::NSEC3PARAM;
     ns3pr.d_flags = 0;
-    zrr.dr.d_content = std::make_shared<NSEC3PARAMRecordContent>(ns3pr);
+    zrr.dr.setContent(std::make_shared<NSEC3PARAMRecordContent>(ns3pr));
     ns3pr.d_flags = flags;
     DNSName keyname = DNSName(toBase32Hex(hashQNameWithSalt(ns3pr, zrr.dr.d_name)));
     zrrs.push_back(zrr);
   }
 
+  const bool rectify = !(presignedZone || ::arg().mustDo("disable-axfr-rectify"));
+  set<DNSName> qnames, nsset, terms;
+
+  // Catalog zone start
+  if (di.kind == DomainInfo::Producer) {
+    // Ignore all records except NS at apex
+    sd.db->lookup(QType::NS, target, di.id);
+    while (sd.db->get(zrr)) {
+      zrrs.emplace_back(zrr);
+    }
+    if (zrrs.empty()) {
+      zrr.dr.d_name = target;
+      zrr.dr.d_ttl = 0;
+      zrr.dr.d_type = QType::NS;
+      zrr.dr.setContent(std::make_shared<NSRecordContent>("invalid."));
+      zrrs.emplace_back(zrr);
+    }
+
+    zrrs.emplace_back(CatalogInfo::getCatalogVersionRecord(target));
+
+    vector<CatalogInfo> members;
+    sd.db->getCatalogMembers(target, members, CatalogInfo::CatalogType::Producer);
+    for (const auto& ci : members) {
+      ci.toDNSZoneRecords(target, zrrs);
+    }
+    if (members.empty()) {
+      g_log << Logger::Warning << logPrefix << "catalog zone '" << target << "' has no members" << endl;
+    }
+    goto send;
+  }
+  // Catalog zone end
+
   // now start list zone
-  if(!(sd.db->list(target, sd.domain_id))) {
+  if (!sd.db->list(target, sd.domain_id, isCatalogZone)) {
     g_log<<Logger::Error<<logPrefix<<"backend signals error condition, aborting AXFR"<<endl;
     outpacket->setRcode(RCode::ServFail);
     sendPacket(outpacket,outsock);
     return 0;
   }
 
-
-  const bool rectify = !(presignedZone || ::arg().mustDo("disable-axfr-rectify"));
-  set<DNSName> qnames, nsset, terms;
-
   while(sd.db->get(zrr)) {
     if (!presignedZone) {
       if (zrr.dr.d_type == QType::RRSIG) {
@@ -754,19 +827,29 @@ 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)->d_content, QType::A, ips);
-        int ret2 = stubDoResolve(getRR<ALIASRecordContent>(zrr.dr)->d_content, QType::AAAA, ips);
-        if(ret1 != RCode::NoError || ret2 != RCode::NoError) {
-          g_log<<Logger::Warning<<logPrefix<<"error resolving for ALIAS "<<zrr.dr.d_content->getZoneRepresentation()<<", aborting AXFR"<<endl;
-          outpacket->setRcode(RCode::ServFail);
-          sendPacket(outpacket,outsock);
-          return 0;
+        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) {
+          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(const auto& ip: ips) {
+        for (auto& ip: ips) {
           zrr.dr.d_type = ip.dr.d_type;
-          zrr.dr.d_content = ip.dr.d_content;
+          zrr.dr.setContent(ip.dr.getContent());
           zrrs.push_back(zrr);
         }
         continue;
@@ -789,7 +872,7 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
     }
   }
 
-  for (auto loopRR : zrrs) {
+  for (auto& loopRR : zrrs) {
     if ((loopRR.dr.d_type == QType::SVCB || loopRR.dr.d_type == QType::HTTPS)) {
       // Process auto hints
       // TODO this is an almost copy of the code in the packethandler
@@ -797,8 +880,12 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
       if (rrc == nullptr) {
         continue;
       }
-      DNSName svcTarget = rrc->getTarget().isRoot() ? loopRR.dr.d_name : rrc->getTarget();
-      if (rrc->autoHint(SvcParam::ipv4hint)) {
+      auto newRRC = rrc->clone();
+      if (!newRRC) {
+        continue;
+      }
+      DNSName svcTarget = newRRC->getTarget().isRoot() ? loopRR.dr.d_name : newRRC->getTarget();
+      if (newRRC->autoHint(SvcParam::ipv4hint)) {
         sd.db->lookup(QType::A, svcTarget, sd.domain_id);
         vector<ComboAddress> hints;
         DNSZoneRecord rr;
@@ -807,13 +894,13 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
           hints.push_back(arrc->getCA());
         }
         if (hints.size() == 0) {
-          rrc->removeParam(SvcParam::ipv4hint);
+          newRRC->removeParam(SvcParam::ipv4hint);
         } else {
-          rrc->setHints(SvcParam::ipv4hint, hints);
+          newRRC->setHints(SvcParam::ipv4hint, hints);
         }
       }
 
-      if (rrc->autoHint(SvcParam::ipv6hint)) {
+      if (newRRC->autoHint(SvcParam::ipv6hint)) {
         sd.db->lookup(QType::AAAA, svcTarget, sd.domain_id);
         vector<ComboAddress> hints;
         DNSZoneRecord rr;
@@ -822,11 +909,13 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
           hints.push_back(arrc->getCA());
         }
         if (hints.size() == 0) {
-          rrc->removeParam(SvcParam::ipv6hint);
+          newRRC->removeParam(SvcParam::ipv6hint);
         } else {
-          rrc->setHints(SvcParam::ipv6hint, hints);
+          newRRC->setHints(SvcParam::ipv6hint, hints);
         }
       }
+
+      loopRR.dr.setContent(std::move(newRRC));
     }
   }
 
@@ -905,21 +994,20 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
     }
   }
 
+send:
 
   /* now write all other records */
 
   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;
   DTime dt;
   dt.set();
-  int records=0;
   for(DNSZoneRecord &loopZRR :  zrrs) {
-    records++;
     if(securedZone && (loopZRR.auth || loopZRR.dr.d_type == QType::NS)) {
       if (NSEC3Zone || loopZRR.dr.d_type) {
         if (presignedZone && NSEC3Zone && loopZRR.dr.d_type == QType::RRSIG && getRR<RRSIGRecordContent>(loopZRR.dr)->d_type == QType::NSEC3) {
@@ -991,7 +1079,7 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
           zrr.dr.d_name = iter->first+sd.qname;
 
           zrr.dr.d_ttl = sd.getNegativeTTL();
-          zrr.dr.d_content = std::make_shared<NSEC3RecordContent>(std::move(n3rc));
+          zrr.dr.setContent(std::make_shared<NSEC3RecordContent>(std::move(n3rc)));
           zrr.dr.d_type = QType::NSEC3;
           zrr.dr.d_place = DNSResourceRecord::ANSWER;
           zrr.auth=true;
@@ -1025,7 +1113,7 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
       zrr.dr.d_name = iter->first;
 
       zrr.dr.d_ttl = sd.getNegativeTTL();
-      zrr.dr.d_content = std::make_shared<NSECRecordContent>(std::move(nrc));
+      zrr.dr.setContent(std::make_shared<NSECRecordContent>(std::move(nrc)));
       zrr.dr.d_type = QType::NSEC;
       zrr.dr.d_place = DNSResourceRecord::ANSWER;
       zrr.auth=true;
@@ -1056,7 +1144,12 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
     if(!outpacket->getRRS().empty()) {
       if(haveTSIGDetails && !tsigkeyname.empty())
         outpacket->setTSIGDetails(trc, tsigkeyname, tsigsecret, trc.d_mac, true); // first answer is 'normal'
-      sendPacket(outpacket, outsock, false);
+      try {
+        sendPacket(outpacket, outsock, false);
+      }
+      catch (PDNSException& pe) {
+        throw PDNSException("during axfr-out of "+target.toString()+", this happened: "+pe.reason);
+      }
       trc.d_mac=outpacket->d_trc.d_mac;
       outpacket=getFreshAXFRPacket(q);
     }
@@ -1085,7 +1178,7 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
 
 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)
@@ -1097,7 +1190,7 @@ int TCPNameserver::doIXFR(std::unique_ptr<DNSPacket>& q, int outsock)
     const DNSRecord *rr = &answer.first;
     if (rr->d_type == QType::SOA && rr->d_place == DNSResourceRecord::AUTHORITY) {
       vector<string>parts;
-      stringtok(parts, rr->d_content->getZoneRepresentation());
+      stringtok(parts, rr->getContent()->getZoneRepresentation());
       if (parts.size() >= 3) {
         try {
           pdns::checked_stoi_into(serial, parts[2]);
@@ -1146,7 +1239,7 @@ int TCPNameserver::doIXFR(std::unique_ptr<DNSPacket>& q, int outsock)
 
     DNSSECKeeper dk((*packetHandler)->getBackend());
     DNSSECKeeper::clearCaches(q->qdomain);
-    bool narrow;
+    bool narrow = false;
     securedZone = dk.isSecuredZone(q->qdomain);
     if(dk.getNSEC3PARAM(q->qdomain, nullptr, &narrow)) {
       if(narrow) {
@@ -1176,8 +1269,8 @@ int TCPNameserver::doIXFR(std::unique_ptr<DNSPacket>& q, int outsock)
       DNSName algorithm=trc.d_algoName; // FIXME400: was toLowerCanonic, compare output
       if (algorithm == DNSName("hmac-md5.sig-alg.reg.int"))
         algorithm = DNSName("hmac-md5");
-      if(!db.getTSIGKey(tsigkeyname, &algorithm, &tsig64)) {
-        g_log<<Logger::Error<<logPrefix<<"TSIG key '"<<tsigkeyname<<"' not found"<<endl;
+      if (!db.getTSIGKey(tsigkeyname, algorithm, tsig64)) {
+        g_log << Logger::Error << "TSIG key '" << tsigkeyname << "' for domain '" << target << "' not found" << endl;
         return 0;
       }
       if (B64Decode(tsig64, tsigsecret) == -1) {
@@ -1210,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 50de3b7ec0943bda08876f711f4d3fcaa4eb48e4..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;
@@ -36,7 +36,7 @@ static inline void addRecordToList(std::vector<DNSRecord>& records, const DNSNam
   rec.d_type = type;
   rec.d_ttl = ttl;
 
-  rec.d_content = getRecordContent(type, content);
+  rec.setContent(getRecordContent(type, content));
 
   records.push_back(rec);
 }
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 89bd6e4a227657e0bd3c585c70455cfe1e2684ff..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>
 
 BOOST_AUTO_TEST_SUITE(credentials_cc)
 
+#if defined(DISABLE_HASHED_CREDENTIALS)
+#undef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+#endif
+
 #ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
 BOOST_AUTO_TEST_CASE(test_CredentialsUtils)
 {
@@ -25,6 +32,7 @@ BOOST_AUTO_TEST_CASE(test_CredentialsUtils)
 
   BOOST_CHECK(!verifyPassword(hashed, "not test"));
   BOOST_CHECK(!verifyPassword(sampleHash, "not test"));
+  BOOST_CHECK(!verifyPassword("test", "test"));
 
   BOOST_CHECK(isPasswordHashed(hashed));
   BOOST_CHECK(isPasswordHashed(sampleHash));
@@ -39,10 +47,19 @@ BOOST_AUTO_TEST_CASE(test_CredentialsUtils)
     BOOST_CHECK(verifyPassword(customParams, plaintext));
   }
 
+  {
+    // hash password with invalid parameters
+    BOOST_CHECK_THROW(hashPassword(plaintext, 0, 2, 16), std::runtime_error);
+    BOOST_CHECK_THROW(hashPassword(plaintext, 512, 0, 16), std::runtime_error);
+    BOOST_CHECK_THROW(hashPassword(plaintext, 512, 2, 0), std::runtime_error);
+  }
+
   // empty
   BOOST_CHECK(!isPasswordHashed(""));
   // missing leading $
   BOOST_CHECK(!isPasswordHashed("scrypt$ln=10,p=1,r=8$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI="));
+  // prefix-only
+  BOOST_CHECK(!isPasswordHashed("$scrypt$"));
   // unknown algo
   BOOST_CHECK(!isPasswordHashed("$tcrypt$ln=10,p=1,r=8$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI="));
   // missing parameters
@@ -72,6 +89,12 @@ BOOST_AUTO_TEST_CASE(test_CredentialsUtils)
   BOOST_CHECK_THROW(verifyPassword("$scrypt$ln=A,p=1,r=8$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=", plaintext), std::runtime_error);
   // invalid p
   BOOST_CHECK_THROW(verifyPassword("$scrypt$ln=10,p=p,r=8$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=", plaintext), std::runtime_error);
+  // missing ln
+  BOOST_CHECK_THROW(verifyPassword("$scrypt$la=10,p=1,r=8$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=", plaintext), std::runtime_error);
+  // missing p
+  BOOST_CHECK_THROW(verifyPassword("$scrypt$ln=10,q=1,r=8$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=", plaintext), std::runtime_error);
+  // missing r
+  BOOST_CHECK_THROW(verifyPassword("$scrypt$l,ln=10,q=1,s=8$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=", plaintext), std::runtime_error);
   // work factor is too large
   BOOST_CHECK_THROW(verifyPassword("$scrypt$ln=16,p=1,r=8$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=", plaintext), std::runtime_error);
   // salt is too long
@@ -114,4 +137,19 @@ BOOST_AUTO_TEST_CASE(test_CredentialsHolder)
 #endif
 }
 
+BOOST_AUTO_TEST_CASE(test_SensitiveData)
+{
+  size_t bytes = 16;
+  SensitiveData data(bytes);
+  BOOST_CHECK_EQUAL(data.getString().size(), bytes);
+
+  SensitiveData data2("test");
+  data2 = std::move(data);
+  BOOST_CHECK_EQUAL(data2.getString().size(), bytes);
+  BOOST_CHECK_EQUAL(data.getString().size(), 0U);
+
+  data2.clear();
+  BOOST_CHECK_EQUAL(data2.getString().size(), 0U);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
index c061a8c204736a9efcb920bca7eecea21e64edc7..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_md5sum("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_sha1sum("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 f8799bd5f10702cc0a60af0b622d1d16a800a7f2..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"
@@ -8,7 +11,9 @@
 #include <boost/test/unit_test.hpp>
 #include "distributor.hh"
 #include "dnspacket.hh"
-#include "namespaces.hh" 
+#include "namespaces.hh"
+
+bool g_doGssTSIG = false;
 
 BOOST_AUTO_TEST_SUITE(test_distributor_hh)
 
@@ -22,6 +27,7 @@ struct Question
   {
     return make_unique<DNSPacket>(false);
   }
+  void cleanupGSS(int){}
 };
 
 struct Backend
@@ -33,7 +39,7 @@ struct Backend
 };
 
 static std::atomic<int> g_receivedAnswers;
-static void report(std::unique_ptr<DNSPacket>& A, int B)
+static void report(std::unique_ptr<DNSPacket>& /* A */, int /* B */)
 {
   g_receivedAnswers++;
 }
@@ -50,7 +56,7 @@ BOOST_AUTO_TEST_CASE(test_distributor_basic) {
   int n;
   for(n=0; n < 100; ++n)  {
     Question q;
-    q.d_dt.set(); 
+    q.d_dt.set();
     d->question(q, report);
   }
   sleep(1);
@@ -67,7 +73,7 @@ struct BackendSlow
 };
 
 static std::atomic<int> g_receivedAnswers1;
-static void report1(std::unique_ptr<DNSPacket>& A, int B)
+static void report1(std::unique_ptr<DNSPacket>& /* A */, int /* B */)
 {
   g_receivedAnswers1++;
 }
@@ -86,7 +92,7 @@ BOOST_AUTO_TEST_CASE(test_distributor_queue) {
     // bound should be higher than max-queue-length
     for(n=0; n < 2000; ++n)  {
       Question q;
-      q.d_dt.set(); 
+      q.d_dt.set();
       d->question(q, report1);
     }
     }, DistributorFatal, [](DistributorFatal) { return true; });
@@ -101,7 +107,7 @@ struct BackendDies
   ~BackendDies()
   {
   }
-  std::unique_ptr<DNSPacket> question(Question& q)
+  std::unique_ptr<DNSPacket> question(Question& /* q */)
   {
     //  cout<<"Q: "<<q->qdomain<<endl;
     if(!d_ourcount && ++d_count == 10) {
@@ -119,7 +125,7 @@ std::atomic<int> BackendDies::s_count;
 
 std::atomic<int> g_receivedAnswers2;
 
-static void report2(std::unique_ptr<DNSPacket>& A, int B)
+static void report2(std::unique_ptr<DNSPacket>& /* A */, int /* B */)
 {
   g_receivedAnswers2++;
 }
@@ -137,7 +143,7 @@ BOOST_AUTO_TEST_CASE(test_distributor_dies) {
   try {
     for(int n=0; n < 100; ++n)  {
       Question q;
-      q.d_dt.set(); 
+      q.d_dt.set();
       q.qdomain=DNSName(std::to_string(n));
       q.qtype = QType(QType::A);
       d->question(q, report2);
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 5e0174e442b6acec98981968315ce64c2ca1accc..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>
 #include "dnsname.hh"
 #include "dnsparser.hh"
 #include "dnswriter.hh"
+#include "dolog.hh"
 #include <unistd.h>
 
 bool g_verbose{false};
-bool g_syslog{true};
-bool g_logtimestamps{false};
 
 BOOST_AUTO_TEST_SUITE(test_dnscrypt_cc)
 
index 8bf0e99b26c5960e6de3c5b7473fd7a2537293df..90a513fc880979ce4e38013462c5455f60bc15c1 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.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 "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>& downstream, uint16_t queryID, DNSQuestion& dnsQuestion, PacketBuffer& query, bool actuallySend)
+{
+  return true;
+}
+
+namespace dnsdist {
+std::unique_ptr<CrossProtocolQuery> getInternalQueryFromDQ(DNSQuestion& dq, bool isResponse)
+{
+  return nullptr;
+}
+}
+
+bool DNSDistSNMPAgent::sendBackendStatusChangeTrap(DownstreamState const&)
+{
+  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<DNSDistResponseRuleAction>& localRespRuleActions, const std::vector<DNSDistResponseRuleAction>& cacheInsertedRespRuleActions, InternalQueryState&& ids)
+{
+  return false;
+}
+
 BOOST_AUTO_TEST_SUITE(test_dnsdist_cc)
 
 static const uint16_t ECSSourcePrefixV4 = 24;
@@ -57,12 +107,11 @@ static void validateQuery(const PacketBuffer& packet, bool hasEdns=true, bool ha
 
 static void validateECS(const PacketBuffer& packet, const ComboAddress& expected)
 {
-  ComboAddress rem("::1");
-  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(&qname, qtype, qclass, nullptr, &rem, const_cast<PacketBuffer&>(packet), dnsdist::Protocol::DoUDP, nullptr);
+  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);
@@ -93,10 +142,11 @@ BOOST_AUTO_TEST_CASE(test_addXPF)
 {
   static const uint16_t xpfOptionCode = 65422;
 
-  struct timespec queryTime;
-  gettime(&queryTime);  // does not have to be accurate ("realTime") in tests
-  ComboAddress remote;
   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);
@@ -107,13 +157,10 @@ BOOST_AUTO_TEST_CASE(test_addXPF)
     PacketBuffer packet = query;
 
     /* large enough packet */
-    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);
-
-    DNSQuestion dq(&qname, qtype, QClass::IN, &remote, &remote, packet, dnsdist::Protocol::DoUDP, &queryTime);
+    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());
@@ -126,13 +173,10 @@ BOOST_AUTO_TEST_CASE(test_addXPF)
 
     /* packet is already too large for the 4096 limit over UDP */
     packet.resize(4096);
-    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);
-
-    DNSQuestion dq(&qname, qtype, QClass::IN, &remote, &remote, packet, dnsdist::Protocol::DoUDP, &queryTime);
+    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);
@@ -144,13 +188,10 @@ BOOST_AUTO_TEST_CASE(test_addXPF)
     PacketBuffer packet = query;
 
     /* packet with trailing data (overriding it) */
-    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);
-
-    DNSQuestion dq(&qname, qtype, QClass::IN, &remote, &remote, packet, dnsdist::Protocol::DoUDP, &queryTime);
+    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;
@@ -318,9 +359,11 @@ BOOST_AUTO_TEST_CASE(addECSWithoutEDNSButWithAnswer)
 
 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;
-  ComboAddress remote("192.0.2.1");
   DNSName name("www.powerdns.com.");
 
   PacketBuffer query;
@@ -329,15 +372,12 @@ BOOST_AUTO_TEST_CASE(addECSWithoutEDNSAlreadyParsed)
 
   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, name);
-  BOOST_CHECK(qtype == QType::A);
-  BOOST_CHECK(qclass == QClass::IN);
+  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(&qname, qtype, qclass, nullptr, &remote, packet, dnsdist::Protocol::DoUDP, nullptr);
+  DNSQuestion dq(ids, packet);
   /* Parse the options before handling ECS, simulating a Lua rule asking for EDNS Options */
   BOOST_CHECK(!parseEDNSOptions(dq));
 
@@ -347,7 +387,7 @@ BOOST_AUTO_TEST_CASE(addECSWithoutEDNSAlreadyParsed)
   BOOST_CHECK_EQUAL(ednsAdded, true);
   BOOST_CHECK_EQUAL(ecsAdded, true);
   validateQuery(packet);
-  validateECS(packet, remote);
+  validateECS(packet, ids.origRemote);
 
   /* trailing data */
   packet = query;
@@ -355,12 +395,12 @@ BOOST_AUTO_TEST_CASE(addECSWithoutEDNSAlreadyParsed)
 
   ednsAdded = false;
   ecsAdded = false;
-  consumed = 0;
-  qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-  BOOST_CHECK(qclass == QClass::IN);
-  DNSQuestion dq2(&qname, qtype, qclass, nullptr, &remote, packet, dnsdist::Protocol::DoUDP, nullptr);
+
+  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());
@@ -368,7 +408,7 @@ BOOST_AUTO_TEST_CASE(addECSWithoutEDNSAlreadyParsed)
   BOOST_CHECK_EQUAL(ednsAdded, true);
   BOOST_CHECK_EQUAL(ecsAdded, true);
   validateQuery(packet);
-  validateECS(packet, remote);
+  validateECS(packet, ids.origRemote);
 }
 
 BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECS) {
@@ -418,9 +458,11 @@ BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECS) {
 }
 
 BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECSAlreadyParsed) {
+  InternalQueryState ids;
+  ids.origRemote = ComboAddress("2001:DB8::1");
+  ids.protocol = dnsdist::Protocol::DoUDP;
   bool ednsAdded = false;
   bool ecsAdded = false;
-  ComboAddress remote("2001:DB8::1");
   DNSName name("www.powerdns.com.");
 
   PacketBuffer query;
@@ -431,15 +473,12 @@ BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECSAlreadyParsed) {
 
   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, name);
-  BOOST_CHECK(qtype == QType::A);
-  BOOST_CHECK(qclass == QClass::IN);
+  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(&qname, qtype, qclass, nullptr, &remote, packet, dnsdist::Protocol::DoUDP, nullptr);
+  DNSQuestion dq(ids, packet);
   /* Parse the options before handling ECS, simulating a Lua rule asking for EDNS Options */
   BOOST_CHECK(parseEDNSOptions(dq));
 
@@ -449,19 +488,18 @@ BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECSAlreadyParsed) {
   BOOST_CHECK_EQUAL(ednsAdded, false);
   BOOST_CHECK_EQUAL(ecsAdded, true);
   validateQuery(packet);
-  validateECS(packet, remote);
+  validateECS(packet, ids.origRemote);
 
   /* trailing data */
   packet = query;
   packet.resize(2048);
-  consumed = 0;
   ednsAdded = false;
   ecsAdded = false;
-  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(qclass == QClass::IN);
-  DNSQuestion dq2(&qname, qtype, qclass, nullptr, &remote, packet, dnsdist::Protocol::DoUDP, nullptr);
+  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());
@@ -469,7 +507,7 @@ BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECSAlreadyParsed) {
   BOOST_CHECK_EQUAL(ednsAdded, false);
   BOOST_CHECK_EQUAL(ecsAdded, true);
   validateQuery(packet);
-  validateECS(packet, remote);
+  validateECS(packet, ids.origRemote);
 }
 
 BOOST_AUTO_TEST_CASE(replaceECSWithSameSize) {
@@ -513,11 +551,14 @@ BOOST_AUTO_TEST_CASE(replaceECSWithSameSizeAlreadyParsed) {
   bool ednsAdded = false;
   bool ecsAdded = false;
   ComboAddress remote("192.168.1.25");
-  DNSName name("www.powerdns.com.");
   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, name, QType::A, QClass::IN, 0);
+  GenericDNSPacketWriter<PacketBuffer> pw(query, ids.qname, QType::A, QClass::IN, 0);
   pw.getHeader()->rd = 1;
   EDNSSubnetOpts ecsOpts;
   ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
@@ -533,11 +574,11 @@ BOOST_AUTO_TEST_CASE(replaceECSWithSameSizeAlreadyParsed) {
   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, name);
+  BOOST_CHECK_EQUAL(qname, ids.qname);
   BOOST_CHECK(qtype == QType::A);
   BOOST_CHECK(qclass == QClass::IN);
 
-  DNSQuestion dq(&qname, qtype, qclass, nullptr, &remote, packet, dnsdist::Protocol::DoUDP, nullptr);
+  DNSQuestion dq(ids, packet);
   dq.ecsOverride = true;
 
   /* Parse the options before handling ECS, simulating a Lua rule asking for EDNS Options */
@@ -1416,18 +1457,13 @@ BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenLastOption) {
   validateResponse(newResponse, true, 1);
 }
 
-static DNSQuestion getDNSQuestion(const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const ComboAddress& lc, const ComboAddress& rem, const struct timespec& realTime, PacketBuffer& query)
-{
-  return DNSQuestion(&qname, qtype, qclass, &lc, &rem, query, dnsdist::Protocol::DoUDP, &realTime);
-}
-
-static DNSQuestion turnIntoResponse(const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const ComboAddress& lc, const ComboAddress& rem, const struct timespec& queryRealTime, PacketBuffer&  query, bool resizeBuffer=true)
+static DNSQuestion turnIntoResponse(InternalQueryState& ids, PacketBuffer& query, bool resizeBuffer=true)
 {
   if (resizeBuffer) {
     query.resize(4096);
   }
 
-  auto dq = getDNSQuestion(qname, qtype, qclass, lc, rem, queryRealTime, query);
+  auto dq = DNSQuestion(ids, query);
 
   BOOST_CHECK(addEDNSToQueryTurnedResponse(dq));
 
@@ -1436,11 +1472,16 @@ static DNSQuestion turnIntoResponse(const DNSName& qname, const uint16_t qtype,
 
 static int getZ(const DNSName& qname, const uint16_t qtype, const uint16_t qclass, PacketBuffer& query)
 {
-  ComboAddress lc("127.0.0.1");
-  ComboAddress rem("127.0.0.1");
-  struct timespec queryRealTime;
-  gettime(&queryRealTime, true);
-  DNSQuestion dq = getDNSQuestion(qname, qtype, qclass, lc, rem, queryRealTime, 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);
 }
@@ -1542,12 +1583,15 @@ BOOST_AUTO_TEST_CASE(test_getEDNSZ) {
 }
 
 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;
-  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);
@@ -1556,20 +1600,16 @@ BOOST_AUTO_TEST_CASE(test_addEDNSToQueryTurnedResponse) {
   GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
   opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
   opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
-  ComboAddress lc("127.0.0.1");
-  ComboAddress rem("127.0.0.1");
-  struct timespec queryRealTime;
-  gettime(&queryRealTime, true);
 
   {
     /* no EDNS */
     PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
+    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(qname, qtype, qclass, lc, rem, queryRealTime, query);
+    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);
@@ -1579,12 +1619,12 @@ BOOST_AUTO_TEST_CASE(test_addEDNSToQueryTurnedResponse) {
   {
     /* truncated EDNS */
     PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
+    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(qname, qtype, qclass, lc, rem, queryRealTime, query, false);
+    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);
@@ -1594,11 +1634,11 @@ BOOST_AUTO_TEST_CASE(test_addEDNSToQueryTurnedResponse) {
   {
     /* valid EDNS, no options, DO not set */
     PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
+    GenericDNSPacketWriter<PacketBuffer> pw(query, ids.qname, ids.qtype, ids.qclass, 0);
     pw.addOpt(512, 0, 0);
     pw.commit();
 
-    auto dq = turnIntoResponse(qname, qtype, qclass, lc, rem, queryRealTime, query);
+    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);
@@ -1608,11 +1648,11 @@ BOOST_AUTO_TEST_CASE(test_addEDNSToQueryTurnedResponse) {
   {
     /* valid EDNS, no options, DO set */
     PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
+    GenericDNSPacketWriter<PacketBuffer> pw(query, ids.qname, ids.qtype, ids.qclass, 0);
     pw.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
     pw.commit();
 
-    auto dq = turnIntoResponse(qname, qtype, qclass, lc, rem, queryRealTime, query);
+    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);
@@ -1622,11 +1662,11 @@ BOOST_AUTO_TEST_CASE(test_addEDNSToQueryTurnedResponse) {
   {
     /* valid EDNS, options, DO not set */
     PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
+    GenericDNSPacketWriter<PacketBuffer> pw(query, ids.qname, ids.qtype, ids.qclass, 0);
     pw.addOpt(512, 0, 0, opts);
     pw.commit();
 
-    auto dq = turnIntoResponse(qname, qtype, qclass, lc, rem, queryRealTime, query);
+    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);
@@ -1636,11 +1676,11 @@ BOOST_AUTO_TEST_CASE(test_addEDNSToQueryTurnedResponse) {
   {
     /* valid EDNS, options, DO set */
     PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
+    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(qname, qtype, qclass, lc, rem, queryRealTime, query);
+    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);
@@ -1893,8 +1933,10 @@ BOOST_AUTO_TEST_CASE(test_isEDNSOptionInOpt) {
 }
 
 BOOST_AUTO_TEST_CASE(test_setNegativeAndAdditionalSOA) {
-  struct timespec queryTime;
-  gettime(&queryTime);  // does not have to be accurate ("realTime") in tests
+  InternalQueryState ids;
+  ids.origRemote = ComboAddress("192.0.2.1");
+  ids.protocol = dnsdist::Protocol::DoUDP;
+
   ComboAddress remote;
   DNSName name("www.powerdns.com.");
 
@@ -1912,12 +1954,10 @@ BOOST_AUTO_TEST_CASE(test_setNegativeAndAdditionalSOA) {
     /* no incoming EDNS */
     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);
-    DNSQuestion dq(&qname, qtype, QClass::IN, &remote, &remote, packet, dnsdist::Protocol::DoUDP, &queryTime);
+    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));
+    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());
 
@@ -1936,12 +1976,10 @@ BOOST_AUTO_TEST_CASE(test_setNegativeAndAdditionalSOA) {
     /* now with incoming EDNS */
     auto packet = queryWithEDNS;
 
-    unsigned int consumed = 0;
-    uint16_t qtype;
-    DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-    DNSQuestion dq(&qname, qtype, QClass::IN, &remote, &remote, packet, dnsdist::Protocol::DoUDP, &queryTime);
+    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));
+    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());
 
@@ -1964,12 +2002,10 @@ BOOST_AUTO_TEST_CASE(test_setNegativeAndAdditionalSOA) {
     /* no incoming EDNS */
     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);
-    DNSQuestion dq(&qname, qtype, QClass::IN, &remote, &remote, packet, dnsdist::Protocol::DoUDP, &queryTime);
+    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));
+    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());
 
@@ -1988,12 +2024,10 @@ BOOST_AUTO_TEST_CASE(test_setNegativeAndAdditionalSOA) {
     /* now with incoming EDNS */
     auto packet = queryWithEDNS;
 
-    unsigned int consumed = 0;
-    uint16_t qtype;
-    DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-    DNSQuestion dq(&qname, qtype, QClass::IN, &remote, &remote, packet, dnsdist::Protocol::DoUDP, &queryTime);
+    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));
+    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());
 
@@ -2010,12 +2044,113 @@ BOOST_AUTO_TEST_CASE(test_setNegativeAndAdditionalSOA) {
     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) {
-  const ComboAddress remote("192.168.1.25");
+  InternalQueryState ids;
+  ids.origRemote = ComboAddress("192.168.1.25");
+  ids.protocol = dnsdist::Protocol::DoUDP;
+
   const DNSName name("www.powerdns.com.");
-  const ComboAddress origRemote("127.0.0.1");
   const ComboAddress v4("192.0.2.1");
 
   {
@@ -2032,7 +2167,7 @@ BOOST_AUTO_TEST_CASE(getEDNSOptionsWithoutEDNS) {
     uint16_t qtype;
     uint16_t qclass;
     DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
-    DNSQuestion dq(&qname, qtype, qclass, nullptr, &remote, packet, dnsdist::Protocol::DoUDP, nullptr);
+    DNSQuestion dq(ids, packet);
 
     BOOST_CHECK(!parseEDNSOptions(dq));
   }
@@ -2053,7 +2188,7 @@ BOOST_AUTO_TEST_CASE(getEDNSOptionsWithoutEDNS) {
     uint16_t qtype;
     uint16_t qclass;
     DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
-    DNSQuestion dq(&qname, qtype, qclass, nullptr, &remote, packet, dnsdist::Protocol::DoUDP, nullptr);
+    DNSQuestion dq(ids, packet);
 
     BOOST_CHECK(!parseEDNSOptions(dq));
   }
@@ -2074,10 +2209,65 @@ BOOST_AUTO_TEST_CASE(getEDNSOptionsWithoutEDNS) {
     uint16_t qtype;
     uint16_t qclass;
     DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
-    DNSQuestion dq(&qname, qtype, qclass, nullptr, &remote, packet, dnsdist::Protocol::DoUDP, nullptr);
+    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();
index 4be01c2fcf1a272e1edd21cbcb4f1ac8d24a41e9..42d2bad4a1d3fd257a7e243ed89901f6e3bc2fcc 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>
@@ -21,18 +24,20 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) {
   const size_t maxEntries = 150000;
   DNSDistPacketCache PC(maxEntries, 86400, 1);
   BOOST_CHECK_EQUAL(PC.getSize(), 0U);
-  struct timespec queryTime;
-  gettime(&queryTime);  // does not have to be accurate ("realTime") in tests
 
-  size_t counter=0;
-  size_t skipped=0;
-  ComboAddress remote;
+  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) {
-      DNSName a=DNSName(std::to_string(counter))+DNSName(" hello");
-      BOOST_CHECK_EQUAL(DNSName(a.toString()), a);
+      auto a = DNSName(std::to_string(counter))+DNSName(" hello");
+      ids.qname = a;
 
       PacketBuffer query;
       GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::A, QClass::IN, 0);
@@ -50,17 +55,17 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) {
 
       uint32_t key = 0;
       boost::optional<Netmask> subnet;
-      DNSQuestion dq(&a, QType::A, QClass::IN, &remote, &remote, query, dnsdist::Protocol::DoUDP, &queryTime);
-      bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
+      DNSQuestion dnsQuestion(ids, query);
+      bool found = PC.get(dnsQuestion, 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);
+      PC.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, a, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
 
-      found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+      found = PC.get(dnsQuestion, 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(dnsQuestion.getData().size(), response.size());
+        int match = memcmp(dnsQuestion.getData().data(), response.data(), dnsQuestion.getData().size());
         BOOST_CHECK_EQUAL(match, 0);
         BOOST_CHECK(!subnet);
       }
@@ -75,16 +80,16 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) {
     size_t deleted=0;
     size_t delcounter=0;
     for (delcounter=0; delcounter < counter/1000; ++delcounter) {
-      DNSName a=DNSName(std::to_string(delcounter))+DNSName(" hello");
+      ids.qname = DNSName(std::to_string(delcounter))+DNSName(" hello");
       PacketBuffer query;
-      GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::A, QClass::IN, 0);
+      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(&a, QType::A, QClass::IN, &remote, &remote, query, dnsdist::Protocol::DoUDP, &queryTime);
-      bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
+      DNSQuestion dnsQuestion(ids, query);
+      bool found = PC.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
       if (found == true) {
-        auto removed = PC.expungeByName(a);
+        auto removed = PC.expungeByName(ids.qname);
         BOOST_CHECK_EQUAL(removed, 1U);
         deleted += removed;
       }
@@ -94,14 +99,14 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) {
     size_t matches=0;
     size_t expected=counter-skipped-deleted;
     for (; delcounter < counter; ++delcounter) {
-      DNSName a(DNSName(std::to_string(delcounter))+DNSName(" hello"));
+      ids.qname = DNSName(std::to_string(delcounter))+DNSName(" hello");
       PacketBuffer query;
-      GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::A, QClass::IN, 0);
+      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(&a, QType::A, QClass::IN, &remote, &remote, query, dnsdist::Protocol::DoUDP, &queryTime);
-      if (PC.get(dq, pwQ.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP)) {
+      DNSQuestion dnsQuestion(ids, query);
+      if (PC.get(dnsQuestion, pwQ.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP)) {
         matches++;
       }
     }
@@ -124,54 +129,54 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) {
   }
 }
 
-
 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);
-  struct timespec queryTime;
-  gettime(&queryTime);  // does not have to be accurate ("realTime") in tests
 
   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) {
-      DNSName a(std::to_string(counter) + ".powerdns.com.");
+      ids.qname = DNSName(std::to_string(counter) + ".powerdns.com.");
 
       PacketBuffer query;
-      GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::AAAA, QClass::IN, 0);
+      GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::AAAA, QClass::IN, 0);
       pwQ.getHeader()->rd = 1;
 
       PacketBuffer response;
-      GenericDNSPacketWriter<PacketBuffer> pwR(response, a, QType::AAAA, QClass::IN, 0);
+      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(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.xfr32BitInt(0x01020304);
+      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 dq(&a, QType::AAAA, QClass::IN, &remote, &remote, query, dnsdist::Protocol::DoUDP, &queryTime);
-      bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
+      DNSQuestion dnsQuestion(ids, query);
+      bool found = PC.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
       BOOST_CHECK_EQUAL(found, false);
       BOOST_CHECK(!subnet);
 
-      PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, a, QType::AAAA, QClass::IN, response, receivedOverUDP, 0, boost::none);
+      PC.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), 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);
+      found = PC.get(dnsQuestion, 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(dnsQuestion.getData().size(), response.size());
+        int match = memcmp(dnsQuestion.getData().data(), response.data(), dnsQuestion.getData().size());
         BOOST_CHECK_EQUAL(match, 0);
         BOOST_CHECK(!subnet);
       }
@@ -185,15 +190,15 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheSharded) {
 
     size_t matches = 0;
     for (counter = 0; counter < 100000; ++counter) {
-      DNSName a(std::to_string(counter) + ".powerdns.com.");
+      ids.qname = DNSName(std::to_string(counter) + ".powerdns.com.");
 
       PacketBuffer query;
-      GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::AAAA, QClass::IN, 0);
+      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(&a, QType::AAAA, QClass::IN, &remote, &remote, query, dnsdist::Protocol::DoUDP, &queryTime);
-      if (PC.get(dq, pwQ.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP)) {
+      DNSQuestion dnsQuestion(ids, query);
+      if (PC.get(dnsQuestion, pwQ.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP)) {
         matches++;
       }
     }
@@ -228,14 +233,16 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheSharded) {
 BOOST_AUTO_TEST_CASE(test_PacketCacheTCP) {
   const size_t maxEntries = 150000;
   DNSDistPacketCache PC(maxEntries, 86400, 1);
-  struct timespec queryTime;
-  gettime(&queryTime);  // does not have to be accurate ("realTime") in tests
+  InternalQueryState ids;
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.protocol = dnsdist::Protocol::DoUDP;
 
   ComboAddress remote;
   bool dnssecOK = false;
   try {
-    DNSName a = DNSName("tcp");
-    BOOST_CHECK_EQUAL(DNSName(a.toString()), a);
+    DNSName a("tcp");
+    ids.qname = a;
 
     PacketBuffer query;
     GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::AAAA, QClass::IN, 0);
@@ -248,22 +255,21 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheTCP) {
     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.xfr32BitInt(0x01020304);
+    ComboAddress v6addr("2001:db8::1");
+    pwR.xfrCAWithoutPort(6, v6addr);
     pwR.commit();
 
     {
       /* UDP */
       uint32_t key = 0;
       boost::optional<Netmask> subnet;
-      DNSQuestion dq(&a, QType::A, QClass::IN, &remote, &remote, query, dnsdist::Protocol::DoUDP, &queryTime);
-      bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
+      DNSQuestion dnsQuestion(ids, query);
+      bool found = PC.get(dnsQuestion, 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);
+      PC.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, a, QType::A, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
+      found = PC.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
       BOOST_CHECK_EQUAL(found, true);
       BOOST_CHECK(!subnet);
     }
@@ -272,13 +278,14 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheTCP) {
       /* same but over TCP */
       uint32_t key = 0;
       boost::optional<Netmask> subnet;
-      DNSQuestion dq(&a, QType::A, QClass::IN, &remote, &remote, query, dnsdist::Protocol::DoTCP, &queryTime);
-      bool found = PC.get(dq, 0, &key, subnet, dnssecOK, !receivedOverUDP);
+      ids.protocol = dnsdist::Protocol::DoTCP;
+      DNSQuestion dnsQuestion(ids, query);
+      bool found = PC.get(dnsQuestion, 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);
+      PC.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, a, QType::A, QClass::IN, response, !receivedOverUDP, RCode::NoError, boost::none);
+      found = PC.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, !receivedOverUDP, 0, true);
       BOOST_CHECK_EQUAL(found, true);
       BOOST_CHECK(!subnet);
     }
@@ -292,14 +299,16 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheTCP) {
 BOOST_AUTO_TEST_CASE(test_PacketCacheServFailTTL) {
   const size_t maxEntries = 150000;
   DNSDistPacketCache PC(maxEntries, 86400, 1);
-  struct timespec queryTime;
-  gettime(&queryTime);  // does not have to be accurate ("realTime") in tests
+  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");
-    BOOST_CHECK_EQUAL(DNSName(a.toString()), a);
+    ids.qname = a;
 
     PacketBuffer query;
     GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::A, QClass::IN, 0);
@@ -316,20 +325,20 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheServFailTTL) {
 
     uint32_t key = 0;
     boost::optional<Netmask> subnet;
-    DNSQuestion dq(&a, QType::A, QClass::IN, &remote, &remote, query, dnsdist::Protocol::DoUDP, &queryTime);
-    bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = PC.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).
-    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);
+    PC.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, a, QType::A, QClass::IN, response, receivedOverUDP, RCode::ServFail, boost::optional<uint32_t>(0));
+    found = PC.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).
-    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);
+    PC.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, a, QType::A, QClass::IN, response, receivedOverUDP, RCode::ServFail, boost::optional<uint32_t>(300));
+    found = PC.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
     BOOST_CHECK_EQUAL(found, true);
     BOOST_CHECK(!subnet);
   }
@@ -343,13 +352,16 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheNoDataTTL) {
   const size_t maxEntries = 150000;
   DNSDistPacketCache PC(maxEntries, /* maxTTL */ 86400, /* minTTL */ 1, /* tempFailureTTL */ 60, /* maxNegativeTTL */ 1);
 
-  struct timespec queryTime;
-  gettime(&queryTime);  // does not have to be accurate ("realTime") in tests
-
   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;
@@ -369,19 +381,19 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheNoDataTTL) {
 
     uint32_t key = 0;
     boost::optional<Netmask> subnet;
-    DNSQuestion dq(&name, QType::A, QClass::IN, &remote, &remote, query, dnsdist::Protocol::DoUDP, &queryTime);
-    bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = PC.get(dnsQuestion, 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);
+    PC.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, name, QType::A, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
+    found = PC.get(dnsQuestion, 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);
+    found = PC.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
     BOOST_CHECK_EQUAL(found, false);
     BOOST_CHECK(!subnet);
   }
@@ -395,13 +407,16 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheNXDomainTTL) {
   const size_t maxEntries = 150000;
   DNSDistPacketCache PC(maxEntries, /* maxTTL */ 86400, /* minTTL */ 1, /* tempFailureTTL */ 60, /* maxNegativeTTL */ 1);
 
-  struct timespec queryTime;
-  gettime(&queryTime);  // does not have to be accurate ("realTime") in tests
+  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;
@@ -421,19 +436,19 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheNXDomainTTL) {
 
     uint32_t key = 0;
     boost::optional<Netmask> subnet;
-    DNSQuestion dq(&name, QType::A, QClass::IN, &remote, &remote, query, dnsdist::Protocol::DoUDP, &queryTime);
-    bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = PC.get(dnsQuestion, 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);
+    PC.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, name, QType::A, QClass::IN, response, receivedOverUDP, RCode::NXDomain, boost::none);
+    found = PC.get(dnsQuestion, 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);
+    found = PC.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
     BOOST_CHECK_EQUAL(found, false);
     BOOST_CHECK(!subnet);
   }
@@ -443,37 +458,243 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheNXDomainTTL) {
   }
 }
 
+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 dnsQuestion(ids, query);
+    bool found = PC.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, false);
+    BOOST_CHECK(!subnet);
+
+    PC.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, RCode::NXDomain, boost::none);
+
+    bool allowTruncated = true;
+    found = PC.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true, allowTruncated);
+    BOOST_CHECK_EQUAL(found, true);
+    BOOST_CHECK(!subnet);
+
+    allowTruncated = false;
+    found = PC.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 g_PC(500000);
 
 static void threadMangler(unsigned int offset)
 {
-  struct timespec queryTime;
-  gettime(&queryTime);  // does not have to be accurate ("realTime") in tests
+  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) {
-      DNSName a=DNSName("hello ")+DNSName(std::to_string(counter+offset));
+      ids.qname = DNSName("hello ")+DNSName(std::to_string(counter+offset));
       PacketBuffer query;
-      GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::A, QClass::IN, 0);
+      GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
       pwQ.getHeader()->rd = 1;
 
       PacketBuffer response;
-      GenericDNSPacketWriter<PacketBuffer> pwR(response, a, QType::A, QClass::IN, 0);
+      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(a, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER);
+      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(&a, QType::A, QClass::IN, &remote, &remote, query, dnsdist::Protocol::DoUDP, &queryTime);
-      g_PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
+      DNSQuestion dnsQuestion(ids, query);
+      g_PC.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
 
-      g_PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, a, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+      g_PC.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
     }
   }
   catch(PDNSException& e) {
@@ -486,22 +707,25 @@ 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;
-  struct timespec queryTime;
-  gettime(&queryTime);  // does not have to be accurate ("realTime") in tests
   try
   {
     ComboAddress remote;
     for(unsigned int counter=0; counter < 100000; ++counter) {
-      DNSName a=DNSName("hello ")+DNSName(std::to_string(counter+offset));
+      ids.qname = DNSName("hello ")+DNSName(std::to_string(counter+offset));
       PacketBuffer query;
-      GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::A, QClass::IN, 0);
+      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(&a, QType::A, QClass::IN, &remote, &remote, query, dnsdist::Protocol::DoUDP, &queryTime);
-      bool found = g_PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
+      DNSQuestion dnsQuestion(ids, query);
+      bool found = g_PC.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
       if (!found) {
        g_missing++;
       }
@@ -551,8 +775,11 @@ BOOST_AUTO_TEST_CASE(test_PCCollision) {
   DNSDistPacketCache PC(maxEntries, 86400, 1, 60, 3600, 60, false, 1, true, true);
   BOOST_CHECK_EQUAL(PC.getSize(), 0U);
 
-  DNSName qname("www.powerdns.com.");
-  uint16_t qtype = QType::AAAA;
+  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;
@@ -563,7 +790,7 @@ BOOST_AUTO_TEST_CASE(test_PCCollision) {
      insert a corresponding response */
   {
     PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, qtype, QClass::IN, 0);
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, ids.qtype, QClass::IN, 0);
     pwQ.getHeader()->rd = 1;
     pwQ.getHeader()->id = qid;
     GenericDNSPacketWriter<PacketBuffer>::optvect_t ednsOptions;
@@ -574,29 +801,28 @@ BOOST_AUTO_TEST_CASE(test_PCCollision) {
     pwQ.commit();
 
     ComboAddress remote("192.0.2.1");
-    struct timespec queryTime;
-    gettime(&queryTime);
-    DNSQuestion dq(&qname, QType::AAAA, QClass::IN, &remote, &remote, query, dnsdist::Protocol::DoUDP, &queryTime);
-    bool found = PC.get(dq, 0, &key, subnetOut, dnssecOK, receivedOverUDP);
+    ids.queryRealTime.start();
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = PC.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, qname, qtype, QClass::IN, 0);
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, ids.qtype, QClass::IN, 0);
     pwR.getHeader()->rd = 1;
     pwR.getHeader()->id = qid;
-    pwR.startRecord(qname, qtype, 100, QClass::IN, DNSResourceRecord::ANSWER);
-    ComboAddress v6("::1");
-    pwR.xfrCAWithoutPort(6, v6);
+    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();
 
-    PC.insert(key, subnetOut, *(getFlagsFromDNSHeader(pwR.getHeader())), dnssecOK, qname, qtype, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
+    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);
+    found = PC.get(dnsQuestion, 0, &key, subnetOut, dnssecOK, receivedOverUDP);
     BOOST_CHECK_EQUAL(found, true);
     BOOST_REQUIRE(subnetOut);
     BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
@@ -606,7 +832,7 @@ BOOST_AUTO_TEST_CASE(test_PCCollision) {
      we should get the same key (collision) but no match */
   {
     PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, qtype, QClass::IN, 0);
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, ids.qtype, QClass::IN, 0);
     pwQ.getHeader()->rd = 1;
     pwQ.getHeader()->id = qid;
     GenericDNSPacketWriter<PacketBuffer>::optvect_t ednsOptions;
@@ -617,10 +843,9 @@ BOOST_AUTO_TEST_CASE(test_PCCollision) {
     pwQ.commit();
 
     ComboAddress remote("192.0.2.1");
-    struct timespec queryTime;
-    gettime(&queryTime);
-    DNSQuestion dq(&qname, QType::AAAA, QClass::IN, &remote, &remote, query, dnsdist::Protocol::DoUDP, &queryTime);
-    bool found = PC.get(dq, 0, &secondKey, subnetOut, dnssecOK, receivedOverUDP);
+    ids.queryRealTime.start();
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = PC.get(dnsQuestion, 0, &secondKey, subnetOut, dnssecOK, receivedOverUDP);
     BOOST_CHECK_EQUAL(found, false);
     BOOST_CHECK_EQUAL(secondKey, key);
     BOOST_REQUIRE(subnetOut);
@@ -643,7 +868,7 @@ BOOST_AUTO_TEST_CASE(test_PCCollision) {
       for (size_t idxB = 0; idxB < 256; idxB++) {
         for (size_t idxC = 0; idxC < 256; idxC++) {
           PacketBuffer secondQuery;
-          GenericDNSPacketWriter<PacketBuffer> pwFQ(secondQuery, qname, QType::AAAA, QClass::IN, 0);
+          GenericDNSPacketWriter<PacketBuffer> pwFQ(secondQuery, ids.qname, QType::AAAA, QClass::IN, 0);
           pwFQ.getHeader()->rd = 1;
           pwFQ.getHeader()->qr = false;
           pwFQ.getHeader()->id = 0x42;
@@ -652,7 +877,7 @@ BOOST_AUTO_TEST_CASE(test_PCCollision) {
           ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
           pwFQ.addOpt(512, 0, 0, ednsOptions);
           pwFQ.commit();
-          secondKey = pc.getKey(qname.toDNSString(), qname.wirelength(), secondQuery, false);
+          secondKey = pc.getKey(ids.qname.toDNSString(), ids.qname.wirelength(), secondQuery, false);
           auto pair = colMap.emplace(secondKey, opt.source);
           total++;
           if (!pair.second) {
@@ -675,8 +900,11 @@ BOOST_AUTO_TEST_CASE(test_PCDNSSECCollision) {
   DNSDistPacketCache PC(maxEntries, 86400, 1, 60, 3600, 60, false, 1, true, true);
   BOOST_CHECK_EQUAL(PC.getSize(), 0U);
 
-  DNSName qname("www.powerdns.com.");
-  uint16_t qtype = QType::AAAA;
+  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;
@@ -686,40 +914,322 @@ BOOST_AUTO_TEST_CASE(test_PCDNSSECCollision) {
      check that it doesn't match without DO, but does with it */
   {
     PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, qtype, QClass::IN, 0);
+    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");
-    struct timespec queryTime;
-    gettime(&queryTime);
-    DNSQuestion dq(&qname, QType::AAAA, QClass::IN, &remote, &remote, query, dnsdist::Protocol::DoUDP, &queryTime);
-    bool found = PC.get(dq, 0, &key, subnetOut, true, receivedOverUDP);
+    ids.queryRealTime.start();
+    ids.origRemote = remote;
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = PC.get(dnsQuestion, 0, &key, subnetOut, true, receivedOverUDP);
     BOOST_CHECK_EQUAL(found, false);
 
     PacketBuffer response;
-    GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, qtype, QClass::IN, 0);
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, ids.qtype, QClass::IN, 0);
     pwR.getHeader()->rd = 1;
     pwR.getHeader()->id = qid;
-    pwR.startRecord(qname, qtype, 100, QClass::IN, DNSResourceRecord::ANSWER);
-    ComboAddress v6("::1");
-    pwR.xfrCAWithoutPort(6, v6);
+    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();
 
-    PC.insert(key, subnetOut, *(getFlagsFromDNSHeader(pwR.getHeader())), /* DNSSEC OK is set */ true, qname, qtype, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
+    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);
+    found = PC.get(dnsQuestion, 0, &key, subnetOut, false, receivedOverUDP);
     BOOST_CHECK_EQUAL(found, false);
 
-    found = PC.get(dq, 0, &key, subnetOut, true, receivedOverUDP);
+    found = PC.get(dnsQuestion, 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 dnsQuestion(ids, query);
+    bool found = PC.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, false);
+    BOOST_CHECK(!subnet);
+
+    PC.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, ids.qtype, ids.qclass, response, receivedOverUDP, 0, boost::none);
+    found = PC.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+    BOOST_CHECK_EQUAL(found, false);
+  }
+}
+
 BOOST_AUTO_TEST_SUITE_END()
index 41f882df531fb41e741ff6db1a5071f12d1fea8f..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(...){}
 }
@@ -523,14 +527,19 @@ BOOST_AUTO_TEST_CASE(test_suffixmatch) {
 
   smn.add(DNSName("news.bbc.co.uk."));
   BOOST_CHECK(smn.check(DNSName("news.bbc.co.uk.")));
+  BOOST_CHECK(smn.getBestMatch(DNSName("news.bbc.co.uk")) == DNSName("news.bbc.co.uk."));
   BOOST_CHECK(smn.check(DNSName("www.news.bbc.co.uk.")));
+  BOOST_CHECK(smn.getBestMatch(DNSName("www.news.bbc.co.uk")) == DNSName("news.bbc.co.uk."));
   BOOST_CHECK(smn.check(DNSName("www.www.www.www.www.news.bbc.co.uk.")));
   BOOST_CHECK(!smn.check(DNSName("images.bbc.co.uk.")));
+  BOOST_CHECK(smn.getBestMatch(DNSName("images.bbc.co.uk")) == std::nullopt);
 
   BOOST_CHECK(!smn.check(DNSName("www.news.gov.uk.")));
+  BOOST_CHECK(smn.getBestMatch(DNSName("www.news.gov.uk")) == std::nullopt);
 
   smn.add(g_rootdnsname); // block the root
   BOOST_CHECK(smn.check(DNSName("a.root-servers.net.")));
+  BOOST_CHECK(smn.getBestMatch(DNSName("a.root-servers.net.")) == g_rootdnsname);
 
   DNSName examplenet("example.net.");
   DNSName net("net.");
@@ -711,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));
   }
@@ -730,7 +739,7 @@ BOOST_AUTO_TEST_CASE(test_compare_canonical) {
        "yyy.XXX."})
     right.push_back(DNSName(b));
 
-  
+
   BOOST_CHECK(vec==right);
 }
 
@@ -971,6 +980,27 @@ BOOST_AUTO_TEST_CASE(test_getrawlabel) {
   BOOST_CHECK_THROW(name.getRawLabel(name.countLabels()), std::out_of_range);
 }
 
+BOOST_AUTO_TEST_CASE(test_getrawlabels_visitor) {
+  DNSName name("a.bb.ccc.dddd.");
+  auto visitor = name.getRawLabelsVisitor();
+  BOOST_CHECK(!visitor.empty());
+  BOOST_CHECK_EQUAL(visitor.front(), *name.getRawLabels().begin());
+  BOOST_CHECK_EQUAL(visitor.back(), *name.getRawLabels().rbegin());
+
+  BOOST_CHECK_EQUAL(visitor.back(), "dddd");
+  BOOST_CHECK(visitor.pop_back());
+  BOOST_CHECK_EQUAL(visitor.back(), "ccc");
+  BOOST_CHECK(visitor.pop_back());
+  BOOST_CHECK_EQUAL(visitor.back(), "bb");
+  BOOST_CHECK(visitor.pop_back());
+  BOOST_CHECK_EQUAL(visitor.back(), "a");
+  BOOST_CHECK(visitor.pop_back());
+  BOOST_CHECK(visitor.empty());
+  BOOST_CHECK(!visitor.pop_back());
+  BOOST_CHECK_THROW(visitor.front(), std::out_of_range);
+  BOOST_CHECK_THROW(visitor.back(), std::out_of_range);
+}
+
 BOOST_AUTO_TEST_CASE(test_getlastlabel) {
   DNSName name("www.powerdns.com");
   DNSName ans = name.getLastLabel();
@@ -993,14 +1023,18 @@ BOOST_AUTO_TEST_CASE(test_getcommonlabels) {
   BOOST_CHECK_EQUAL(name2.getCommonLabels(name1), DNSName("powerdns.com"));
 
   const DNSName name3("www.powerdns.org");
-  BOOST_CHECK_EQUAL(name1.getCommonLabels(name3), DNSName());
-  BOOST_CHECK_EQUAL(name2.getCommonLabels(name3), DNSName());
-  BOOST_CHECK_EQUAL(name3.getCommonLabels(name1), DNSName());
-  BOOST_CHECK_EQUAL(name3.getCommonLabels(name2), DNSName());
+  BOOST_CHECK_EQUAL(name1.getCommonLabels(name3), g_rootdnsname);
+  BOOST_CHECK_EQUAL(name2.getCommonLabels(name3), g_rootdnsname);
+  BOOST_CHECK_EQUAL(name3.getCommonLabels(name1), g_rootdnsname);
+  BOOST_CHECK_EQUAL(name3.getCommonLabels(name2), g_rootdnsname);
 
   const DNSName name4("WWw.PowErDnS.org");
   BOOST_CHECK_EQUAL(name3.getCommonLabels(name4), name3);
   BOOST_CHECK_EQUAL(name4.getCommonLabels(name3), name4);
+
+  const DNSName name5;
+  BOOST_CHECK_EQUAL(name1.getCommonLabels(name5), DNSName());
+  BOOST_CHECK_EQUAL(name5.getCommonLabels(name1), DNSName());
 }
 
 BOOST_AUTO_TEST_SUITE_END()
index b040237881f3d0b8c3f73f29b5027a7ba413b61e..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
@@ -117,7 +120,8 @@ BOOST_AUTO_TEST_CASE(test_ageDNSPacket) {
   auto firstPacket = generatePacket(3600);
   auto expectedAlteredPacket = generatePacket(1800);
 
-  ageDNSPacket(reinterpret_cast<char*>(firstPacket.data()), firstPacket.size(), 1800);
+  dnsheader_aligned dh_aligned(firstPacket.data());
+  ageDNSPacket(reinterpret_cast<char*>(firstPacket.data()), firstPacket.size(), 1800, dh_aligned);
 
   BOOST_REQUIRE_EQUAL(firstPacket.size(), expectedAlteredPacket.size());
   for (size_t idx = 0; idx < firstPacket.size(); idx++) {
@@ -127,14 +131,14 @@ BOOST_AUTO_TEST_CASE(test_ageDNSPacket) {
 
   /* now call it with a truncated packet, missing the last TTL and rdata,
      the packet should not be altered. */
-  ageDNSPacket(reinterpret_cast<char*>(firstPacket.data()), firstPacket.size() - sizeof(uint32_t) - /* rdata length */ sizeof (uint16_t) - /* IPv4 payload in rdata */ 4 - /* size of OPT record */ 11, 900);
+  ageDNSPacket(reinterpret_cast<char*>(firstPacket.data()), firstPacket.size() - sizeof(uint32_t) - /* rdata length */ sizeof (uint16_t) - /* IPv4 payload in rdata */ 4 - /* size of OPT record */ 11, 900, dh_aligned);
 
   BOOST_CHECK(firstPacket == expectedAlteredPacket);
 
   /* now remove more than the remaining TTL. We expect ageDNSPacket
      to cap this at zero and not cause an unsigned underflow into
      the 2^32-1 neighbourhood */
-  ageDNSPacket(reinterpret_cast<char*>(firstPacket.data()), firstPacket.size(), 1801);
+  ageDNSPacket(reinterpret_cast<char*>(firstPacket.data()), firstPacket.size(), 1801, dh_aligned);
 
   uint32_t ttl = 0;
 
@@ -519,7 +523,7 @@ BOOST_AUTO_TEST_CASE(test_clearDNSPacketRecordTypes) {
     BOOST_CHECK_EQUAL(getRecordsOfTypeCount(reinterpret_cast<char*>(packet.data()), packet.size(), 1, QType::AAAA), 1);
     BOOST_CHECK_EQUAL(getRecordsOfTypeCount(reinterpret_cast<char*>(packet.data()), packet.size(), 3, QType::A), 1);
 
-    std::set<QType> toremove{QType::AAAA};
+    std::unordered_set<QType> toremove{QType::AAAA};
     clearDNSPacketRecordTypes(packet, toremove);
 
     BOOST_CHECK_EQUAL(getRecordsOfTypeCount(reinterpret_cast<char*>(packet.data()), packet.size(), 1, QType::A), 1);
@@ -591,7 +595,7 @@ BOOST_AUTO_TEST_CASE(test_clearDNSPacketUnsafeRecordTypes) {
     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), 1);
 
-    std::set<QType> toremove{QType::AAAA};
+    std::unordered_set<QType> toremove{QType::AAAA};
     clearDNSPacketRecordTypes(packet, toremove);
 
     // nothing should have been removed as an "unsafe" MX RR is in the packet
@@ -606,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 cb028c4bf503131da37a80b45caf9e3ea84fb5be..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)
 
@@ -332,7 +342,7 @@ BOOST_AUTO_TEST_CASE(test_record_types) {
  }
 }
 
-static bool test_dnsrecords_cc_predicate( std::exception const &ex ) { return true; }
+static bool test_dnsrecords_cc_predicate(std::exception const& /* ex */) { return true; }
 
 // these *MUST NOT* parse properly!
 BOOST_AUTO_TEST_CASE(test_record_types_bad_values) {
@@ -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);
@@ -697,7 +704,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_records_types) {
     const auto& record = parser.d_answers.at(0).first;
     BOOST_REQUIRE(record.d_type == QType::NSEC3);
     BOOST_REQUIRE(record.d_class == QClass::IN);
-    auto content = std::dynamic_pointer_cast<NSEC3RecordContent>(record.d_content);
+    auto content = getRR<NSEC3RecordContent>(record);
     BOOST_REQUIRE(content);
     BOOST_CHECK_EQUAL(content->numberOfTypesSet(), 0U);
     for (size_t idx = 0; idx < 65536; idx++) {
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 7c55af0348991a0a6d995c758aacffc92b6b3752..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"
@@ -16,24 +19,24 @@ BOOST_AUTO_TEST_SUITE(test_ipcrypt_hh)
 
 BOOST_AUTO_TEST_CASE(test_ipcrypt4)
 {
-  ComboAddress ca("127.0.0.1");
-  std::string key="0123456789ABCDEF";
-  auto encrypted = encryptCA(ca, key);
+  ComboAddress address("127.0.0.1");
+  std::string key = "0123456789ABCDEF";
+  auto encrypted = encryptCA(address, key);
 
   auto decrypted = decryptCA(encrypted, key);
-  BOOST_CHECK_EQUAL(ca.toString(), decrypted.toString());
+  BOOST_CHECK_EQUAL(address.toString(), decrypted.toString());
 }
 
 BOOST_AUTO_TEST_CASE(test_ipcrypt4_vector)
 {
-  vector<pair<string,string>>  tests{   // test vector from https://github.com/veorq/ipcrypt
-    {{"127.0.0.1"},{"114.62.227.59"}},
-    {{"8.8.8.8"},  {"46.48.51.50"}},
-    {{"1.2.3.4"},  {"171.238.15.199"}}};
+  // test vector from https://github.com/veorq/ipcrypt
+  vector<pair<string, string>> tests{{{"127.0.0.1"}, {"114.62.227.59"}},
+                                     {{"8.8.8.8"}, {"46.48.51.50"}},
+                                     {{"1.2.3.4"}, {"171.238.15.199"}}};
 
-  std::string key="some 16-byte key";
+  std::string key = "some 16-byte key";
 
-  for(const auto& p : tests) {
+  for (const auto& p : tests) {
     auto encrypted = encryptCA(ComboAddress(p.first), key);
     BOOST_CHECK_EQUAL(encrypted.toString(), p.second);
     auto decrypted = decryptCA(encrypted, key);
@@ -41,32 +44,34 @@ BOOST_AUTO_TEST_CASE(test_ipcrypt4_vector)
   }
 
   // test from Frank Denis' test.cc
-  ComboAddress ip("192.168.69.42"), out, dec;
+  ComboAddress address("192.168.69.42");
+  ComboAddress out;
+  ComboAddress dec;
   string key2;
-  for(int n=0; n<16; ++n)
-    key2.append(1, (char)n+1);
+  for (int n = 0; n < 16; ++n) {
+    key2.append(1, (char)n + 1);
+  }
 
   for (unsigned int i = 0; i < 100000000UL; i++) {
-    out=encryptCA(ip, key2);
+    out = encryptCA(address, key2);
     //    dec=decryptCA(out, key2);
     // BOOST_CHECK(ip==dec);
-    ip=out;
+    address = out;
   }
 
   ComboAddress expected("93.155.197.186");
 
-  BOOST_CHECK_EQUAL(ip.toString(), expected.toString());
+  BOOST_CHECK_EQUAL(address.toString(), expected.toString());
 }
 
-
 BOOST_AUTO_TEST_CASE(test_ipcrypt6)
 {
-  ComboAddress ca("::1");
-  std::string key="0123456789ABCDEF";
-  auto encrypted = encryptCA(ca, key);
+  ComboAddress address("::1");
+  std::string key = "0123456789ABCDEF";
+  auto encrypted = encryptCA(address, key);
 
   auto decrypted = decryptCA(encrypted, key);
-  BOOST_CHECK_EQUAL(ca.toString(), decrypted.toString());
+  BOOST_CHECK_EQUAL(address.toString(), decrypted.toString());
 }
 
 BOOST_AUTO_TEST_SUITE_END()
index 36e65779c4a26a454d2fb0287957cc4baa928e1a..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
@@ -21,16 +25,16 @@ BOOST_AUTO_TEST_CASE(test_ComboAddress) {
   ComboAddress remote("130.161.33.15", 53);
   BOOST_CHECK(!(local == remote));
   BOOST_CHECK_EQUAL(remote.sin4.sin_port, htons(53));
-  
+
   ComboAddress withport("213.244.168.210:53");
   BOOST_CHECK_EQUAL(withport.sin4.sin_port, htons(53));
-  
+
   ComboAddress withportO("213.244.168.210:53", 5300);
   BOOST_CHECK_EQUAL(withportO.sin4.sin_port, htons(53));
+
   withport = ComboAddress("[::]:53");
   BOOST_CHECK_EQUAL(withport.sin4.sin_port, htons(53));
-  
+
   withport = ComboAddress("[::]:5300", 53);
   BOOST_CHECK_EQUAL(withport.sin4.sin_port, htons(5300));
 
@@ -112,7 +116,7 @@ BOOST_AUTO_TEST_CASE(test_ComboAddressTruncate) {
 
   ca4.truncate(29);
   BOOST_CHECK_EQUAL(ca4.toString(), "130.161.252.24");
-  
+
   ca4.truncate(23);
   BOOST_CHECK_EQUAL(ca4.toString(), "130.161.252.0");
 
@@ -139,7 +143,7 @@ BOOST_AUTO_TEST_CASE(test_ComboAddressTruncate) {
   BOOST_CHECK_EQUAL(ca6.toString(), "2001::");
   ca6.truncate(8);
   BOOST_CHECK_EQUAL(ca6.toString(), "2000::");
-  
+
 
   orig=ca6=ComboAddress("2001:888:2000:1d::2");
   for(int n=128; n; --n) {
@@ -162,6 +166,27 @@ BOOST_AUTO_TEST_CASE(test_ComboAddressTruncate) {
   }
 }
 
+BOOST_AUTO_TEST_CASE(test_ComboAddressReverse)
+{
+  ComboAddress a{"1.2.3.4"};
+  BOOST_CHECK_EQUAL(a.toStringReversed(), "4.3.2.1");
+
+  ComboAddress b{"192.168.0.1"};
+  BOOST_CHECK_EQUAL(b.toStringReversed(), "1.0.168.192");
+
+  ComboAddress c{"2001:db8::567:89ab"};
+  BOOST_CHECK_EQUAL(c.toStringReversed(), "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2");
+
+  ComboAddress d{"::1"};
+  BOOST_CHECK_EQUAL(d.toStringReversed(), "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");
+
+  ComboAddress e{"ab:cd::10"};
+  BOOST_CHECK_EQUAL(e.toStringReversed(), "0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.c.0.0.b.a.0.0");
+
+  ComboAddress f{"4321:0:1:2:3:4:567:89ab"};
+  BOOST_CHECK_EQUAL(f.toStringReversed(), "b.a.9.8.7.6.5.0.4.0.0.0.3.0.0.0.2.0.0.0.1.0.0.0.0.0.0.0.1.2.3.4");
+}
+
 BOOST_AUTO_TEST_CASE(test_Mapping)
 {
   ComboAddress lh("::1");
@@ -171,7 +196,7 @@ BOOST_AUTO_TEST_CASE(test_Mapping)
 BOOST_AUTO_TEST_CASE(test_Netmask) {
   ComboAddress local("127.0.0.1", 53);
   ComboAddress remote("130.161.252.29", 53);
-  
+
   Netmask nm("127.0.0.1/24");
   BOOST_CHECK(nm.getBits() == 24);
   BOOST_CHECK(nm.match(local));
@@ -243,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 248ef8d611682630bbf144c898af3ca105a1a144..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
@@ -127,7 +130,7 @@ BOOST_AUTO_TEST_CASE(test_endianness) {
   uint32_t i = 1;
 #if BYTE_ORDER == BIG_ENDIAN
   BOOST_CHECK_EQUAL(i, htonl(i));
-#elif BYTE_ORDER == LITTLE_ENDIAN 
+#elif BYTE_ORDER == LITTLE_ENDIAN
   uint32_t j=0x01000000;
   BOOST_CHECK_EQUAL(i, ntohl(j));
 #else
@@ -135,15 +138,6 @@ BOOST_AUTO_TEST_CASE(test_endianness) {
 #endif
 }
 
-BOOST_AUTO_TEST_CASE(test_parseService) {
-    ServiceTuple tp;
-    parseService("smtp.powerdns.com:25", tp);
-    BOOST_CHECK_EQUAL(tp.host, "smtp.powerdns.com");
-    BOOST_CHECK_EQUAL(tp.port, 25);
-    parseService("smtp.powerdns.com", tp);    
-    BOOST_CHECK_EQUAL(tp.port, 25);
-}
-
 BOOST_AUTO_TEST_CASE(test_ternary) {
   int maxqps=1024;
   BOOST_CHECK_EQUAL(defTer(maxqps, 16384), maxqps);
@@ -396,6 +390,33 @@ BOOST_AUTO_TEST_CASE(test_makeBytesFromHex) {
   BOOST_CHECK_EQUAL(out, "\x12\x34\x56\x78\x90\xab\xcd\xef");
 
   BOOST_CHECK_THROW(makeBytesFromHex("123"), std::range_error);
+
+  BOOST_CHECK_THROW(makeBytesFromHex("1234GG"), std::range_error);
+}
+
+BOOST_AUTO_TEST_CASE(test_makeHexDump) {
+  auto out = makeHexDump("\x12\x34\x56\x78\x90\xab\xcd\xef");
+  // there is a trailing white space by design
+  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 7fe483d4bd7930c095cff905b9361b9bc78542ce..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>
@@ -24,7 +26,7 @@ BOOST_AUTO_TEST_CASE(test_getMultiplexerSilent)
 BOOST_AUTO_TEST_CASE(test_MPlexer)
 {
   for (const auto& entry : FDMultiplexer::getMultiplexerMap()) {
-    auto mplexer = std::unique_ptr<FDMultiplexer>(entry.second());
+    auto mplexer = std::unique_ptr<FDMultiplexer>(entry.second(FDMultiplexer::s_maxevents));
     BOOST_REQUIRE(mplexer != nullptr);
     //cerr<<"Testing multiplexer "<<mplexer->getName()<<endl;
 
@@ -51,7 +53,7 @@ BOOST_AUTO_TEST_CASE(test_MPlexer)
     ttd.tv_sec -= 5;
 
     bool writeCBCalled = false;
-    auto writeCB = [](int fd, FDMultiplexer::funcparam_t& param) {
+    auto writeCB = [](int /* fd */, FDMultiplexer::funcparam_t& param) {
       auto calledPtr = boost::any_cast<bool*>(param);
       BOOST_REQUIRE(calledPtr != nullptr);
       *calledPtr = true;
@@ -72,7 +74,8 @@ BOOST_AUTO_TEST_CASE(test_MPlexer)
     BOOST_REQUIRE_EQUAL(readyFDs.size(), 1U);
     BOOST_CHECK_EQUAL(readyFDs.at(0), pipes[1]);
 
-    ready = mplexer->run(&now, 100);
+    /* wait until we have at least one descriptor ready */
+    ready = mplexer->run(&now, -1);
     BOOST_CHECK_EQUAL(ready, 1);
     BOOST_CHECK_EQUAL(writeCBCalled, true);
 
@@ -99,7 +102,7 @@ BOOST_AUTO_TEST_CASE(test_MPlexer)
     BOOST_CHECK_EQUAL(ready, 0);
 
     bool readCBCalled = false;
-    auto readCB = [](int fd, FDMultiplexer::funcparam_t& param) {
+    auto readCB = [](int /* fd */, FDMultiplexer::funcparam_t& param) {
       auto calledPtr = boost::any_cast<bool*>(param);
       BOOST_REQUIRE(calledPtr != nullptr);
       *calledPtr = true;
@@ -224,7 +227,7 @@ BOOST_AUTO_TEST_CASE(test_MPlexer)
 BOOST_AUTO_TEST_CASE(test_MPlexer_ReadAndWrite)
 {
   for (const auto& entry : FDMultiplexer::getMultiplexerMap()) {
-    auto mplexer = std::unique_ptr<FDMultiplexer>(entry.second());
+    auto mplexer = std::unique_ptr<FDMultiplexer>(entry.second(FDMultiplexer::s_maxevents));
     BOOST_REQUIRE(mplexer != nullptr);
     //cerr<<"Testing multiplexer "<<mplexer->getName()<<" for read AND write"<<endl;
 
@@ -242,12 +245,12 @@ BOOST_AUTO_TEST_CASE(test_MPlexer_ReadAndWrite)
 
     bool readCBCalled = false;
     bool writeCBCalled = false;
-    auto readCB = [](int fd, FDMultiplexer::funcparam_t& param) {
+    auto readCB = [](int /* fd */, FDMultiplexer::funcparam_t& param) {
       auto calledPtr = boost::any_cast<bool*>(param);
       BOOST_REQUIRE(calledPtr != nullptr);
       *calledPtr = true;
     };
-    auto writeCB = [](int fd, FDMultiplexer::funcparam_t& param) {
+    auto writeCB = [](int /* fd */, FDMultiplexer::funcparam_t& param) {
       auto calledPtr = boost::any_cast<bool*>(param);
       BOOST_REQUIRE(calledPtr != nullptr);
       *calledPtr = true;
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 1e361a7fa66e24e58e73c312145d428cad52dca3..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
@@ -53,7 +56,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheAuthCollision) {
     pw1.getHeader()->rd = true;
     pw1.getHeader()->qr = false;
     pw1.getHeader()->id = 0x42;
-    opt.source = Netmask("10.0.152.74/32");
+    opt.source = Netmask("10.0.59.220/32");
     ednsOptions.clear();
     ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
     pw1.addOpt(512, 0, 0, ednsOptions);
@@ -67,7 +70,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheAuthCollision) {
     pw2.getHeader()->rd = true;
     pw2.getHeader()->qr = false;
     pw2.getHeader()->id = 0x84;
-    opt.source = Netmask("10.2.70.250/32");
+    opt.source = Netmask("10.0.167.48/32");
     ednsOptions.clear();
     ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
     pw2.addOpt(512, 0, 0, ednsOptions);
@@ -125,7 +128,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheAuthCollision) {
     pw1.getHeader()->rd = true;
     pw1.getHeader()->qr = false;
     pw1.getHeader()->id = 0x42;
-    opt.source = Netmask("10.0.34.159/32");
+    opt.source = Netmask("10.0.41.6/32");
     ednsOptions.clear();
     ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
     pw1.addOpt(512, 0, EDNSOpts::DNSSECOK, ednsOptions);
@@ -139,7 +142,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheAuthCollision) {
     pw2.getHeader()->rd = true;
     pw2.getHeader()->qr = false;
     pw2.getHeader()->id = 0x84;
-    opt.source = Netmask("10.0.179.58/32");
+    opt.source = Netmask("10.0.119.79/32");
     ednsOptions.clear();
     ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
     /* no EDNSOpts::DNSSECOK !! */
index e61f9787618e69303a520645ee135af190dfa797..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"
@@ -101,6 +105,18 @@ BOOST_AUTO_TEST_CASE(test_tlv_values_content_len_signedness) {
   }
 }
 
+BOOST_AUTO_TEST_CASE(test_payload_too_large) {
+  const bool tcp = false;
+  const ComboAddress src("[2001:db8::1]:0");
+  const ComboAddress dest("[::1]:65535");
+  std::string largeValue;
+  /* this value is larger than the maximum size for a TLV */
+  largeValue.resize(65536, 'A');
+  const std::vector<ProxyProtocolValue> values = {{ largeValue, 255 }};
+
+  BOOST_CHECK_THROW(makeProxyHeader(tcp, src, dest, values), std::runtime_error);
+}
+
 BOOST_AUTO_TEST_CASE(test_tlv_values_length_signedness) {
   std::string largeValue;
   /* this value will make the TLV length parsing fail in case of signedness mistake */
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);
diff --git a/pdns/test-recpacketcache_cc.cc b/pdns/test-recpacketcache_cc.cc
deleted file mode 100644 (file)
index b79bf3c..0000000
+++ /dev/null
@@ -1,398 +0,0 @@
-#define BOOST_TEST_DYN_LINK
-#define BOOST_TEST_NO_MAIN
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-#include <boost/test/unit_test.hpp>
-#include "arguments.hh"
-#include "dnswriter.hh"
-#include "dnsrecords.hh"
-#include "dns_random.hh"
-#include "iputils.hh"
-#include "recpacketcache.hh"
-#include <utility>
-
-BOOST_AUTO_TEST_SUITE(test_recpacketcache_cc)
-
-BOOST_AUTO_TEST_CASE(test_recPacketCacheSimple)
-{
-  RecursorPacketCache rpc;
-  string fpacket;
-  unsigned int tag = 0;
-  uint32_t age = 0;
-  uint32_t qhash = 0;
-  uint32_t ttd = 3600;
-  BOOST_CHECK_EQUAL(rpc.size(), 0U);
-
-  ::arg().set("rng") = "auto";
-  ::arg().set("entropy-source") = "/dev/urandom";
-
-  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);
-
-  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);
-
-  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);
-  BOOST_CHECK_EQUAL(rpc.size(), 1U);
-  rpc.doPruneTo(0);
-  BOOST_CHECK_EQUAL(rpc.size(), 0U);
-  rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, 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);
-  BOOST_CHECK_EQUAL(rpc.size(), 1U);
-  uint32_t qhash2 = 0;
-  bool found = rpc.getResponsePacket(tag, qpacket, time(nullptr), &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);
-  BOOST_CHECK_EQUAL(found, true);
-  BOOST_CHECK_EQUAL(qhash, qhash2);
-  BOOST_CHECK_EQUAL(fpacket, rpacket);
-
-  packet.clear();
-  qname += DNSName("co.uk");
-  DNSPacketWriter pw2(packet, qname, QType::A);
-
-  pw2.getHeader()->rd = true;
-  pw2.getHeader()->qr = false;
-  pw2.getHeader()->id = dns_random_uint16();
-  qpacket.assign((const char*)&packet[0], packet.size());
-
-  found = rpc.getResponsePacket(tag, qpacket, time(nullptr), &fpacket, &age, &qhash);
-  BOOST_CHECK_EQUAL(found, false);
-  found = rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, time(nullptr), &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_recPacketCacheSimplePost2038)
-{
-  RecursorPacketCache rpc;
-  string fpacket;
-  unsigned int tag = 0;
-  uint32_t age = 0;
-  uint32_t qhash = 0;
-  uint32_t ttd = 3600;
-  BOOST_CHECK_EQUAL(rpc.size(), 0U);
-
-  ::arg().set("rng") = "auto";
-  ::arg().set("entropy-source") = "/dev/urandom";
-
-  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 future = INT_MAX - 1800; // with ttd of 3600, we pass the cliff
-
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag, qpacket, future, &fpacket, &age, &qhash), false);
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, future, &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), future, ttd, vState::Indeterminate, boost::none, false);
-  BOOST_CHECK_EQUAL(rpc.size(), 1U);
-  rpc.doPruneTo(0);
-  BOOST_CHECK_EQUAL(rpc.size(), 0U);
-  rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), future, 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), future, ttd, vState::Indeterminate, boost::none, false);
-  BOOST_CHECK_EQUAL(rpc.size(), 1U);
-  uint32_t qhash2 = 0;
-  bool found = rpc.getResponsePacket(tag, qpacket, future, &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, future, &fpacket, &age, &qhash2);
-  BOOST_CHECK_EQUAL(found, true);
-  BOOST_CHECK_EQUAL(qhash, qhash2);
-  BOOST_CHECK_EQUAL(fpacket, rpacket);
-
-  found = rpc.getResponsePacket(tag, qpacket, future + 3601, &fpacket, &age, &qhash2);
-  BOOST_CHECK_EQUAL(found, false);
-  found = rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, future + 3601, &fpacket, &age, &qhash2);
-  BOOST_CHECK_EQUAL(found, false);
-
-  packet.clear();
-  qname += DNSName("co.uk");
-  DNSPacketWriter pw2(packet, qname, QType::A);
-
-  pw2.getHeader()->rd = true;
-  pw2.getHeader()->qr = false;
-  pw2.getHeader()->id = dns_random_uint16();
-  qpacket.assign((const char*)&packet[0], packet.size());
-
-  found = rpc.getResponsePacket(tag, qpacket, future, &fpacket, &age, &qhash);
-  BOOST_CHECK_EQUAL(found, false);
-  found = rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, future, &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_recPacketCache_Tags)
-{
-  /* Insert a response with tag1, the exact same query with a different tag
-     should lead to a miss. Inserting a different response with the second tag
-     should not override the first one, and we should get a hit for the
-     query with either tags, with the response matching the tag.
-  */
-  RecursorPacketCache rpc;
-  string fpacket;
-  const unsigned int tag1 = 0;
-  const unsigned int tag2 = 42;
-  uint32_t age = 0;
-  uint32_t qhash = 0;
-  uint32_t temphash = 0;
-  uint32_t ttd = 3600;
-  BOOST_CHECK_EQUAL(rpc.size(), 0U);
-
-  ::arg().set("rng") = "auto";
-  ::arg().set("entropy-source") = "/dev/urandom";
-
-  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(reinterpret_cast<const char*>(&packet[0]), packet.size());
-  pw.startRecord(qname, QType::A, ttd);
-
-  /* Both interfaces (with and without the qname/qtype/qclass) should get the same hash */
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, time(nullptr), &fpacket, &age, &qhash), false);
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), false);
-  BOOST_CHECK_EQUAL(qhash, temphash);
-
-  /* Different tag, should still get get the same hash, for both interfaces */
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, time(nullptr), &fpacket, &age, &temphash), false);
-  BOOST_CHECK_EQUAL(qhash, temphash);
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), false);
-  BOOST_CHECK_EQUAL(qhash, temphash);
-
-  {
-    ARecordContent ar("127.0.0.1");
-    ar.toPacket(pw);
-    pw.commit();
-  }
-  string r1packet(reinterpret_cast<const char*>(&packet[0]), packet.size());
-
-  {
-    ARecordContent ar("127.0.0.2");
-    ar.toPacket(pw);
-    pw.commit();
-  }
-  string r2packet(reinterpret_cast<const char*>(&packet[0]), packet.size());
-
-  BOOST_CHECK(r1packet != r2packet);
-
-  /* inserting a response for tag1 */
-  rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, boost::none, false);
-  BOOST_CHECK_EQUAL(rpc.size(), 1U);
-
-  /* inserting a different response for tag2, should not override the first one */
-  rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, boost::none, false);
-  BOOST_CHECK_EQUAL(rpc.size(), 2U);
-
-  /* remove all responses from the cache */
-  rpc.doPruneTo(0);
-  BOOST_CHECK_EQUAL(rpc.size(), 0U);
-
-  /* reinsert both */
-  rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, boost::none, false);
-  BOOST_CHECK_EQUAL(rpc.size(), 1U);
-
-  rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, boost::none, false);
-  BOOST_CHECK_EQUAL(rpc.size(), 2U);
-
-  /* remove the responses by qname, should remove both */
-  rpc.doWipePacketCache(qname);
-  BOOST_CHECK_EQUAL(rpc.size(), 0U);
-
-  /* insert the response for tag1 */
-  rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, boost::none, false);
-  BOOST_CHECK_EQUAL(rpc.size(), 1U);
-
-  /* we can retrieve it */
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), true);
-  BOOST_CHECK_EQUAL(qhash, temphash);
-  BOOST_CHECK_EQUAL(fpacket, r1packet);
-
-  /* with both interfaces */
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, time(nullptr), &fpacket, &age, &temphash), true);
-  BOOST_CHECK_EQUAL(qhash, temphash);
-  BOOST_CHECK_EQUAL(fpacket, r1packet);
-
-  /* but not with the second tag */
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), false);
-  /* we should still get the same hash */
-  BOOST_CHECK_EQUAL(temphash, qhash);
-
-  /* adding a response for the second tag */
-  rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, boost::none, false);
-  BOOST_CHECK_EQUAL(rpc.size(), 2U);
-
-  /* We still get the correct response for the first tag */
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, time(nullptr), &fpacket, &age, &temphash), true);
-  BOOST_CHECK_EQUAL(qhash, temphash);
-  BOOST_CHECK_EQUAL(fpacket, r1packet);
-
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), true);
-  BOOST_CHECK_EQUAL(qhash, temphash);
-  BOOST_CHECK_EQUAL(fpacket, r1packet);
-
-  /* and the correct response for the second tag */
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, time(nullptr), &fpacket, &age, &temphash), true);
-  BOOST_CHECK_EQUAL(qhash, temphash);
-  BOOST_CHECK_EQUAL(fpacket, r2packet);
-
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), true);
-  BOOST_CHECK_EQUAL(qhash, temphash);
-  BOOST_CHECK_EQUAL(fpacket, r2packet);
-}
-
-BOOST_AUTO_TEST_CASE(test_recPacketCache_TCP)
-{
-  /* Insert a response with UDP, the exact same query with a TCP flag
-     should lead to a miss. 
-  */
-  RecursorPacketCache rpc;
-  string fpacket;
-  uint32_t age = 0;
-  uint32_t qhash = 0;
-  uint32_t temphash = 0;
-  uint32_t ttd = 3600;
-  BOOST_CHECK_EQUAL(rpc.size(), 0U);
-
-  ::arg().set("rng") = "auto";
-  ::arg().set("entropy-source") = "/dev/urandom";
-
-  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(reinterpret_cast<const char*>(&packet[0]), packet.size());
-  pw.startRecord(qname, QType::A, ttd);
-
-  /* Both interfaces (with and without the qname/qtype/qclass) should get the same hash */
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, time(nullptr), &fpacket, &age, &qhash), false);
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, nullptr, &temphash, nullptr, false), false);
-  BOOST_CHECK_EQUAL(qhash, temphash);
-
-  /* Different tcp/udp, should still get get the same hash, for both interfaces */
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, time(nullptr), &fpacket, &age, &qhash), false);
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, nullptr, &temphash, nullptr, true), false);
-  BOOST_CHECK_EQUAL(qhash, temphash);
-
-  {
-    ARecordContent ar("127.0.0.1");
-    ar.toPacket(pw);
-    pw.commit();
-  }
-  string r1packet(reinterpret_cast<const char*>(&packet[0]), packet.size());
-
-  {
-    ARecordContent ar("127.0.0.2");
-    ar.toPacket(pw);
-    pw.commit();
-  }
-  string r2packet(reinterpret_cast<const char*>(&packet[0]), packet.size());
-
-  BOOST_CHECK(r1packet != r2packet);
-
-  /* inserting a response for udp */
-  rpc.insertResponsePacket(0, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, boost::none, false);
-  BOOST_CHECK_EQUAL(rpc.size(), 1U);
-
-  /* inserting a different response for tcp, should not override the first one */
-  rpc.insertResponsePacket(0, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, boost::none, true);
-  BOOST_CHECK_EQUAL(rpc.size(), 2U);
-
-  /* remove all responses from the cache */
-  rpc.doPruneTo(0);
-  BOOST_CHECK_EQUAL(rpc.size(), 0U);
-
-  /* reinsert both */
-  rpc.insertResponsePacket(0, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, boost::none, false);
-  BOOST_CHECK_EQUAL(rpc.size(), 1U);
-
-  rpc.insertResponsePacket(0, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, boost::none, true);
-  BOOST_CHECK_EQUAL(rpc.size(), 2U);
-
-  /* remove the responses by qname, should remove both */
-  rpc.doWipePacketCache(qname);
-  BOOST_CHECK_EQUAL(rpc.size(), 0U);
-
-  /* insert the response for tcp */
-  rpc.insertResponsePacket(0, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, boost::none, true);
-  BOOST_CHECK_EQUAL(rpc.size(), 1U);
-
-  vState vState;
-  /* we can retrieve it */
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &vState, &temphash, nullptr, true), true);
-  BOOST_CHECK_EQUAL(qhash, temphash);
-  BOOST_CHECK_EQUAL(fpacket, r1packet);
-
-  /* first interface assumes udp */
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, time(nullptr), &fpacket, &age, &temphash), false);
-  BOOST_CHECK_EQUAL(qhash, temphash);
-  BOOST_CHECK_EQUAL(fpacket, r1packet);
-
-  /* and not with explicit udp */
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &vState, &temphash, nullptr, false), false);
-  /* we should still get the same hash */
-  BOOST_CHECK_EQUAL(temphash, qhash);
-
-  /* adding a response for udp */
-  rpc.insertResponsePacket(0, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, boost::none, false);
-  BOOST_CHECK_EQUAL(rpc.size(), 2U);
-
-  /* We get the correct response for udp now */
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, time(nullptr), &fpacket, &age, &temphash), true);
-  BOOST_CHECK_EQUAL(qhash, temphash);
-  BOOST_CHECK_EQUAL(fpacket, r2packet);
-
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), true);
-  BOOST_CHECK_EQUAL(qhash, temphash);
-  BOOST_CHECK_EQUAL(fpacket, r2packet);
-
-  /* and the correct response for tcp */
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(0, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &vState, &temphash, nullptr, true), true);
-  BOOST_CHECK_EQUAL(qhash, temphash);
-  BOOST_CHECK_EQUAL(fpacket, r1packet);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
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 364017d58c90c796cdeec3d6ca7e61f2d2eea733..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
 #include "misc.hh"
 
 #include <cstdio>
+#include <unordered_map>
 
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables): Boost stuff.
 BOOST_AUTO_TEST_SUITE(test_signers)
 
-static const std::string message = "Very good, young padawan.";
-
 struct SignerParams
 {
   std::string iscMap;
@@ -34,123 +38,299 @@ struct SignerParams
   uint16_t rfcFlags;
   uint8_t algorithm;
   bool isDeterministic;
-  std::optional<std::string> pem;
+  std::string pem;
+};
+
+// clang-format off
+static const SignerParams rsaSha256SignerParams = SignerParams
+{
+  .iscMap = "Algorithm: 8\n"
+            "Modulus: qtunSiHnYq4XRLBehKAw1Glxb+48oIpAC7w3Jhpj570bb2uHt6orWGqnuyRtK8oqUi2ABoV0PFm8+IPgDMEdCQ==\n"
+            "PublicExponent: AQAB\n"
+            "PrivateExponent: MiItniUAngXzMeaGdWgDq/AcpvlCtOCcFlVt4TJRKkfp8DNRSxIxG53NNlOFkp1W00iLHqYC2GrH1qkKgT9l+Q==\n"
+            "Prime1: 3sZmM+5FKFy5xaRt0n2ZQOZ2C+CoKzVil6/al9LmYVs=\n"
+            "Prime2: xFcNWSIW6v8dDL2JQ1kxFDm/8RVeUSs1BNXXnvCjBGs=\n"
+            "Exponent1: WuUwhjfN1+4djlrMxHmisixWNfpwI1Eg7Ss/UXsnrMk=\n"
+            "Exponent2: vfMqas1cNsXRqP3Fym6D2Pl2BRuTQBv5E1B/ZrmQPTk=\n"
+            "Coefficient: Q10z43cA3hkwOkKsj5T0W5jrX97LBwZoY5lIjDCa4+M=\n",
+
+  .dsSHA1 = "1506 8 1 "
+            "172a500b374158d1a64ba3073cdbbc319b2fdf2c",
+
+  .dsSHA256 = "1506 8 2 "
+              "253b099ff47b02c6ffa52695a30a94c6681c56befe0e71a5077d6f79514972f9",
+
+  .dsSHA384 = "1506 8 4 "
+              "22ea940600dc2d9a98b1126c26ac0dc5c91b31eb50fe784b"
+              "36ad675e9eecfe6573c1f85c53b6bc94580f3ac443d13c4c",
+
+  /* from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sign.c */
+  .signature = {
+    0x93, 0x93, 0x5f, 0xd8, 0xa1, 0x2b, 0x4c, 0x0b, 0xf3, 0x67, 0x42, 0x13, 0x52,
+    0x00, 0x35, 0xdc, 0x09, 0xe0, 0xdf, 0xe0, 0x3e, 0xc2, 0xcf, 0x64, 0xab, 0x9f,
+    0x9f, 0x51, 0x5f, 0x5c, 0x27, 0xbe, 0x13, 0xd6, 0x17, 0x07, 0xa6, 0xe4, 0x3b,
+    0x63, 0x44, 0x85, 0x06, 0x13, 0xaa, 0x01, 0x3c, 0x58, 0x52, 0xa3, 0x98, 0x20,
+    0x65, 0x03, 0xd0, 0x40, 0xc8, 0xa0, 0xe9, 0xd2, 0xc0, 0x03, 0x5a, 0xab
+  },
+
+  .zoneRepresentation = "256 3 8 "
+                        "AwEAAarbp0oh52KuF0SwXoSgMNRpcW/uPKCKQAu8NyYaY+"
+                        "e9G29rh7eqK1hqp7skbSvKKlItgAaFdDxZvPiD4AzBHQk=",
+
+  .name = "rsa.",
+
+  .rfcMsgDump = "",
+  .rfcB64Signature = "",
+
+  .bits = 512,
+  .flags = 256,
+  .rfcFlags = 0,
+
+  .algorithm = DNSSECKeeper::RSASHA256,
+  .isDeterministic = true,
+
+#if OPENSSL_VERSION_MAJOR >= 3
+  // OpenSSL 3.0.0 uses a generic key interface which stores the key PKCS#8-encoded.
+  .pem = "-----BEGIN PRIVATE KEY-----\n"
+         "MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqtunSiHnYq4XRLBe\n"
+         "hKAw1Glxb+48oIpAC7w3Jhpj570bb2uHt6orWGqnuyRtK8oqUi2ABoV0PFm8+IPg\n"
+         "DMEdCQIDAQABAkAyIi2eJQCeBfMx5oZ1aAOr8Bym+UK04JwWVW3hMlEqR+nwM1FL\n"
+         "EjEbnc02U4WSnVbTSIsepgLYasfWqQqBP2X5AiEA3sZmM+5FKFy5xaRt0n2ZQOZ2\n"
+         "C+CoKzVil6/al9LmYVsCIQDEVw1ZIhbq/x0MvYlDWTEUOb/xFV5RKzUE1dee8KME\n"
+         "awIgWuUwhjfN1+4djlrMxHmisixWNfpwI1Eg7Ss/UXsnrMkCIQC98ypqzVw2xdGo\n"
+         "/cXKboPY+XYFG5NAG/kTUH9muZA9OQIgQ10z43cA3hkwOkKsj5T0W5jrX97LBwZo\n"
+         "Y5lIjDCa4+M=\n"
+         "-----END PRIVATE KEY-----\n"
+#else
+  .pem = "-----BEGIN RSA PRIVATE KEY-----\n"
+         "MIIBOgIBAAJBAKrbp0oh52KuF0SwXoSgMNRpcW/uPKCKQAu8NyYaY+e9G29rh7eq\n"
+         "K1hqp7skbSvKKlItgAaFdDxZvPiD4AzBHQkCAwEAAQJAMiItniUAngXzMeaGdWgD\n"
+         "q/AcpvlCtOCcFlVt4TJRKkfp8DNRSxIxG53NNlOFkp1W00iLHqYC2GrH1qkKgT9l\n"
+         "+QIhAN7GZjPuRShcucWkbdJ9mUDmdgvgqCs1Ypev2pfS5mFbAiEAxFcNWSIW6v8d\n"
+         "DL2JQ1kxFDm/8RVeUSs1BNXXnvCjBGsCIFrlMIY3zdfuHY5azMR5orIsVjX6cCNR\n"
+         "IO0rP1F7J6zJAiEAvfMqas1cNsXRqP3Fym6D2Pl2BRuTQBv5E1B/ZrmQPTkCIENd\n"
+         "M+N3AN4ZMDpCrI+U9FuY61/eywcGaGOZSIwwmuPj\n"
+         "-----END RSA PRIVATE KEY-----\n"
+#endif
 };
+// clang-format on
 
-static const std::array<struct SignerParams, 3> signers
+/* ECDSA-P256-SHA256 from
+ * https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sample_keys.h
+ */
+// clang-format off
+static const SignerParams ecdsaSha256 = SignerParams
 {
-  /* RSA from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sample_keys.h */
-  SignerParams{
-    "Algorithm: 8\n"
-    "Modulus: qtunSiHnYq4XRLBehKAw1Glxb+48oIpAC7w3Jhpj570bb2uHt6orWGqnuyRtK8oqUi2ABoV0PFm8+IPgDMEdCQ==\n"
-    "PublicExponent: AQAB\n"
-    "PrivateExponent: MiItniUAngXzMeaGdWgDq/AcpvlCtOCcFlVt4TJRKkfp8DNRSxIxG53NNlOFkp1W00iLHqYC2GrH1qkKgT9l+Q==\n"
-    "Prime1: 3sZmM+5FKFy5xaRt0n2ZQOZ2C+CoKzVil6/al9LmYVs=\n"
-    "Prime2: xFcNWSIW6v8dDL2JQ1kxFDm/8RVeUSs1BNXXnvCjBGs=\n"
-    "Exponent1: WuUwhjfN1+4djlrMxHmisixWNfpwI1Eg7Ss/UXsnrMk=\n"
-    "Exponent2: vfMqas1cNsXRqP3Fym6D2Pl2BRuTQBv5E1B/ZrmQPTk=\n"
-    "Coefficient: Q10z43cA3hkwOkKsj5T0W5jrX97LBwZoY5lIjDCa4+M=\n",
-
-    "1506 8 1 172a500b374158d1a64ba3073cdbbc319b2fdf2c",
-    "1506 8 2 253b099ff47b02c6ffa52695a30a94c6681c56befe0e71a5077d6f79514972f9",
-    "1506 8 4 22ea940600dc2d9a98b1126c26ac0dc5c91b31eb50fe784b36ad675e9eecfe6573c1f85c53b6bc94580f3ac443d13c4c",
-
-    // clang-format off
-    /* from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sign.c */
-    { 0x93, 0x93, 0x5f, 0xd8, 0xa1, 0x2b, 0x4c, 0x0b, 0xf3, 0x67, 0x42, 0x13, 0x52, 0x00, 0x35, 0xdc,
-      0x09, 0xe0, 0xdf, 0xe0, 0x3e, 0xc2, 0xcf, 0x64, 0xab, 0x9f, 0x9f, 0x51, 0x5f, 0x5c, 0x27, 0xbe,
-      0x13, 0xd6, 0x17, 0x07, 0xa6, 0xe4, 0x3b, 0x63, 0x44, 0x85, 0x06, 0x13, 0xaa, 0x01, 0x3c, 0x58,
-      0x52, 0xa3, 0x98, 0x20, 0x65, 0x03, 0xd0, 0x40, 0xc8, 0xa0, 0xe9, 0xd2, 0xc0, 0x03, 0x5a, 0xab },
-    // clang-format on
-
-    "256 3 8 AwEAAarbp0oh52KuF0SwXoSgMNRpcW/uPKCKQAu8NyYaY+e9G29rh7eqK1hqp7skbSvKKlItgAaFdDxZvPiD4AzBHQk=",
-    "rsa.",
-    "",
-    "",
-    512,
-    256,
-    0,
-    DNSSECKeeper::RSASHA256,
-    true,
-
-    std::nullopt},
+  .iscMap = "Algorithm: 13\n"
+            "PrivateKey: iyLIPdk3DOIxVmmSYlmTstbtUPiVlEyDX46psyCwNVQ=\n",
+
+  .dsSHA1 = "5345 13 1 "
+            "954103ac7c43810ce9f414e80f30ab1cbe49b236",
+
+  .dsSHA256 = "5345 13 2 "
+              "bac2107036e735b50f85006ce409a19a3438cab272e70769ebda032239a3d0ca",
+
+  .dsSHA384 = "5345 13 4 "
+              "a0ac6790483872be72a258314200a88ab75cdd70f66a18a0"
+              "9f0f414c074df0989fdb1df0e67d82d4312cda67b93a76c1",
+
+  /* from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sign.c */
+  .signature = {
+    0xa2, 0x95, 0x76, 0xb5, 0xf5, 0x7e, 0xbd, 0xdd, 0xf5, 0x62, 0xa2, 0xc3, 0xa4,
+    0x8d, 0xd4, 0x53, 0x5c, 0xba, 0x29, 0x71, 0x8c, 0xcc, 0x28, 0x7b, 0x58, 0xf3,
+    0x1e, 0x4e, 0x58, 0xe2, 0x36, 0x7e, 0xa0, 0x1a, 0xb6, 0xe6, 0x29, 0x71, 0x1b,
+    0xd3, 0x8c, 0x88, 0xc3, 0xee, 0x12, 0x0e, 0x69, 0x70, 0x55, 0x99, 0xec, 0xd5,
+    0xf6, 0x4f, 0x4b, 0xe2, 0x41, 0xd9, 0x10, 0x7e, 0x67, 0xe5, 0xad, 0x2f
+  },
+
+  .zoneRepresentation = "256 3 13 "
+                        "8uD7C4THTM/w7uhryRSToeE/jKT78/p853RX0L5EwrZ"
+                        "rSLBubLPiBw7gbvUP6SsIga5ZQ4CSAxNmYA/gZsuXzA==",
+
+  .name = "ecdsa.",
+
+  .rfcMsgDump = "",
+  .rfcB64Signature = "",
+
+  .bits = 256,
+  .flags = 256,
+  .rfcFlags = 0,
+
+  .algorithm = DNSSECKeeper::ECDSA256,
+  .isDeterministic = false,
+
+#if OPENSSL_VERSION_MAJOR >= 3
+  // OpenSSL 3.0.0 uses a generic key interface which stores the key PKCS#8-encoded.
+  .pem = "-----BEGIN PRIVATE KEY-----\n"
+         "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgiyLIPdk3DOIxVmmS\n"
+         "YlmTstbtUPiVlEyDX46psyCwNVShRANCAATy4PsLhMdMz/Du6GvJFJOh4T+MpPvz\n"
+         "+nzndFfQvkTCtmtIsG5ss+IHDuBu9Q/pKwiBrllDgJIDE2ZgD+Bmy5fM\n"
+         "-----END PRIVATE KEY-----\n"
+#else
+  .pem = "-----BEGIN EC PRIVATE KEY-----\n"
+         "MHcCAQEEIIsiyD3ZNwziMVZpkmJZk7LW7VD4lZRMg1+OqbMgsDVUoAoGCCqGSM49\n"
+         "AwEHoUQDQgAE8uD7C4THTM/w7uhryRSToeE/jKT78/p853RX0L5EwrZrSLBubLPi\n"
+         "Bw7gbvUP6SsIga5ZQ4CSAxNmYA/gZsuXzA==\n"
+         "-----END EC PRIVATE KEY-----\n"
+#endif
+};
+// clang-format on
+
+/* Ed25519 from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sample_keys.h,
+ * also from rfc8080 section 6.1
+ */
+// clang-format off
+static const SignerParams ed25519 = SignerParams{
+  .iscMap = "Algorithm: 15\n"
+            "PrivateKey: ODIyNjAzODQ2MjgwODAxMjI2NDUxOTAyMDQxNDIyNjI=\n",
+
+  .dsSHA1 = "3612 15 1 "
+            "501249721e1f09a79d30d5c6c4dca1dc1da4ed5d",
+
+  .dsSHA256 = "3612 15 2 "
+              "1b1c8766b2a96566ff196f77c0c4194af86aaa109c5346ff60231a27d2b07ac0",
+
+  .dsSHA384 = "3612 15 4 "
+              "d11831153af4985efbd0ae792c967eb4aff3c35488db95f7"
+              "e2f85dcec74ae8f59f9a72641798c91c67c675db1d710c18",
+
+  /* from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sign.c */
+  .signature = {
+    0x0a, 0x9e, 0x51, 0x5f, 0x16, 0x89, 0x49, 0x27, 0x0e, 0x98, 0x34, 0xd3, 0x48,
+    0xef, 0x5a, 0x6e, 0x85, 0x2f, 0x7c, 0xd6, 0xd7, 0xc8, 0xd0, 0xf4, 0x2c, 0x68,
+    0x8c, 0x1f, 0xf7, 0xdf, 0xeb, 0x7c, 0x25, 0xd6, 0x1a, 0x76, 0x3e, 0xaf, 0x28,
+    0x1f, 0x1d, 0x08, 0x10, 0x20, 0x1c, 0x01, 0x77, 0x1b, 0x5a, 0x48, 0xd6, 0xe5,
+    0x1c, 0xf9, 0xe3, 0xe0, 0x70, 0x34, 0x5e, 0x02, 0x49, 0xfb, 0x9e, 0x05
+  },
+
+  .zoneRepresentation = "256 3 15 l02Woi0iS8Aa25FQkUd9RMzZHJpBoRQwAQEX1SxZJA4=",
+
+  .name = "ed25519.",
+
+  // vector extracted from https://gitlab.labs.nic.cz/labs/ietf/blob/master/dnskey.py (rev
+  // 476d6ded) by printing signature_data
+  .rfcMsgDump = "00 0f 0f 02 00 00 0e 10 55 d4 fc 60 55 b9 4c e0 0e 1d 07 65 78 "
+                "61 6d 70 6c 65 03 63 6f 6d 00 07 65 78 61 6d 70 6c 65 03 63 6f "
+                "6d 00 00 0f 00 01 00 00 0e 10 00 14 00 0a 04 6d 61 69 6c 07 65 "
+                "78 61 6d 70 6c 65 03 63 6f 6d 00 ",
+
+  // vector verified from dnskey.py as above, and confirmed with
+  // https://www.rfc-editor.org/errata_search.php?rfc=8080&eid=4935
+  .rfcB64Signature = "oL9krJun7xfBOIWcGHi7mag5/hdZrKWw15jPGrHpjQeR"
+                     "AvTdszaPD+QLs3fx8A4M3e23mRZ9VrbpMngwcrqNAg==",
+
+  .bits = 256,
+  .flags = 256,
+  .rfcFlags = 257,
+
+  .algorithm = DNSSECKeeper::ED25519,
+  .isDeterministic = true,
+
+  .pem = "-----BEGIN PRIVATE KEY-----\n"
+         "MC4CAQAwBQYDK2VwBCIEIDgyMjYwMzg0NjI4MDgwMTIyNjQ1MTkwMjA0MTQyMjYy\n"
+         "-----END PRIVATE KEY-----\n"
+};
+// clang-format on
+
+/* Ed448.
+ */
+// clang-format off
+static const SignerParams ed448 = SignerParams{
+  .iscMap = "Private-key-format: v1.2\n"
+            "Algorithm: 16 (ED448)\n"
+            "PrivateKey: xZ+5Cgm463xugtkY5B0Jx6erFTXp13rYegst0qRtNsOYnaVpMx0Z/c5EiA9x8wWbDDct/U3FhYWA\n",
+
+  .dsSHA1 = "9712 16 1 "
+            "2873e800eb2d784cdd1802f884b3c540b573eaa0",
+
+  .dsSHA256 = "9712 16 2 "
+              "9aa27306f8a04a0a6fae8affd65d6f35875dcb134c05bd7c7b61bd0dc44009cd",
+
+  .dsSHA384 = "9712 16 4 "
+              "3876e5d892d3f31725f9964a332f9b9afd791171833480f2"
+              "e71af78efb985cde9900ba95315287123a5908ca8f334369",
+
+  .signature = {
+    0xb5, 0xcc, 0x21, 0x5a, 0x52, 0x21, 0x60, 0xa3, 0xb8, 0xd9, 0x3a, 0xd7, 0x05,
+    0xdd, 0x4a, 0x32, 0x96, 0xce, 0x08, 0xde, 0x74, 0x5f, 0xdb, 0xde, 0x54, 0x95,
+    0x97, 0x93, 0x6f, 0x3a, 0x4a, 0x34, 0x41, 0x14, 0xba, 0x99, 0x86, 0x0d, 0xe2,
+    0x99, 0xf1, 0x14, 0x6a, 0x1b, 0x7a, 0xfa, 0xef, 0xab, 0x62, 0xd2, 0x71, 0x85,
+    0xae, 0xd1, 0x84, 0x80, 0x00, 0x50, 0x03, 0x9e, 0x73, 0x53, 0xe8, 0x9e, 0x19,
+    0xb8, 0xc0, 0xdb, 0xd4, 0xf0, 0x1e, 0x44, 0x4c, 0xb7, 0x32, 0x07, 0xda, 0x0b,
+    0x64, 0x22, 0xa8, 0x63, 0xaa, 0x7a, 0x12, 0x73, 0xc9, 0x29, 0xfd, 0x50, 0x85,
+    0x0f, 0x43, 0x72, 0x77, 0x86, 0xec, 0x88, 0x1a, 0x96, 0x95, 0x4a, 0x01, 0xfe,
+    0xf2, 0xe6, 0x77, 0x4a, 0x2e, 0x43, 0xdd, 0x60, 0x29, 0x00,
+  },
+
+  .zoneRepresentation = "256 3 16 "
+                        "3kgROaDjrh0H2iuixWBrc8g2EpBBLCdGzHmn+"
+                        "G2MpTPhpj/OiBVHHSfPodx1FYYUcJKm1MDpJtIA",
+
+  .name = "ed448.",
+
+  // vector extracted from https://gitlab.labs.nic.cz/labs/ietf/blob/master/dnskey.py (rev
+  // 476d6ded) by printing signature_data
+  .rfcMsgDump = "00 0f 10 02 00 00 0e 10 55 d4 fc 60 55 b9 4c e0 25 f1 07 65 78 "
+                "61 6d 70 6c 65 03 63 6f 6d 00 07 65 78 61 6d 70 6c 65 03 63 6f "
+                "6d 00 00 0f 00 01 00 00 0e 10 00 14 00 0a 04 6d 61 69 6c 07 65 "
+                "78 61 6d 70 6c 65 03 63 6f 6d 00 ",
+
+  // vector verified from dnskey.py as above, and confirmed with
+  // https://www.rfc-editor.org/errata_search.php?rfc=8080&eid=4935
+  .rfcB64Signature = "3cPAHkmlnxcDHMyg7vFC34l0blBhuG1qpwLmjInI8w1CMB29FkEA"
+                     "IJUA0amxWndkmnBZ6SKiwZSAxGILn/NBtOXft0+Gj7FSvOKxE/07"
+                     "+4RQvE581N3Aj/JtIyaiYVdnYtyMWbSNyGEY2213WKsJlwEA",
+
+  .bits = 456,
+  .flags = 256,
+  .rfcFlags = 257,
+
+  .algorithm = DNSSECKeeper::ED448,
+  .isDeterministic = true,
+
+  .pem = "-----BEGIN PRIVATE KEY-----\n"
+         "MEcCAQAwBQYDK2VxBDsEOcWfuQoJuOt8boLZGOQdCcenqxU16dd62HoLLdKkbTbD\n"
+         "mJ2laTMdGf3ORIgPcfMFmww3Lf1NxYWFgA==\n"
+         "-----END PRIVATE KEY-----\n"
+};
+// clang-format on
+
+struct Fixture
+{
+  Fixture()
+  {
+    BOOST_TEST_MESSAGE("All available/supported algorithms:");
+    auto pairs = DNSCryptoKeyEngine::listAllAlgosWithBackend();
+    for (auto const& pair : pairs) {
+      BOOST_TEST_MESSAGE("  " + std::to_string(pair.first) + ": " + pair.second);
+    }
+
+    BOOST_TEST_MESSAGE("Setting up signer params:");
+
+    addSignerParams(DNSSECKeeper::RSASHA256, "RSA SHA256", rsaSha256SignerParams);
 
 #ifdef HAVE_LIBCRYPTO_ECDSA
-    /* ECDSA-P256-SHA256 from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sample_keys.h */
-    SignerParams{
-      "Algorithm: 13\n"
-      "PrivateKey: iyLIPdk3DOIxVmmSYlmTstbtUPiVlEyDX46psyCwNVQ=\n",
-
-      "5345 13 1 954103ac7c43810ce9f414e80f30ab1cbe49b236",
-      "5345 13 2 bac2107036e735b50f85006ce409a19a3438cab272e70769ebda032239a3d0ca",
-      "5345 13 4 a0ac6790483872be72a258314200a88ab75cdd70f66a18a09f0f414c074df0989fdb1df0e67d82d4312cda67b93a76c1",
-
-      // clang-format off
-      /* from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sign.c */
-      { 0xa2, 0x95, 0x76, 0xb5, 0xf5, 0x7e, 0xbd, 0xdd, 0xf5, 0x62, 0xa2, 0xc3, 0xa4, 0x8d, 0xd4, 0x53,
-        0x5c, 0xba, 0x29, 0x71, 0x8c, 0xcc, 0x28, 0x7b, 0x58, 0xf3, 0x1e, 0x4e, 0x58, 0xe2, 0x36, 0x7e,
-        0xa0, 0x1a, 0xb6, 0xe6, 0x29, 0x71, 0x1b, 0xd3, 0x8c, 0x88, 0xc3, 0xee, 0x12, 0x0e, 0x69, 0x70,
-        0x55, 0x99, 0xec, 0xd5, 0xf6, 0x4f, 0x4b, 0xe2, 0x41, 0xd9, 0x10, 0x7e, 0x67, 0xe5, 0xad, 0x2f },
-      // clang-format on
-
-      "256 3 13 8uD7C4THTM/w7uhryRSToeE/jKT78/p853RX0L5EwrZrSLBubLPiBw7gbvUP6SsIga5ZQ4CSAxNmYA/gZsuXzA==",
-      "ecdsa.",
-      "",
-      "",
-      256,
-      256,
-      0,
-      DNSSECKeeper::ECDSA256,
-      false,
-
-      std::make_optional(std::string{
-        "-----BEGIN EC PRIVATE KEY-----\n"
-        "MHcCAQEEIIsiyD3ZNwziMVZpkmJZk7LW7VD4lZRMg1+OqbMgsDVUoAoGCCqGSM49\n"
-        "AwEHoUQDQgAE8uD7C4THTM/w7uhryRSToeE/jKT78/p853RX0L5EwrZrSLBubLPi\n"
-        "Bw7gbvUP6SsIga5ZQ4CSAxNmYA/gZsuXzA==\n"
-        "-----END EC PRIVATE KEY-----\n"})},
-#endif /* HAVE_LIBCRYPTO_ECDSA */
-
-#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED25519)
-    /* ed25519 from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sample_keys.h,
-       also from rfc8080 section 6.1 */
-    SignerParams{
-      "Algorithm: 15\n"
-      "PrivateKey: ODIyNjAzODQ2MjgwODAxMjI2NDUxOTAyMDQxNDIyNjI=\n",
-
-      "3612 15 1 501249721e1f09a79d30d5c6c4dca1dc1da4ed5d",
-      "3612 15 2 1b1c8766b2a96566ff196f77c0c4194af86aaa109c5346ff60231a27d2b07ac0",
-      "3612 15 4 d11831153af4985efbd0ae792c967eb4aff3c35488db95f7e2f85dcec74ae8f59f9a72641798c91c67c675db1d710c18",
-
-      // clang-format off
-      /* from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sign.c */
-      { 0x0a, 0x9e, 0x51, 0x5f, 0x16, 0x89, 0x49, 0x27, 0x0e, 0x98, 0x34, 0xd3, 0x48, 0xef, 0x5a, 0x6e,
-        0x85, 0x2f, 0x7c, 0xd6, 0xd7, 0xc8, 0xd0, 0xf4, 0x2c, 0x68, 0x8c, 0x1f, 0xf7, 0xdf, 0xeb, 0x7c,
-        0x25, 0xd6, 0x1a, 0x76, 0x3e, 0xaf, 0x28, 0x1f, 0x1d, 0x08, 0x10, 0x20, 0x1c, 0x01, 0x77, 0x1b,
-        0x5a, 0x48, 0xd6, 0xe5, 0x1c, 0xf9, 0xe3, 0xe0, 0x70, 0x34, 0x5e, 0x02, 0x49, 0xfb, 0x9e, 0x05 },
-      // clang-format on
-
-      "256 3 15 l02Woi0iS8Aa25FQkUd9RMzZHJpBoRQwAQEX1SxZJA4=",
-      "ed25519.",
-
-      // vector extracted from https://gitlab.labs.nic.cz/labs/ietf/blob/master/dnskey.py
-      // (rev 476d6ded) by printing signature_data
-      "00 0f 0f 02 00 00 0e 10 55 d4 fc 60 55 b9 4c e0 0e 1d 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 "
-      "07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 0f 00 01 00 00 0e 10 00 14 00 0a 04 6d 61 69 6c 07 "
-      "65 78 61 6d 70 6c 65 03 63 6f 6d 00 ",
-
-      // vector verified from dnskey.py as above, and confirmed with
-      // https://www.rfc-editor.org/errata_search.php?rfc=8080&eid=4935
-      "oL9krJun7xfBOIWcGHi7mag5/hdZrKWw15jPGrHpjQeRAvTdszaPD+QLs3fx8A4M3e23mRZ9VrbpMngwcrqNAg==",
-
-      256,
-      256,
-      257,
-      DNSSECKeeper::ED25519,
-      true,
-
-      std::nullopt},
-#endif /* defined(HAVE_LIBSODIUM) || defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED25519) */
+    addSignerParams(DNSSECKeeper::ECDSA256, "ECDSA SHA256", ecdsaSha256);
+#endif
+
+// We need to have HAVE_LIBCRYPTO_ED25519 for the PEM reader/writer.
+#if defined(HAVE_LIBCRYPTO_ED25519)
+    addSignerParams(DNSSECKeeper::ED25519, "ED25519", ed25519);
+#endif
+
+#if defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED448)
+    addSignerParams(DNSSECKeeper::ED448, "ED448", ed448);
+#endif
+  }
+
+  void addSignerParams(const uint8_t algorithm, const std::string& name, const SignerParams& params)
+  {
+    BOOST_TEST_MESSAGE("  " + std::to_string(algorithm) + ": " + name + " (" + params.name + ")");
+    signerParams.insert_or_assign(algorithm, params);
+  }
+
+  const std::string message{"Very good, young padawan."};
+  std::unordered_map<uint8_t, struct SignerParams> signerParams;
 };
 
 static void checkRR(const SignerParams& signer)
@@ -158,12 +338,11 @@ static void checkRR(const SignerParams& signer)
   DNSKEYRecordContent drc;
   auto dcke = std::shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromISCString(drc, signer.iscMap));
   DNSSECPrivateKey dpk;
-  dpk.setKey(dcke);
-  dpk.d_flags = signer.rfcFlags;
+  dpk.setKey(dcke, signer.rfcFlags);
 
   sortedRecords_t rrs;
   /* values taken from rfc8080 for ed25519 and ed448, rfc5933 for gost */
-  DNSName qname(dpk.d_algorithm == 12 ? "www.example.net." : "example.com.");
+  DNSName qname(dpk.getAlgorithm() == DNSSECKeeper::ECCGOST ? "www.example.net." : "example.com.");
 
   reportBasicTypes();
 
@@ -171,15 +350,15 @@ static void checkRR(const SignerParams& signer)
   uint32_t expire = 1440021600;
   uint32_t inception = 1438207200;
 
-  if (dpk.d_algorithm == 12) {
+  if (dpk.getAlgorithm() == DNSSECKeeper::ECCGOST) {
     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;
@@ -188,7 +367,7 @@ static void checkRR(const SignerParams& signer)
   rrc.d_type = (*rrs.cbegin())->getType();
   rrc.d_labels = qname.countLabels();
   rrc.d_tag = dpk.getTag();
-  rrc.d_algorithm = dpk.d_algorithm;
+  rrc.d_algorithm = dpk.getAlgorithm();
 
   string msg = getMessageForRRSET(qname, rrc, rrs, false);
 
@@ -196,6 +375,7 @@ static void checkRR(const SignerParams& signer)
 
   string signature = dcke->sign(msg);
 
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg): Boost stuff.
   BOOST_CHECK(dcke->verify(msg, signature));
 
   if (signer.isDeterministic) {
@@ -205,21 +385,29 @@ static void checkRR(const SignerParams& signer)
   else {
     std::string raw;
     B64Decode(signer.rfcB64Signature, raw);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg): Boost stuff.
     BOOST_CHECK(dcke->verify(msg, raw));
   }
 }
 
-static auto test_generic_signer(std::shared_ptr<DNSCryptoKeyEngine> dcke, DNSKEYRecordContent& drc, const SignerParams& signer)
+static void test_generic_signer(std::shared_ptr<DNSCryptoKeyEngine> dcke, DNSKEYRecordContent& drc, const SignerParams& signer, const std::string& message)
 {
   BOOST_CHECK_EQUAL(dcke->getAlgorithm(), signer.algorithm);
   BOOST_CHECK_EQUAL(dcke->getBits(), signer.bits);
-  BOOST_CHECK_EQUAL(dcke->checkKey(nullptr), true);
+
+  vector<string> errorMessages{};
+  BOOST_CHECK_EQUAL(dcke->checkKey(errorMessages), true);
+  if (!errorMessages.empty()) {
+    BOOST_TEST_MESSAGE("Errors from " + dcke->getName() + " checkKey()");
+    for (auto& errorMessage : errorMessages) {
+      BOOST_TEST_MESSAGE("  " + errorMessage);
+    }
+  }
 
   BOOST_CHECK_EQUAL(drc.d_algorithm, signer.algorithm);
 
   DNSSECPrivateKey dpk;
-  dpk.setKey(dcke);
-  dpk.d_flags = signer.flags;
+  dpk.setKey(dcke, signer.flags);
   drc = dpk.getDNSKEY();
 
   BOOST_CHECK_EQUAL(drc.d_algorithm, signer.algorithm);
@@ -243,15 +431,20 @@ static auto test_generic_signer(std::shared_ptr<DNSCryptoKeyEngine> dcke, DNSKEY
   }
 
   auto signature = dcke->sign(message);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg): Boost stuff.
   BOOST_CHECK(dcke->verify(message, signature));
 
+  auto signerSignature = std::string(signer.signature.begin(), signer.signature.end());
   if (signer.isDeterministic) {
-    BOOST_CHECK_EQUAL(signature, std::string(signer.signature.begin(), signer.signature.end()));
+    auto signatureBase64 = Base64Encode(signature);
+    auto signerSignatureBase64 = Base64Encode(signerSignature);
+    BOOST_CHECK_EQUAL(signatureBase64, signerSignatureBase64);
   }
   else {
     /* since the signing process is not deterministic, we can't directly compare our signature
        with the one we have. Still the one we have should also validate correctly. */
-    BOOST_CHECK(dcke->verify(message, std::string(signer.signature.begin(), signer.signature.end())));
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg): Boost stuff.
+    BOOST_CHECK(dcke->verify(message, signerSignature));
   }
 
   if (!signer.rfcMsgDump.empty() && !signer.rfcB64Signature.empty()) {
@@ -259,108 +452,51 @@ static auto test_generic_signer(std::shared_ptr<DNSCryptoKeyEngine> dcke, DNSKEY
   }
 }
 
-BOOST_AUTO_TEST_CASE(test_generic_signers)
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables,readability-identifier-length): Boost stuff.
+BOOST_FIXTURE_TEST_CASE(test_generic_signers, Fixture)
 {
-  for (const auto& signer : signers) {
+  for (const auto& algoSignerPair : signerParams) {
+    auto signer = algoSignerPair.second;
+
     DNSKEYRecordContent drc;
     auto dcke = std::shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromISCString(drc, signer.iscMap));
-    test_generic_signer(dcke, drc, signer);
-
-    if (signer.pem.has_value()) {
-      unique_ptr<std::FILE, decltype(&std::fclose)> fp{fmemopen((void*)signer.pem->c_str(), signer.pem->length(), "r"), &std::fclose};
-      BOOST_REQUIRE(fp.get() != nullptr);
+    test_generic_signer(dcke, drc, signer, message);
 
-      DNSKEYRecordContent pemDRC;
-      shared_ptr<DNSCryptoKeyEngine> pemKey{DNSCryptoKeyEngine::makeFromPEMFile(pemDRC, "<buffer>", *fp, signer.algorithm)};
+    DNSKEYRecordContent pemDRC;
+    shared_ptr<DNSCryptoKeyEngine> pemKey{DNSCryptoKeyEngine::makeFromPEMString(pemDRC, signer.algorithm, signer.pem)};
 
-      BOOST_CHECK_EQUAL(pemKey->convertToISC(), dcke->convertToISC());
+    BOOST_CHECK_EQUAL(pemKey->convertToISC(), dcke->convertToISC());
 
-      test_generic_signer(pemKey, pemDRC, signer);
+    test_generic_signer(pemKey, pemDRC, signer, message);
 
-      const size_t buflen = 4096;
+    auto dckePEMOutput = dcke->convertToPEMString();
+    BOOST_CHECK_EQUAL(dckePEMOutput, signer.pem);
 
-      std::string dckePEMOutput{};
-      dckePEMOutput.resize(buflen);
-      unique_ptr<std::FILE, decltype(&std::fclose)> dckePEMOutputFp{fmemopen(static_cast<void*>(dckePEMOutput.data()), dckePEMOutput.length() - 1, "w"), &std::fclose};
-      dcke->convertToPEM(*dckePEMOutputFp);
-      std::fflush(dckePEMOutputFp.get());
-      dckePEMOutput.resize(std::ftell(dckePEMOutputFp.get()));
-
-      BOOST_CHECK_EQUAL(dckePEMOutput, *signer.pem);
-
-      std::string pemKeyOutput{};
-      pemKeyOutput.resize(buflen);
-      unique_ptr<std::FILE, decltype(&std::fclose)> pemKeyOutputFp{fmemopen(static_cast<void*>(pemKeyOutput.data()), pemKeyOutput.length() - 1, "w"), &std::fclose};
-      pemKey->convertToPEM(*pemKeyOutputFp);
-      std::fflush(pemKeyOutputFp.get());
-      pemKeyOutput.resize(std::ftell(pemKeyOutputFp.get()));
-
-      BOOST_CHECK_EQUAL(pemKeyOutput, *signer.pem);
-    }
+    auto pemKeyOutput = pemKey->convertToPEMString();
+    BOOST_CHECK_EQUAL(pemKeyOutput, signer.pem);
   }
 }
 
-#if defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED448)
-BOOST_AUTO_TEST_CASE(test_ed448_signer) {
-    sortedRecords_t rrs;
-    DNSName qname("example.com.");
-    DNSKEYRecordContent drc;
-
-    // TODO: make this a collection of inputs and resulting sigs for various algos
-    shared_ptr<DNSCryptoKeyEngine> engine = DNSCryptoKeyEngine::makeFromISCString(drc,
-"Private-key-format: v1.2\n"
-"Algorithm: 16 (ED448)\n"
-"PrivateKey: xZ+5Cgm463xugtkY5B0Jx6erFTXp13rYegst0qRtNsOYnaVpMx0Z/c5EiA9x8wWbDDct/U3FhYWA\n");
-
-    DNSSECPrivateKey dpk;
-    dpk.setKey(engine);
-
-    reportBasicTypes();
-
-    rrs.insert(DNSRecordContent::mastermake(QType::MX, 1, "10 mail.example.com."));
-
-    RRSIGRecordContent rrc;
-    rrc.d_originalttl = 3600;
-    rrc.d_sigexpire = 1440021600;
-    rrc.d_siginception = 1438207200;
-    rrc.d_signer = qname;
-    rrc.d_type = QType::MX;
-    rrc.d_labels = 2;
-    // TODO: derive the next two from the key
-    rrc.d_tag = 9713;
-    rrc.d_algorithm = 16;
-
-    string msg = getMessageForRRSET(qname, rrc, rrs, false);
-
-    // vector extracted from https://gitlab.labs.nic.cz/labs/ietf/blob/master/dnskey.py (rev 476d6ded) by printing signature_data
-    BOOST_CHECK_EQUAL(makeHexDump(msg), "00 0f 10 02 00 00 0e 10 55 d4 fc 60 55 b9 4c e0 25 f1 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 0f 00 01 00 00 0e 10 00 14 00 0a 04 6d 61 69 6c 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ");
-
-    string signature = engine->sign(msg);
-    string b64 = Base64Encode(signature);
-
-    // vector verified from dnskey.py as above, and confirmed with https://www.rfc-editor.org/errata_search.php?rfc=8080&eid=4935
-    BOOST_CHECK_EQUAL(b64, "3cPAHkmlnxcDHMyg7vFC34l0blBhuG1qpwLmjInI8w1CMB29FkEAIJUA0amxWndkmnBZ6SKiwZSAxGILn/NBtOXft0+Gj7FSvOKxE/07+4RQvE581N3Aj/JtIyaiYVdnYtyMWbSNyGEY2213WKsJlwEA");
-}
-#endif /* defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED448) */
-
-BOOST_AUTO_TEST_CASE(test_hash_qname_with_salt) {
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables,readability-identifier-length): Boost stuff.
+BOOST_AUTO_TEST_CASE(test_hash_qname_with_salt)
+{
   {
     // rfc5155 appendix A
-    const unsigned char salt[] = { 0xaa, 0xbb, 0xcc, 0xdd };
+    const unsigned char salt[] = {0xaa, 0xbb, 0xcc, 0xdd};
     const unsigned int iterations{12};
     const std::vector<std::pair<std::string, std::string>> namesToHashes = {
-      { "example", "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom" },
-      { "a.example", "35mthgpgcu1qg68fab165klnsnk3dpvl" },
-      { "ai.example", "gjeqe526plbf1g8mklp59enfd789njgi" },
-      { "ns1.example", "2t7b4g4vsa5smi47k61mv5bv1a22bojr" },
-      { "ns2.example", "q04jkcevqvmu85r014c7dkba38o0ji5r" },
-      { "w.example", "k8udemvp1j2f7eg6jebps17vp3n8i58h" },
-      { "*.w.example", "r53bq7cc2uvmubfu5ocmm6pers9tk9en" },
-      { "x.w.example", "b4um86eghhds6nea196smvmlo4ors995" },
-      { "y.w.example", "ji6neoaepv8b5o6k4ev33abha8ht9fgc" },
-      { "x.y.w.example", "2vptu5timamqttgl4luu9kg21e0aor3s" },
-      { "xx.example", "t644ebqk9bibcna874givr6joj62mlhv" },
-      { "2t7b4g4vsa5smi47k61mv5bv1a22bojr.example", "kohar7mbb8dc2ce8a9qvl8hon4k53uhi" },
+      {"example", "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom"},
+      {"a.example", "35mthgpgcu1qg68fab165klnsnk3dpvl"},
+      {"ai.example", "gjeqe526plbf1g8mklp59enfd789njgi"},
+      {"ns1.example", "2t7b4g4vsa5smi47k61mv5bv1a22bojr"},
+      {"ns2.example", "q04jkcevqvmu85r014c7dkba38o0ji5r"},
+      {"w.example", "k8udemvp1j2f7eg6jebps17vp3n8i58h"},
+      {"*.w.example", "r53bq7cc2uvmubfu5ocmm6pers9tk9en"},
+      {"x.w.example", "b4um86eghhds6nea196smvmlo4ors995"},
+      {"y.w.example", "ji6neoaepv8b5o6k4ev33abha8ht9fgc"},
+      {"x.y.w.example", "2vptu5timamqttgl4luu9kg21e0aor3s"},
+      {"xx.example", "t644ebqk9bibcna874givr6joj62mlhv"},
+      {"2t7b4g4vsa5smi47k61mv5bv1a22bojr.example", "kohar7mbb8dc2ce8a9qvl8hon4k53uhi"},
     };
 
     for (const auto& [name, expectedHash] : namesToHashes) {
@@ -371,10 +507,10 @@ BOOST_AUTO_TEST_CASE(test_hash_qname_with_salt) {
 
   {
     /* no additional iterations, very short salt */
-    const unsigned char salt[] = { 0xFF };
+    const unsigned char salt[] = {0xFF};
     const unsigned int iterations{0};
     const std::vector<std::pair<std::string, std::string>> namesToHashes = {
-      { "example", "s9dp8o2l6jgqgg26ecobtjooe7p019cs" },
+      {"example", "s9dp8o2l6jgqgg26ecobtjooe7p019cs"},
     };
 
     for (const auto& [name, expectedHash] : namesToHashes) {
@@ -385,10 +521,10 @@ BOOST_AUTO_TEST_CASE(test_hash_qname_with_salt) {
 
   {
     /* only one iteration */
-    const unsigned char salt[] = { 0xaa, 0xbb, 0xcc, 0xdd };
+    const unsigned char salt[] = {0xaa, 0xbb, 0xcc, 0xdd};
     const unsigned int iterations{1};
     const std::vector<std::pair<std::string, std::string>> namesToHashes = {
-      { "example", "ulddquehrj5jpf50ga76vgqr1oq40133" },
+      {"example", "ulddquehrj5jpf50ga76vgqr1oq40133"},
     };
 
     for (const auto& [name, expectedHash] : namesToHashes) {
@@ -405,7 +541,7 @@ BOOST_AUTO_TEST_CASE(test_hash_qname_with_salt) {
     };
     const unsigned int iterations{65535};
     const std::vector<std::pair<std::string, std::string>> namesToHashes = {
-      { "example", "no95j4cfile8avstr7bn4aj9he18trri" },
+      {"example", "no95j4cfile8avstr7bn4aj9he18trri"},
     };
 
     for (const auto& [name, expectedHash] : namesToHashes) {
@@ -415,4 +551,5 @@ BOOST_AUTO_TEST_CASE(test_hash_qname_with_salt) {
   }
 }
 
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables): Boost stuff.
 BOOST_AUTO_TEST_SUITE_END()
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 c3231c04860d78db367ba6b6736cf7acb484cf87..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
@@ -85,7 +88,7 @@ static void checkTSIG(const DNSName& tsigName, const DNSName& tsigAlgo, const st
       BOOST_CHECK_EQUAL(answer.first.d_ttl, 0U);
       BOOST_CHECK_EQUAL(tsigFound, false);
 
-      shared_ptr<TSIGRecordContent> rectrc = getRR<TSIGRecordContent>(answer.first);
+      auto rectrc = getRR<TSIGRecordContent>(answer.first);
       if (rectrc) {
         trc = *rectrc;
         theirMac = rectrc->d_mac;
index dc6c147bad2ade3f38343c0f2de5747439483d13..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;
       }
@@ -184,7 +187,7 @@ public:
     return true;
   }
 
-  bool list(const DNSName& target, int zoneId, bool include_disabled = false) override
+  bool list(const DNSName& target, int zoneId, bool /* include_disabled */ = false) override
   {
     findZone(target, zoneId, d_records, d_currentZone);
 
@@ -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;
       }
@@ -283,12 +286,12 @@ public:
   {
   }
 
-  bool getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta) override
+  bool getDomainMetadata(const DNSName& /* name */, const std::string& /* kind */, std::vector<std::string>& /* meta */) override
   {
     return false;
   }
 
-  bool setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta) override
+  bool setDomainMetadata(const DNSName& /* name */, const std::string& /* kind */, const std::vector<std::string>& /* meta */) override
   {
     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()
@@ -1132,9 +1137,9 @@ BOOST_AUTO_TEST_CASE(test_multi_backends_metadata) {
 
     {
       // update the values
-      BOOST_CHECK(ub.setDomainMetadata(DNSName("powerdns.com."), "test-data-a", { "value3" }));
-      BOOST_CHECK(ub.setDomainMetadata(DNSName("powerdns.org."), "test-data-a", { "value4" }));
-      BOOST_CHECK(ub.setDomainMetadata(DNSName("powerdns.org."), "test-data-b", { "value5" }));
+      BOOST_CHECK(ub.setDomainMetadata(DNSName("powerdns.com."), "test-data-a", std::vector<std::string>({"value3"})));
+      BOOST_CHECK(ub.setDomainMetadata(DNSName("powerdns.org."), "test-data-a", std::vector<std::string>({"value4"})));
+      BOOST_CHECK(ub.setDomainMetadata(DNSName("powerdns.org."), "test-data-b", std::vector<std::string>({"value5"})));
     }
 
     // check the updated values
@@ -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 4f24cb4d655c32cfb5b6e09868d5dd0eb9740cf2..ef4f16a014e7b012ebf2024a4e27bcbc3b188f24 100644 (file)
@@ -1,28 +1,32 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
 
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
+
 #include <boost/test/unit_test.hpp>
 #include "arguments.hh"
 #include "auth-packetcache.hh"
 #include "auth-querycache.hh"
 #include "auth-zonecache.hh"
 #include "statbag.hh"
+
 StatBag S;
 AuthPacketCache PC;
 AuthQueryCache QC;
 AuthZoneCache g_zoneCache;
 uint16_t g_maxNSEC3Iterations{0};
 
-ArgvMap &arg()
+ArgvMaparg()
 {
   static ArgvMap theArg;
   return theArg;
 }
 
-
-static bool init_unit_test() {
+static bool init_unit_test()
+{
   reportAllTypes();
   return true;
 }
@@ -31,5 +35,5 @@ static bool init_unit_test() {
 int main(int argc, char* argv[])
 {
   S.d_allowRedeclare = true;
-  return boost::unit_test::unit_test_main( &init_unit_test, argc, argv );
+  return boost::unit_test::unit_test_main(&init_unit_test, argc, argv);
 }
index 4318117d0a2da5d7009efc7c5c348844f778da7a..39e904ce9111d7edea29035c9bd5415e12508c20 100644 (file)
@@ -35,6 +35,7 @@
 #include "dolog.hh"
 #else
 #include "logger.hh"
+#include "logging.hh"
 #endif
 
 #include "threadname.hh"
@@ -72,7 +73,8 @@ void setThreadName(const std::string& threadName) {
 #ifdef DNSDIST
     warnlog("Could not set thread name %s for thread: %s", threadName, strerror(retval));
 #else
-    g_log<<Logger::Warning<<"Could not set thread name "<<threadName<<" for thread: "<<strerror(retval)<<endl;
+    SLOG(g_log<<Logger::Warning<<"Could not set thread name "<<threadName<<" for thread: "<<strerror(retval)<<endl,
+         g_slog->withName("runtime")->error(Logr::Warning, retval, "Could not set thread name", "name", Logging::Loggable(threadName)));
 #endif
   }
 }
index d8bb30b17d67f6d2beab8df539334f7ef30dca9c..23214af3cd1b053cffd7812df40e63c491b9f316 100644 (file)
@@ -2,11 +2,23 @@
 #include "config.h"
 #endif
 #include "packethandler.hh"
+#include "gss_context.hh"
+#include "auth-main.hh"
 
 void PacketHandler::tkeyHandler(const DNSPacket& p, std::unique_ptr<DNSPacket>& r) {
+#ifdef ENABLE_GSS_TSIG
+  if (g_doGssTSIG) {
+    auto [i,a,s] = GssContext::getCounts();
+    g_log << Logger::Debug << "GSS #init_creds: " << i << " #accept_creds: " << a << " #secctxs: " << s << endl;
+  }
+#endif
+
   TKEYRecordContent tkey_in;
   std::shared_ptr<TKEYRecordContent> tkey_out(new TKEYRecordContent());
   DNSName name;
+#ifdef ENABLE_GSS_TSIG
+  bool sign = false;
+#endif
 
   if (!p.getTKEYRecord(&tkey_in, &name)) {
     g_log<<Logger::Error<<"TKEY request but no TKEY RR found"<<endl;
@@ -14,18 +26,53 @@ void PacketHandler::tkeyHandler(const DNSPacket& p, std::unique_ptr<DNSPacket>&
     return;
   }
 
+  auto inception = time(nullptr);
   // retain original name for response
   tkey_out->d_error = 0;
   tkey_out->d_mode = tkey_in.d_mode;
   tkey_out->d_algo = tkey_in.d_algo;
-  tkey_out->d_inception = time((time_t*)nullptr);
+  // coverity[store_truncates_time_t]
+  tkey_out->d_inception = inception;
   tkey_out->d_expiration = tkey_out->d_inception+15;
 
   if (tkey_in.d_mode == 3) { // establish context
-    if (tkey_in.d_algo == DNSName("gss-tsig.")) {
-      tkey_out->d_error = 19;
-    } else {
+#ifdef ENABLE_GSS_TSIG
+    if (g_doGssTSIG) {
+      if (tkey_in.d_algo == DNSName("gss-tsig.")) {
+        std::vector<std::string> meta;
+        DNSName tmpName(name);
+        do {
+          if (B.getDomainMetadata(tmpName, "GSS-ACCEPTOR-PRINCIPAL", meta) && meta.size()>0) {
+            break;
+          }
+        } while(tmpName.chopOff());
+
+        if (meta.size() == 0) {
+          tkey_out->d_error = 20;
+        } else {
+          GssContext ctx(name);
+          ctx.setLocalPrincipal(meta[0]);
+          // try to get a context
+          if (!ctx.accept(tkey_in.d_key, tkey_out->d_key)) {
+            ctx.destroy();
+            tkey_out->d_error = 19;
+          }
+          else {
+            sign = true;
+          }
+        }
+      } else {
+        tkey_out->d_error = 21; // BADALGO
+      }
+    } else
+#endif
+      {
       tkey_out->d_error = 21; // BADALGO
+#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 feature not compiled in"<<endl;
+#endif
     }
   } else if (tkey_in.d_mode == 5) { // destroy context
     if (p.d_havetsig == false) { // unauthenticated
@@ -35,8 +82,13 @@ void PacketHandler::tkeyHandler(const DNSPacket& p, std::unique_ptr<DNSPacket>&
         r->setRcode(RCode::NotAuth);
       return;
     }
-
-    tkey_out->d_error = 20; // BADNAME (because we have no support for anything here)
+    GssContext ctx(name);
+    if (ctx.valid()) {
+      ctx.destroy();
+    }
+    else {
+      tkey_out->d_error = 20; // BADNAME (because we have no support for anything here)
+    }
   } else {
     if (p.d_havetsig == false && tkey_in.d_mode != 2) { // unauthenticated
       if (p.d.opcode == Opcode::Update)
@@ -57,8 +109,25 @@ void PacketHandler::tkeyHandler(const DNSPacket& p, std::unique_ptr<DNSPacket>&
   zrr.dr.d_ttl = 0;
   zrr.dr.d_type = QType::TKEY;
   zrr.dr.d_class = QClass::ANY;
-  zrr.dr.d_content = tkey_out;
+  zrr.dr.setContent(std::move(tkey_out));
   zrr.dr.d_place = DNSResourceRecord::ANSWER;
   r->addRecord(std::move(zrr));
+
+#ifdef ENABLE_GSS_TSIG
+  if (sign)
+  {
+    TSIGRecordContent trc;
+    trc.d_algoName = DNSName("gss-tsig");
+    trc.d_time = inception;
+    trc.d_fudge = 300;
+    trc.d_mac = "";
+    trc.d_origID = p.d.id;
+    trc.d_eRcode = 0;
+    trc.d_otherData = "";
+    // this should cause it to lookup name context
+    r->setTSIGDetails(trc, name, name.toStringNoDot(), "", false);
+  }
+#endif
+
   r->commitD();
 }
index a0f66998c2e1ee0af69634e650c51e728c6ef6d7..62dfbf6787a38f1c814df6e23fa9474fbd5342bb 100755 (executable)
@@ -78,8 +78,8 @@ function makeGraphs()
         LINE1:alloutqueries#00ff00:"outqueries/s"
 
   rrdtool graph $GRAPHOPTS --start -$1 $WWWPREFIX/qa-latency-$2.png -w $WSIZE -h $HSIZE -l 0 \
-       -t "Questions/answer latency in milliseconds" \
-       -v "msec" \
+       -t "Questions/answer latency in ms" \
+       -v "ms" \
        DEF:qalatency=pdns_recursor.rrd:qa-latency:AVERAGE  \
        CDEF:mqalatency=qalatency,1000,/ \
         LINE1:mqalatency#ff0000:"questions/s"
diff --git a/pdns/toysdig.cc b/pdns/toysdig.cc
deleted file mode 100644 (file)
index 80a6329..0000000
+++ /dev/null
@@ -1,181 +0,0 @@
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-#include "dnsparser.hh"
-#include "rec-lua-conf.hh"
-#include "sstuff.hh"
-#include "misc.hh"
-#include "dnswriter.hh"
-#include "dnsrecords.hh"
-#include "statbag.hh"
-#include "ednssubnet.hh"
-#include "dnssecinfra.hh"
-#include "recursor_cache.hh"
-#include "base32.hh"
-#include "root-dnssec.hh"
-
-#include "validate.hh"
-StatBag S;
-
-class TCPResolver : public boost::noncopyable
-{
-public:
-  TCPResolver(ComboAddress addr) : d_rsock(AF_INET, SOCK_STREAM)
-  {
-    d_rsock.connect(addr);
-  }
-
-  string query(const DNSName& qname, uint16_t qtype)
-  {
-    cerr<<"Q "<<qname<<"/"<<DNSRecordContent::NumberToType(qtype)<<endl;
-    vector<uint8_t> packet;
-    DNSPacketWriter pw(packet, qname, qtype);
-
-    // recurse
-    pw.getHeader()->rd=true;
-
-    // we'll do the validation
-    pw.getHeader()->cd=true;
-    pw.getHeader()->ad=true;
-
-    // we do require DNSSEC records to do that!
-    pw.addOpt(2800, 0, EDNSOpts::DNSSECOK);
-    pw.commit();
-
-    uint16_t len;
-    len = htons(packet.size());
-    if(d_rsock.write((char *) &len, 2) != 2)
-      throw PDNSException("tcp write failed");
-
-    d_rsock.writen(string(packet.begin(), packet.end()));
-    
-    int bread=d_rsock.read((char *) &len, 2);
-    if( bread <0)
-      throw PDNSException("tcp read failed: "+stringerror());
-    if(bread != 2) 
-      throw PDNSException("EOF on TCP read");
-
-    len=ntohs(len);
-    auto creply = std::make_unique<char[]>(len);
-    int n=0;
-    int numread;
-    while(n<len) {
-      numread=d_rsock.read(creply.get()+n, len-n);
-      if(numread<0) {
-        throw PDNSException("tcp read failed: "+stringerror());
-      }
-      n+=numread;
-    }
-
-    string reply(creply.get(), len);
-
-    return reply;
-  }
-
-  Socket d_rsock;
-};
-
-
-class TCPRecordOracle : public DNSRecordOracle
-{
-public:
-  TCPRecordOracle(const ComboAddress& dest) : d_dest(dest) {}
-  vector<DNSRecord> get(const DNSName& qname, uint16_t qtype) override
-  {
-    TCPResolver tr(d_dest);
-    string resp=tr.query(qname, qtype);
-    MOADNSParser mdp(false, resp);
-    vector<DNSRecord> ret;
-    ret.reserve(mdp.d_answers.size());
-    for(const auto& a : mdp.d_answers) {
-      ret.push_back(a.first);
-    }
-    return ret;
-  }
-private:
-  ComboAddress d_dest;
-};
-
-GlobalStateHolder<LuaConfigItems> g_luaconfs;
-LuaConfigItems::LuaConfigItems()
-{
-  for (const auto &dsRecord : rootDSs) {
-    auto ds=std::dynamic_pointer_cast<DSRecordContent>(DSRecordContent::make(dsRecord));
-    dsAnchors[g_rootdnsname].insert(*ds);
-  }
-}
-
-DNSFilterEngine::DNSFilterEngine() {}
-
-int main(int argc, char** argv)
-try
-{
-  reportAllTypes();
-//  g_rootDS =  "19036 8 2 49aac11d7b6f6446702e54a1607371607a1a41855200fd2ce1cdde32f24e8fb5";
-
-//  if(argv[5])
-//    g_rootDS = argv[5];
-  
-  //  g_anchors.insert(DSRecordContent("19036 8 2 49aac11d7b6f6446702e54a1607371607a1a41855200fd2ce1cdde32f24e8fb5"));
-  if(argc < 4) {
-    cerr<<"Syntax: toysdig IP-address port question question-type [rootDS]\n";
-    exit(EXIT_FAILURE);
-  }
-  ComboAddress dest(argv[1] + (*argv[1]=='@'), atoi(argv[2]));
-  TCPRecordOracle tro(dest);
-  DNSName qname(argv[3]);
-  uint16_t qtype=DNSRecordContent::TypeToNumber(argv[4]);
-  cout<<"digraph oneshot {"<<endl;
-
-  auto recs=tro.get(qname, qtype);
-
-  cspmap_t cspmap=harvestCSPFromRecs(recs);
-  cerr<<"Got "<<cspmap.size()<<" RRSETs: ";
-  int numsigs=0;
-  for(const auto& csp : cspmap) {
-    cerr<<" "<<csp.first.first<<'/'<<DNSRecordContent::NumberToType(csp.first.second)<<": "<<csp.second.signatures.size()<<" sigs for "<<csp.second.records.size()<<" records"<<endl;
-    numsigs+= csp.second.signatures.size();
-  }
-   
-  skeyset_t keys;
-  cspmap_t validrrsets;
-
-  if(numsigs) {
-    for(const auto& csp : cspmap) {
-      for(const auto& sig : csp.second.signatures) {
-       cerr<<"got rrsig "<<sig->d_signer<<"/"<<sig->d_tag<<endl;
-       vState state = getKeysFor(tro, sig->d_signer, keys);
-       cerr<<"! state = "<<state<<", now have "<<keys.size()<<" keys at "<<qname<<endl;
-        // dsmap.emplace(dsrc.d_tag, dsrc);
-      }
-    }
-
-    validateWithKeySet(cspmap, validrrsets, keys);
-  }
-  else {
-    cerr<<"no sigs, hoping for Insecure"<<endl;
-    vState state = getKeysFor(tro, qname, keys);
-    cerr<<"! state = "<<state<<", now have "<<keys.size()<<" keys at "<<qname<<endl;
-  }
-  cerr<<"! validated "<<validrrsets.size()<<" RRsets out of "<<cspmap.size()<<endl;
-
-  cerr<<"% validated RRs:"<<endl;
-  for(auto i=validrrsets.begin(); i!=validrrsets.end(); i++) {
-    cerr<<"% "<<i->first.first<<"/"<<DNSRecordContent::NumberToType(i->first.second)<<endl;
-    for(auto j=i->second.records.begin(); j!=i->second.records.end(); j++) {
-      cerr<<"\t% > "<<(*j)->getZoneRepresentation()<<endl;
-    }
-  }
-
-  cout<<"}"<<endl;
-  exit(0);
-}
-catch(std::exception &e)
-{
-  cerr<<"Fatal: "<<e.what()<<endl;
-}
-catch(PDNSException &pe)
-{
-  cerr<<"Fatal: "<<pe.reason<<endl;
-}
-
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 c379c841afa6b724981b964747e33be0c75ffecb..b5300179751730f8d4bfbc2e6a44b2b2395eaf81 100644 (file)
@@ -1,6 +1,7 @@
 
 #include "tsigverifier.hh"
 #include "dnssecinfra.hh"
+#include "gss_context.hh"
 
 bool TSIGTCPVerifier::check(const string& data, const MOADNSParser& mdp)
 {
@@ -27,7 +28,7 @@ bool TSIGTCPVerifier::check(const string& data, const MOADNSParser& mdp)
     }
 
     if(answer.first.d_type == QType::TSIG) {
-      shared_ptr<TSIGRecordContent> trc = getRR<TSIGRecordContent>(answer.first);
+      auto trc = getRR<TSIGRecordContent>(answer.first);
       if(trc) {
         theirMac = trc->d_mac;
         d_trc.d_time = trc->d_time;
@@ -61,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 84243afdc49b6b587f66f0aa1f53cca764efedb8..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 <cerrno>
 #include <dlfcn.h>
-#include <string>
-#include <map>
-#include <sys/types.h>
-#include <sstream>
-#include <errno.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,150 +142,143 @@ 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::setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta)
+bool UeberBackend::getDomainMetadata(const DNSName& name, const std::string& kind, std::string& meta)
 {
-  for(DNSBackend* db :  backends) {
-    if(db->setDomainMetadata(name, kind, meta))
-      return true;
+  meta.clear();
+  std::vector<string> tmp;
+  const bool ret = getDomainMetadata(name, kind, tmp);
+  if (ret && !tmp.empty()) {
+    meta = *tmp.begin();
   }
-  return false;
+  return ret;
 }
 
-bool UeberBackend::activateDomainKey(const DNSName& name, unsigned int id)
-{
-  for(DNSBackend* db :  backends) {
-    if(db->activateDomainKey(name, id))
-      return true;
-  }
-  return false;
-}
-
-bool UeberBackend::deactivateDomainKey(const DNSName& name, unsigned int id)
+bool UeberBackend::setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta)
 {
-  for(DNSBackend* db :  backends) {
-    if(db->deactivateDomainKey(name, id))
+  for (auto& backend : backends) {
+    if (backend->setDomainMetadata(name, kind, meta)) {
       return true;
+    }
   }
   return false;
 }
 
-bool UeberBackend::publishDomainKey(const DNSName& name, unsigned int id)
+bool UeberBackend::setDomainMetadata(const DNSName& name, const std::string& kind, const std::string& meta)
 {
-  for(DNSBackend* db :  backends) {
-    if(db->publishDomainKey(name, id))
-      return true;
+  std::vector<string> tmp;
+  if (!meta.empty()) {
+    tmp.push_back(meta);
   }
-  return false;
+  return setDomainMetadata(name, kind, tmp);
 }
 
-bool UeberBackend::unpublishDomainKey(const DNSName& name, unsigned int id)
+bool UeberBackend::activateDomainKey(const DNSName& name, unsigned int keyID)
 {
-  for(DNSBackend* db :  backends) {
-    if(db->unpublishDomainKey(name, id))
+  for (auto& backend : backends) {
+    if (backend->activateDomainKey(name, keyID)) {
       return true;
+    }
   }
   return false;
 }
 
-
-bool UeberBackend::removeDomainKey(const DNSName& name, unsigned int id)
+bool UeberBackend::deactivateDomainKey(const DNSName& name, unsigned int keyID)
 {
-  for(DNSBackend* db :  backends) {
-    if(db->removeDomainKey(name, id))
+  for (auto& backend : backends) {
+    if (backend->deactivateDomainKey(name, keyID)) {
       return true;
+    }
   }
   return false;
 }
 
-
-bool UeberBackend::getTSIGKey(const DNSName& name, DNSName* algorithm, string* content)
+bool UeberBackend::publishDomainKey(const DNSName& name, unsigned int keyID)
 {
-  for(DNSBackend* db :  backends) {
-    if(db->getTSIGKey(name, algorithm, content))
+  for (auto& backend : backends) {
+    if (backend->publishDomainKey(name, keyID)) {
       return true;
+    }
   }
   return false;
 }
 
-
-bool UeberBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content)
+bool UeberBackend::unpublishDomainKey(const DNSName& name, unsigned int keyID)
 {
-  for(DNSBackend* db :  backends) {
-    if(db->setTSIGKey(name, algorithm, content))
+  for (auto& backend : backends) {
+    if (backend->unpublishDomainKey(name, keyID)) {
       return true;
+    }
   }
   return false;
 }
 
-bool UeberBackend::deleteTSIGKey(const DNSName& name)
+bool UeberBackend::removeDomainKey(const DNSName& name, unsigned int keyID)
 {
-  for(DNSBackend* db :  backends) {
-    if(db->deleteTSIGKey(name))
+  for (auto& backend : backends) {
+    if (backend->removeDomainKey(name, keyID)) {
       return true;
+    }
   }
   return false;
 }
 
-bool UeberBackend::getTSIGKeys(std::vector< struct TSIGKey > &keys)
-{
-  for(DNSBackend* db :  backends) {
-    db->getTSIGKeys(keys);
-  }
-  return true;
-}
-
 void UeberBackend::reload()
 {
-  for (auto & backend : backends)
-  {
+  for (auto& backend : backends) {
     backend->reload();
   }
 }
 
-void UeberBackend::updateZoneCache() {
+void UeberBackend::updateZoneCache()
+{
   if (!g_zoneCache.isEnabled()) {
     return;
   }
@@ -281,60 +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::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
+{
+  for (auto& backend : backends) {
+    backend->getUnfreshSecondaryInfos(domains);
+  }
+}
+
+void UeberBackend::getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
+{
+  for (auto& backend : backends) {
+    backend->getUpdatedPrimaries(domains, catalogs, catalogHashes);
+  }
+}
 
-void UeberBackend::getUnfreshSlaveInfos(vector<DomainInfo>* domains)
+bool UeberBackend::inTransaction()
 {
-  for (auto & backend : backends)
-  {
-    backend->getUnfreshSlaveInfos( domains );
-  }  
+  for (auto& backend : backends) {
+    if (backend->inTransaction()) {
+      return true;
+    }
+  }
+  return false;
 }
 
+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;
+}
 
-void UeberBackend::getUpdatedMasters(vector<DomainInfo>* domains)
+UeberBackend::CacheResult UeberBackend::fillSOAFromCache(SOAData* soaData, DNSName& shorter)
 {
-  for (auto & backend : backends)
-  {
-    backend->getUpdatedMasters( domains );
+  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;
 }
 
-bool UeberBackend::inTransaction()
+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)
 {
-  for (auto* b : backends )
-  {
-    if(b->inTransaction())
-      return true;
+  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* sd, bool cachedOk)
+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
@@ -342,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;
     }
 
@@ -390,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.d_content = 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.d_content = 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);
   }
@@ -739,20 +801,75 @@ bool UeberBackend::get(DNSZoneRecord &rr)
   return false;
 }
 
-bool UeberBackend::searchRecords(const string& pattern, int maxResults, vector<DNSResourceRecord>& result)
+// TSIG
+//
+bool UeberBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content)
+{
+  for (auto& backend : backends) {
+    if (backend->setTSIGKey(name, algorithm, content)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool UeberBackend::getTSIGKey(const DNSName& name, DNSName& algorithm, string& content)
+{
+  algorithm.clear();
+  content.clear();
+
+  for (auto& backend : backends) {
+    if (backend->getTSIGKey(name, algorithm, content)) {
+      break;
+    }
+  }
+  return (!algorithm.empty() && !content.empty());
+}
+
+bool UeberBackend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
+{
+  keys.clear();
+
+  for (auto& backend : backends) {
+    if (backend->getTSIGKeys(keys)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool UeberBackend::deleteTSIGKey(const DNSName& name)
+{
+  for (auto& backend : backends) {
+    if (backend->deleteTSIGKey(name)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+// API Search
+//
+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);
@@ -761,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()
@@ -773,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 3ee73644276d827c72bdd15f91cdec1c97e4261c..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);
-  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 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);
 
-  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);
+  void alsoNotifies(const DNSName& domain, set<string>* ips);
+  void rediscover(string* status = nullptr);
+  void reload();
 
-  bool getTSIGKey(const DNSName& name, DNSName* algorithm, string* content);
   bool setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content);
+  bool getTSIGKey(const DNSName& name, DNSName& algorithm, string& content);
+  bool getTSIGKeys(std::vector<struct TSIGKey>& keys);
   bool deleteTSIGKey(const DNSName& name);
-  bool getTSIGKeys(std::vector< struct TSIGKey > &keys);
 
-  void alsoNotifies(const DNSName &domain, set<string> *ips); 
-  void rediscover(string* status=0);
-  void reload();
-  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();
 
   bool inTransaction();
+
 private:
   handle d_handle;
   vector<DNSZoneRecord> d_answers;
@@ -150,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 8d209275d2e3f3ee3322db5e990914c25d102d17..c58bf404221cd97cf42289459ff9bddbe38f5874 100644 (file)
 #include <cstring>
 #include <fcntl.h>
 #include <unistd.h>
-#include <stdlib.h> 
+#include <stdlib.h>
 #include "pdnsexception.hh"
 #include "logger.hh"
+#include "logging.hh"
 #include "misc.hh"
 #include <pwd.h>
 #include <grp.h>
@@ -77,29 +78,42 @@ int Utility::timed_connect( Utility::sock_t sock,
 
 
 
-void Utility::setBindAny(int af, sock_t sock)
+void Utility::setBindAny([[maybe_unused]] int af, [[maybe_unused]] sock_t sock)
 {
   const int one = 1;
 
   (void) one; // avoids 'unused var' warning on systems that have none of the defines checked below
 #ifdef IP_FREEBIND
-  if (setsockopt(sock, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one)) < 0)
-      g_log<<Logger::Warning<<"Warning: IP_FREEBIND setsockopt failed: "<<stringerror()<<endl;
+  if (setsockopt(sock, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one)) < 0) {
+    int err = errno;
+    SLOG(g_log<<Logger::Warning<<"Warning: IP_FREEBIND setsockopt failed: "<<stringerror(err)<<endl,
+         g_slog->withName("runtime")->error(Logr::Warning, err, "Warning: IP_FREEBIND setsockopt failed"));
+  }
 #endif
 
 #ifdef IP_BINDANY
   if (af == AF_INET)
-    if (setsockopt(sock, IPPROTO_IP, IP_BINDANY, &one, sizeof(one)) < 0)
-      g_log<<Logger::Warning<<"Warning: IP_BINDANY setsockopt failed: "<<stringerror()<<endl;
+    if (setsockopt(sock, IPPROTO_IP, IP_BINDANY, &one, sizeof(one)) < 0) {
+      int err = errno;
+      SLOG(g_log<<Logger::Warning<<"Warning: IP_BINDANY setsockopt failed: "<<stringerror(err)<<endl,
+           g_slog->withName("runtime")->error(Logr::Warning, err, "Warning: IP_BINDANY setsockopt failed"));
+    }
 #endif
 #ifdef IPV6_BINDANY
-  if (af == AF_INET6)
-    if (setsockopt(sock, IPPROTO_IPV6, IPV6_BINDANY, &one, sizeof(one)) < 0)
-      g_log<<Logger::Warning<<"Warning: IPV6_BINDANY setsockopt failed: "<<stringerror()<<endl;
+  if (af == AF_INET6) {
+    if (setsockopt(sock, IPPROTO_IPV6, IPV6_BINDANY, &one, sizeof(one)) < 0) {
+      int err = errno;
+      SLOG(g_log<<Logger::Warning<<"Warning: IPV6_BINDANY setsockopt failed: "<<stringerror(err)<<endl,
+           g_slog->withName("runtime")->error(Logr::Warning, err, "Warning: IPV6_BINDANY setsockopt failed"));
+    }
+  }
 #endif
 #ifdef SO_BINDANY
-  if (setsockopt(sock, SOL_SOCKET, SO_BINDANY, &one, sizeof(one)) < 0)
-      g_log<<Logger::Warning<<"Warning: SO_BINDANY setsockopt failed: "<<stringerror()<<endl;
+  if (setsockopt(sock, SOL_SOCKET, SO_BINDANY, &one, sizeof(one)) < 0) {
+    int err = errno;
+    SLOG(g_log<<Logger::Warning<<"Warning: SO_BINDANY setsockopt failed: "<<stringerror(err)<<endl,
+         g_slog->withName("runtime")->error(Logr::Warning, err, "Warning: SO_BINDANY setsockopt failed"));
+  }
 #endif
 }
 
@@ -119,7 +133,7 @@ void Utility::usleep(unsigned long usec)
   ts.tv_sec = usec / 1000000;
   ts.tv_nsec = (usec % 1000000) * 1000;
   // POSIX.1 recommends using nanosleep instead of usleep
-  ::nanosleep(&ts, nullptr); 
+  ::nanosleep(&ts, nullptr);
 }
 
 
@@ -128,22 +142,30 @@ void Utility::dropGroupPrivs( uid_t uid, gid_t gid )
 {
   if(gid && gid != getegid()) {
     if(setgid(gid)<0) {
-      g_log<<Logger::Critical<<"Unable to set effective group id to "<<gid<<": "<<stringerror()<<endl;
+      int err = errno;
+      SLOG(g_log<<Logger::Critical<<"Unable to set effective group id to "<<gid<<": "<<stringerror(err)<<endl,
+           g_slog->withName("runtime")->error(Logr::Critical, err, "Unable to set effective group id", "gid", Logging::Loggable(gid)));
       exit(1);
     }
-    else
-      g_log<<Logger::Info<<"Set effective group id to "<<gid<<endl;
-
+    else {
+      SLOG(g_log<<Logger::Info<<"Set effective group id to "<<gid<<endl,
+           g_slog->withName("runtime")->info(Logr::Info, "Set effective group id", "gid", Logging::Loggable(gid)));
+    }
     struct passwd *pw=getpwuid(uid);
     if(!pw) {
-      g_log<<Logger::Warning<<"Unable to determine user name for uid "<<uid<<endl;
+      SLOG(g_log<<Logger::Warning<<"Unable to determine user name for uid "<<uid<<endl,
+           g_slog->withName("runtime")->info(Logr::Warning, "Unable to determine user name", "uid", Logging::Loggable(uid)));
       if (setgroups(0, nullptr)<0) {
-        g_log<<Logger::Critical<<"Unable to drop supplementary gids: "<<stringerror()<<endl;
+        int err = errno;
+        SLOG(g_log<<Logger::Critical<<"Unable to drop supplementary gids: "<<stringerror(err)<<endl,
+             g_slog->withName("runtime")->error(Logr::Critical, err, "Unable to drop supplementary gids"));
         exit(1);
       }
     } else {
       if (initgroups(pw->pw_name, gid)<0) {
-        g_log<<Logger::Critical<<"Unable to set supplementary groups: "<<stringerror()<<endl;
+        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 set supplementary groups"));
         exit(1);
       }
     }
@@ -156,11 +178,15 @@ void Utility::dropUserPrivs( uid_t uid )
 {
   if(uid && uid != geteuid()) {
     if(setuid(uid)<0) {
-      g_log<<Logger::Critical<<"Unable to set effective user id to "<<uid<<": "<<stringerror()<<endl;
+      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::Critical, err, "Unable to set effective user id", "uid", Logging::Loggable(uid)));
       exit(1);
     }
-    else
-      g_log<<Logger::Info<<"Set effective user id to "<<uid<<endl;
+    else {
+      SLOG(g_log<<Logger::Info<<"Set effective user id to "<<uid<<endl,
+           g_slog->withName("runtime")->info(Logr::Info, "Set effective user", "uid", Logging::Loggable(uid)));
+    }
   }
 }
 
@@ -173,17 +199,9 @@ Utility::pid_t Utility::getpid( )
 
 
 // Returns the current time.
-int Utility::gettimeofday( struct timeval *tv, void *tz )
-{
-  return ::gettimeofday(tv,nullptr);
-}
-
-// Sets the random seed.
-void Utility::srandom()
+int Utility::gettimeofday( struct timeval *tv, void * /* tz */)
 {
-  struct timeval tv;
-  gettimeofday(&tv, nullptr);
-  ::srandom(tv.tv_sec ^ tv.tv_usec ^ getpid());
+  return ::gettimeofday(tv, nullptr);
 }
 
 // Writes a vector.
@@ -199,7 +217,7 @@ static int isleap(int year) {
   return (!(year%4) && ((year%100) || !(year%400)));
 }
 
-time_t Utility::timegm(struct tm *const t) 
+time_t Utility::timegm(struct tm *const t)
 {
   const static short spm[13] = /* days per month -- nonleap! */
   { 0,
@@ -225,7 +243,7 @@ time_t Utility::timegm(struct tm *const t)
   if (t->tm_min>60) { t->tm_hour += t->tm_min/60; t->tm_min%=60; }
   if (t->tm_hour>60) { t->tm_mday += t->tm_hour/60; t->tm_hour%=60; }
   if (t->tm_mon>11) { t->tm_year += t->tm_mon/12; t->tm_mon%=12; }
+
   while (t->tm_mday>spm[1+t->tm_mon]) {
     if (t->tm_mon==1 && isleap(t->tm_year+1900)) {
       if (t->tm_mon==31+29) break;
@@ -256,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;
@@ -263,4 +282,3 @@ time_t Utility::timegm(struct tm *const t)
   i = 60;
   return ((day + t->tm_hour) * i + t->tm_min) * i + t->tm_sec;
 }
-
index d94f299cb9eb0712c6385f1ae1a901e96a4a0bef..7429a10e16de292fba07073a1e6ebeb504ede1af 100644 (file)
@@ -34,11 +34,11 @@ typedef unsigned long long uint64_t;
 #include <sys/socket.h>
 #include <sys/time.h>
 #include <sys/uio.h>
-#include <signal.h>
+#include <csignal>
 #include <pthread.h>
 #include <semaphore.h>
-#include <signal.h>
-#include <errno.h>
+#include <csignal>
+#include <cerrno>
 #include <unistd.h>
 #include <string>
 
@@ -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 );
 
diff --git a/pdns/validate-recursor.cc b/pdns/validate-recursor.cc
deleted file mode 100644 (file)
index 4c2bb10..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-#include "validate.hh"
-#include "validate-recursor.hh"
-#include "syncres.hh"
-#include "logger.hh"
-#include "rec-lua-conf.hh"
-#include "dnssecinfra.hh"
-#include "dnsseckeeper.hh"
-#include "zoneparser-tng.hh"
-
-DNSSECMode g_dnssecmode{DNSSECMode::ProcessNoValidate};
-bool g_dnssecLogBogus;
-
-bool checkDNSSECDisabled()
-{
-  return warnIfDNSSECDisabled("");
-}
-
-bool warnIfDNSSECDisabled(const string& msg)
-{
-  if (g_dnssecmode == DNSSECMode::Off) {
-    if (!msg.empty())
-      g_log << Logger::Warning << msg << endl;
-    return true;
-  }
-  return false;
-}
-
-vState increaseDNSSECStateCounter(const vState& state)
-{
-  g_stats.dnssecResults[state]++;
-  return state;
-}
-
-vState increaseXDNSSECStateCounter(const vState& state)
-{
-  g_stats.xdnssecResults[state]++;
-  return state;
-}
-
-// Returns true if dsAnchors were modified
-bool updateTrustAnchorsFromFile(const std::string& fname, map<DNSName, dsmap_t>& dsAnchors)
-{
-  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);
-        if (dsr == nullptr) {
-          throw PDNSException("Unable to parse DS record '" + rr.qname.toString() + " " + rr.getZoneRepresentation() + "'");
-        }
-        newDSAnchors[rr.qname].insert(*dsr);
-      }
-      if (rr.qtype == QType::DNSKEY) {
-        auto dnskeyr = getRR<DNSKEYRecordContent>(dr);
-        if (dnskeyr == nullptr) {
-          throw PDNSException("Unable to parse DNSKEY record '" + rr.qname.toString() + " " + rr.getZoneRepresentation() + "'");
-        }
-        auto dsr = makeDSFromDNSKey(rr.qname, *dnskeyr, DNSSECKeeper::DIGEST_SHA256);
-        newDSAnchors[rr.qname].insert(dsr);
-      }
-    }
-    if (dsAnchors == newDSAnchors) {
-      g_log << Logger::Debug << "Read Trust Anchors from file, no changes detected" << endl;
-      return false;
-    }
-    g_log << Logger::Info << "Read changed Trust Anchors from file, updating" << endl;
-    dsAnchors = newDSAnchors;
-    return true;
-  }
-  catch (const std::exception& e) {
-    throw PDNSException("Error while reading Trust Anchors from file '" + fname + "': " + e.what());
-  }
-  catch (...) {
-    throw PDNSException("Error while reading Trust Anchors from file '" + fname + "'");
-  }
-}
index 2dfd0ae94cb627647003a41c25bc98a98c1d29b9..16d144e643b0a4d9afdcf937f51464fda4bd94ab 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"
 #include "rec-lua-conf.hh"
 #include "base32.hh"
 #include "logger.hh"
-bool g_dnssecLOG{false};
+
 time_t g_signatureInceptionSkew{0};
 uint16_t g_maxNSEC3Iterations{0};
-
-#define LOG(x) if(g_dnssecLOG) { g_log <<Logger::Warning << x; }
+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)
 {
@@ -32,18 +56,18 @@ static bool isRevokedKey(const DNSKEYRecordContent& key)
   return (key.d_flags & 128) != 0;
 }
 
-static vector<shared_ptr<DNSKEYRecordContent > > getByTag(const skeyset_t& keys, uint16_t tag, uint8_t algorithm)
+static vector<shared_ptr<const DNSKEYRecordContent > > getByTag(const skeyset_t& keys, uint16_t tag, uint8_t algorithm, const OptLog& log)
 {
-  vector<shared_ptr<DNSKEYRecordContent>> ret;
+  vector<shared_ptr<const DNSKEYRecordContent>> ret;
 
   for (const auto& key : keys) {
     if (!isAZoneKey(*key)) {
-      LOG("Key for tag "<<std::to_string(tag)<<" and algorithm "<<std::to_string(algorithm)<<" is not a zone key, skipping"<<endl;);
+      VLOG(log, "Key for tag "<<std::to_string(tag)<<" and algorithm "<<std::to_string(algorithm)<<" is not a zone key, skipping"<<endl;);
       continue;
     }
 
     if (isRevokedKey(*key)) {
-      LOG("Key for tag "<<std::to_string(tag)<<" and algorithm "<<std::to_string(algorithm)<<" has been revoked, skipping"<<endl;);
+      VLOG(log, "Key for tag "<<std::to_string(tag)<<" and algorithm "<<std::to_string(algorithm)<<" has been revoked, skipping"<<endl;);
       continue;
     }
 
@@ -55,20 +79,20 @@ static vector<shared_ptr<DNSKEYRecordContent > > getByTag(const skeyset_t& keys,
   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)
@@ -88,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 std::shared_ptr<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;
+  }
+
+  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(nsec3->d_salt, nsec3->d_iterations, qname);
-  cache[key] = result;
+  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) {
@@ -141,17 +175,22 @@ 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) {
+        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,16 +204,12 @@ bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>&
    Labels field of the covering RRSIG RR, then the RRset and its
    covering RRSIG RR were created as a result of wildcard expansion."
 */
-bool isWildcardExpanded(unsigned int labelCount, const std::shared_ptr<RRSIGRecordContent>& sign)
+bool isWildcardExpanded(unsigned int labelCount, const RRSIGRecordContent& sign)
 {
-  if (sign && 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<RRSIGRecordContent> >& signatures)
+static bool isWildcardExpanded(const DNSName& owner, const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures)
 {
   if (signatures.empty()) {
     return false;
@@ -182,19 +217,16 @@ static bool isWildcardExpanded(const DNSName& owner, const std::vector<std::shar
 
   const auto& sign = signatures.at(0);
   unsigned int labelsCount = owner.countLabels();
-  return isWildcardExpanded(labelsCount, sign);
+  return isWildcardExpanded(labelsCount, *sign);
 }
 
-bool isWildcardExpandedOntoItself(const DNSName& owner, unsigned int labelCount, const std::shared_ptr<RRSIGRecordContent>& sign)
+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<RRSIGRecordContent> >& signatures)
+static bool isWildcardExpandedOntoItself(const DNSName& owner, const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures)
 {
   if (signatures.empty()) {
     return false;
@@ -202,12 +234,12 @@ static bool isWildcardExpandedOntoItself(const DNSName& owner, const std::vector
 
   const auto& sign = signatures.at(0);
   unsigned int labelsCount = owner.countLabels();
-  return isWildcardExpandedOntoItself(owner, labelsCount, sign);
+  return isWildcardExpandedOntoItself(owner, labelsCount, *sign);
 }
 
 /* if this is a wildcard NSEC, the owner name has been modified
    to match the name. Make sure we use the original '*' form. */
-DNSName getNSECOwnerName(const DNSName& initialOwner, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures)
+DNSName getNSECOwnerName(const DNSName& initialOwner, const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures)
 {
   DNSName result = initialOwner;
 
@@ -230,45 +262,45 @@ DNSName getNSECOwnerName(const DNSName& initialOwner, const std::vector<std::sha
   return result;
 }
 
-static bool isNSECAncestorDelegation(const DNSName& signer, const DNSName& owner, const std::shared_ptr<NSECRecordContent>& nsec)
+static bool isNSECAncestorDelegation(const DNSName& signer, const DNSName& owner, const NSECRecordContent& nsec)
 {
-  return nsec->isSet(QType::NS) &&
-    !nsec->isSet(QType::SOA) &&
+  return nsec.isSet(QType::NS) &&
+    !nsec.isSet(QType::SOA) &&
     signer.countLabels() < owner.countLabels();
 }
 
-bool isNSEC3AncestorDelegation(const DNSName& signer, const DNSName& owner, const std::shared_ptr<NSEC3RecordContent>& nsec3)
+bool isNSEC3AncestorDelegation(const DNSName& signer, const DNSName& owner, const NSEC3RecordContent& nsec3)
 {
-  return nsec3->isSet(QType::NS) &&
-    !nsec3->isSet(QType::SOA) &&
+  return nsec3.isSet(QType::NS) &&
+    !nsec3.isSet(QType::SOA) &&
     signer.countLabels() < owner.countLabels();
 }
 
-static bool provesNoDataWildCard(const DNSName& qname, const uint16_t qtype, const DNSName& closestEncloser, const cspmap_t& validrrsets)
+static bool provesNoDataWildCard(const DNSName& qname, const uint16_t qtype, const DNSName& closestEncloser, const cspmap_t& validrrsets, const OptLog& log)
 {
   const DNSName wildcard = g_wildcarddnsname + closestEncloser;
-  LOG("Trying to prove that there is no data in wildcard for "<<qname<<"/"<<QType(qtype)<<endl);
-  for (const auto& v : validrrsets) {
-    LOG("Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl);
-    if (v.first.second == QType::NSEC) {
-      for (const auto& r : v.second.records) {
-        LOG("\t"<<r->getZoneRepresentation()<<endl);
-        auto nsec = std::dynamic_pointer_cast<NSECRecordContent>(r);
+  VLOG(log, qname << ": Trying to prove that there is no data in wildcard for "<<qname<<"/"<<QType(qtype)<<endl);
+  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;
         }
 
-        LOG("\tWildcard matches");
-        if (qtype == 0 || isTypeDenied(nsec, QType(qtype))) {
-          LOG(" and proves that the type did not exist"<<endl);
+        VLOG(log, qname << ":\tWildcard matches");
+        if (qtype == 0 || isTypeDenied(*nsec, QType(qtype))) {
+          VLOG_NO_PREFIX(log, " and proves that the type did not exist"<<endl);
           return true;
         }
-        LOG(" BUT the type did exist!"<<endl);
+        VLOG_NO_PREFIX(log, " BUT the type did exist!"<<endl);
         return false;
       }
     }
@@ -291,22 +323,22 @@ DNSName getClosestEncloserFromNSEC(const DNSName& name, const DNSName& owner, co
   This function checks whether the non-existence of a wildcard covering qname|qtype
   is proven by the NSEC records in validrrsets.
 */
-static bool provesNoWildCard(const DNSName& qname, const uint16_t qtype, const DNSName& closestEncloser, const cspmap_t & validrrsets)
+static bool provesNoWildCard(const DNSName& qname, const uint16_t qtype, const DNSName& closestEncloser, const cspmap_t & validrrsets, const OptLog& log)
 {
-  LOG("Trying to prove that there is no wildcard for "<<qname<<"/"<<QType(qtype)<<endl);
+  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) {
-    LOG("Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl);
-    if (v.first.second == QType::NSEC) {
-      for (const auto& r : v.second.records) {
-        LOG("\t"<<r->getZoneRepresentation()<<endl);
-        auto nsec = std::dynamic_pointer_cast<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);
-        LOG("Comparing owner: "<<owner<<" with target: "<<wildcard<<endl);
+        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)) {
           /* rfc6672 section 5.3.2: DNAME Bit in NSEC Type Map
@@ -318,12 +350,12 @@ static bool provesNoWildCard(const DNSName& qname, const uint16_t qtype, const D
              asserted, then DNAME substitution should have been done, but the
              substitution has not been done as specified.
           */
-          LOG("\tThe qname is a subdomain of the NSEC and the DNAME bit is set"<<endl);
+          VLOG(log, qname << ":\tThe qname is a subdomain of the NSEC and the DNAME bit is set"<<endl);
           return false;
         }
 
         if (wildcard != owner && isCoveredByNSEC(wildcard, owner, nsec->d_next)) {
-          LOG("\tWildcard is covered"<<endl);
+          VLOG(log, qname << ":\tWildcard is covered"<<endl);
           return true;
         }
       }
@@ -339,37 +371,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)
+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;
-  LOG("Trying to prove that there is no wildcard for "<<wildcard<<"/"<<QType(qtype)<<endl);
-
-  for (const auto& v : validrrsets) {
-    LOG("Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl);
-    if (v.first.second == QType::NSEC3) {
-      for (const auto& r : v.second.records) {
-        LOG("\t"<<r->getZoneRepresentation()<<endl);
-        auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(r);
+  VLOG(log, closestEncloser << ": Trying to prove that there is no wildcard for "<<wildcard<<"/"<<QType(qtype)<<endl);
+
+  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;
         }
-        LOG("\tWildcard hash: "<<toBase32Hex(h)<<endl);
-        string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]);
-        LOG("\tNSEC3 hash: "<<toBase32Hex(beginHash)<<" -> "<<toBase32Hex(nsec3->d_nexthash)<<endl);
+        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) {
-          LOG("\tWildcard hash matches");
-          if (wildcardExists) {
+        if (beginHash == hash) {
+          VLOG(log, closestEncloser << ":\tWildcard hash matches");
+          if (wildcardExists != nullptr) {
             *wildcardExists = true;
           }
 
@@ -379,22 +412,22 @@ 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 */
-            LOG(" BUT an ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
+            VLOG_NO_PREFIX(log, " BUT an ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
             return false;
           }
 
-          if (qtype == 0 || isTypeDenied(nsec3, QType(qtype))) {
-            LOG(" and proves that the type did not exist"<<endl);
+          if (qtype == 0 || isTypeDenied(*nsec3, QType(qtype))) {
+            VLOG_NO_PREFIX(log, " and proves that the type did not exist"<<endl);
             return true;
           }
-          LOG(" BUT the type did exist!"<<endl);
+          VLOG_NO_PREFIX(log, " BUT the type did exist!"<<endl);
           return false;
         }
 
-        if (isCoveredByNSEC3Hash(h, beginHash, nsec3->d_nexthash)) {
-          LOG("\tWildcard hash is covered"<<endl);
+        if (isCoveredByNSEC3Hash(hash, beginHash, nsec3->d_nexthash)) {
+          VLOG(log, closestEncloser << ":\tWildcard hash is covered"<<endl);
           return true;
         }
       }
@@ -404,7 +437,7 @@ static bool provesNSEC3NoWildCard(const DNSName& closestEncloser, uint16_t const
   return false;
 }
 
-dState matchesNSEC(const DNSName& name, uint16_t qtype, const DNSName& nsecOwner, const std::shared_ptr<NSECRecordContent>& nsec, const std::vector<std::shared_ptr<RRSIGRecordContent>>& signatures)
+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& log)
 {
   const DNSName signer = getSigner(signatures);
   if (!name.isPartOf(signer) || !nsecOwner.isPartOf(signer)) {
@@ -420,8 +453,8 @@ 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)) {
-      LOG("An ancestor delegation NSEC RR can only deny the existence of a DS"<<endl);
+    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;
     }
   }
@@ -429,20 +462,20 @@ dState matchesNSEC(const DNSName& name, uint16_t qtype, const DNSName& nsecOwner
   /* check if the type is denied */
   if (name == owner) {
     if (!isTypeDenied(nsec, QType(qtype))) {
-      LOG("Does _not_ deny existence of type "<<QType(qtype)<<endl);
+      VLOG_NO_PREFIX(log, "does _not_ deny existence of type "<<QType(qtype)<<endl);
       return dState::NODENIAL;
     }
 
     if (qtype == QType::DS && signer == name) {
-      LOG("The NSEC comes from the child zone and cannot be used to deny a DS");
+      VLOG_NO_PREFIX(log, "the NSEC comes from the child zone and cannot be used to deny a DS"<<endl);
       return dState::NODENIAL;
     }
 
-    LOG("Denies existence of type "<<QType(qtype)<<endl);
+    VLOG_NO_PREFIX(log, "Denies existence of type "<<QType(qtype)<<endl);
     return dState::NXQTYPE;
   }
 
-  if (name.isPartOf(owner) && nsec->isSet(QType::DNAME)) {
+  if (name.isPartOf(owner) && nsec.isSet(QType::DNAME)) {
     /* rfc6672 section 5.3.2: DNAME Bit in NSEC Type Map
 
        In any negative response, the NSEC or NSEC3 [RFC5155] record type
@@ -452,15 +485,15 @@ dState matchesNSEC(const DNSName& name, uint16_t qtype, const DNSName& nsecOwner
        asserted, then DNAME substitution should have been done, but the
        substitution has not been done as specified.
     */
-    LOG("The DNAME bit is set and the query name is a subdomain of that NSEC");
+    VLOG(log, "the DNAME bit is set and the query name is a subdomain of that NSEC");
     return dState::NODENIAL;
   }
 
-  if (isCoveredByNSEC(name, owner, nsec->d_next)) {
-    LOG(name<<" is covered by ("<<owner<<" to "<<nsec->d_next<<") ");
+  if (isCoveredByNSEC(name, owner, nsec.d_next)) {
+    VLOG_NO_PREFIX(log, name << ": is covered by ("<<owner<<" to "<<nsec.d_next<<")");
 
-    if (nsecProvesENT(name, owner, nsec->d_next)) {
-      LOG("Denies existence of type "<<name<<"/"<<QType(qtype)<<" by proving that "<<name<<" is an ENT"<<endl);
+    if (nsecProvesENT(name, owner, nsec.d_next)) {
+      VLOG_NO_PREFIX(log, " denies existence of type "<<name<<"/"<<QType(qtype)<<" by proving that "<<name<<" is an ENT"<<endl);
       return dState::NXQTYPE;
     }
 
@@ -470,6 +503,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 (iterations + 1 + (saltLength > 0 ? 1 : 0)) * maxLabels;
+}
+
 /*
   This function checks whether the existence of qname|qtype is denied by the NSEC and NSEC3
   in validrrsets.
@@ -481,34 +519,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, 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) {
-    LOG("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) {
-        LOG("\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<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;
         }
 
@@ -518,7 +556,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
          */
         const bool notApex = signer.countLabels() < owner.countLabels();
         if (notApex && nsec->isSet(QType::NS) && nsec->isSet(QType::SOA)) {
-          LOG("However, that NSEC is not at the apex and has both the NS and the SOA bits set!"<<endl);
+          VLOG(log, qname << ": However, that NSEC is not at the apex and has both the NS and the SOA bits set!"<<endl);
           continue;
         }
 
@@ -528,27 +566,27 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
            that (original) owner name other than DS RRs, and all RRs below that
            owner name regardless of type.
         */
-        if (qname.isPartOf(owner) && isNSECAncestorDelegation(signer, owner, nsec)) {
+        if (qname.isPartOf(owner) && isNSECAncestorDelegation(signer, owner, *nsec)) {
           /* this is an "ancestor delegation" NSEC RR */
-          if (!(qtype == QType::DS && qname == owner)) {
-            LOG("An ancestor delegation NSEC RR can only deny the existence of a DS"<<endl);
+          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;
           }
         }
 
         if (qtype == QType::DS && !qname.isRoot() && signer == qname) {
-          LOG("A NSEC RR from the child zone cannot deny the existence of a DS"<<endl);
+          VLOG(log, qname << ": A NSEC RR from the child zone cannot deny the existence of a DS"<<endl);
           continue;
         }
 
         /* check if the type is denied */
         if (qname == owner) {
-          if (!isTypeDenied(nsec, QType(qtype))) {
-            LOG("Does _not_ deny existence of type "<<QType(qtype)<<endl);
+          if (!isTypeDenied(*nsec, QType(qtype))) {
+            VLOG(log, qname << ": Does _not_ deny existence of type "<<QType(qtype)<<endl);
             return dState::NODENIAL;
           }
 
-          LOG("Denies existence of type "<<QType(qtype)<<endl);
+          VLOG(log, qname << ": Denies existence of type "<<QType(qtype)<<endl);
 
           /*
            * RFC 4035 Section 2.3:
@@ -558,7 +596,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
            */
           if (referralToUnsigned && qtype == QType::DS) {
             if (!nsec->isSet(QType::NS)) {
-              LOG("However, no NS record exists at this level!"<<endl);
+              VLOG(log, qname << ": However, no NS record exists at this level!"<<endl);
               return dState::NODENIAL;
             }
           }
@@ -566,7 +604,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;
           }
 
@@ -575,11 +613,11 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
           }
 
           DNSName closestEncloser = getClosestEncloserFromNSEC(qname, owner, nsec->d_next);
-          if (provesNoWildCard(qname, qtype, closestEncloser, validrrsets)) {
+          if (provesNoWildCard(qname, qtype, closestEncloser, validrrsets, log)) {
             return dState::NXQTYPE;
           }
 
-          LOG("But the existence of a wildcard is not denied for "<<qname<<"/"<<endl);
+          VLOG(log, qname << ": But the existence of a wildcard is not denied for "<<qname<<"/"<<endl);
           return dState::NODENIAL;
         }
 
@@ -593,91 +631,96 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
              asserted, then DNAME substitution should have been done, but the
              substitution has not been done as specified.
           */
-          LOG("The DNAME bit is set and the query name is a subdomain of that NSEC");
+          VLOG(log, qname << ": The DNAME bit is set and the query name is a subdomain of that NSEC"<< endl);
           return dState::NODENIAL;
         }
 
         /* check if the whole NAME is denied existing */
         if (isCoveredByNSEC(qname, owner, nsec->d_next)) {
-          LOG(qname<<" is covered by ("<<owner<<" to "<<nsec->d_next<<") ");
+          VLOG(log, qname<< ": Is covered by ("<<owner<<" to "<<nsec->d_next<<") ");
 
           if (nsecProvesENT(qname, owner, nsec->d_next)) {
             if (wantsNoDataProof) {
               /* if the name is an ENT and we received a NODATA answer,
                  we are fine with a NSEC proving that the name does not exist. */
-              LOG("Denies existence of type "<<qname<<"/"<<QType(qtype)<<" by proving that "<<qname<<" is an ENT"<<endl);
+              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! */
-              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) {
-            LOG("and we did not need a wildcard proof"<<endl);
+            VLOG_NO_PREFIX(log, "and we did not need a wildcard proof"<<endl);
             return dState::NXDOMAIN;
           }
 
-          LOG("but we do need a wildcard proof so ");
+          VLOG_NO_PREFIX(log, "but we do need a wildcard proof so ");
           DNSName closestEncloser = getClosestEncloserFromNSEC(qname, owner, nsec->d_next);
           if (wantsNoDataProof) {
-            LOG("looking for NODATA proof"<<endl);
-            if (provesNoDataWildCard(qname, qtype, closestEncloser, validrrsets)) {
+            VLOG_NO_PREFIX(log, "looking for NODATA proof"<<endl);
+            if (provesNoDataWildCard(qname, qtype, closestEncloser, validrrsets, log)) {
               return dState::NXQTYPE;
             }
           }
           else {
-            LOG("looking for NO wildcard proof"<<endl);
-            if (provesNoWildCard(qname, qtype, closestEncloser, validrrsets)) {
+            VLOG_NO_PREFIX(log, "looking for NO wildcard proof"<<endl);
+            if (provesNoWildCard(qname, qtype, closestEncloser, validrrsets, log)) {
               return dState::NXDOMAIN;
             }
           }
 
-          LOG("But the existence of a wildcard is not denied for "<<qname<<"/"<<endl);
+          VLOG(log, qname << ": But the existence of a wildcard is not denied for "<<qname<<"/"<<endl);
           return dState::NODENIAL;
         }
 
-        LOG("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) {
-        LOG("\t"<<r->getZoneRepresentation()<<endl);
-        auto nsec3 = std::dynamic_pointer_cast<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)) {
-          LOG("Owner "<<hashedOwner<<" is not part of the signer "<<signer<<", ignoring"<<endl);
+          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) {
-          LOG("A NSEC3 RR from the child zone cannot deny the existence of a DS"<<endl);
+          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()) {
-          LOG("Unsupported hash, ignoring"<<endl);
+        if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
+          VLOG(log, qname << ": Too many NSEC3s for this record"<<endl);
+          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;
 
-        LOG("\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
@@ -685,7 +728,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
            */
           const bool notApex = signer.countLabels() < qname.countLabels();
           if (notApex && nsec3->isSet(QType::NS) && nsec3->isSet(QType::SOA)) {
-            LOG("However, that NSEC3 is not at the apex and has both the NS and the SOA bits set!"<<endl);
+            VLOG(log, qname << ": However, that NSEC3 is not at the apex and has both the NS and the SOA bits set!"<<endl);
             continue;
           }
 
@@ -695,18 +738,18 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
              that (original) owner name other than DS RRs, and all RRs below that
              owner name regardless of type.
           */
-          if (qtype != QType::DS && isNSEC3AncestorDelegation(signer, qname, nsec3)) {
+          if (qtype != QType::DS && isNSEC3AncestorDelegation(signer, qname, *nsec3)) {
             /* this is an "ancestor delegation" NSEC3 RR */
-            LOG("An ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
+            VLOG(log, qname << ": An ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
             return dState::NODENIAL;
           }
 
-          if (!isTypeDenied(nsec3, QType(qtype))) {
-            LOG("Does _not_ deny existence of type "<<QType(qtype)<<" for name "<<qname<<" (not opt-out)."<<endl);
+          if (!isTypeDenied(*nsec3, QType(qtype))) {
+            VLOG(log, qname << ": Does _not_ deny existence of type "<<QType(qtype)<<" for name "<<qname<<" (not opt-out)."<<endl);
             return dState::NODENIAL;
           }
 
-          LOG("Denies existence of type "<<QType(qtype)<<" for name "<<qname<<" (not opt-out)."<<endl);
+          VLOG(log, qname << ": Denies existence of type "<<QType(qtype)<<" for name "<<qname<<" (not opt-out)."<<endl);
 
           /*
            * RFC 5155 section 8.9:
@@ -717,7 +760,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
            */
           if (referralToUnsigned && qtype == QType::DS) {
             if (!nsec3->isSet(QType::NS)) {
-              LOG("However, no NS record exists at this level!"<<endl);
+              VLOG(log, qname << ": However, no NS record exists at this level!"<<endl);
               return dState::NODENIAL;
             }
           }
@@ -735,47 +778,54 @@ 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
     */
-    LOG("Now looking for the closest encloser for "<<qname<<endl);
+    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) {
-            LOG("\t"<<r->getZoneRepresentation()<<endl);
-            auto nsec3 = std::dynamic_pointer_cast<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)) {
-              LOG("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);
+              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]);
 
-            LOG("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)) {
-                LOG("An ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
+              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;
               }
 
-              LOG("Closest encloser for "<<qname<<" is "<<closestEncloser<<endl);
+              VLOG(log, qname << ": Closest encloser for "<<qname<<" is "<<closestEncloser<<endl);
               found = true;
 
               if (nsec3->isSet(QType::DNAME)) {
@@ -788,7 +838,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
                    asserted, then DNAME substitution should have been done, but the
                    substitution has not been done as specified.
                 */
-                LOG("\tThe closest encloser NSEC3 has the DNAME bit is set"<<endl);
+                VLOG(log, qname << ":\tThe closest encloser NSEC3 has the DNAME bit is set"<<endl);
                 return dState::NODENIAL;
               }
 
@@ -796,7 +846,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
             }
           }
         }
-        if (found == true) {
+        if (found) {
           break;
         }
       }
@@ -819,7 +869,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 */
@@ -827,43 +877,52 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
     if (labelIdx >= 1) {
       DNSName nextCloser(closestEncloser);
       nextCloser.prependRawLabel(qname.getRawLabel(labelIdx - 1));
-      LOG("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) {
-            LOG("\t"<<r->getZoneRepresentation()<<endl);
-            auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(r);
-            if(!nsec3)
+      nsec3sConsidered = 0;
+      VLOG(log, qname << ":Looking for a NSEC3 covering the next closer name "<<nextCloser<<endl);
+
+      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);
+              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)) {
-              LOG("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]);
 
-            LOG("Comparing "<<toBase32Hex(h)<<" against "<<toBase32Hex(beginHash)<<" -> "<<toBase32Hex(nsec3->d_nexthash)<<endl);
-            if (isCoveredByNSEC3Hash(h, beginHash, nsec3->d_nexthash)) {
-              LOG("Denies existence of name "<<qname<<"/"<<QType(qtype));
+            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;
 
               if (nsec3->isOptOut()) {
-                LOG(" but is opt-out!");
+                VLOG_NO_PREFIX(log, " but is opt-out!");
                 isOptOut = true;
               }
 
-              LOG(endl);
+              VLOG_NO_PREFIX(log, endl);
               break;
             }
-            LOG("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) {
@@ -876,9 +935,9 @@ 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)) {
+    if (needWildcardProof && !provesNSEC3NoWildCard(closestEncloser, qtype, validrrsets, &wildcardExists, log, context)) {
       if (!isOptOut) {
-        LOG("But the existence of a wildcard is not denied for "<<qname<<"/"<<QType(qtype)<<endl);
+        VLOG(log, qname << ": But the existence of a wildcard is not denied for "<<qname<<"/"<<QType(qtype)<<endl);
         return dState::NODENIAL;
       }
     }
@@ -886,132 +945,128 @@ 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
   return dState::NODENIAL;
 }
 
-/*
- * Finds all the zone-cuts between begin (longest name) and end (shortest name),
- * returns them all zone cuts, including end, but (possibly) not begin
- */
-static const vector<DNSName> getZoneCuts(const DNSName& begin, const DNSName& end, DNSRecordOracle& dro)
+bool isRRSIGNotExpired(const time_t now, const RRSIGRecordContent& sig)
 {
-  vector<DNSName> ret;
-  if(!begin.isPartOf(end))
-    throw PDNSException(end.toLogString() + "is not part of " + begin.toLogString());
-
-  DNSName qname(end);
-  vector<string> labelsToAdd = begin.makeRelative(end).getRawLabels();
-
-  // The shortest name is assumed to a zone cut
-  ret.push_back(qname);
-  while(qname != begin) {
-    bool foundCut = false;
-    if (labelsToAdd.empty())
-      break;
-
-    qname.prependRawLabel(labelsToAdd.back());
-    labelsToAdd.pop_back();
-    auto records = dro.get(qname, (uint16_t)QType::NS);
-    for (const auto& record : records) {
-      if(record.d_type != QType::NS || record.d_name != qname)
-        continue;
-      foundCut = true;
-      break;
-    }
-    if (foundCut)
-      ret.push_back(qname);
-  }
-  return ret;
+  // Should use https://www.rfc-editor.org/rfc/rfc4034.txt section 3.1.5
+  return sig.d_sigexpire >= now;
 }
 
-bool isRRSIGNotExpired(const time_t now, const shared_ptr<RRSIGRecordContent>& sig)
+bool isRRSIGIncepted(const time_t now, const RRSIGRecordContent& sig)
 {
   // Should use https://www.rfc-editor.org/rfc/rfc4034.txt section 3.1.5
-  return sig->d_sigexpire >= now;
+  return sig.d_siginception - g_signatureInceptionSkew <= now;
 }
 
-bool isRRSIGIncepted(const time_t now, const shared_ptr<RRSIGRecordContent>& sig)
+namespace {
+[[nodiscard]] bool checkSignatureInceptionAndExpiry(const DNSName& qname, time_t now, const RRSIGRecordContent& sig, vState& ede, const OptLog& log)
 {
-  // Should use https://www.rfc-editor.org/rfc/rfc4034.txt section 3.1.5
-  return sig->d_siginception - g_signatureInceptionSkew <= now;
+  /* 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;
 }
 
-static bool checkSignatureWithKey(time_t now, const shared_ptr<RRSIGRecordContent> sig, const shared_ptr<DNSKEYRecordContent> key, const std::string& msg, vState& ede)
+[[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);
-      LOG("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;
-      LOG("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) {
-    LOG("Could not make a validator for signature: "<<e.what()<<endl);
+    VLOG(log, qname << ": Could not make a validator for signature: "<<e.what()<<endl);
     ede = vState::BogusUnsupportedDNSKEYAlgo;
   }
   return result;
 }
 
-vState validateWithKeySet(time_t now, const DNSName& name, const sortedRecords_t& toSign, const vector<shared_ptr<RRSIGRecordContent> >& signatures, const skeyset_t& keys, 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) {
-      LOG(name<<": Discarding invalid RRSIG whose label count is "<<signature->d_labels<<" while the RRset owner name has only "<<labelCount<<endl);
+      VLOG(log, name<<": Discarding invalid RRSIG whose label count is "<<signature->d_labels<<" while the RRset owner name has only "<<labelCount<<endl);
       continue;
     }
 
-    auto keysMatchingTag = getByTag(keys, signature->d_tag, signature->d_algorithm);
+    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
+      break;
+    }
+    signaturesConsidered++;
+    context.d_validationsCounter++;
+
+    auto keysMatchingTag = getByTag(keys, signature->d_tag, signature->d_algorithm, log);
 
     if (keysMatchingTag.empty()) {
-      LOG("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(now, signature, key, msg, ede);
-      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;);
+        return isValid ? vState::Secure : vState::BogusNoValidRRSIG;
+      }
+      dnskeysConsidered++;
+
+      bool signIsValid = checkSignatureWithKey(name, *signature, *key, msg, ede, log);
 
       if (signIsValid) {
         isValid = true;
-        LOG("Validated "<<name<<"/"<<DNSRecordContent::NumberToType(signature->d_type)<<endl);
+        VLOG(log, name<< ": Validated "<<name<<"/"<<DNSRecordContent::NumberToType(signature->d_type)<<endl);
         //       cerr<<"valid"<<endl;
         //       cerr<<"! validated "<<i->first.first<<"/"<<)<<endl;
       }
       else {
-        LOG("signature invalid"<<endl);
-        if (isRRSIGIncepted(now, signature)) {
+        VLOG(log, name << ": signature invalid"<<endl);
+        if (isRRSIGIncepted(now, *signature)) {
           noneIncepted = false;
         }
-        if (isRRSIGNotExpired(now, signature)) {
+        if (isRRSIGNotExpired(now, *signature)) {
           allExpired = false;
         }
       }
@@ -1025,7 +1080,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) {
@@ -1040,23 +1095,6 @@ vState validateWithKeySet(time_t now, const DNSName& name, const sortedRecords_t
   return vState::BogusNoValidRRSIG;
 }
 
-void validateWithKeySet(const cspmap_t& rrsets, cspmap_t& validated, const skeyset_t& keys)
-{
-  validated.clear();
-  /*  cerr<<"Validating an rrset with following keys: "<<endl;
-  for(auto& key : keys) {
-    cerr<<"\tTag: "<<key->getTag()<<" -> "<<key->getZoneRepresentation()<<endl;
-  }
-  */
-  time_t now = time(nullptr);
-  for(auto i=rrsets.cbegin(); i!=rrsets.cend(); i++) {
-    LOG("validating "<<(i->first.first)<<"/"<<DNSRecordContent::NumberToType(i->first.second)<<" with "<<i->second.signatures.size()<<" sigs"<<endl);
-    if (validateWithKeySet(now, i->first.first, i->second.records, i->second.signatures, keys, true) == vState::Secure) {
-      validated[i->first] = i->second;
-    }
-  }
-}
-
 // returns vState
 // should return vState, zone cut and validated keyset
 // i.e. www.7bits.nl -> insecure/7bits.nl/[]
@@ -1068,7 +1106,9 @@ 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::OPT) {
+      continue;
+    }
 
     if(rec.d_type == QType::RRSIG) {
       auto rrc = getRR<RRSIGRecordContent>(rec);
@@ -1077,7 +1117,7 @@ cspmap_t harvestCSPFromRecs(const vector<DNSRecord>& recs)
       }
     }
     else {
-      cspmap[{rec.d_name, rec.d_type}].records.insert(rec.d_content);
+      cspmap[{rec.d_name, rec.d_type}].records.insert(rec.getContent());
     }
   }
   return cspmap;
@@ -1085,61 +1125,77 @@ cspmap_t harvestCSPFromRecs(const vector<DNSRecord>& recs)
 
 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<RRSIGRecordContent> >& sigs, skeyset_t& validkeys)
+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);
+  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, ...)
+        break;
+      }
+      dnskeysConsidered++;
+
       try {
         dsrc2 = makeDSFromDNSKey(zone, *drc, dsrc.d_digesttype);
         dsCreated = true;
         isValid = dsrc == dsrc2;
       }
       catch (const std::exception &e) {
-        LOG("Unable to make DS from DNSKey: "<<e.what()<<endl);
+        VLOG(log, zone << ": Unable to make DS from DNSKey: "<<e.what()<<endl);
       }
 
       if (isValid) {
-        LOG("got valid DNSKEY (it matches the DS) with tag "<<dsrc.d_tag<<" and algorithm "<<std::to_string(dsrc.d_algorithm)<<" for "<<zone<<endl);
+        VLOG(log, zone << ": got valid DNSKEY (it matches the DS) with tag "<<dsrc.d_tag<<" and algorithm "<<std::to_string(dsrc.d_algorithm)<<" for "<<zone<<endl);
 
         validkeys.insert(drc);
       }
       else {
         if (dsCreated) {
-          LOG("DNSKEY did not match the DS, parent DS: "<<dsrc.getZoneRepresentation() << " ! = "<<dsrc2.getZoneRepresentation()<<endl);
+          VLOG(log, zone << ": DNSKEY did not match the DS, parent DS: "<<dsrc.getZoneRepresentation() << " ! = "<<dsrc2.getZoneRepresentation()<<endl);
         }
       }
     }
@@ -1150,35 +1206,60 @@ 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);
+      auto bytag = getByTag(validkeys, sig->d_tag, sig->d_algorithm, log);
 
       if (bytag.empty()) {
         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
+        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;);
+          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
+          return vState::BogusNoValidDNSKEY;
+        }
         //          cerr<<"validating : ";
-        bool signIsValid = checkSignatureWithKey(now, sig, key, msg, ede);
+        bool signIsValid = checkSignatureWithKey(zone, *sig, *key, msg, ede, log);
+        signaturesConsidered++;
+        context.d_validationsCounter++;
 
-        if (signIsValid)
-        {
-          LOG("validation succeeded - whole DNSKEY set is valid"<<endl);
+        if (signIsValid) {
+          VLOG(log, zone << ": Validation succeeded - whole DNSKEY set is valid"<<endl);
           validkeys = tkeys;
           break;
         }
-        else {
-          LOG("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;
     }
@@ -1243,176 +1324,22 @@ vState validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t&
   return vState::Secure;
 }
 
-vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, skeyset_t& keyset)
+bool isSupportedDS(const DSRecordContent& dsrec, const OptLog& log)
 {
-  auto luaLocal = g_luaconfs.getLocal();
-  const auto anchors = luaLocal->dsAnchors;
-  if (anchors.empty()) // Nothing to do here
-    return vState::Insecure;
-
-  // Determine the lowest (i.e. with the most labels) Trust Anchor for zone
-  DNSName lowestTA(".");
-  for (auto const &anchor : anchors)
-    if (zone.isPartOf(anchor.first) && lowestTA.countLabels() < anchor.first.countLabels())
-      lowestTA = anchor.first;
-
-  // Before searching for the keys, see if we have a Negative Trust Anchor. If
-  // so, test if the NTA is valid and return an NTA state
-  const auto negAnchors = luaLocal->negAnchors;
-
-  if (!negAnchors.empty()) {
-    DNSName lowestNTA;
-
-    for (auto const &negAnchor : negAnchors)
-      if (zone.isPartOf(negAnchor.first) && lowestNTA.countLabels() <= negAnchor.first.countLabels())
-        lowestNTA = negAnchor.first;
-
-    if(!lowestNTA.empty()) {
-      LOG("Found a Negative Trust Anchor for "<<lowestNTA<<", which was added with reason '"<<negAnchors.at(lowestNTA)<<"', ");
-
-      /* RFC 7646 section 2.1 tells us that we SHOULD still validate if there
-       * is a Trust Anchor below the Negative Trust Anchor for the name we
-       * attempt validation for. However, section 3 tells us this positive
-       * Trust Anchor MUST be *below* the name and not the name itself
-       */
-      if(lowestTA.countLabels() <= lowestNTA.countLabels()) {
-        LOG("marking answer Insecure"<<endl);
-        return vState::NTA; // Not Insecure, this way validateRecords() can shortcut
-      }
-      LOG("but a Trust Anchor for "<<lowestTA<<" is configured, continuing validation."<<endl);
-    }
-  }
-
-  skeyset_t validkeys;
-  dsmap_t dsmap;
-
-  dsmap_t* tmp = (dsmap_t*) rplookup(anchors, lowestTA);
-  if (tmp)
-    dsmap = *tmp;
-
-  auto zoneCuts = getZoneCuts(zone, lowestTA, dro);
-
-  LOG("Found the following zonecuts:")
-  for(const auto& zonecut : zoneCuts)
-    LOG(" => "<<zonecut);
-  LOG(endl);
-
-  for(auto zoneCutIter = zoneCuts.cbegin(); zoneCutIter != zoneCuts.cend(); ++zoneCutIter)
-  {
-    vector<shared_ptr<RRSIGRecordContent> > sigs;
-    sortedRecords_t toSign;
-
-    skeyset_t tkeys; // tentative keys
-    validkeys.clear();
-
-    //    cerr<<"got DS for ["<<qname<<"], grabbing DNSKEYs"<<endl;
-    auto records=dro.get(*zoneCutIter, (uint16_t)QType::DNSKEY);
-    // this should use harvest perhaps
-    for(const auto& rec : records) {
-      if(rec.d_name != *zoneCutIter)
-        continue;
-
-      if(rec.d_type == QType::RRSIG)
-      {
-        auto rrc=getRR<RRSIGRecordContent> (rec);
-        if(rrc) {
-          LOG("Got signature: "<<rrc->getZoneRepresentation()<<" with tag "<<rrc->d_tag<<", for type "<<DNSRecordContent::NumberToType(rrc->d_type)<<endl);
-          if(rrc->d_type != QType::DNSKEY)
-            continue;
-          sigs.push_back(rrc);
-        }
-      }
-      else if(rec.d_type == QType::DNSKEY)
-      {
-        auto drc=getRR<DNSKEYRecordContent> (rec);
-        if(drc) {
-          tkeys.insert(drc);
-          LOG("Inserting key with tag "<<drc->getTag()<<" and algorithm "<<DNSSECKeeper::algorithm2name(drc->d_algorithm)<<": "<<drc->getZoneRepresentation()<<endl);
-
-          toSign.insert(rec.d_content);
-        }
-      }
-    }
-    LOG("got "<<tkeys.size()<<" keys and "<<sigs.size()<<" sigs from server"<<endl);
-
-    /*
-     * 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
-     */
-    auto state = validateDNSKeysAgainstDS(time(nullptr), *zoneCutIter, dsmap, tkeys, toSign, sigs, validkeys);
-
-    if (validkeys.empty())
-    {
-      LOG("ended up with zero valid DNSKEYs, going Bogus"<<endl);
-      return state;
-    }
-    LOG("situation: we have one or more valid DNSKEYs for ["<<*zoneCutIter<<"] (want ["<<zone<<"])"<<endl);
-
-    if (zoneCutIter == zoneCuts.cend()-1) {
-      LOG("requested keyset found! returning Secure for the keyset"<<endl);
-      keyset.insert(validkeys.cbegin(), validkeys.cend());
-      return state;
-    }
-
-    // We now have the DNSKEYs, use them to validate the DS records at the next zonecut
-    LOG("next name ["<<*(zoneCutIter+1)<<"], trying to get DS"<<endl);
-
-    dsmap_t tdsmap; // tentative DSes
-    dsmap.clear();
-    toSign.clear();
-
-    auto recs=dro.get(*(zoneCutIter+1), QType::DS);
-
-    cspmap_t cspmap=harvestCSPFromRecs(recs);
-
-    cspmap_t validrrsets;
-    validateWithKeySet(cspmap, validrrsets, validkeys);
-
-    LOG("got "<<cspmap.count(pair(*(zoneCutIter+1),QType::DS))<<" records for DS query of which "<<validrrsets.count(pair(*(zoneCutIter+1),QType::DS))<<" valid "<<endl);
-
-    auto r = validrrsets.equal_range(pair(*(zoneCutIter+1), QType::DS));
-    if(r.first == r.second) {
-      LOG("No DS for "<<*(zoneCutIter+1)<<", now look for a secure denial"<<endl);
-      dState res = getDenial(validrrsets, *(zoneCutIter+1), QType::DS, true, true);
-      if (res == dState::INSECURE || res == dState::NXDOMAIN)
-        return vState::BogusInvalidDenial;
-      if (res == dState::NXQTYPE || res == dState::OPTOUT)
-        return vState::Insecure;
-    }
-
-    /*
-     * Collect all DS records and add them to the dsmap for the next iteration
-     */
-    for(auto cspiter =r.first;  cspiter!=r.second; cspiter++) {
-      for(auto j=cspiter->second.records.cbegin(); j!=cspiter->second.records.cend(); j++)
-      {
-        const auto dsrc=std::dynamic_pointer_cast<DSRecordContent>(*j);
-        if(dsrc) {
-          dsmap.insert(*dsrc);
-        }
-      }
-    }
-  }
-  // There were no zone cuts (aka, we should never get here)
-  return vState::BogusUnableToGetDNSKEYs;
-}
-
-bool isSupportedDS(const DSRecordContent& ds)
-{
-  if (!DNSCryptoKeyEngine::isAlgorithmSupported(ds.d_algorithm)) {
-    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)) {
-    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;
   }
 
   return true;
 }
 
-DNSName getSigner(const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures)
+DNSName getSigner(const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures)
 {
   for (const auto& sig : signatures) {
     if (sig) {
@@ -1420,7 +1347,7 @@ DNSName getSigner(const std::vector<std::shared_ptr<RRSIGRecordContent> >& signa
     }
   }
 
-  return DNSName();
+  return {};
 }
 
 const std::string& vStateToString(vState state)
@@ -1429,17 +1356,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)
@@ -1450,10 +1377,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 ec11f0b27b36b4a66f76bebab2dd27ea1c103708..7d844bf11c6d07ce66fe5137139a85512aa998cb 100644 (file)
 #include "namespaces.hh"
 #include "dnsrecords.hh"
 #include "dnssecinfra.hh"
-extern bool g_dnssecLOG;
+#include "logger.hh"
+
 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,70 +47,95 @@ 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;
 };
 
 
 struct ContentSigPair
 {
   sortedRecords_t records;
-  vector<shared_ptr<RRSIGRecordContent>> signatures;
+  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<DNSKEYRecordContent>& a, const shared_ptr<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<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};
+};
+
+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<RRSIGRecordContent> >& signatures, const skeyset_t& keys, 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);
-void validateWithKeySet(const cspmap_t& rrsets, cspmap_t& validated, const skeyset_t& keys);
+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);
 cspmap_t harvestCSPFromRecs(const vector<DNSRecord>& recs);
-vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, skeyset_t& keyset);
 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<RRSIGRecordContent> >& sigs, skeyset_t& validkeys);
-dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype, bool referralToUnsigned, bool wantsNoDataProof, bool needsWildcardProof=true, unsigned int wildcardLabelsCount=0);
-bool isSupportedDS(const DSRecordContent& ds);
-DNSName getSigner(const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures);
-bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>& dsrecords);
-bool isRRSIGNotExpired(const time_t now, const std::shared_ptr<RRSIGRecordContent>& sig);
-bool isRRSIGIncepted(const time_t now, const shared_ptr<RRSIGRecordContent>& sig);
-bool isWildcardExpanded(unsigned int labelCount, const std::shared_ptr<RRSIGRecordContent>& sign);
-bool isWildcardExpandedOntoItself(const DNSName& owner, unsigned int labelCount, const std::shared_ptr<RRSIGRecordContent>& sign);
-void updateDNSSECValidationState(vState& state, const vState stateUpdate);
-
-dState matchesNSEC(const DNSName& name, uint16_t qtype, const DNSName& nsecOwner, const std::shared_ptr<NSECRecordContent>& nsec, const std::vector<std::shared_ptr<RRSIGRecordContent>>& signatures);
-
-bool isNSEC3AncestorDelegation(const DNSName& signer, const DNSName& owner, const std::shared_ptr<NSEC3RecordContent>& nsec3);
-DNSName getNSECOwnerName(const DNSName& initialOwner, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures);
+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, 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, 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)
 {
-  if (nsec->isSet(type.getCode())) {
+  if (nsec.isSet(type.getCode())) {
     return false;
   }
 
   /* RFC 6840 section 4.3 */
-  if (nsec->isSet(QType::CNAME)) {
+  if (nsec.isSet(QType::CNAME)) {
     return false;
   }
 
index d8f5d404bdc44ca5683fb552db569bc804e2189d..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()
@@ -144,13 +147,18 @@ void showBuildConfiguration()
 #ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
     "scrypt " <<
 #endif
+#ifdef ENABLE_GSS_TSIG
+    "gss-tsig " <<
+#endif
 #ifdef VERBOSELOG
     "verboselog" <<
 #endif
     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
index 90ae04043d4b5641ca76b76f039237fa4bb22f75..ec4b09f5f94a37eb8d61c1c159f79cfe05de1aa1 100644 (file)
 #include "json.hh"
 #include "uuid-utils.hh"
 #include <yahttp/router.hpp>
+#include <algorithm>
+#include <unordered_set>
 
 json11::Json HttpRequest::json()
 {
   string err;
   if(this->body.empty()) {
-    g_log<<Logger::Debug<<logprefix<<"JSON document expected in request body, but body was empty" << endl;
+    SLOG(g_log<<Logger::Debug<<logprefix<<"JSON document expected in request body, but body was empty" << endl,
+         d_slog->info(Logr::Debug, "JSON document expected in request body, but body was empty"));
     throw HttpBadRequestException();
   }
   json11::Json doc = json11::Json::parse(this->body, err);
   if (doc.is_null()) {
-    g_log<<Logger::Debug<<logprefix<<"parsing of JSON document failed:" << err << endl;
+    SLOG(g_log<<Logger::Debug<<logprefix<<"parsing of JSON document failed:" << err << endl,
+         d_slog->error(Logr::Debug, err, "parsing of JSON document failed"));
     throw HttpBadRequestException();
   }
   return doc;
@@ -138,33 +142,18 @@ 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) {
-    g_log<<Logger::Error<<req->logprefix<<"HTTP API Request \"" << req->url.path << "\": Authentication failed, API Key missing in config" << endl;
+    SLOG(g_log<<Logger::Error<<req->logprefix<<"HTTP API Request \"" << req->url.path << "\": Authentication failed, API Key missing in config" << endl,
+         d_slog->info(Logr::Error, "Authentication failed, API Key missing in config", "urlpath", Logging::Loggable(req->url.path)));
     throw HttpUnauthorizedException("X-API-Key");
   }
 
@@ -179,7 +168,8 @@ void WebServer::apiWrapper(const WebServer::HandlerFunction& handler, HttpReques
   }
 
   if (!auth_ok) {
-    g_log<<Logger::Error<<req->logprefix<<"HTTP Request \"" << req->url.path << "\": Authentication by API Key failed" << endl;
+    SLOG(g_log<<Logger::Error<<req->logprefix<<"HTTP Request \"" << req->url.path << "\": Authentication by API Key failed" << endl,
+         d_slog->info(Logr::Error, "Authentication by API Key failed", "urlpath", Logging::Loggable(req->url.path)));
     throw HttpUnauthorizedException("X-API-Key");
   }
 
@@ -209,16 +199,17 @@ 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) {
   if (d_webserverPassword) {
     bool auth_ok = req->compareAuthorization(*d_webserverPassword);
     if (!auth_ok) {
-      g_log<<Logger::Debug<<req->logprefix<<"HTTP Request \"" << req->url.path << "\": Web Authentication failed" << endl;
+      SLOG(g_log<<Logger::Debug<<req->logprefix<<"HTTP Request \"" << req->url.path << "\": Web Authentication failed" << endl,
+           d_slog->info(Logr::Debug, "HTTP Request: Web Authentication failed",  "urlpath",  Logging::Loggable(req->url.path)));
       throw HttpUnauthorizedException("Basic");
     }
   }
@@ -226,24 +217,29 @@ 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) {
-  setThreadName("pdns-r/webhndlr");
+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 {
     webServer->serveConnection(client);
   }
   catch(PDNSException &e) {
-    g_log<<Logger::Error<<"PDNSException while serving a connection in main webserver thread: "<<e.reason<<endl;
+    SLOG(g_log<<Logger::Error<<"PDNSException while serving a connection in main webserver thread: "<<e.reason<<endl,
+         webServer->d_slog->error(Logr::Error, e.reason, msg, "exception", Logging::Loggable("PDNSException")));
   }
   catch(std::exception &e) {
-    g_log<<Logger::Error<<"STL Exception while serving a connection in main webserver thread: "<<e.what()<<endl;
+    SLOG(g_log<<Logger::Error<<"STL Exception while serving a connection in main webserver thread: "<<e.what()<<endl,
+         webServer->d_slog->error(Logr::Error, e.what(), msg, "exception", Logging::Loggable("std::exception")));
   }
   catch(...) {
-    g_log<<Logger::Error<<"Unknown exception while serving a connection in main webserver thread"<<endl;
+    SLOG(g_log<<Logger::Error<<"Unknown exception while serving a connection in main webserver thread"<<endl,
+         webServer->d_slog->info(Logr::Error, msg));
   }
   return nullptr;
 }
@@ -253,13 +249,18 @@ void WebServer::handleRequest(HttpRequest& req, HttpResponse& resp) const
   // set default headers
   resp.headers["Content-Type"] = "text/html; charset=utf-8";
 
+#ifdef RECURSOR
+    auto log = req.d_slog->withValues("urlpath", Logging::Loggable(req.url.path));
+#endif
+
   try {
     if (!req.complete) {
-      g_log<<Logger::Debug<<req.logprefix<<"Incomplete request" << endl;
+      SLOG(g_log<<Logger::Debug<<req.logprefix<<"Incomplete request" << endl,
+           d_slog->info(Logr::Debug, "Incomplete request"));
       throw HttpBadRequestException();
     }
-
-    g_log<<Logger::Debug<<req.logprefix<<"Handling request \"" << req.url.path << "\"" << endl;
+    SLOG(g_log<<Logger::Debug<<req.logprefix<<"Handling request \"" << req.url.path << "\"" << endl,
+         log->info(Logr::Debug, "Handling request"));
 
     YaHTTP::strstr_map_t::iterator header;
 
@@ -277,35 +278,53 @@ void WebServer::handleRequest(HttpRequest& req, HttpResponse& resp) const
     }
 
     YaHTTP::THandlerFunction handler;
-    if (!YaHTTP::Router::Route(&req, handler)) {
-      g_log<<Logger::Debug<<req.logprefix<<"No route found for \"" << req.url.path << "\"" << endl;
+    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 {
       handler(&req, &resp);
-      g_log<<Logger::Debug<<req.logprefix<<"Result for \"" << req.url.path << "\": " << resp.status << ", body length: " << resp.body.size() << endl;
+      SLOG(g_log<<Logger::Debug<<req.logprefix<<"Result for \"" << req.url.path << "\": " << resp.status << ", body length: " << resp.body.size() << endl,
+           log->info(Logr::Debug, "Result", "status", Logging::Loggable(resp.status), "bodyLength", Logging::Loggable(resp.body.size())));
     }
     catch(HttpException&) {
       throw;
     }
     catch(PDNSException &e) {
-      g_log<<Logger::Error<<req.logprefix<<"HTTP ISE for \""<< req.url.path << "\": Exception: " << e.reason << endl;
+      SLOG(g_log<<Logger::Error<<req.logprefix<<"HTTP ISE for \""<< req.url.path << "\": Exception: " << e.reason << endl,
+           log->error(Logr::Error, e.reason, msg, "exception", Logging::Loggable("PDNSException")));
       throw HttpInternalServerErrorException();
     }
     catch(std::exception &e) {
-      g_log<<Logger::Error<<req.logprefix<<"HTTP ISE for \""<< req.url.path << "\": STL Exception: " << e.what() << endl;
+      SLOG(g_log<<Logger::Error<<req.logprefix<<"HTTP ISE for \""<< req.url.path << "\": STL Exception: " << e.what() << endl,
+           log->error(Logr::Error, e.what(), msg, "exception", Logging::Loggable("std::exception")));
       throw HttpInternalServerErrorException();
     }
     catch(...) {
-      g_log<<Logger::Error<<req.logprefix<<"HTTP ISE for \""<< req.url.path << "\": Unknown Exception" << endl;
+      SLOG(g_log<<Logger::Error<<req.logprefix<<"HTTP ISE for \""<< req.url.path << "\": Unknown Exception" << endl,
+           log->info(Logr::Error, msg));
       throw HttpInternalServerErrorException();
     }
   }
   catch(HttpException &e) {
     resp = e.response();
+#ifdef RECURSOR
+    // An HttpException does not initialize d_slog
+    if (!resp.d_slog) {
+      resp.setSLog(log);
+    }
+#endif
     // TODO rm this logline?
-    g_log<<Logger::Debug<<req.logprefix<<"Error result for \"" << req.url.path << "\": " << resp.status << endl;
+    SLOG(g_log<<Logger::Debug<<req.logprefix<<"Error result for \"" << req.url.path << "\": " << resp.status << endl,
+         d_slog->error(Logr::Debug, resp.status, "Error result", "urlpath", Logging::Loggable(req.url.path)));
     string what = YaHTTP::Utility::status2text(resp.status);
     if (req.accept_json) {
       resp.headers["Content-Type"] = "application/json";
@@ -317,12 +336,11 @@ 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);
     }
   }
 
   // always set these headers
-  resp.headers["Server"] = "PowerDNS/" VERSION;
   resp.headers["Connection"] = "close";
 
   if (req.method == "HEAD") {
@@ -332,72 +350,168 @@ void WebServer::handleRequest(HttpRequest& req, HttpResponse& resp) const
   }
 }
 
-void WebServer::logRequest(const HttpRequest& req, const ComboAddress& remote) const {
+#ifdef RECURSOR
+// Helper to log key-value maps used by YaHTTP
+template<>
+std::string Logging::IterLoggable<YaHTTP::strstr_map_t::const_iterator>::to_string() const
+{
+  std::ostringstream oss;
+  bool first = true;
+  for (auto i = _t1; i != _t2; i++) {
+    if (!first) {
+      oss << '\n';
+    }
+    else {
+      first = false;
+    }
+    oss << i->first << ": " << i->second;
+  }
+  return oss.str();
+}
+#endif
+
+void WebServer::logRequest(const HttpRequest& req, [[maybe_unused]] const ComboAddress& remote) const {
   if (d_loglevel >= WebServer::LogLevel::Detailed) {
-    auto logprefix = req.logprefix;
-    g_log<<Logger::Notice<<logprefix<<"Request details:"<<endl;
-
-    bool first = true;
-    for (const auto& r : req.getvars) {
-      if (first) {
-        first = false;
-        g_log<<Logger::Notice<<logprefix<<" GET params:"<<endl;
+#ifdef RECURSOR
+    if (!g_slogStructured) {
+#endif
+      const auto& logprefix = req.logprefix;
+      g_log<<Logger::Notice<<logprefix<<"Request details:"<<endl;
+
+      bool first = true;
+      for (const auto& r : req.getvars) {
+        if (first) {
+          first = false;
+          g_log<<Logger::Notice<<logprefix<<" GET params:"<<endl;
+        }
+        g_log<<Logger::Notice<<logprefix<<"  "<<r.first<<": "<<r.second<<endl;
       }
-      g_log<<Logger::Notice<<logprefix<<"  "<<r.first<<": "<<r.second<<endl;
-    }
 
-    first = true;
-    for (const auto& r : req.postvars) {
-      if (first) {
-        first = false;
-        g_log<<Logger::Notice<<logprefix<<" POST params:"<<endl;
+      first = true;
+      for (const auto& r : req.postvars) {
+        if (first) {
+          first = false;
+          g_log<<Logger::Notice<<logprefix<<" POST params:"<<endl;
+        }
+        g_log<<Logger::Notice<<logprefix<<"  "<<r.first<<": "<<r.second<<endl;
       }
-      g_log<<Logger::Notice<<logprefix<<"  "<<r.first<<": "<<r.second<<endl;
-    }
 
-    first = true;
-    for (const auto& h : req.headers) {
-      if (first) {
-        first = false;
-        g_log<<Logger::Notice<<logprefix<<" Headers:"<<endl;
+      first = true;
+      for (const auto& h : req.headers) {
+        if (first) {
+          first = false;
+          g_log<<Logger::Notice<<logprefix<<" Headers:"<<endl;
+        }
+        g_log<<Logger::Notice<<logprefix<<"  "<<h.first<<": "<<h.second<<endl;
       }
-      g_log<<Logger::Notice<<logprefix<<"  "<<h.first<<": "<<h.second<<endl;
-    }
 
-    if (req.body.empty()) {
-      g_log<<Logger::Notice<<logprefix<<" No body"<<endl;
-    } else {
-      g_log<<Logger::Notice<<logprefix<<" Full body: "<<endl;
-      g_log<<Logger::Notice<<logprefix<<"  "<<req.body<<endl;
+      if (req.body.empty()) {
+        g_log<<Logger::Notice<<logprefix<<" No body"<<endl;
+      } else {
+        g_log<<Logger::Notice<<logprefix<<" Full body: "<<endl;
+        g_log<<Logger::Notice<<logprefix<<"  "<<req.body<<endl;
+      }
+#ifdef RECURSOR
+    }
+    else {
+      req.d_slog->info(Logr::Info, "Request details", "getParams", Logging::IterLoggable(req.getvars.cbegin(), req.getvars.cend()),
+                       "postParams", Logging::IterLoggable(req.postvars.cbegin(), req.postvars.cend()),
+                       "body", Logging::Loggable(req.body),
+                       "address", Logging::Loggable(remote));
     }
+#endif
   }
 }
 
-void WebServer::logResponse(const HttpResponse& resp, const ComboAddress& remote, const string& logprefix) const {
+void WebServer::logResponse(const HttpResponse& resp, const ComboAddress& /* remote */, const string& logprefix) const {
   if (d_loglevel >= WebServer::LogLevel::Detailed) {
-    g_log<<Logger::Notice<<logprefix<<"Response details:"<<endl;
-    bool first = true;
-    for (const auto& h : resp.headers) {
-      if (first) {
-        first = false;
-        g_log<<Logger::Notice<<logprefix<<" Headers:"<<endl;
+#ifdef RECURSOR
+    if (!g_slogStructured) {
+#endif
+      g_log<<Logger::Notice<<logprefix<<"Response details:"<<endl;
+      bool first = true;
+      for (const auto& h : resp.headers) {
+        if (first) {
+          first = false;
+          g_log<<Logger::Notice<<logprefix<<" Headers:"<<endl;
+        }
+        g_log<<Logger::Notice<<logprefix<<"  "<<h.first<<": "<<h.second<<endl;
+      }
+      if (resp.body.empty()) {
+        g_log<<Logger::Notice<<logprefix<<" No body"<<endl;
+      } else {
+        g_log<<Logger::Notice<<logprefix<<" Full body: "<<endl;
+        g_log<<Logger::Notice<<logprefix<<"  "<<resp.body<<endl;
       }
-      g_log<<Logger::Notice<<logprefix<<"  "<<h.first<<": "<<h.second<<endl;
+#ifdef RECURSOR
     }
-    if (resp.body.empty()) {
-      g_log<<Logger::Notice<<logprefix<<" No body"<<endl;
-    } else {
-      g_log<<Logger::Notice<<logprefix<<" Full body: "<<endl;
-      g_log<<Logger::Notice<<logprefix<<"  "<<resp.body<<endl;
+    else {
+      resp.d_slog->info(Logr::Info, "Response details", "headers", Logging::IterLoggable(resp.headers.cbegin(), resp.headers.cend()),
+                        "body", Logging::Loggable(resp.body));
+    }
+#endif
+  }
+}
+
+
+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 string logprefix = d_logprefix + to_string(getUniqueID()) + " ";
+  const auto unique = getUniqueID();
+  const string logprefix = d_logprefix + to_string(unique) + " ";
 
   HttpRequest req(logprefix);
+
   HttpResponse resp;
+#ifdef RECURSOR
+  auto log = d_slog->withValues("uniqueid",  Logging::Loggable(to_string(unique)));
+  req.setSLog(log);
+  resp.setSLog(log);
+#endif
   resp.max_response_size=d_maxbodysize;
   ComboAddress remote;
   string reply;
@@ -425,10 +539,15 @@ void WebServer::serveConnection(const std::shared_ptr<Socket>& client) const {
       yarl.finalize();
     } catch (YaHTTP::ParseError &e) {
       // request stays incomplete
-      g_log<<Logger::Warning<<logprefix<<"Unable to parse request: "<<e.what()<<endl;
+      SLOG(g_log<<Logger::Warning<<logprefix<<"Unable to parse request: "<<e.what()<<endl,
+           d_slog->error(Logr::Warning, e.what(), "Unable to parse request"));
     }
 
-    if (d_loglevel >= WebServer::LogLevel::None) {
+    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);
     }
 
@@ -444,18 +563,24 @@ void WebServer::serveConnection(const std::shared_ptr<Socket>& client) const {
     client->writenWithTimeout(reply.c_str(), reply.size(), timeout);
   }
   catch(PDNSException &e) {
-    g_log<<Logger::Error<<logprefix<<"HTTP Exception: "<<e.reason<<endl;
+    SLOG(g_log<<Logger::Error<<logprefix<<"HTTP Exception: "<<e.reason<<endl,
+         d_slog->error(Logr::Error, e.reason, "HTTP Exception", "exception", Logging::Loggable("PDNSException")));
   }
   catch(std::exception &e) {
     if(strstr(e.what(), "timeout")==nullptr)
-      g_log<<Logger::Error<<logprefix<<"HTTP STL Exception: "<<e.what()<<endl;
+      SLOG(g_log<<Logger::Error<<logprefix<<"HTTP STL Exception: "<<e.what()<<endl,
+           d_slog->error(Logr::Error, e.what(), "HTTP Exception", "exception", Logging::Loggable("std::exception")));
   }
   catch(...) {
-    g_log<<Logger::Error<<logprefix<<"Unknown exception"<<endl;
+    SLOG(g_log<<Logger::Error<<logprefix<<"Unknown exception"<<endl,
+         d_slog->info(Logr::Error, "HTTP Exception"));
   }
 
   if (d_loglevel >= WebServer::LogLevel::Normal) {
-    g_log<<Logger::Notice<<logprefix<<remote<<" \""<<req.method<<" "<<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())));
   }
 }
 
@@ -465,16 +590,48 @@ 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()
 {
   try {
     d_server = createServer();
-    g_log<<Logger::Warning<<d_logprefix<<"Listening for HTTP requests on "<<d_server->d_local.toStringWithPort()<<endl;
+    SLOG(g_log<<Logger::Warning<<d_logprefix<<"Listening for HTTP requests on "<<d_server->d_local.toStringWithPort()<<endl,
+         d_slog->info(Logr::Info, "Listening for HTTP requests", "address", Logging::Loggable(d_server->d_local)));
   }
   catch(NetworkError &e) {
-    g_log<<Logger::Error<<d_logprefix<<"Listening on HTTP socket failed: "<<e.what()<<endl;
+    SLOG(g_log<<Logger::Error<<d_logprefix<<"Listening on HTTP socket failed: "<<e.what()<<endl,
+         d_slog->error(Logr::Error, e.what(), "Listening on HTTP socket failed", "exception", Logging::Loggable("NetworkError")));
     d_server = nullptr;
   }
 }
@@ -483,8 +640,10 @@ void WebServer::go()
 {
   if(!d_server)
     return;
+  const string msg = "Exception in main webserver thread";
   try {
     while(true) {
+      const string acceptmsg = "Exception while accepting a connection in main webserver thread";
       try {
         auto client = d_server->accept();
         if (!client) {
@@ -500,24 +659,30 @@ void WebServer::go()
         }
       }
       catch(PDNSException &e) {
-        g_log<<Logger::Error<<d_logprefix<<"PDNSException while accepting a connection in main webserver thread: "<<e.reason<<endl;
+        SLOG(g_log<<Logger::Error<<d_logprefix<<"PDNSException while accepting a connection in main webserver thread: "<<e.reason<<endl,
+             d_slog->error(Logr::Error, e.reason, acceptmsg, Logging::Loggable("PDNSException")));
       }
       catch(std::exception &e) {
-        g_log<<Logger::Error<<d_logprefix<<"STL Exception while accepting a connection in main webserver thread: "<<e.what()<<endl;
+        SLOG(g_log<<Logger::Error<<d_logprefix<<"STL Exception while accepting a connection in main webserver thread: "<<e.what()<<endl,
+             d_slog->error(Logr::Error, e.what(), acceptmsg, Logging::Loggable("std::exception")));
       }
       catch(...) {
-        g_log<<Logger::Error<<d_logprefix<<"Unknown exception while accepting a connection in main webserver thread"<<endl;
+        SLOG(g_log<<Logger::Error<<d_logprefix<<"Unknown exception while accepting a connection in main webserver thread"<<endl,
+             d_slog->info(Logr::Error, msg));
       }
     }
   }
   catch(PDNSException &e) {
-    g_log<<Logger::Error<<d_logprefix<<"PDNSException in main webserver thread: "<<e.reason<<endl;
+    SLOG(g_log<<Logger::Error<<d_logprefix<<"PDNSException in main webserver thread: "<<e.reason<<endl,
+         d_slog->error(Logr::Error, e.reason, msg, Logging::Loggable("PDNSException")));
   }
   catch(std::exception &e) {
-    g_log<<Logger::Error<<d_logprefix<<"STL Exception in main webserver thread: "<<e.what()<<endl;
+    SLOG(g_log<<Logger::Error<<d_logprefix<<"STL Exception in main webserver thread: "<<e.what()<<endl,
+         d_slog->error(Logr::Error, e.what(), msg, Logging::Loggable("std::exception")));
   }
   catch(...) {
-    g_log<<Logger::Error<<d_logprefix<<"Unknown exception in main webserver thread"<<endl;
+    SLOG(g_log<<Logger::Error<<d_logprefix<<"Unknown exception in main webserver thread"<<endl,
+         d_slog->info(Logr::Error, msg));
   }
   _exit(1);
 }
index d8707e09149a3c7dcc5b66904b896f015163f04a..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"
 
 #include "credentials.hh"
 #include "namespaces.hh"
 #include "sstuff.hh"
+#include "logging.hh"
 
 class HttpRequest : public YaHTTP::Request {
 public:
@@ -48,6 +52,14 @@ public:
   bool compareAuthorization(const CredentialsHolder& expectedCredentials) const;
   bool compareHeader(const string &header_name, const CredentialsHolder& expectedCredentials) const;
   bool compareHeader(const string &header_name, const string &expected_value) const;
+
+#ifdef RECURSOR
+  void setSLog(Logr::log_t log)
+  {
+    d_slog = log;
+  }
+  std::shared_ptr<Logr::Logger> d_slog;
+#endif
 };
 
 class HttpResponse: public YaHTTP::Response {
@@ -61,6 +73,14 @@ public:
   void setJsonBody(const json11::Json& document);
   void setErrorResult(const std::string& message, const int status);
   void setSuccessResult(const std::string& message, const int status = 200);
+
+#ifdef RECURSOR
+  void setSLog(Logr::log_t log)
+  {
+    d_slog = log;
+  }
+  std::shared_ptr<Logr::Logger> d_slog;
+#endif
 };
 
 
@@ -145,7 +165,7 @@ public:
     d_server_socket.bind(d_local);
     d_server_socket.listen();
   }
-  virtual ~Server() { };
+  virtual ~Server() = default;
 
   ComboAddress d_local;
 
@@ -161,7 +181,14 @@ class WebServer : public boost::noncopyable
 {
 public:
   WebServer(string listenaddress, int port);
-  virtual ~WebServer() { };
+  virtual ~WebServer() = default;
+
+#ifdef RECURSOR
+  void setSLog(Logr::log_t log)
+  {
+    d_slog = log;
+  }
+#endif
 
   void setApiKey(const string &apikey, bool hashPlaintext) {
     if (!apikey.empty()) {
@@ -189,15 +216,17 @@ public:
     d_acl = nmg;
   }
 
+  static bool validURL(const YaHTTP::URL& url);
+
   void bind();
   void go();
 
   void serveConnection(const std::shared_ptr<Socket>& client) const;
   void handleRequest(HttpRequest& request, HttpResponse& resp) const;
 
-  typedef boost::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);
+  typedef std::function<void(HttpRequest* req, HttpResponse* resp)> HandlerFunction;
+  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
@@ -232,8 +261,12 @@ public:
     return d_loglevel;
   };
 
+#ifdef RECURSOR
+  std::shared_ptr<Logr::Logger> d_slog;
+#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 08f3d5cbac758a780a04658a3b6040ea5fca5ba1..329e5aeba6e43f2aa85825cc32026c9581945d04 100644 (file)
 #include "version.hh"
 #include "arguments.hh"
 #include "dnsparser.hh"
+#ifdef RECURSOR
+#include "syncres.hh"
+#else
 #include "responsestats.hh"
-#ifndef RECURSOR
 #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>
 
 using json11::Json;
 
-extern string s_programname;
-extern ResponseStats g_rs;
 #ifndef RECURSOR
 extern StatBag S;
 #endif
@@ -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,93 +188,104 @@ 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},
     });
   }
 
+#ifdef RECURSOR
+  auto stats = g_Counters.sum(rec::ResponseStats::responseStats);
+  auto resp_qtype_stats = stats.getQTypeResponseCounts();
+  auto resp_size_stats = stats.getSizeResponseCounts();
+  auto resp_rcode_stats = stats.getRCodeResponseCounts();
+#else
   auto resp_qtype_stats = g_rs.getQTypeResponseCounts();
   auto resp_size_stats = g_rs.getSizeResponseCounts();
   auto resp_rcode_stats = g_rs.getRCodeResponseCounts();
+#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},
       });
     }
   }
@@ -295,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 883cb0618de301d7374a330ee33c4942fe72fba3..9c82d7dafa0f31b0fd4441e5269ec26d6dc39e5e 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
@@ -42,7 +45,7 @@
 #include "dnsseckeeper.hh"
 #include <iomanip>
 #include "zoneparser-tng.hh"
-#include "common_startup.hh"
+#include "auth-main.hh"
 #include "auth-caches.hh"
 #include "auth-zonecache.hh"
 #include "threadname.hh"
 
 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,176 +267,178 @@ 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)-s_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 {
+  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() },
-    { "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);
+    {"id", zoneId},
+    {"url", "/api/v1/servers/localhost/zones/" + zoneId},
+    {"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;
-  di.backend->getDomainMetadataOne(zonename, "NSEC3PARAM", nsec3param);
-  doc["nsec3param"] = nsec3param;
-  string nsec3narrow;
   bool nsec3narrowbool = false;
-  di.backend->getDomainMetadataOne(zonename, "NSEC3NARROW", nsec3narrow);
-  if (nsec3narrow == "1")
-    nsec3narrowbool = true;
+  bool is_secured = dnssecKeeper.isSecuredZone(zonename);
+  if (is_secured) { // ignore NSEC3PARAM and NSEC3NARROW metadata present in the db for unsigned zones
+    domainInfo.backend->getDomainMetadataOne(zonename, "NSEC3PARAM", nsec3param);
+    string nsec3narrow;
+    domainInfo.backend->getDomainMetadataOne(zonename, "NSEC3NARROW", nsec3narrow);
+    if (nsec3narrow == "1") {
+      nsec3narrowbool = true;
+    }
+  }
+  doc["nsec3param"] = nsec3param;
   doc["nsec3narrow"] = nsec3narrowbool;
-  doc["dnssec"] = dk.isSecuredZone(zonename);
+  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;
@@ -401,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;
+        }
+        else {
+          qType = req->getvars["rrset_type"];
         }
-        di.backend->lookup(qt, DNSName(req->getvars["rrset_name"]), di.id);
+        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;
@@ -453,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();
 
@@ -463,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++;
       }
 
@@ -491,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();
@@ -503,15 +550,15 @@ 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));
   }
 
   // add uptime
-  out["uptime"] = std::to_string(time(nullptr) - s_starttime);
+  out["uptime"] = std::to_string(time(nullptr) - g_starttime);
 }
 
 std::optional<uint64_t> productServerStatisticsFetch(const std::string& name)
@@ -525,283 +572,352 @@ 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(UeberBackend& B, const string& logprefix, 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 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>>& masters, boost::optional<string>& account) {
+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()) {
+    string catstring = document["catalog"].string_value();
+    catalog = (!catstring.empty() ? DNSName(catstring) : DNSName());
+  }
+  else {
+    catalog = boost::none;
   }
 
   if (document["account"].is_string()) {
     account = document["account"].string_value();
-  } else {
+  }
+  else {
     account = boost::none;
   }
 }
 
-static void updateDomainSettingsFromDocument(UeberBackend& B, const DomainInfo& di, const DNSName& zonename, const Json& document, bool rectifyTransaction=true) {
+/*
+ * 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& 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, 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) {
+    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);
-  bool shouldRectify = false;
+  DNSSECKeeper dnssecKeeper(&backend);
+  bool shouldRectify = zoneWasModified;
   bool dnssecInJSON = false;
   bool dnssecDocVal = false;
   bool nsec3paramInJSON = false;
+  bool updateNsec3Param = false;
   string nsec3paramDocVal;
 
   try {
     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);
         }
         shouldRectify = true;
+        updateNsec3Param = true;
       }
-    } else {
+    }
+    else {
       // "dnssec": false in json
       if (isDNSSECZone) {
-        string info, error;
-        if (!dk.unSecureZone(zonename, error, info)) {
-          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;
       }
     }
   }
 
-  if (nsec3paramInJSON) {
+  if (nsec3paramInJSON || updateNsec3Param) {
     shouldRectify = true;
-    if (!isDNSSECZone) {
-      throw ApiException("NSEC3PARAMs provided for zone '"+zonename.toString()+"', but zone is not DNSSEC secured.");
+    if (!isDNSSECZone && !nsec3paramDocVal.empty()) {
+      throw ApiException("NSEC3PARAM value provided for zone '" + zonename.toString() + "', but zone is not DNSSEC secured.");
     }
 
-    if (nsec3paramDocVal.length() == 0) {
+    if (nsec3paramDocVal.empty()) {
       // Switch to NSEC
-      if (!dk.unsetNSEC3PARAM(zonename)) {
+      if (!dnssecKeeper.unsetNSEC3PARAM(zonename)) {
         throw ApiException("Unable to remove NSEC3PARAMs from zone '" + zonename.toString());
       }
     }
-
-    if (nsec3paramDocVal.length() > 0) {
+    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, rectifyTransaction)) {
+      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.");
         }
       }
@@ -810,40 +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;
-      B.getTSIGKey(keyname, &keyAlgo, &keyContent);
-      if (keyAlgo.empty() || keyContent.empty()) {
-        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;
-      B.getTSIGKey(keyname, &keyAlgo, &keyContent);
-      if (keyAlgo.empty() || keyContent.empty()) {
-        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",
@@ -853,6 +952,8 @@ static bool isValidMetadataKind(const string& kind, bool readonly) {
     "NOTIFY-DNSUPDATE",
     "ALSO-NOTIFY",
     "AXFR-MASTER-TSIG",
+    "GSS-ALLOW-AXFR-PRINCIPAL",
+    "GSS-ACCEPTOR-PRINCIPAL",
     "IXFR",
     "LUA-AXFR-SCRIPT",
     "NSEC3NARROW",
@@ -863,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;
@@ -900,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);
+}
+
+static void apiZoneMetadataPOST(HttpRequest* req, HttpResponse* resp)
+{
+  ZoneData zoneData{req};
 
-    try {
-      kind = stringFromJson(document, "kind");
-    } catch (const JsonException&) {
-      throw ApiException("kind is not specified or not a string");
-    }
+  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 (!zoneData.backend.setDomainMetadata(zoneData.zoneName, kind, vecMetadata)) {
+    throw ApiException("Could not update metadata entries for domain '" + zoneData.zoneName.toString() + "'");
+  }
+
+  DNSSECKeeper::clearMetaCache(zoneData.zoneName);
 
-    if (!B.setDomainMetadata(zonename, kind, vecMetadata))
-      throw ApiException("Could not update metadata entries for domain '" + zonename.toString() + "'");
+  Json::object key{
+    {"type", "Metadata"},
+    {"kind", kind},
+    {"metadata", metadata}};
 
-    DNSSECKeeper::clearMetaCache(zonename);
+  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;
     }
@@ -1086,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.d_flags },
-        { "dnskey", value.first.getDNSKEY().getZoneRepresentation() },
-        { "algorithm", DNSSECKeeper::algorithm2name(value.first.d_algorithm) },
-        { "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_SHA1, DNSSECKeeper::DIGEST_SHA256, DNSSECKeeper::DIGEST_GOST, 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;
       }
     }
@@ -1151,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) {
@@ -1159,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);
 }
 
 /*
@@ -1173,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);
   }
 }
@@ -1218,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()) {
@@ -1229,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"));
   }
 
@@ -1247,64 +1430,77 @@ 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 {
       shared_ptr<DNSCryptoKeyEngine> dke(DNSCryptoKeyEngine::makeFromISCString(dkrc, keyData));
-      dpk.d_algorithm = dkrc.d_algorithm;
-      // TODO remove in 4.2.0
-      if(dpk.d_algorithm == DNSSECKeeper::RSASHA1NSEC3SHA1)
-        dpk.d_algorithm = DNSSECKeeper::RSASHA1;
-
-      if (keyOrZone)
-        dpk.d_flags = 257;
-      else
-        dpk.d_flags = 256;
+      uint16_t flags = 0;
+      if (keyOrZone) {
+        flags = 257;
+      }
+      else {
+        flags = 256;
+      }
 
-      dpk.setKey(dke);
+      uint8_t algorithm = dkrc.d_algorithm;
+      // TODO remove in 4.2.0
+      if (algorithm == DNSSECKeeper::RSASHA1NSEC3SHA1) {
+        algorithm = DNSSECKeeper::RSASHA1;
+      }
+      dpk.setKey(dke, flags, algorithm);
     }
     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;
 }
 
@@ -1319,81 +1515,52 @@ 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;
+static void gatherRecordsFromZone(const std::string& zonestring, vector<DNSResourceRecord>& new_records, const DNSName& zonename)
+{
+  DNSResourceRecord resourceRecord;
   vector<string> zonedata;
   stringtok(zonedata, zonestring, "\r\n");
 
@@ -1401,23 +1568,25 @@ static void gatherRecordsFromZone(const std::string& zonestring, vector<DNSResou
   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()));
   }
 }
 
@@ -1432,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");
       }
     }
 
@@ -1465,23 +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;
-  B.getTSIGKey(keyname, &algoFromDB, &contentFromDB);
-  if (!contentFromDB.empty() || !algoFromDB.empty()) {
-    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());
   }
@@ -1492,218 +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(B, keyname, algo, content);
+  // Will throw an ApiException or HttpConflictException on error
+  checkTSIGKey(backend, keyname, algo, content);
 
-    if(!B.setTSIGKey(keyname, algo, content)) {
-      throw HttpInternalServerErrorException("Unable to add TSIG key");
+  if (!backend.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 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;
 }
 
-static void apiServerAutoprimaries(HttpRequest* req, HttpResponse* resp) {
-  UeberBackend B;
+static void apiServerAutoprimariesGET(HttpRequest* /* req */, HttpResponse* resp)
+{
+  UeberBackend backend;
 
-  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"), "");
+  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 (document["account"].is_string()) {
-      primary.account = document["account"].string_value();
-    }
+static void apiServerAutoprimariesPOST(HttpRequest* req, HttpResponse* resp)
+{
+  UeberBackend backend;
 
-    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();
+  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)
-      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");
+  }
 
-    string soa_edit_api_kind;
-    if (document["soa_edit_api"].is_string()) {
-      soa_edit_api_kind = document["soa_edit_api"].string_value();
-    }
-    else {
-      soa_edit_api_kind = "DEFAULT";
-    }
-    string soa_edit_kind = document["soa_edit"].string_value();
+  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;
+  // 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"));
@@ -1711,151 +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");
-          gatherRecords(B, req->logprefix, rrset, qname, qtype, ttl, new_records);
+          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()));
+  }
 
-    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());
+  if (zonekind == DomainInfo::Consumer && !new_records.empty()) {
+    throw ApiException("Zone data MUST NOT be given for Consumer zones");
+  }
 
-      if (rr.qtype.getCode() == QType::SOA && rr.qname==zonename) {
-        have_soa = true;
-        increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind);
-      }
-      if (rr.qtype.getCode() == QType::NS && rr.qname==zonename) {
-        have_zone_ns = true;
-      }
+  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");
     }
 
-    // synthesize RRs as needed
-    DNSResourceRecord autorr;
-    autorr.qname = zonename;
-    autorr.auth = true;
-    autorr.ttl = ::arg().asNum("default-ttl");
-
-    if (!have_soa && zonekind != DomainInfo::Slave) {
-      // 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);
-      increaseSOARecord(autorr, soa_edit_api_kind, soa_edit_kind);
-      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");
-      }
+    apiCheckQNameAllowedCharacters(resourceRecord.qname.toString());
+
+    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;
     }
+  }
 
-    checkNewRecords(new_records, zonename);
+  // synthesize RRs as needed
+  DNSResourceRecord autorr;
+  autorr.qname = zonename;
+  autorr.auth = true;
+  autorr.ttl = ::arg().asNum("default-ttl");
 
-    if (boolFromJson(document, "dnssec", false)) {
-      checkDefaultDNSSECAlgos();
+  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);
+  }
 
-      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);
-        }
-      }
+  // 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");
+    }
+  }
 
-    boost::optional<DomainInfo::DomainKind> kind;
-    boost::optional<vector<ComboAddress>> masters;
-    boost::optional<string> account;
-    extractDomainInfoFromDocument(document, kind, masters, account);
+  checkNewRecords(new_records, zonename);
 
-    // 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");
+  if (boolFromJson(document, "dnssec", false)) {
+    checkDefaultDNSSECAlgos();
 
-    if(!B.getDomainInfo(zonename, di))
-      throw ApiException("Creating domain '"+zonename.toString()+"' failed: lookup of domain ID failed");
+    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);
+      }
+    }
+  }
 
-    di.backend->startTransaction(zonename, di.id);
+  // 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");
+  }
 
-    // updateDomainSettingsFromDocument does NOT fill out the default we've established above.
-    if (!soa_edit_api_kind.empty()) {
-      di.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind);
-    }
+  if (!backend.getDomainInfo(zonename, domainInfo)) {
+    throw ApiException("Creating domain '" + zonename.toString() + "' failed: lookup of domain ID failed");
+  }
 
-    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);
-    }
+  domainInfo.backend->startTransaction(zonename, static_cast<int>(domainInfo.id));
 
-    updateDomainSettingsFromDocument(B, di, zonename, document, false);
+  // will be overridden by updateDomainSettingsFromDocument, if given in document.
+  domainInfo.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", "DEFAULT");
 
-    di.backend->commitTransaction();
+  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.");
+    }
+  }
 
-    g_zoneCache.add(zonename, di.id); // make new zone visible
+  updateDomainSettingsFromDocument(backend, domainInfo, zonename, document, !new_records.empty());
 
-    fillZone(B, zonename, resp, req);
-    resp->status = 201;
-    return;
+  if (!catalog && kind == DomainInfo::Primary) {
+    const auto& defaultCatalog = ::arg()["default-catalog-zone"];
+    if (!defaultCatalog.empty()) {
+      domainInfo.backend->setCatalog(zonename, DNSName(defaultCatalog));
+    }
   }
 
-  if(req->method != "GET")
-    throw HttpMethodNotAllowedException();
+  domainInfo.backend->commitTransaction();
+
+  g_zoneCache.add(zonename, static_cast<int>(domainInfo.id)); // make new zone visible
+
+  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") {
@@ -1865,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()));
+    }
+
+    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());
 
-      di.backend->commitTransaction();
+      if (resourceRecord.qtype.getCode() == QType::SOA && resourceRecord.qname == zoneData.zoneName) {
+        haveSoa = true;
+      }
+    }
 
-      g_zoneCache.remove(zonename);
-    } catch (...) {
-      di.backend->abortTransaction();
-      throw;
+    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");
     }
 
-    // clear caches
-    DNSSECKeeper::clearCaches(zonename);
-    purgeAuthCaches(zonename.toString() + "$");
+    checkNewRecords(new_records, zoneData.zoneName);
 
-    // 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;
+    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);
+    }
+  }
+  else {
+    // avoid deleting current zone contents
+    zoneData.domainInfo.backend->startTransaction(zoneData.zoneName, -1);
   }
-  throw HttpMethodNotAllowedException();
+
+  // 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);
+}
+
+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 rr;
-  SOAData sd;
-  di.backend->list(zonename, di.id);
-  while(di.backend->get(rr)) {
-    if (!rr.qtype.getCode())
+  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;
@@ -2046,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;
@@ -2067,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();
@@ -2097,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(B, req->logprefix, 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
@@ -2236,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())
-    objectType = ObjectType::ALL;
-  else if (sObjectType == "all")
+  if (sObjectTypeVar.empty() || sObjectTypeVar == "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, "-", "_");
 
@@ -2377,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;
 }
@@ -2426,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 9807c2f5e39027a2c0b5f67023fca3d4cc604660..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, boost::function<void(HttpRequest*, HttpResponse*)> handler);
-  void printvars(ostringstream &ret);
-  void printargs(ostringstream &ret);
+  void registerApiHandler(const string& url, std::function<void(HttpRequest*, HttpResponse*)> handler);
   void webThread();
-  void statThread();
+  void statThread(StatBag& stats);
 
   time_t d_start;
   double d_min10, d_min5, d_min1;
diff --git a/pdns/ws-recursor.cc b/pdns/ws-recursor.cc
deleted file mode 100644 (file)
index f3372e9..0000000
+++ /dev/null
@@ -1,1443 +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 "ws-recursor.hh"
-#include "json.hh"
-
-#include <string>
-#include "namespaces.hh"
-#include <iostream>
-#include "iputils.hh"
-#include "rec_channel.hh"
-#include "rec_metrics.hh"
-#include "arguments.hh"
-#include "misc.hh"
-#include "syncres.hh"
-#include "dnsparser.hh"
-#include "json11.hpp"
-#include "webserver.hh"
-#include "ws-api.hh"
-#include "logger.hh"
-#include "rec-lua-conf.hh"
-#include "rpzloader.hh"
-#include "uuid-utils.hh"
-#include "tcpiohandler.hh"
-#include "rec-main.hh"
-
-using json11::Json;
-
-void productServerStatisticsFetch(map<string, string>& out)
-{
-  auto stats = getAllStatsMap(StatComponent::API);
-  map<string, string> ret;
-  for (const auto& entry : stats) {
-    ret.emplace(entry.first, entry.second.d_value);
-  }
-  out.swap(ret);
-}
-
-std::optional<uint64_t> productServerStatisticsFetch(const std::string& name)
-{
-  return getStatByName(name);
-}
-
-static void apiWriteConfigFile(const string& filebasename, const string& content)
-{
-  if (::arg()["api-config-dir"].empty()) {
-    throw ApiException("Config Option \"api-config-dir\" must be set");
-  }
-
-  string filename = ::arg()["api-config-dir"] + "/" + filebasename + ".conf";
-  ofstream ofconf(filename.c_str());
-  if (!ofconf) {
-    throw ApiException("Could not open config fragment file '" + filename + "' for writing: " + stringerror());
-  }
-  ofconf << "# Generated by pdns-recursor REST API, DO NOT EDIT" << endl;
-  ofconf << content << endl;
-  ofconf.close();
-}
-
-static void apiServerConfigACL(const std::string& aclType, HttpRequest* req, HttpResponse* resp)
-{
-  if (req->method == "PUT") {
-    Json document = req->json();
-
-    auto jlist = document["value"];
-    if (!jlist.is_array()) {
-      throw ApiException("'value' must be an array");
-    }
-
-    NetmaskGroup nmg;
-    for (auto value : jlist.array_items()) {
-      try {
-        nmg.addMask(value.string_value());
-      }
-      catch (const NetmaskException& e) {
-        throw ApiException(e.reason);
-      }
-    }
-
-    ostringstream ss;
-
-    // Clear <foo>-from-file if set, so our changes take effect
-    ss << aclType << "-file=" << endl;
-
-    // Clear ACL setting, and provide a "parent" value
-    ss << aclType << "=" << endl;
-    ss << aclType << "+=" << nmg.toString() << endl;
-
-    apiWriteConfigFile(aclType, ss.str());
-
-    parseACLs();
-
-    // fall through to GET
-  }
-  else if (req->method != "GET") {
-    throw HttpMethodNotAllowedException();
-  }
-
-  // Return currently configured ACLs
-  vector<string> entries;
-  if (aclType == "allow-from") {
-    t_allowFrom->toStringVector(&entries);
-  }
-  else if (aclType == "allow-notify-from") {
-    t_allowNotifyFrom->toStringVector(&entries);
-  }
-
-  resp->setJsonBody(Json::object{
-    {"name", aclType},
-    {"value", entries},
-  });
-}
-
-static void apiServerConfigAllowFrom(HttpRequest* req, HttpResponse* resp)
-{
-  apiServerConfigACL("allow-from", req, resp);
-}
-
-static void apiServerConfigAllowNotifyFrom(HttpRequest* req, HttpResponse* resp)
-{
-  apiServerConfigACL("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())
-    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());
-  }
-
-  Json::array records;
-  for (const SyncRes::AuthDomain::records_t::value_type& dr : 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.d_content->getZoneRepresentation()}});
-  }
-
-  // id is the canonical lookup key, which doesn't actually match the name (in some cases)
-  string zoneId = apiZoneNameToId(iter->first);
-  Json::object doc = {
-    {"id", zoneId},
-    {"url", "/api/v1/servers/localhost/zones/" + zoneId},
-    {"name", iter->first.toString()},
-    {"kind", zone.d_servers.empty() ? "Native" : "Forwarded"},
-    {"servers", servers},
-    {"recursion_desired", zone.d_servers.empty() ? false : zone.d_rdForward},
-    {"records", records}};
-
-  resp->setJsonBody(doc);
-}
-
-static void doCreateZone(const Json document)
-{
-  if (::arg()["api-config-dir"].empty()) {
-    throw ApiException("Config Option \"api-config-dir\" must be set");
-  }
-
-  DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
-  apiCheckNameAllowedCharacters(zonename.toString());
-
-  string singleIPTarget = document["single_target_ip"].string_value();
-  string kind = toUpper(stringFromJson(document, "kind"));
-  bool rd = boolFromJson(document, "recursion_desired");
-  string confbasename = "zone-" + apiZoneNameToId(zonename);
-
-  if (kind == "NATIVE") {
-    if (rd)
-      throw ApiException("kind=Native and recursion_desired are mutually exclusive");
-    if (!singleIPTarget.empty()) {
-      try {
-        ComboAddress rem(singleIPTarget);
-        if (rem.sin4.sin_family != AF_INET)
-          throw ApiException("");
-        singleIPTarget = rem.toString();
-      }
-      catch (...) {
-        throw ApiException("Single IP target '" + singleIPTarget + "' is invalid");
-      }
-    }
-    string zonefilename = ::arg()["api-config-dir"] + "/" + confbasename + ".zone";
-    ofstream ofzone(zonefilename.c_str());
-    if (!ofzone) {
-      throw ApiException("Could not open '" + zonefilename + "' for writing: " + stringerror());
-    }
-    ofzone << "; Generated by pdns-recursor REST API, DO NOT EDIT" << endl;
-    ofzone << zonename << "\tIN\tSOA\tlocal.zone.\thostmaster." << zonename << " 1 1 1 1 1" << endl;
-    if (!singleIPTarget.empty()) {
-      ofzone << zonename << "\t3600\tIN\tA\t" << singleIPTarget << endl;
-      ofzone << "*." << zonename << "\t3600\tIN\tA\t" << singleIPTarget << endl;
-    }
-    ofzone.close();
-
-    apiWriteConfigFile(confbasename, "auth-zones+=" + zonename.toString() + "=" + zonefilename);
-  }
-  else if (kind == "FORWARDED") {
-    string serverlist;
-    for (auto value : document["servers"].array_items()) {
-      string server = value.string_value();
-      if (server == "") {
-        throw ApiException("Forwarded-to server must not be an empty string");
-      }
-      try {
-        ComboAddress ca = parseIPAndPort(server, 53);
-        if (!serverlist.empty()) {
-          serverlist += ";";
-        }
-        serverlist += ca.toStringWithPort();
-      }
-      catch (const PDNSException& e) {
-        throw ApiException(e.reason);
-      }
-    }
-    if (serverlist == "")
-      throw ApiException("Need at least one upstream server when forwarding");
-
-    if (rd) {
-      apiWriteConfigFile(confbasename, "forward-zones-recurse+=" + zonename.toString() + "=" + serverlist);
-    }
-    else {
-      apiWriteConfigFile(confbasename, "forward-zones+=" + zonename.toString() + "=" + serverlist);
-    }
-  }
-  else {
-    throw ApiException("invalid kind");
-  }
-}
-
-static bool doDeleteZone(const DNSName& zonename)
-{
-  if (::arg()["api-config-dir"].empty()) {
-    throw ApiException("Config Option \"api-config-dir\" must be set");
-  }
-
-  string filename;
-
-  // 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());
-
-  return true;
-}
-
-static void apiServerZones(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();
-
-    DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
-
-    auto iter = SyncRes::t_sstorage.domainmap->find(zonename);
-    if (iter != SyncRes::t_sstorage.domainmap->end())
-      throw ApiException("Zone already exists");
-
-    doCreateZone(document);
-    reloadZoneConfiguration();
-    fillZone(zonename, resp);
-    resp->status = 201;
-    return;
-  }
-
-  if (req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
-  Json::array doc;
-  for (const SyncRes::domainmap_t::value_type& 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());
-    }
-    // id is the canonical lookup key, which doesn't actually match the name (in some cases)
-    string zoneId = apiZoneNameToId(val.first);
-    doc.push_back(Json::object{
-      {"id", zoneId},
-      {"url", "/api/v1/servers/localhost/zones/" + zoneId},
-      {"name", val.first.toString()},
-      {"kind", zone.d_servers.empty() ? "Native" : "Forwarded"},
-      {"servers", servers},
-      {"recursion_desired", zone.d_servers.empty() ? false : zone.d_rdForward}});
-  }
-  resp->setJsonBody(doc);
-}
-
-static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp)
-{
-  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())
-    throw ApiException("Could not find domain '" + zonename.toLogString() + "'");
-
-  if (req->method == "PUT") {
-    Json document = req->json();
-
-    doDeleteZone(zonename);
-    doCreateZone(document);
-    reloadZoneConfiguration();
-    resp->body = "";
-    resp->status = 204; // No Content, but indicate success
-  }
-  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();
-  }
-}
-
-static void apiServerSearchData(HttpRequest* req, HttpResponse* resp)
-{
-  if (req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
-  string q = req->getvars["q"];
-  if (q.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) {
-      doc.push_back(Json::object{
-        {"type", "zone"},
-        {"zone_id", zoneId},
-        {"name", zoneName}});
-    }
-
-    // if zone name is an exact match, don't bother with returning all records/comments in it
-    if (val.first == DNSName(q)) {
-      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.d_content->getZoneRepresentation(), q) == string::npos)
-        continue;
-
-      doc.push_back(Json::object{
-        {"type", "record"},
-        {"zone_id", zoneId},
-        {"zone_name", zoneName},
-        {"name", rr.d_name.toString()},
-        {"content", rr.d_content->getZoneRepresentation()}});
-    }
-  }
-  resp->setJsonBody(doc);
-}
-
-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);
-  uint16_t qtype = 0xffff;
-  if (req->getvars.count("type")) {
-    qtype = QType::chartocode(req->getvars["type"].c_str());
-  }
-
-  struct WipeCacheResult res = wipeCaches(canon, subtree, qtype);
-  resp->setJsonBody(Json::object{
-    {"count", res.record_count + res.packet_count + res.negative_record_count},
-    {"result", "Flushed cache."}});
-}
-
-static void apiServerRPZStats(HttpRequest* req, HttpResponse* resp)
-{
-  if (req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
-  auto luaconf = g_luaconfs.getLocal();
-  auto numZones = luaconf->dfe.size();
-
-  Json::object ret;
-
-  for (size_t i = 0; i < numZones; i++) {
-    auto zone = luaconf->dfe.getZone(i);
-    if (zone == nullptr)
-      continue;
-    const auto& name = zone->getName();
-    auto stats = getRPZZoneStats(name);
-    if (stats == nullptr)
-      continue;
-    Json::object zoneInfo = {
-      {"transfers_failed", (double)stats->d_failedTransfers},
-      {"transfers_success", (double)stats->d_successfulTransfers},
-      {"transfers_full", (double)stats->d_fullTransfers},
-      {"records", (double)stats->d_numberOfRecords},
-      {"last_update", (double)stats->d_lastUpdate},
-      {"serial", (double)stats->d_serial},
-    };
-    ret[name] = zoneInfo;
-  }
-  resp->setJsonBody(ret);
-}
-
-static void prometheusMetrics(HttpRequest* req, HttpResponse* resp)
-{
-  static MetricDefinitionStorage s_metricDefinitions;
-
-  if (req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
-  registerAllStats();
-
-  std::ostringstream output;
-
-  // Argument controls disabling of any stats. So
-  // stats-api-disabled-list will be used to block returned stats.
-  auto varmap = getAllStatsMap(StatComponent::API);
-  for (const auto& tup : varmap) {
-    std::string metricName = tup.first;
-    std::string prometheusMetricName = tup.second.d_prometheusName;
-    std::string helpname = tup.second.d_prometheusName;
-    MetricDefinition metricDetails;
-
-    if (s_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
-      std::string prometheusTypeName = s_metricDefinitions.getPrometheusStringMetricType(
-        metricDetails.d_prometheusType);
-
-      if (prometheusTypeName.empty()) {
-        continue;
-      }
-      if (metricDetails.d_prometheusType == PrometheusMetricType::multicounter) {
-        helpname = prometheusMetricName.substr(0, prometheusMetricName.find('{'));
-      }
-      else if (metricDetails.d_prometheusType == PrometheusMetricType::histogram) {
-        helpname = prometheusMetricName.substr(0, prometheusMetricName.find('{'));
-        // name is XXX_count, strip the _count part
-        helpname = helpname.substr(0, helpname.length() - 6);
-      }
-      output << "# TYPE " << helpname << " " << prometheusTypeName << "\n";
-      output << "# HELP " << helpname << " " << metricDetails.d_description << "\n";
-    }
-    output << prometheusMetricName << " " << tup.second.d_value << "\n";
-  }
-
-  output << "# HELP pdns_recursor_info "
-         << "Info from pdns_recursor, value is always 1"
-         << "\n";
-  output << "# TYPE pdns_recursor_info "
-         << "gauge"
-         << "\n";
-  output << "pdns_recursor_info{version=\"" << VERSION << "\"} "
-         << "1"
-         << "\n";
-
-  resp->body = output.str();
-  resp->headers["Content-Type"] = "text/plain";
-  resp->status = 200;
-}
-
-#include "htmlfiles.h"
-
-static void serveStuff(HttpRequest* req, HttpResponse* resp)
-{
-  resp->headers["Cache-Control"] = "max-age=86400";
-
-  if (req->url.path == "/")
-    req->url.path = "/index.html";
-
-  const string charset = "; charset=utf-8";
-  if (boost::ends_with(req->url.path, ".html"))
-    resp->headers["Content-Type"] = "text/html" + charset;
-  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"))
-    resp->headers["Content-Type"] = "application/javascript" + charset;
-  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";
-  resp->headers["X-Permitted-Cross-Domain-Policies"] = "none";
-
-  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);
-    resp->status = 200;
-  }
-  else {
-    resp->status = 404;
-  }
-}
-
-const std::map<std::string, MetricDefinition> MetricDefinitionStorage::d_metrics = {
-  {"all-outqueries",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of outgoing UDP queries since starting")},
-
-  {"answers-slow",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries answered after 1 second")},
-  {"answers0-1",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries answered within 1 millisecond")},
-  {"answers1-10",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries answered within 10 milliseconds")},
-  {"answers10-100",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries answered within 100 milliseconds")},
-  {"answers100-1000",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries answered within 1 second")},
-
-  {"auth4-answers-slow",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries answered by authoritatives over IPv4 after 1 second")},
-  {"auth4-answers0-1",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries answered by authoritatives over IPv4within 1 millisecond")},
-  {"auth4-answers1-10",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries answered by authoritatives over IPv4 within 10 milliseconds")},
-  {"auth4-answers10-100",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries answered by authoritatives over IPv4 within 100 milliseconds")},
-  {"auth4-answers100-1000",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries answered by authoritatives over IPv4 within 1 second")},
-
-  {"auth6-answers-slow",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries answered by authoritatives over IPv6 after 1 second")},
-  {"auth6-answers0-1",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries answered by authoritatives over IPv6 within 1 millisecond")},
-  {"auth6-answers1-10",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries answered by authoritatives over IPv6 within 10 milliseconds")},
-  {"auth6-answers10-100",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries answered by authoritatives over IPv6 within 100 milliseconds")},
-  {"auth6-answers100-1000",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries answered by authoritatives over IPv6 within 1 second")},
-
-  {"auth-zone-queries",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries to locally hosted authoritative zones (`setting-auth-zones`) since starting")},
-  {"cache-bytes",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "Size of the cache in bytes")},
-  {"cache-entries",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "Number of entries in the cache")},
-  {"cache-hits",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of of cache hits since starting, this does **not** include hits that got answered from the packet-cache")},
-  {"cache-misses",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of cache misses since starting")},
-  {"case-mismatches",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of mismatches in character case since starting")},
-  {"chain-resends",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries chained to existing outstanding")},
-  {"client-parse-errors",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of client packets that could not be parsed")},
-  {"concurrent-queries",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "Number of MThreads currently running")},
-
-  // For multicounters, state the first
-  {"cpu-msec-thread-0",
-   MetricDefinition(PrometheusMetricType::multicounter,
-                    "Number of milliseconds spent in thread n")},
-
-  {"zone-disallowed-notify",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of NOTIFY operations denied because of allow-notify-for restrictions")},
-  {"dnssec-authentic-data-queries",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries received with the AD bit set")},
-  {"dnssec-check-disabled-queries",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries received with the CD bit set")},
-  {"dnssec-queries",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries received with the DO bit set")},
-  {"dnssec-result-bogus",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of DNSSEC validations that had the Bogus state")},
-  {"dnssec-result-indeterminate",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of DNSSEC validations that had the Indeterminate state")},
-  {"dnssec-result-insecure",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of DNSSEC validations that had the Insecure state")},
-  {"dnssec-result-nta",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of DNSSEC validations that had the (negative trust anchor) state")},
-  {"dnssec-result-secure",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of DNSSEC validations that had the Secure state")},
-  {"x-dnssec-result-bogus",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of DNSSEC validations that had the Bogus state")},
-  {"x-dnssec-result-indeterminate",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of DNSSEC validations that had the Indeterminate state")},
-  {"x-dnssec-result-insecure",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of DNSSEC validations that had the Insecure state")},
-  {"x-dnssec-result-nta",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of DNSSEC validations that had the (negative trust anchor) state")},
-  {"x-dnssec-result-secure",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of DNSSEC validations that had the Secure state")},
-
-  {"dnssec-validations",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of DNSSEC validations performed")},
-  {"dont-outqueries",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of outgoing queries dropped because of `setting-dont-query` setting")},
-  {"qname-min-fallback-success",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of successful queries due to fallback mechanism within 'qname-minimization' setting")},
-  {"ecs-queries",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of outgoing queries adorned with an EDNS Client Subnet option")},
-  {"ecs-responses",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of responses received from authoritative servers with an EDNS Client Subnet option we used")},
-  {"edns-ping-matches",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of servers that sent a valid EDNS PING response")},
-  {"edns-ping-mismatches",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of servers that sent an invalid EDNS PING response")},
-  {"failed-host-entries",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "Number of entries in the failed NS cache")},
-  {"non-resolving-nameserver-entries",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "Number of entries in the non-resolving NS name cache")},
-  {"ignored-packets",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of non-query packets received on server sockets that should only get query packets")},
-  {"ipv6-outqueries",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of outgoing queries over IPv6")},
-  {"ipv6-questions",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of end-user initiated queries with the RD bit set, received over IPv6 UDP")},
-  {"malloc-bytes",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of bytes allocated by the process (broken, always returns 0)")},
-  {"max-cache-entries",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "Currently configured maximum number of cache entries")},
-  {"max-packetcache-entries",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "Currently configured maximum number of packet cache entries")},
-  {"max-mthread-stack",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "Maximum amount of thread stack ever used")},
-
-  {"negcache-entries",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "Number of entries in the negative answer cache")},
-  {"no-packet-error",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of erroneous received packets")},
-  {"nod-lookups-dropped-oversize",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of NOD lookups dropped because they would exceed the maximum name length")},
-  {"noedns-outqueries",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries sent out without EDNS")},
-  {"noerror-answers",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of NOERROR answers since starting")},
-  {"noping-outqueries",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries sent out without ENDS PING")},
-  {"nsset-invalidations",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of times an nsset was dropped because it no longer worked")},
-  {"nsspeeds-entries",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "Number of entries in the NS speeds map")},
-  {"nxdomain-answers",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of NXDOMAIN answers since starting")},
-  {"outgoing-timeouts",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of timeouts on outgoing UDP queries since starting")},
-  {"outgoing4-timeouts",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of timeouts on outgoing UDP IPv4 queries since starting")},
-  {"outgoing6-timeouts",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of timeouts on outgoing UDP IPv6 queries since starting")},
-  {"over-capacity-drops",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of questions dropped because over maximum concurrent query limit")},
-  {"packetcache-bytes",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "Size of the packet cache in bytes")},
-  {"packetcache-entries",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "Number of packet cache entries")},
-  {"packetcache-hits",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of packet cache hits")},
-  {"packetcache-misses",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of packet cache misses")},
-
-  {"policy-drops",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of packets dropped because of (Lua) policy decision")},
-  {"policy-result-noaction",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of packets that were not acted upon by the RPZ/filter engine")},
-  {"policy-result-drop",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of packets that were dropped by the RPZ/filter engine")},
-  {"policy-result-nxdomain",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of packets that were replied to with NXDOMAIN by the RPZ/filter engine")},
-  {"policy-result-nodata",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of packets that were replied to with no data by the RPZ/filter engine")},
-  {"policy-result-truncate",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of packets that were were forced to TCP by the RPZ/filter engine")},
-  {"policy-result-custom",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of packets that were sent a custom answer by the RPZ/filter engine")},
-
-  {"qa-latency",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "Shows the current latency average, in microseconds, exponentially weighted over past 'latency-statistic-size' packets")},
-  {"query-pipe-full-drops",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of questions dropped because the query distribution pipe was full")},
-  {"questions",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Counts all end-user initiated queries with the RD bit set")},
-  {"rebalanced-queries",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries balanced to a different worker thread because the first selected one was above the target load configured with 'distribution-load-factor'")},
-  {"resource-limits",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of queries that could not be performed because of resource limits")},
-  {"security-status",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "security status based on `securitypolling`")},
-  {"server-parse-errors",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of server replied packets that could not be parsed")},
-  {"servfail-answers",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of SERVFAIL answers since starting")},
-  {"spoof-prevents",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of times PowerDNS considered itself spoofed, and dropped the data")},
-  {"sys-msec",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of CPU milliseconds spent in 'system' mode")},
-  {"tcp-client-overflow",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of times an IP address was denied TCP access because it already had too many connections")},
-  {"tcp-clients",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "Number of currently active TCP/IP clients")},
-  {"tcp-outqueries",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of outgoing TCP queries since starting")},
-  {"tcp-questions",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of all incoming TCP queries since starting")},
-  {"throttle-entries",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "Number of of entries in the throttle map")},
-  {"throttled-out",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of throttled outgoing UDP queries since starting")},
-  {"throttled-outqueries",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of throttled outgoing UDP queries since starting")},
-  {"too-old-drops",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of questions dropped that were too old")},
-  {"truncated-drops",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of questions dropped because they were larger than 512 bytes")},
-  {"empty-queries",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Questions dropped because they had a QD count of 0")},
-  {"unauthorized-tcp",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of TCP questions denied because of allow-from restrictions")},
-  {"unauthorized-udp",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of UDP questions denied because of allow-from restrictions")},
-  {"source-disallowed-notify",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of NOTIFY operations denied because of allow-notify-from restrictions")},
-  {"unexpected-packets",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of answers from remote servers that were unexpected (might point to spoofing)")},
-  {"unreachables",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of times nameservers were unreachable since starting")},
-  {"uptime",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of seconds process has been running")},
-  {"user-msec",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of CPU milliseconds spent in 'user' mode")},
-  {"variable-responses",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of responses that were marked as 'variable'")},
-
-  {"x-our-latency",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "Shows the averaged time spent within PowerDNS, in microseconds, exponentially weighted over past 'latency-statistic-size' packets")},
-  {"x-ourtime0-1",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Counts responses where between 0 and 1 milliseconds was spent within the Recursor")},
-  {"x-ourtime1-2",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Counts responses where between 1 and 2 milliseconds was spent within the Recursor")},
-  {"x-ourtime2-4",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Counts responses where between 2 and 4 milliseconds was spent within the Recursor")},
-  {"x-ourtime4-8",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Counts responses where between 4 and 8 milliseconds was spent within the Recursor")},
-  {"x-ourtime8-16",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Counts responses where between 8 and 16 milliseconds was spent within the Recursor")},
-  {"x-ourtime16-32",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Counts responses where between 16 and 32 milliseconds was spent within the Recursor")},
-  {"x-ourtime-slow",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Counts responses where more than 32 milliseconds was spent within the Recursor")},
-
-  {"fd-usage",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "Number of open file descriptors")},
-  {"real-memory-usage",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "Number of bytes real process memory usage")},
-  {"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 InErrors")},
-  {"udp6-noport-errors",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "From /proc/net/snmp6 NoPorts")},
-  {"udp6-recvbuf-errors",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "From /proc/net/snmp6 RcvbufErrors")},
-  {"udp6-sndbuf-errors",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "From /proc/net/snmp6 SndbufErrors")},
-  {"udp6-in-csum-errors",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "From /proc/net/snmp6 InCsumErrors")},
-
-  {"cpu-iowait",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Time spent waiting for I/O to complete by the whole system, in units of USER_HZ")},
-  {"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")},
-
-  {"dnssec-result-bogus-invalid-denial",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because a valid denial of existence proof could not be found")},
-
-  {"dnssec-result-bogus-invalid-dnskey-protocol",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because all DNSKEYs had invalid protocols")},
-
-  {"dnssec-result-bogus-missing-negative-indication",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because a NODATA or NXDOMAIN answer lacked the required SOA and/or NSEC(3) records")},
-
-  {"dnssec-result-bogus-no-rrsig",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because required RRSIG records were not present in an answer")},
-
-  {"dnssec-result-bogus-no-valid-dnskey",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because a valid DNSKEY could not be found")},
-
-  {"dnssec-result-bogus-no-valid-rrsig",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because only invalid RRSIG records were present in an answer")},
-
-  {"dnssec-result-bogus-no-zone-key-bit-set",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because no DNSKEY with the Zone Key bit set was found")},
-
-  {"dnssec-result-bogus-revoked-dnskey",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because all DNSKEYs were revoked")},
-
-  {"dnssec-result-bogus-self-signed-ds",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because a DS record was signed by itself")},
-
-  {"dnssec-result-bogus-signature-expired",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because the signature expired time in the RRSIG was in the past")},
-
-  {"dnssec-result-bogus-signature-not-yet-valid",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because the signature inception time in the RRSIG was not yet valid")},
-
-  {"dnssec-result-bogus-unable-to-get-dnskeys",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because a valid DNSKEY could not be retrieved")},
-
-  {"dnssec-result-bogus-unable-to-get-dss",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because a valid DS could not be retrieved")},
-  {"dnssec-result-bogus-unsupported-dnskey-algo",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because a DNSKEY RRset contained only unsupported DNSSEC algorithms")},
-
-  {"dnssec-result-bogus-unsupported-ds-digest-type",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because a DS RRset contained only unsupported digest types")},
-  {"x-dnssec-result-bogus-invalid-denial",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because a valid denial of existence proof could not be found")},
-
-  {"x-dnssec-result-bogus-invalid-dnskey-protocol",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because all DNSKEYs had invalid protocols")},
-
-  {"x-dnssec-result-bogus-missing-negative-indication",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because a NODATA or NXDOMAIN answer lacked the required SOA and/or NSEC(3) records")},
-
-  {"x-dnssec-result-bogus-no-rrsig",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because required RRSIG records were not present in an answer")},
-
-  {"x-dnssec-result-bogus-no-valid-dnskey",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because a valid DNSKEY could not be found")},
-
-  {"x-dnssec-result-bogus-no-valid-rrsig",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because only invalid RRSIG records were present in an answer")},
-
-  {"x-dnssec-result-bogus-no-zone-key-bit-set",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because no DNSKEY with the Zone Key bit set was found")},
-
-  {"x-dnssec-result-bogus-revoked-dnskey",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because all DNSKEYs were revoked")},
-
-  {"x-dnssec-result-bogus-self-signed-ds",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because a DS record was signed by itself")},
-
-  {"x-dnssec-result-bogus-signature-expired",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because the signature expired time in the RRSIG was in the past")},
-
-  {"x-dnssec-result-bogus-signature-not-yet-valid",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because the signature inception time in the RRSIG was not yet valid")},
-
-  {"x-dnssec-result-bogus-unable-to-get-dnskeys",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because a valid DNSKEY could not be retrieved")},
-
-  {"x-dnssec-result-bogus-unable-to-get-dss",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because a valid DS could not be retrieved")},
-  {"x-dnssec-result-bogus-unsupported-dnskey-algo",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because a DNSKEY RRset contained only unsupported DNSSEC algorithms")},
-
-  {"x-dnssec-result-bogus-unsupported-ds-digest-type",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of DNSSEC validations that had the Bogus state because a DS RRset contained only unsupported digest types")},
-
-  {"proxy-protocol-invalid",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "invalid proxy-protocol headers received")},
-
-  {"record-cache-acquired",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of record cache lock acquisitions")},
-
-  {"record-cache-contended",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of contented record cache lock acquisitions")},
-
-  {"taskqueue-expired",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of tasks expired before they could be run")},
-
-  {"taskqueue-pushed",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of tasks pushed to the taskqueues")},
-
-  {"taskqueue-size",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "number of tasks currently in the taskqueue")},
-
-  {"dot-outqueries",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of outgoing DoT queries since starting")},
-
-  {"dns64-prefix-answers",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of AAAA and PTR generated by a matching dns64-prefix")},
-  {"aggressive-nsec-cache-entries",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of entries in the aggressive NSEC cache")},
-
-  {"aggressive-nsec-cache-nsec-hits",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of NSEC-related hits from the aggressive NSEC cache")},
-
-  {"aggressive-nsec-cache-nsec-wc-hits",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of answers synthesized from the NSEC aggressive cache")},
-
-  {"aggressive-nsec-cache-nsec3-hits",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of NSEC3-related hits from the aggressive NSEC cache")},
-
-  {"aggressive-nsec-cache-nsec3-wc-hits",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "Number of answers synthesized from the NSEC3 aggressive cache")},
-
-  // For cumulative histogram, state the xxx_count name where xxx matches the name in rec_channel_rec
-  {"cumul-clientanswers-count",
-   MetricDefinition(PrometheusMetricType::histogram,
-                    "histogram of our answer times to clients")},
-  // For cumulative histogram, state the xxx_count name where xxx matches the name in rec_channel_rec
-  {"cumul-authanswers-count4",
-   MetricDefinition(PrometheusMetricType::histogram,
-                    "histogram of answer times of authoritative servers")},
-  {"almost-expired-pushed",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of almost-expired tasks pushed")},
-
-  {"almost-expired-run",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of almost-expired tasks run to completion")},
-
-  {"almost-expired-exceptions",
-   MetricDefinition(PrometheusMetricType::counter,
-                    "number of almost-expired tasks that caused an exception")},
-
-  // For multicounters, state the first
-  {"policy-hits",
-   MetricDefinition(PrometheusMetricType::multicounter,
-                    "Number of filter or RPZ policy hits")},
-
-  {"idle-tcpout-connections",
-   MetricDefinition(PrometheusMetricType::gauge,
-                    "Number of connections in the TCP idle outgoing connections pool")},
-
-};
-
-#define CHECK_PROMETHEUS_METRICS 0
-
-#if CHECK_PROMETHEUS_METRICS
-static void validatePrometheusMetrics()
-{
-  MetricDefinitionStorage s_metricDefinitions;
-
-  auto varmap = getAllStatsMap(StatComponent::API);
-  for (const auto& tup : varmap) {
-    std::string metricName = tup.first;
-    if (metricName.find("cpu-msec-") == 0) {
-      continue;
-    }
-    if (metricName.find("cumul-") == 0) {
-      continue;
-    }
-    MetricDefinition metricDetails;
-
-    if (!s_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
-      g_log << Logger::Debug << "{ \"" << metricName << "\", MetricDefinition(PrometheusMetricType::counter, \"\")}," << endl;
-    }
-  }
-}
-#endif
-
-RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm)
-{
-  registerAllStats();
-
-#if CHECK_PROMETHEUS_METRICS
-  validatePrometheusMetrics();
-#endif
-
-  d_ws = make_unique<AsyncWebServer>(fdm, arg()["webserver-address"], arg().asNum("webserver-port"));
-
-  d_ws->setApiKey(arg()["api-key"], arg().mustDo("webserver-hash-plaintext-credentials"));
-  d_ws->setPassword(arg()["webserver-password"], arg().mustDo("webserver-hash-plaintext-credentials"));
-  d_ws->setLogLevel(arg()["webserver-loglevel"]);
-
-  NetmaskGroup acl;
-  acl.toMasks(::arg()["webserver-allow-from"]);
-  d_ws->setACL(acl);
-
-  d_ws->bind();
-
-  // 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);
-  }
-
-  d_ws->registerWebHandler("/", serveStuff);
-  d_ws->registerWebHandler("/metrics", prometheusMetrics);
-  d_ws->go();
-}
-
-void RecursorWebServer::jsonstat(HttpRequest* req, HttpResponse* resp)
-{
-  string command;
-
-  if (req->getvars.count("command")) {
-    command = req->getvars["command"];
-    req->getvars.erase("command");
-  }
-
-  map<string, string> stats;
-  if (command == "get-query-ring") {
-    typedef pair<DNSName, uint16_t> query_t;
-    vector<query_t> queries;
-    bool filter = !req->getvars["public-filtered"].empty();
-
-    if (req->getvars["name"] == "servfail-queries")
-      queries = broadcastAccFunction<vector<query_t>>(pleaseGetServfailQueryRing);
-    else if (req->getvars["name"] == "bogus-queries")
-      queries = broadcastAccFunction<vector<query_t>>(pleaseGetBogusQueryRing);
-    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)]++;
-    }
-
-    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);
-
-    Json::array entries;
-    unsigned int tot = 0, totIncluded = 0;
-    for (const rcounts_t::value_type& q : rcounts) {
-      totIncluded -= q.first;
-      entries.push_back(Json::array{
-        -q.first, q.second.first.toLogString(), DNSRecordContent::NumberToType(q.second.second)});
-      if (tot++ >= 100)
-        break;
-    }
-    if (queries.size() != totIncluded) {
-      entries.push_back(Json::array{
-        (int)(queries.size() - totIncluded), "", ""});
-    }
-    resp->setJsonBody(Json::object{{"entries", entries}});
-    return;
-  }
-  else if (command == "get-remote-ring") {
-    vector<ComboAddress> queries;
-    if (req->getvars["name"] == "remotes")
-      queries = broadcastAccFunction<vector<ComboAddress>>(pleaseGetRemotes);
-    else if (req->getvars["name"] == "servfail-remotes")
-      queries = broadcastAccFunction<vector<ComboAddress>>(pleaseGetServfailRemotes);
-    else if (req->getvars["name"] == "bogus-remotes")
-      queries = broadcastAccFunction<vector<ComboAddress>>(pleaseGetBogusRemotes);
-    else if (req->getvars["name"] == "large-answer-remotes")
-      queries = broadcastAccFunction<vector<ComboAddress>>(pleaseGetLargeAnswerRemotes);
-    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]++;
-    }
-
-    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);
-
-    Json::array entries;
-    unsigned int tot = 0, totIncluded = 0;
-    for (const rcounts_t::value_type& q : rcounts) {
-      totIncluded -= q.first;
-      entries.push_back(Json::array{
-        -q.first, q.second.toString()});
-      if (tot++ >= 100)
-        break;
-    }
-    if (queries.size() != totIncluded) {
-      entries.push_back(Json::array{
-        (int)(queries.size() - totIncluded), ""});
-    }
-
-    resp->setJsonBody(Json::object{{"entries", entries}});
-    return;
-  }
-  else {
-    resp->setErrorResult("Command '" + command + "' not found", 404);
-  }
-}
-
-void AsyncServerNewConnectionMT(void* p)
-{
-  AsyncServer* server = (AsyncServer*)p;
-
-  try {
-    auto socket = server->accept(); // this is actually a shared_ptr
-    if (socket) {
-      server->d_asyncNewConnectionCallback(socket);
-    }
-  }
-  catch (NetworkError& e) {
-    // we're running in a shared process/thread, so can't just terminate/abort.
-    g_log << Logger::Warning << "Network error in web thread: " << e.what() << endl;
-    return;
-  }
-  catch (...) {
-    g_log << Logger::Warning << "Unknown error in web thread" << endl;
-
-    return;
-  }
-}
-
-void AsyncServer::asyncWaitForConnections(FDMultiplexer* fdm, const newconnectioncb_t& callback)
-{
-  d_asyncNewConnectionCallback = callback;
-  fdm->addReadFD(d_server_socket.getHandle(), [this](int, boost::any&) { newConnection(); });
-}
-
-void AsyncServer::newConnection()
-{
-  getMT()->makeThread(&AsyncServerNewConnectionMT, this);
-}
-
-// This is an entry point from FDM, so it needs to catch everything.
-void AsyncWebServer::serveConnection(std::shared_ptr<Socket> client) const
-{
-  if (!client->acl(d_acl)) {
-    return;
-  }
-
-  const string logprefix = d_logprefix + to_string(getUniqueID()) + " ";
-
-  HttpRequest req(logprefix);
-  HttpResponse resp;
-  ComboAddress remote;
-  PacketBuffer reply;
-
-  try {
-    YaHTTP::AsyncRequestLoader yarl;
-    yarl.initialize(&req);
-    client->setNonBlocking();
-
-    const struct timeval timeout
-    {
-      g_networkTimeoutMsec / 1000, static_cast<suseconds_t>(g_networkTimeoutMsec) % 1000 * 1000
-    };
-    std::shared_ptr<TLSCtx> tlsCtx{nullptr};
-    auto handler = std::make_shared<TCPIOHandler>("", false, client->releaseHandle(), timeout, tlsCtx, time(nullptr));
-
-    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());
-          req.complete = yarl.feed(str);
-        }
-        else {
-          // read error OR EOF
-          break;
-        }
-      }
-      yarl.finalize();
-    }
-    catch (YaHTTP::ParseError& e) {
-      // request stays incomplete
-      g_log << Logger::Warning << logprefix << "Unable to parse request: " << e.what() << endl;
-    }
-
-    if (d_loglevel >= WebServer::LogLevel::None) {
-      client->getRemote(remote);
-    }
-
-    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());
-
-    logResponse(resp, remote, logprefix);
-
-    // now send the reply
-    if (asendtcp(reply, handler) != LWResult::Result::Success || reply.empty()) {
-      g_log << Logger::Error << logprefix << "Failed sending reply to HTTP client" << endl;
-    }
-    handler->close(); // needed to signal "done" to client
-  }
-  catch (PDNSException& e) {
-    g_log << Logger::Error << logprefix << "Exception: " << e.reason << endl;
-  }
-  catch (std::exception& e) {
-    if (strstr(e.what(), "timeout") == 0)
-      g_log << Logger::Error << logprefix << "STL Exception: " << e.what() << endl;
-  }
-  catch (...) {
-    g_log << Logger::Error << logprefix << "Unknown exception" << endl;
-  }
-
-  if (d_loglevel >= WebServer::LogLevel::Normal) {
-    g_log << Logger::Notice << logprefix << remote << " \"" << req.method << " " << req.url.path << " HTTP/" << req.versionStr(req.version) << "\" " << resp.status << " " << reply.size() << endl;
-  }
-}
-
-void AsyncWebServer::go()
-{
-  if (!d_server)
-    return;
-  auto server = std::dynamic_pointer_cast<AsyncServer>(d_server);
-  if (!server)
-    return;
-  server->asyncWaitForConnections(d_fdm, [this](const std::shared_ptr<Socket>& c) { serveConnection(c); });
-}
diff --git a/pdns/ws-recursor.hh b/pdns/ws-recursor.hh
deleted file mode 100644 (file)
index 0a1a7fd..0000000
+++ /dev/null
@@ -1,77 +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 <boost/utility.hpp>
-#include "namespaces.hh"
-#include "mplexer.hh"
-#include "webserver.hh"
-
-class HttpRequest;
-class HttpResponse;
-
-class AsyncServer : public Server
-{
-public:
-  AsyncServer(const string& localaddress, int port) :
-    Server(localaddress, port)
-  {
-    d_server_socket.setNonBlocking();
-  };
-
-  friend void AsyncServerNewConnectionMT(void* p);
-
-  typedef boost::function<void(std::shared_ptr<Socket>)> newconnectioncb_t;
-  void asyncWaitForConnections(FDMultiplexer* fdm, const newconnectioncb_t& callback);
-
-private:
-  void newConnection();
-
-  newconnectioncb_t d_asyncNewConnectionCallback;
-};
-
-class AsyncWebServer : public WebServer
-{
-public:
-  AsyncWebServer(FDMultiplexer* fdm, const string& listenaddress, int port) :
-    WebServer(listenaddress, port), d_fdm(fdm){};
-  void go();
-
-private:
-  FDMultiplexer* d_fdm;
-  void serveConnection(std::shared_ptr<Socket> socket) const;
-
-protected:
-  virtual std::shared_ptr<Server> createServer() override
-  {
-    return std::make_shared<AsyncServer>(d_listenaddress, d_port);
-  };
-};
-
-class RecursorWebServer : public boost::noncopyable
-{
-public:
-  explicit RecursorWebServer(FDMultiplexer* fdm);
-  void jsonstat(HttpRequest* req, HttpResponse* resp);
-
-private:
-  std::unique_ptr<AsyncWebServer> d_ws{nullptr};
-};
diff --git a/pdns/xsk.cc b/pdns/xsk.cc
new file mode 100644 (file)
index 0000000..d0cb885
--- /dev/null
@@ -0,0 +1,1259 @@
+/*
+ * 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_link.h>
+#include <linux/if_xdp.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/tcp.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..e181e63
--- /dev/null
@@ -0,0 +1,341 @@
+/*
+ * 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 <linux/if_ether.h>
+#include <linux/types.h>
+#include <linux/udp.h>
+
+#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 iphdr;
+struct ipv6hdr;
+
+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 788f9e00124a9f811920577fa3f087f6df9d9523..51e76fa03b429a2223f5f1ee7e09c0ea08f30202 100644 (file)
@@ -53,7 +53,7 @@ using namespace json11;
 StatBag S;
 static int g_numRecords;
 
-static Json::object emitRecord(const string& zoneName, const DNSName &DNSqname, const string &qtype, const string &ocontent, int ttl)
+static Json::object emitRecord(const DNSName &DNSqname, const string &qtype, const string &ocontent, int ttl)
 {
   int prio=0;
   string retval;
@@ -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;
@@ -177,7 +177,7 @@ try
             obj["name"] = i->name.toString();
 
             while(zpt.get(rr))
-              recs.push_back(emitRecord(i->name.toString(), rr.qname, rr.qtype.toString(), rr.content, rr.ttl));
+              recs.push_back(emitRecord(rr.qname, rr.qtype.toString(), rr.content, rr.ttl));
             obj["records"] = recs;
             Json tmp = obj;
             cout<<tmp.dump();
@@ -213,7 +213,7 @@ try
       obj["name"] = ::arg()["zone-name"];
 
       while(zpt.get(rr))
-        records.push_back(emitRecord(::arg()["zone-name"], rr.qname, rr.qtype.toString(), rr.content, rr.ttl));
+        records.push_back(emitRecord(rr.qname, rr.qtype.toString(), rr.content, rr.ttl));
       obj["records"] = records;
 
       Json tmp = obj;
index 38c3b7e72808be1e262783fdbe0ed1c7d36c40a4..5752350e0df455ec483b726cc62b705f7340d6cc 100644 (file)
@@ -33,7 +33,6 @@
 #include "arguments.hh"
 #include "bindparserclasses.hh"
 #include "statbag.hh"
-#include <boost/function.hpp>
 #include "dnsrecords.hh"
 #include "misc.hh"
 #include "dns.hh"
@@ -274,7 +273,7 @@ int main( int argc, char* argv[] )
 
                 g_basedn = args["basedn"];
                 g_dnsttl = args.mustDo( "dnsttl" );
-                typedef boost::function<void(unsigned int, const DNSName &, const string &, const string &, int)> callback_t;
+                typedef std::function<void(unsigned int, const DNSName &, const string &, const string &, int)> callback_t;
                 callback_t callback = callback_simple;
                 if( args["layout"] == "tree" )
                 {
@@ -307,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 942447837381ca81b455f83bdf5dcd852aad7b15..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;
@@ -25,7 +25,7 @@ void pdns::ZoneMD::readRecords(ZoneParserTNG& zpt)
     }
     DNSRecord rec;
     rec.d_name = dnsResourceRecord.qname;
-    rec.d_content = drc;
+    rec.setContent(std::move(drc));
     rec.d_type = dnsResourceRecord.qtype;
     rec.d_class = dnsResourceRecord.qclass;
     rec.d_ttl = dnsResourceRecord.ttl;
@@ -36,31 +36,24 @@ 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: {
-      d_soaRecordContent = std::dynamic_pointer_cast<SOARecordContent>(record.d_content);
+      d_soaRecordContent = getRR<SOARecordContent>(record);
       if (d_soaRecordContent == nullptr) {
         throw PDNSException("Invalid SOA record");
       }
       break;
     }
     case QType::DNSKEY: {
-      auto dnskey = std::dynamic_pointer_cast<DNSKEYRecordContent>(record.d_content);
+      auto dnskey = getRR<DNSKEYRecordContent>(record);
       if (dnskey == nullptr) {
         throw PDNSException("Invalid DNSKEY record");
       }
@@ -68,7 +61,7 @@ void pdns::ZoneMD::readRecord(const DNSRecord& record)
       break;
     }
     case QType::ZONEMD: {
-      auto zonemd = std::dynamic_pointer_cast<ZONEMDRecordContent>(record.d_content);
+      auto zonemd = getRR<ZONEMDRecordContent>(record);
       if (zonemd == nullptr) {
         throw PDNSException("Invalid ZONEMD record");
       }
@@ -80,11 +73,11 @@ void pdns::ZoneMD::readRecord(const DNSRecord& record)
       break;
     }
     case QType::RRSIG: {
-      auto rrsig = std::dynamic_pointer_cast<RRSIGRecordContent>(record.d_content);
+      auto rrsig = getRR<RRSIGRecordContent>(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);
       }
@@ -92,7 +85,7 @@ void pdns::ZoneMD::readRecord(const DNSRecord& record)
       break;
     }
     case QType::NSEC: {
-      auto nsec = std::dynamic_pointer_cast<NSECRecordContent>(record.d_content);
+      auto nsec = getRR<NSECRecordContent>(record);
       if (nsec == nullptr) {
         throw PDNSException("Invalid NSEC record");
       }
@@ -103,34 +96,47 @@ void pdns::ZoneMD::readRecord(const DNSRecord& record)
       // Handled below
       break;
     case QType::NSEC3PARAM: {
-      auto param = std::dynamic_pointer_cast<NSEC3PARAMRecordContent>(record.d_content);
+      auto param = getRR<NSEC3PARAMRecordContent>(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) {
     case QType::NSEC3: {
-      auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(record.d_content);
+      auto nsec3 = getRR<NSEC3RecordContent>(record);
       if (nsec3 == nullptr) {
         throw PDNSException("Invalid NSEC3 record");
       }
@@ -138,7 +144,7 @@ void pdns::ZoneMD::readRecord(const DNSRecord& record)
       break;
     }
     case QType::RRSIG: {
-      auto rrsig = std::dynamic_pointer_cast<RRSIGRecordContent>(record.d_content);
+      auto rrsig = getRR<RRSIGRecordContent>(record);
       if (rrsig == nullptr) {
         throw PDNSException("Invalid RRSIG record");
       }
@@ -150,7 +156,7 @@ void pdns::ZoneMD::readRecord(const DNSRecord& record)
     }
   }
   RRSetKey_t key = std::pair(record.d_name, record.d_type);
-  d_resourceRecordSets[key].push_back(record.d_content);
+  d_resourceRecordSets[key].push_back(record.getContent());
   d_resourceRecordSetTTLs[key] = record.d_ttl;
 }
 
@@ -159,12 +165,16 @@ void pdns::ZoneMD::verify(bool& validationDone, bool& validationOK)
   validationDone = false;
   validationOK = false;
 
+  if (!d_soaRecordContent) {
+    return;
+  }
   // 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.
@@ -176,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);
       }
     }
@@ -213,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<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()) {
@@ -238,7 +248,7 @@ void pdns::ZoneMD::verify(bool& validationDone, bool& validationOK)
       // RRSIG is special, since  original TTL depends on qtype covered by RRSIG
       // which can be different per record
       for (const auto& rrsig : sorted) {
-        auto rrsigc = std::dynamic_pointer_cast<RRSIGRecordContent>(rrsig);
+        auto rrsigc = std::dynamic_pointer_cast<const RRSIGRecordContent>(rrsig);
         RRSIGRecordContent rrc;
         rrc.d_originalttl = d_resourceRecordSetTTLs[pair(rrset.first.first, rrsigc->d_type)];
         rrc.d_type = qtype;
index 63551fec3e011df9966d88a276a400be7168dd9c..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<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<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<ZONEMDRecordContent>> getZONEMDs() const
+  [[nodiscard]] std::vector<shared_ptr<const ZONEMDRecordContent>> getZONEMDs() const
   {
-    std::vector<shared_ptr<ZONEMDRecordContent>> ret;
+    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,52 +86,52 @@ 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<NSEC3PARAMRecordContent>>& getNSEC3Params() const
+  [[nodiscard]] const std::vector<shared_ptr<const NSEC3PARAMRecordContent>>& getNSEC3Params() const
   {
     return d_nsec3params;
   }
 
 private:
-  typedef std::pair<DNSName, QType> RRSetKey_t;
-  typedef std::vector<std::shared_ptr<DNSRecordContent>> RRVector_t;
+  using RRSetKey_t = std::pair<DNSName, QType>;
+  using RRVector_t = std::vector<std::shared_ptr<const DNSRecordContent>>;
 
-  struct CanonRRSetKeyCompare : public std::binary_function<RRSetKey_t, RRSetKey_t, bool>
+  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;
     }
   };
 
-  typedef std::map<RRSetKey_t, RRVector_t, CanonRRSetKeyCompare> RRSetMap_t;
+  using RRSetMap_t = std::map<RRSetKey_t, RRVector_t, CanonRRSetKeyCompare>;
 
   struct ZoneMDAndDuplicateFlag
   {
-    std::shared_ptr<ZONEMDRecordContent> record;
+    const std::shared_ptr<const ZONEMDRecordContent> record;
     bool duplicate;
   };
 
@@ -136,10 +141,10 @@ private:
   RRSetMap_t d_resourceRecordSets;
   std::map<RRSetKey_t, uint32_t> d_resourceRecordSetTTLs;
 
-  std::shared_ptr<SOARecordContent> d_soaRecordContent;
-  std::set<shared_ptr<DNSKEYRecordContent>> d_dnskeys;
-  std::vector<shared_ptr<RRSIGRecordContent>> d_rrsigs;
-  std::vector<shared_ptr<NSEC3PARAMRecordContent>> d_nsec3params;
+  std::shared_ptr<const SOARecordContent> d_soaRecordContent;
+  std::set<shared_ptr<const DNSKEYRecordContent>> d_dnskeys;
+  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;
   DNSName d_nsec3label;
index d9c1594763f7bb388ae12cc2261a0c39166a8d31..1ab6c7119aaa0b2e21e353f2bf500822d12ab2c6 100644 (file)
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
-#include "ascii.hh"
 #include "dnsparser.hh"
 #include "sstuff.hh"
 #include "misc.hh"
 #include "dnswriter.hh"
 #include "dnsrecords.hh"
-#include "misc.hh"
 #include <fstream>
 #include "dns.hh"
 #include "zoneparser-tng.hh"
@@ -68,7 +66,8 @@ void ZoneParserTNG::stackFile(const std::string& fname)
     std::error_code ec (err, std::generic_category());
     throw std::system_error(ec, "Unable to open file '" + fname + "': " + stringerror(err));
   }
-  struct stat st;
+
+  struct stat st = {};
   if (fstat(fd, &st) == -1) {
     int err = errno;
     close(fd);
@@ -81,7 +80,7 @@ void ZoneParserTNG::stackFile(const std::string& fname)
     throw std::system_error(ec, "File '" + fname + "': not a regular file");
   }
   FILE *fp = fdopen(fd, "r");
-  if (!fp) {
+  if (fp == nullptr) {
     int err = errno;
     close(fd);
     std::error_code ec (err, std::generic_category());
@@ -108,14 +107,21 @@ static string makeString(const string& line, const pair<string::size_type, strin
 
 static bool isTimeSpec(const string& nextpart)
 {
-  if(nextpart.empty())
+  if (nextpart.empty()) {
     return false;
-  for(string::const_iterator iter = nextpart.begin(); iter != nextpart.end(); ++iter) {
-    if(isdigit(*iter))
+  }
+
+  for (auto iter = nextpart.begin(); iter != nextpart.end(); ++iter) {
+    auto current = static_cast<unsigned char>(*iter);
+    if (isdigit(current) != 0) {
       continue;
-    if(iter+1 != nextpart.end())
+    }
+
+    if (iter + 1 != nextpart.end()) {
       return false;
-    char c=tolower(*iter);
+    }
+
+    char c = static_cast<char>(tolower(current));
     return (c=='s' || c=='m' || c=='h' || c=='d' || c=='w' || c=='y');
   }
   return true;
@@ -127,7 +133,7 @@ unsigned int ZoneParserTNG::makeTTLFromZone(const string& str)
   if(str.empty())
     return 0;
 
-  unsigned int val;
+  unsigned int val = 0;
   try {
     pdns::checked_stoi_into(val, str);
   }
@@ -170,9 +176,10 @@ bool ZoneParserTNG::getTemplateLine()
   }
 
   string retline;
-  for(parts_t::const_iterator iter = d_templateparts.begin() ; iter != d_templateparts.end(); ++iter) {
-    if(iter != d_templateparts.begin())
-      retline+=" ";
+  for (auto iter = d_templateparts.begin() ; iter != d_templateparts.end(); ++iter) {
+    if(iter != d_templateparts.begin()) {
+      retline += " ";
+    }
 
     string part=makeString(d_templateline, *iter);
 
@@ -264,7 +271,7 @@ bool ZoneParserTNG::getTemplateLine()
     d_templatecounter += d_templatestep;
   }
 
-  d_line = retline;
+  d_line = std::move(retline);
   return true;
 }
 
@@ -272,9 +279,11 @@ static void chopComment(string& line)
 {
   if(line.find(';')==string::npos)
     return;
-  string::size_type pos, len = line.length();
-  bool inQuote=false;
-  for(pos = 0 ; pos < len; ++pos) {
+
+  string::size_type pos = 0;
+  auto len = line.length();
+  bool inQuote = false;
+  for(; pos < len; ++pos) {
     if(line[pos]=='\\')
       pos++;
     else if(line[pos]=='"')
@@ -312,7 +321,7 @@ DNSName ZoneParserTNG::getZoneName()
 
 string ZoneParserTNG::getLineOfFile()
 {
-  if (d_zonedata.size() > 0)
+  if (!d_zonedata.empty())
     return "on line "+std::to_string(std::distance(d_zonedata.begin(), d_zonedataline))+" of given string";
 
   if (d_filestates.empty())
@@ -622,8 +631,9 @@ bool ZoneParserTNG::get(DNSResourceRecord& rr, std::string* comment)
     }
     rr.content.clear();
     for(string::size_type n = 0; n < recparts.size(); ++n) {
-      if(n)
+      if (n != 0) {
         rr.content.append(1,' ');
+      }
 
       rr.content+=recparts[n];
     }
@@ -642,8 +652,9 @@ bool ZoneParserTNG::get(DNSResourceRecord& rr, std::string* comment)
     }
     rr.content.clear();
     for(string::size_type n = 0; n < recparts.size(); ++n) {
-      if(n)
+      if (n != 0) {
         rr.content.append(1,' ');
+      }
 
       if(n > 1)
         rr.content+=std::to_string(makeTTLFromZone(recparts[n]));
@@ -659,7 +670,7 @@ bool ZoneParserTNG::get(DNSResourceRecord& rr, std::string* comment)
 
 bool ZoneParserTNG::getLine()
 {
-  if (d_zonedata.size() > 0) {
+  if (!d_zonedata.empty()) {
     if (d_zonedataline != d_zonedata.end()) {
       d_line = *d_zonedataline;
       ++d_zonedataline;
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 487f5ce14e3368c47ab9c55310a4ecca8200778c..e6d94c721444d6b7354a0a732a3c4ee8c3211e15 100644 (file)
@@ -1,5 +1,5 @@
 requests==2.20.0
-nose>=1.3.7
+pytest
 parameterized
 mysql-connector
 psycopg2
index 645628fe9435c65b67f45f6740e73cf6b5fabae0..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,31 +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
+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
 """
 
 
@@ -221,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
@@ -236,7 +243,26 @@ else:
 # Now run pdns and the tests.
 print("Launching server...")
 print(format_call_args(servercmd))
-serverproc = subprocess.Popen(servercmd, close_fds=True, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+server_stdout = tempfile.TemporaryFile()
+server_stderr = tempfile.TemporaryFile()
+serverproc = subprocess.Popen(servercmd, close_fds=True, text=True, stdout=server_stdout, stderr=server_stderr)
+
+def finalize_server():
+    serverproc.terminate()
+    serverproc.wait()
+
+    print("==STDOUT==")
+    server_stdout.seek(0, 0)
+    sys.stdout.flush()
+    sys.stdout.buffer.write(server_stdout.read())
+    server_stdout.close()
+
+    print("==STDERR==")
+    server_stderr.seek(0, 0)
+    sys.stdout.flush()
+    sys.stdout.buffer.write(server_stderr.read())
+    server_stderr.close()
+
 
 print("Waiting for webserver port to become available...")
 available = False
@@ -254,16 +280,14 @@ for try_number in range(0, 10):
 
 if not available:
     print("Webserver port not reachable after 10 tries, giving up.")
-    serverproc.terminate()
-    serverproc.wait()
-    print("==STDOUT===")
-    print(serverproc.stdout.read())
-    print("==STDERRR===")
-    print(serverproc.stderr.read())
+    finalize_server()
     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
@@ -289,18 +313,13 @@ test_env.update({
 
 try:
     print("")
-    run_check_call(["nosetests", "--with-xunit", "-v"] + tests, env=test_env)
+    run_check_call(["pytest", "--junitxml=pytest.xml", "-v"] + tests, env=test_env)
 except subprocess.CalledProcessError as ex:
     returncode = ex.returncode
 finally:
     if wait:
         print("Waiting as requested, press ENTER to stop.")
         raw_input()
-    serverproc.terminate()
-    serverproc.wait()
-    print("==STDOUT===")
-    print(serverproc.stdout.read())
-    print("==STDERRR===")
-    print(serverproc.stderr.read())
+    finalize_server()
 
 sys.exit(returncode)
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 774d366ad089249caf43bfa9d26c3e23c0a15797..242297112111323ed46508330fa0d811f1153b7e 100644 (file)
@@ -22,6 +22,17 @@ class RecursorAllowFromConfig(ApiTestCase):
         self.assertEqual(len(data["value"]), 1)
         self.assertEqual("127.0.0.1/32", data["value"][0])
 
+    def test_config_allow_from_replace_empty(self):
+        payload = {'value': []}
+        r = self.session.put(
+            self.url("/api/v1/servers/localhost/config/allow-from"),
+            data=json.dumps(payload),
+            headers={'content-type': 'application/json'})
+        self.assert_success_json(r)
+        data = r.json()
+        self.assertIn("value", data)
+        self.assertEqual(len(data["value"]), 0)
+
     def test_config_allow_from_replace_error(self):
         """Test the error case, should return 422."""
         payload = {'value': ["abcdefgh"]}
@@ -53,6 +64,17 @@ class RecursorAllowNotifyFromConfig(ApiTestCase):
         self.assertEqual(len(data["value"]), 1)
         self.assertEqual("127.0.0.1/32", data["value"][0])
 
+    def test_config_allow_notify_from_replace_empty(self):
+        payload = {'value': []}
+        r = self.session.put(
+            self.url("/api/v1/servers/localhost/config/allow-notify-from"),
+            data=json.dumps(payload),
+            headers={'content-type': 'application/json'})
+        self.assert_success_json(r)
+        data = r.json()
+        self.assertIn("value", data)
+        self.assertEqual(len(data["value"]), 0)
+
     def test_config_allow_notify_from_replace_error(self):
         """Test the error case, should return 422."""
         payload = {'value': ["abcdefgh"]}
index 9dabb5fca0950e11925c7c92f64f71cc97f2541e..83b99587a5dbfcf450fac998141a3b7b12e471a8 100644 (file)
@@ -4,7 +4,7 @@ import time
 import unittest
 from copy import deepcopy
 from pprint import pprint
-from test_helper import ApiTestCase, unique_tsigkey_name, is_auth, is_auth_lmdb, is_recursor, get_db_tsigkeys
+from test_helper import ApiTestCase, unique_tsigkey_name, is_auth, is_recursor
 
 class AuthTSIGHelperMixin(object):
     def create_tsig_key(self, name=None, algorithm='hmac-md5', key=None):
@@ -76,10 +76,12 @@ class AuthTSIG(ApiTestCase, AuthTSIGHelperMixin):
         name, payload, data = self.create_tsig_key()
         r = self.session.delete(self.url("/api/v1/servers/localhost/tsigkeys/" + data['id']))
         self.assertEqual(r.status_code, 204)
-
-        if not is_auth_lmdb():
-            keys_from_db = get_db_tsigkeys(name)
-            self.assertListEqual(keys_from_db, [])
+        r = self.session.get(self.url(
+            "/api/v1/servers/localhost/tsigkeys"),
+            headers={'accept': 'application/json'})
+        self.assertEqual(r.status_code, 200)
+        keys = r.json()
+        self.assertEqual(len([key for key in keys if key['name'] == name]), 0)
 
     def test_put_key_change_name(self):
         """
index d9291f5c2db9cc19b3a9a1eea8867bf33ba9c6c1..d769859896f4e9d4540a965be522aba6074160c2 100644 (file)
@@ -3,10 +3,11 @@ import json
 import operator
 import time
 import unittest
+import requests.exceptions
 from copy import deepcopy
 from parameterized import parameterized
 from pprint import pprint
-from test_helper import ApiTestCase, unique_zone_name, is_auth, is_auth_lmdb, is_recursor, get_db_records, pdnsutil_rectify
+from test_helper import ApiTestCase, unique_zone_name, is_auth, is_auth_lmdb, is_recursor, get_db_records, pdnsutil_rectify, sdig
 
 
 def get_rrset(data, qname, qtype):
@@ -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):
@@ -145,15 +228,36 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         self.assertGreater(soa_serial, payload['serial'])
         self.assertEqual(soa_serial, data['serial'])
 
+    def test_create_zone_with_catalog(self):
+        # soa_edit_api wins over serial
+        name, payload, data = self.create_zone(catalog='catalog.invalid.', serial=10)
+        print(data)
+        for k in ('catalog', ):
+            self.assertIn(k, data)
+            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)
@@ -198,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')
 
@@ -233,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 = [
@@ -242,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,
                   }],
               },
@@ -279,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
@@ -449,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)
@@ -476,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))
-
-        zoneinfo = r.json()
+        self.put_zone(name, {'dnssec': False})
 
-        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'))
@@ -500,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)
@@ -527,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)
@@ -551,48 +655,49 @@ 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):
+        """
+        Create a non dnssec zone and set an empty "nsec3param"
+        """
+        name, payload, data = self.create_zone(dnssec=False)
+        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)
+        self.put_zone(name, {'nsec3param': '1 0 1 ab'}, expect_error=True)
+
     def test_create_zone_dnssec_serial(self):
         """
-        Create a zone set/unset "dnssec" and see if the serial was increased
+        Create a zone, then set and unset "dnssec", then check if the serial was increased
         after every step
         """
-        name = unique_zone_name()
         name, payload, data = self.create_zone()
 
         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])
@@ -633,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)
@@ -669,6 +774,25 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         print("zonelist:", zonelist)
         self.assertIn(payload['name'], [zone['name'] for zone in zonelist])
         # Also test that fetching the zone works.
+        data = self.get_zone(data['id'])
+        print("zone (fetched):", data)
+        for k in ('name', 'masters', 'kind'):
+            self.assertIn(k, data)
+            self.assertEqual(data[k], payload[k])
+        self.assertEqual(data['serial'], 0)
+        self.assertEqual(data['rrsets'], [])
+
+    def test_create_consumer_zone(self):
+        # Test that nameservers can be absent for consumer zones.
+        _, 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.
+        r = self.session.get(self.url("/api/v1/servers/localhost/zones"))
+        zonelist = r.json()
+        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()
         print("zone (fetched):", data)
@@ -678,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)
@@ -691,6 +832,11 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         r = self.session.delete(self.url("/api/v1/servers/localhost/zones/" + data['id']))
         r.raise_for_status()
 
+    def test_delete_consumer_zone(self):
+        name, payload, data = self.create_zone(kind='Consumer', nameservers=None, masters=['127.0.0.2'])
+        r = self.session.delete(self.url("/api/v1/servers/localhost/zones/" + data['id']))
+        r.raise_for_status()
+
     def test_retrieve_slave_zone(self):
         name, payload, data = self.create_zone(kind='Slave', nameservers=None, masters=['127.0.0.2'])
         print("payload:", payload)
@@ -699,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')
@@ -714,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:
@@ -725,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.')
@@ -738,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'],
@@ -763,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')),
@@ -800,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'],
@@ -1032,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
@@ -1053,30 +1202,24 @@ $ORIGIN %NAME%
         payload = {
             'kind': 'Master',
             'masters': ['192.0.2.1', '192.0.2.2'],
+            'catalog': 'catalog.invalid.',
             '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])
         # update, back to Native and empty(off)
         payload = {
             'kind': 'Native',
+            'catalog': '',
             '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])
@@ -1107,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):
@@ -1133,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):
@@ -1158,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):
@@ -1218,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'])
 
@@ -1294,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):
@@ -1323,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):
@@ -1349,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!)
@@ -1367,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)
@@ -1585,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 = {
@@ -1605,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([
@@ -1630,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 = {
@@ -1652,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):
@@ -1790,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 = {
@@ -1814,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'], [])
@@ -1827,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()
@@ -1844,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'], [])
@@ -1872,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):
@@ -1917,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'])
@@ -2046,9 +2191,8 @@ $ORIGIN %NAME%
         self.assertEqual(len(r.json()), 5)
 
     @unittest.skipIf(is_auth_lmdb(), "No get_db_records for LMDB")
-    def test_default_api_rectify(self):
+    def test_default_api_rectify_dnssec(self):
         name = unique_zone_name()
-        search = name.split('.')[0]
         rrsets = [
             {
                 "name": 'a.' + name,
@@ -2073,6 +2217,35 @@ $ORIGIN %NAME%
         dbrecs = get_db_records(name, 'AAAA')
         self.assertIsNotNone(dbrecs[0]['ordername'])
 
+    def test_default_api_rectify_nodnssec(self):
+        """Without any DNSSEC settings, rectify should still add ENTs. Setup the zone
+        so ENTs are necessary, and check for their existence using sdig.
+        """
+        name = unique_zone_name()
+        rrsets = [
+            {
+                "name": 'a.sub.' + name,
+                "type": "AAAA",
+                "ttl": 3600,
+                "records": [{
+                    "content": "2001:DB8::1",
+                    "disabled": False,
+                }],
+            },
+            {
+                "name": 'b.sub.' + name,
+                "type": "AAAA",
+                "ttl": 3600,
+                "records": [{
+                    "content": "2001:DB8::2",
+                    "disabled": False,
+                }],
+            },
+        ]
+        self.create_zone(name=name, rrsets=rrsets)
+        # default-api-rectify is yes (by default). expect rectify to have happened.
+        assert 'Rcode: 0 ' in sdig('sub.' + name, 'TXT')
+
     @unittest.skipIf(is_auth_lmdb(), "No get_db_records for LMDB")
     def test_override_api_rectify(self):
         name = unique_zone_name()
@@ -2114,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():
@@ -2178,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()
@@ -2205,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()
@@ -2218,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")
@@ -2253,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)
@@ -2270,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])
@@ -2285,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])
@@ -2463,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 af7c90845213875ce757c948f1e29afaed35d5a9..54d70126287ef208d58e0a867639c64e22cb7268 100644 (file)
@@ -116,21 +116,11 @@ def pdnsutil_rectify(zonename):
     pdnsutil('rectify-zone', zonename)
 
 def sdig(*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_call([SDIG, '127.0.0.1', str(DNSPORT)] + list(args))
+        return subprocess.check_output(sdig_command_line).decode('utf-8')
     except subprocess.CalledProcessError as except_inst:
-        raise RuntimeError("sdig %s %s failed: %s" % (command, args, except_inst.output.decode('ascii', errors='replace')))
-
-def get_db_tsigkeys(keyname):
-    db, placeholder = get_auth_db()
-    cur = db.cursor()
-    cur.execute("""
-        SELECT name, algorithm, secret
-        FROM tsigkeys
-        WHERE name = """+placeholder, (keyname, ))
-    rows = cur.fetchall()
-    cur.close()
-    db.close()
-    keys = [{'name': row[0], 'algorithm': row[1], 'secret': row[2]} for row in rows]
-    print("DB TSIG keys:", keys)
-    return keys
+        raise RuntimeError("sdig %s failed: %s" % (sdig_command_line, except_inst.output.decode('ascii', errors='replace')))
index 2047e6d57498900fe79b517f4ab9e3137ee60cbc..ef2bbe8132cde099b266818d48d2755f3cc80575 100644 (file)
@@ -103,7 +103,7 @@ options {
 
                 namedconf.write("""
         zone "%s" {
-            type master;
+            type primary;
             file "%s.zone";
         };""" % (zone, zonename))
 
@@ -171,6 +171,20 @@ options {
             if cls._zone_keys.get(zonename, None):
                 cls.secureZone(confdir, zonename, cls._zone_keys.get(zonename))
 
+    @classmethod
+    def waitForTCPSocket(cls, ipaddress, port):
+        for try_number in range(0, 100):
+            try:
+                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+                sock.settimeout(1.0)
+                sock.connect((ipaddress, port))
+                sock.close()
+                return
+            except Exception as err:
+                if err.errno != errno.ECONNREFUSED:
+                    print(f'Error occurred: {try_number} {err}', file=sys.stderr)
+            time.sleep(0.1)
+
     @classmethod
     def startAuth(cls, confdir, ipaddress):
 
@@ -180,8 +194,6 @@ options {
         authcmd.append('--local-address=%s' % ipaddress)
         authcmd.append('--local-port=%s' % cls._authPort)
         authcmd.append('--loglevel=9')
-        authcmd.append('--enable-lua-records')
-        authcmd.append('--lua-health-checks-interval=1')
         authcmd.append('--zone-cache-refresh-interval=0')
         print(' '.join(authcmd))
         logFile = os.path.join(confdir, 'pdns.log')
@@ -189,18 +201,14 @@ options {
             cls._auths[ipaddress] = subprocess.Popen(authcmd, close_fds=True,
                                                      stdout=fdLog, stderr=fdLog,
                                                      env=cls._auth_env)
-
-        time.sleep(2)
+        cls.waitForTCPSocket(ipaddress, cls._authPort)
 
         if cls._auths[ipaddress].poll() is not None:
-            try:
-                cls._auths[ipaddress].kill()
-            except OSError as e:
-                if e.errno != errno.ESRCH:
-                    raise
-                with open(logFile, 'r') as fdLog:
-                    print(fdLog.read())
-            sys.exit(cls._auths[ipaddress].returncode)
+            print(f"\n*** startAuth log for {logFile} ***")
+            with open(logFile, 'r') as fdLog:
+                print(fdLog.read())
+            print(f"*** End startAuth log for {logFile} ***")
+            raise AssertionError('%s failed (%d)' % (authcmd, cls._auths[ipaddress].returncode))
 
     @classmethod
     def setUpSockets(cls):
@@ -241,23 +249,32 @@ options {
         cls.tearDownAuth()
 
     @classmethod
-    def tearDownAuth(cls):
-        if 'PDNSRECURSOR_FAST_TESTS' in os.environ:
-            delay = 0.1
-        else:
-            delay = 1.0
+    def killProcess(cls, p):
+        # Don't try to kill it if it's already dead
+        if p.poll() is not None:
+            return
+        try:
+            p.terminate()
+            for count in range(10):
+                x = p.poll()
+                if x is not None:
+                    break
+                time.sleep(0.1)
+            if x is None:
+                print("kill...", p, file=sys.stderr)
+                p.kill()
+                p.wait()
+        except OSError as e:
+            # There is a race-condition with the poll() and
+            # kill() statements, when the process is dead on the
+            # kill(), this is fine
+            if e.errno != errno.ESRCH:
+                raise
 
+    @classmethod
+    def tearDownAuth(cls):
         for _, auth in cls._auths.items():
-            try:
-                auth.terminate()
-                if auth.poll() is None:
-                    time.sleep(delay)
-                    if auth.poll() is None:
-                        auth.kill()
-                    auth.wait()
-            except OSError as e:
-                if e.errno != errno.ESRCH:
-                    raise
+            cls.killProcess(auth)
 
     @classmethod
     def sendUDPQuery(cls, query, timeout=2.0, decode=True, fwparams=dict()):
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
diff --git a/regression-tests.auth-py/kerberos-client/init-keytab.sh b/regression-tests.auth-py/kerberos-client/init-keytab.sh
new file mode 100755 (executable)
index 0000000..b3a68f9
--- /dev/null
@@ -0,0 +1,9 @@
+echo commands to run:
+echo Passwords entered should match those in the kerberos-server setup script
+echo rm -f kt.keytab
+echo ktutil
+echo add_entry -password -p testuser1@EXAMPLE.COM -k 1 -e aes256-cts-hmac-sha1-96
+echo add_entry -password -p testuser2@EXAMPLE.COM -k 1 -e aes256-cts-hmac-sha1-96
+echo add_entry -password -p DNS/ns1.example.net@EXAMPLE.COM -k 1 -e aes256-cts-hmac-sha1-96
+echo wkt kt.keytab
+echo quit
diff --git a/regression-tests.auth-py/kerberos-client/krb5.conf b/regression-tests.auth-py/kerberos-client/krb5.conf
new file mode 100755 (executable)
index 0000000..287fcc2
--- /dev/null
@@ -0,0 +1,9 @@
+[libdefaults]
+        default_realm = EXAMPLE.COM
+
+[realms]
+        EXAMPLE.COM = {
+                kdc = kerberos-server:1188
+                admin_server = kerberos-server:1749
+        }
+
diff --git a/regression-tests.auth-py/kerberos-client/kt.keytab b/regression-tests.auth-py/kerberos-client/kt.keytab
new file mode 100644 (file)
index 0000000..f70cdff
Binary files /dev/null and b/regression-tests.auth-py/kerberos-client/kt.keytab differ
diff --git a/regression-tests.auth-py/kerberos-client/update-policy.lua b/regression-tests.auth-py/kerberos-client/update-policy.lua
new file mode 100644 (file)
index 0000000..509927f
--- /dev/null
@@ -0,0 +1,4 @@
+function updatepolicy(arg)
+  princ = arg:getPeerPrincipal()
+  return princ == "testuser2@EXAMPLE.COM"
+end
diff --git a/regression-tests.auth-py/kerberos-server/Dockerfile b/regression-tests.auth-py/kerberos-server/Dockerfile
new file mode 100644 (file)
index 0000000..b2d3f42
--- /dev/null
@@ -0,0 +1,20 @@
+FROM debian:bullseye
+
+EXPOSE 749 88
+
+ENV DEBIAN_FRONTEND noninteractive
+# The -qq implies --yes
+RUN apt-get -qq update
+RUN apt-get -qq install locales krb5-kdc krb5-admin-server
+RUN apt-get -qq clean
+
+#RUN locale-gen "en_US.UTF-8"
+#RUN echo "LC_ALL=\"en_US.UTF-8\"" >> /etc/default/locale
+
+ENV REALM ${REALM:-EXAMPLE.COM}
+ENV SUPPORTED_ENCRYPTION_TYPES ${SUPPORTED_ENCRYPTION_TYPES:-aes256-cts-hmac-sha1-96:normal}
+ENV KADMIN_PRINCIPAL ${KADMIN_PRINCIPAL:-kadmin/admin}
+ENV KADMIN_PASSWORD ${KADMIN_PASSWORD:-MITiys4K5}
+
+COPY kerberos-init.sh /tmp/
+CMD /tmp/kerberos-init.sh
diff --git a/regression-tests.auth-py/kerberos-server/docker-compose.yml b/regression-tests.auth-py/kerberos-server/docker-compose.yml
new file mode 100644 (file)
index 0000000..8afa3f9
--- /dev/null
@@ -0,0 +1,10 @@
+version: "2"
+services:
+  kerberos:
+    build: .
+    ports:
+      - "1188:88"
+      - "1749:749"
+    volumes:
+      # This is needed otherwise there won't be enough entropy to generate a new kerberos realm
+      - /dev/urandom:/dev/random
diff --git a/regression-tests.auth-py/kerberos-server/kerberos-init.sh b/regression-tests.auth-py/kerberos-server/kerberos-init.sh
new file mode 100755 (executable)
index 0000000..34dca0c
--- /dev/null
@@ -0,0 +1,57 @@
+#!/bin/bash
+
+KADMIN_PRINCIPAL_FULL=$KADMIN_PRINCIPAL@$REALM
+
+echo "REALM: $REALM"
+echo "KADMIN_PRINCIPAL_FULL: $KADMIN_PRINCIPAL_FULL"
+echo "KADMIN_PASSWORD: $KADMIN_PASSWORD"
+echo ""
+
+KDC_KADMIN_SERVER=$(hostname -f)
+tee /etc/krb5.conf <<EOF
+[libdefaults]
+       default_realm = $REALM
+
+[realms]
+       $REALM = {
+               kdc_ports = 88,750
+               kadmind_port = 749
+               kdc = $KDC_KADMIN_SERVER
+               admin_server = $KDC_KADMIN_SERVER
+       }
+EOF
+echo ""
+
+tee /etc/krb5kdc/kdc.conf <<EOF
+[realms]
+       $REALM = {
+               acl_file = /etc/krb5kdc/kadm5.acl
+               max_renewable_life = 7d 0h 0m 0s
+               supported_enctypes = $SUPPORTED_ENCRYPTION_TYPES
+               default_principal_flags = +preauth
+       }
+EOF
+
+tee /etc/krb5kdc/kadm5.acl <<EOF
+$KADMIN_PRINCIPAL_FULL *
+EOF
+
+MASTER_PASSWORD=$(tr -cd '[:alnum:]' < /dev/urandom | fold -w30 | head -n1)
+# This command also starts the krb5-kdc and krb5-admin-server services
+krb5_newrealm <<EOF
+$MASTER_PASSWORD
+$MASTER_PASSWORD
+EOF
+
+kadmin.local -q "delete_principal -force $KADMIN_PRINCIPAL_FULL"
+kadmin.local -q "addprinc -pw $KADMIN_PASSWORD $KADMIN_PRINCIPAL_FULL"
+
+kadmin.local -q "delete_principal -force testuser1@$REALM"
+kadmin.local -q "delete_principal -force testuser2@$REALM"
+kadmin.local -q "delete_principal -force DNS/ns1.example.net@$REALM"
+kadmin.local -q "addprinc -pw pw1 testuser1@$REALM"
+kadmin.local -q "addprinc -pw pw2 testuser2@$REALM"
+kadmin.local -q "addprinc -pw pw3 DNS/ns1.example.net@$REALM"
+
+krb5kdc
+kadmind -nofork
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 496652c485077109ecc01fe0a8837851980b00de..22f0b14639c339f9f5e01ed1dc98a027f9644c7b 100755 (executable)
@@ -16,6 +16,7 @@ mkdir -p configs
 
 export PDNS=${PDNS:-${PWD}/../pdns/pdns_server}
 export PDNSUTIL=${PDNSUTIL:-${PWD}/../pdns/pdnsutil}
+export PDNSCONTROL=${PDNSCONTROL:-${PWD}/../pdns/pdns_control}
 
 export PREFIX=127.0.0
 
@@ -26,9 +27,20 @@ for bin in "$PDNS" "$PDNSUTIL"; do
   fi
 done
 
-set -e
 if [ "${PDNS_DEBUG}" = "YES" ]; then
   set -x
 fi
 
-nosetests --with-xunit $@
+ignore="--ignore=test_GSSTSIG.py"
+if [ "${WITHKERBEROS}" = "YES" ]; then
+    ignore=""
+    (cd kerberos-server && sudo docker compose up --detach --build)
+fi
+
+pytest --junitxml=pytest.xml $ignore $@
+ret=$?
+
+if [ "${WITHKERBEROS}" = "YES" ]; then
+    (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)
diff --git a/regression-tests.auth-py/test_AnyBind.py b/regression-tests.auth-py/test_AnyBind.py
new file mode 100644 (file)
index 0000000..e0b6c4c
--- /dev/null
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+import dns
+import os
+import socket
+
+from authtests import AuthTest
+
+
+class TestBindAny(AuthTest):
+    _config_template = """
+launch=bind
+"""
+
+    _zones = {
+        'example.org': """
+example.org.                 3600 IN SOA  {soa}
+example.org.                 3600 IN NS   ns1.example.org.
+example.org.                 3600 IN NS   ns2.example.org.
+ns1.example.org.             3600 IN A    192.0.2.10
+ns2.example.org.             3600 IN A    192.0.2.11
+
+www.example.org.             3600 IN A    192.0.2.5
+        """,
+    }
+
+    @classmethod
+    def setUpClass(cls):
+        cls.setUpSockets()
+
+        cls.startResponders()
+
+        confdir = os.path.join('configs', cls._confdir)
+        cls.createConfigDir(confdir)
+
+        cls.generateAllAuthConfig(confdir)
+        cls.startAuth(confdir, "0.0.0.0")
+
+        print("Launching tests..")
+
+    @classmethod
+    def setUpSockets(cls):
+         print("Setting up UDP socket..")
+         cls._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+         cls._sock.settimeout(2.0)
+         cls._sock.connect((cls._PREFIX + ".2", cls._authPort))
+
+    def testA(self):
+        """Test to see if we get a reply from 127.0.0.2 if auth is bound to ANY address"""
+        query = dns.message.make_query('www.example.org', 'A')
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, 0)
diff --git a/regression-tests.auth-py/test_GSSTSIG.py b/regression-tests.auth-py/test_GSSTSIG.py
new file mode 100644 (file)
index 0000000..066f6f9
--- /dev/null
@@ -0,0 +1,135 @@
+#!/usr/bin/env python
+import dns
+import os
+import subprocess
+
+from authtests import AuthTest
+
+
+class GSSTSIGBase(AuthTest):
+    _config_template_default = """
+module-dir=../regression-tests/modules
+daemon=no
+socket-dir={confdir}
+cache-ttl=0
+negquery-cache-ttl=0
+query-cache-ttl=0
+log-dns-queries=yes
+log-dns-details=yes
+loglevel=9
+distributor-threads=1"""
+
+    _config_template = """
+launch=gsqlite3
+gsqlite3-database=configs/auth/powerdns.sqlite
+gsqlite3-pragma-foreign-keys=yes
+gsqlite3-dnssec=yes
+enable-gss-tsig=yes
+allow-dnsupdate-from=0.0.0.0/0
+dnsupdate=yes
+"""
+    _auth_env = {'KRB5_CONFIG' : './kerberos-client/krb5.conf',
+                 'KRB5_KTNAME' : './kerberos-client/kt.keytab'
+                 }
+
+    @classmethod
+    def setUpClass(cls):
+        super(GSSTSIGBase, cls).setUpClass()
+        os.system("$PDNSUTIL --config-dir=configs/auth delete-zone example.net")
+        os.system("$PDNSUTIL --config-dir=configs/auth delete-zone noacceptor.net")
+        os.system("$PDNSUTIL --config-dir=configs/auth delete-zone wrongacceptor.net")
+        os.system("$PDNSUTIL --config-dir=configs/auth create-zone example.net")
+        os.system("$PDNSUTIL --config-dir=configs/auth create-zone noacceptor.net")
+        os.system("$PDNSUTIL --config-dir=configs/auth create-zone wrongacceptor.net")
+
+        os.system("$PDNSUTIL --config-dir=configs/auth add-record example.net . SOA 3600 'ns1.example.net otto.example.net 2022010403 10800 3600 604800 3600'")
+        os.system("$PDNSUTIL --config-dir=configs/auth add-record noacceptor.net . SOA 3600 'ns1.noacceptor.net otto.example.net 2022010403 10800 3600 604800 3600'")
+        os.system("$PDNSUTIL --config-dir=configs/auth add-record wrongacceptor.net . SOA 3600 'ns1.wrongacceptor.net otto.example.net 2022010403 10800 3600 604800 3600'")
+
+        os.system("$PDNSUTIL --config-dir=configs/auth set-meta example.net GSS-ACCEPTOR-PRINCIPAL DNS/ns1.example.net@EXAMPLE.COM")
+        os.system("$PDNSUTIL --config-dir=configs/auth set-meta wrongacceptor.net GSS-ACCEPTOR-PRINCIPAL DNS/ns1.example.net@EXAMPLE.COM")
+        os.system("$PDNSUTIL --config-dir=configs/auth set-meta example.net TSIG-ALLOW-DNSUPDATE testuser1@EXAMPLE.COM")
+
+    def kinit(self, user):
+        ret = subprocess.run(["kinit", "-Vt", "./kerberos-client/kt.keytab", user], env=self._auth_env)
+        self.assertEqual(ret.returncode, 0)
+
+    def nsupdate(self, commands, expected=0):
+        full = "server 127.0.0.1 %s\n" % self._authPort
+        full += commands + "\nsend\nquit\n"
+        ret = subprocess.run(["nsupdate", "-g"], input=full, env=self._auth_env, capture_output=True, text=True)
+        self.assertEqual(ret.returncode, expected)
+
+    def checkInDB(self, zone, record):
+        ret = os.system("$PDNSUTIL --config-dir=configs/auth list-zone %s | egrep -q %s" % (zone, record))
+        self.assertEqual(ret, 0)
+
+    def checkNotInDB(self, zone, record):
+        ret = os.system("$PDNSUTIL --config-dir=configs/auth list-zone %s | fgrep -q %s" % (zone, record))
+        self.assertNotEqual(ret, 0)
+
+class TestBasicGSSTSIG(GSSTSIGBase):
+
+    _config_template = """
+launch=gsqlite3
+gsqlite3-database=configs/auth/powerdns.sqlite
+gsqlite3-pragma-foreign-keys=yes
+gsqlite3-dnssec=yes
+enable-gss-tsig=yes
+allow-dnsupdate-from=0.0.0.0/0
+dnsupdate=yes
+"""
+    def testAllowedUpdate(self):
+        self.checkNotInDB('example.net', 'inserted1.example.net')
+        self.kinit("testuser1")
+        self.nsupdate("add inserted1.example.net 10 A 1.2.3.1")
+        self.checkInDB('example.net', '^inserted1.example.net.*10.*IN.*A.*1.2.3.1$')
+
+    def testDisallowedUpdate(self):
+        self.kinit("testuser2")
+        self.nsupdate("add inserted2.example.net 10 A 1.2.3.2", 2)
+        self.checkNotInDB('example.net', 'inserted2.example.net')
+
+    def testNoAcceptor(self):
+        self.kinit("testuser1")
+        self.nsupdate("add inserted3.noacceptor.net 10 A 1.2.3.3", 2)
+        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('wrongacceptor.net', 'inserted4.wrongacceptor.net')
+
+class TestLuaGSSTSIG(GSSTSIGBase):
+
+    _config_template = """
+launch=gsqlite3
+gsqlite3-database=configs/auth/powerdns.sqlite
+gsqlite3-pragma-foreign-keys=yes
+gsqlite3-dnssec=yes
+enable-gss-tsig=yes
+allow-dnsupdate-from=0.0.0.0/0
+dnsupdate=yes
+lua-dnsupdate-policy-script=kerberos-client/update-policy.lua
+"""
+    def testDisallowedByLuaUpdate(self):
+        self.kinit("testuser1")
+        self.nsupdate("add inserted10.example.net 10 A 1.2.3.10", 0) # Lua deny is still a NOERROR
+        self.checkNotInDB('example.net', 'inserted10.example.net')
+
+    def testAllowedByLuaUpdate(self):
+        self.kinit("testuser2")
+        self.nsupdate("add inserted11.example.net 10 A 1.2.3.11")
+        self.checkInDB('example.net', '^inserted11.example.net.*10.*IN.*A.*1.2.3.11$')
+
+
+    def testNoAcceptor(self):
+        self.kinit("testuser1")
+        self.nsupdate("add inserted12.noacceptor.net 10 A 1.2.3.12", 2)
+        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('wrongacceptor.net', 'inserted13.wrongacceptor.net')
+
index c1803a66c44ee40d287955c35ea115c826fd2b6f..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):
@@ -148,19 +148,13 @@ negquery-cache-ttl=60
             answerPos = answerPos + 1
 
     def test_a_XFR(self):
-        print("x1")
         self.waitUntilCorrectSerialIsLoaded(1)
-        print("x2")
         self.checkFullZone(1)
-        print("x3")
 
         self.waitUntilCorrectSerialIsLoaded(2)
-        print("x4")
         self.checkFullZone(2)
-        print("x5")
 
         self.waitUntilCorrectSerialIsLoaded(3)
-        print("x7")
         self.checkFullZone(3, data=["""
 $ORIGIN example.""","""
 @        86400   SOA    foo bar 3 2 3 4 5
@@ -171,10 +165,8 @@ ns1.example.    4242    A       192.0.2.1
 ns2.example.    4242    A       192.0.2.2
 newrecord.example.        8484    A       192.0.2.42
 """])
-        print("x8")
 
         self.waitUntilCorrectSerialIsLoaded(5)
-        print("x7")
         self.checkFullZone(5, data=["""
 $ORIGIN example.""","""
 @        86400   SOA    foo bar 5 2 3 4 5
@@ -186,7 +178,6 @@ ns1.example.    4242    A       192.0.2.1
 ns2.example.    4242    A       192.0.2.2
 newrecord.example.        8484    A       192.0.2.42
 """])
-        print("x8")
 
 
     # _b_ because we expect post-XFR testing state
@@ -223,7 +214,6 @@ newrecord.example.        8484    A       192.0.2.42
 
     def test_d_XFR(self):
         self.waitUntilCorrectSerialIsLoaded(8)
-        print("x7")
         self.checkFullZone(7, data=["""
 $ORIGIN example.""","""
 @        86400   SOA    foo bar 8 2 3 4 5
@@ -235,10 +225,9 @@ ns1.example.    4242    A       192.0.2.1
 ns2.example.    4242    A       192.0.2.2
 newrecord.example.        8484    A       192.0.2.42
 """])
-        print("x8")
         ret = subprocess.check_output([os.environ['PDNSUTIL'],
                            '--config-dir=configs/auth',
                            'list-zone', 'example'], stderr=subprocess.STDOUT)
         rets = ret.split(b'\n')
 
-        self.assertEqual(1, sum(b'SOA' in l for l in ret.split(b'\n')))
\ No newline at end of file
+        self.assertEqual(1, sum(b'SOA' in l for l in ret.split(b'\n')))
index 409d3951efee1f6134a10cbcb3876388a8480b90..42aac90372a838b4fa9ff5ffd4abbf70d6a384bc 100644 (file)
@@ -10,6 +10,8 @@ from authtests import AuthTest
 
 from http.server import BaseHTTPRequestHandler, HTTPServer
 
+webserver = None
+
 class FakeHTTPServer(BaseHTTPRequestHandler):
     def _set_headers(self, response_code=200):
         self.send_response(response_code)
@@ -40,6 +42,8 @@ geoip-database-files=../modules/geoipbackend/regression-tests/GeoLiteCity.mmdb
 edns-subnet-processing=yes
 launch=bind geoip
 any-to-tcp=no
+enable-lua-records
+lua-health-checks-interval=1
 """
 
     _zones = {
@@ -56,44 +60,60 @@ web3.example.org.            3600 IN A    {prefix}.103
 
 all.ifportup                 3600 IN LUA  A     "ifportup(8080, {{'{prefix}.101', '{prefix}.102'}})"
 some.ifportup                3600 IN LUA  A     "ifportup(8080, {{'192.168.42.21', '{prefix}.102'}})"
+multi.ifportup               3600 IN LUA  A     "ifportup(8080, {{ {{'192.168.42.23'}}, {{'192.168.42.21', '{prefix}.102'}}, {{'{prefix}.101'}} }})"
 none.ifportup                3600 IN LUA  A     "ifportup(8080, {{'192.168.42.21', '192.168.21.42'}})"
 all.noneup.ifportup          3600 IN LUA  A     "ifportup(8080, {{'192.168.42.21', '192.168.21.42'}}, {{ backupSelector='all' }})"
 
+hashed.example.org.          3600 IN LUA  A     "pickhashed({{ '1.2.3.4', '4.3.2.1' }})"
+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' }} )"
 v6-bogus.rand.example.org.   3600 IN LUA  AAAA  "pickrandom({{'{prefix}.101', '{prefix}.102'}})"
-v6.rand.example.org.         3600 IN LUA  AAAA  "pickrandom({{'2001:db8:a0b:12f0::1', 'fe80::2a1:9bff:fe9b:f268'}})"
-closest.geo                  3600 IN LUA  A     "pickclosest({{'1.1.1.2','1.2.3.4'}})"
+v6.rand.example.org.         3600 IN LUA  AAAA  "pickrandom({{ '2001:db8:a0b:12f0::1', 'fe80::2a1:9bff:fe9b:f268' }})"
+closest.geo                  3600 IN LUA  A     "pickclosest({{ '1.1.1.2', '1.2.3.4' }})"
 empty.rand.example.org.      3600 IN LUA  A     "pickrandom()"
 timeout.example.org.         3600 IN LUA  A     "; local i = 0 ;  while i < 1000 do pickrandom() ; i = i + 1 end return '1.2.3.4'"
 wrand.example.org.           3600 IN LUA  A     "pickwrandom({{ {{30, '{prefix}.102'}}, {{15, '{prefix}.103'}} }})"
+wrand-txt.example.org.       3600 IN LUA  TXT   "pickwrandom({{ {{30, 'bob'}}, {{15, 'alice'}} }})"
+all.example.org.             3600 IN LUA  A     "all({{'1.2.3.4','4.3.2.1'}})"
 
 config    IN    LUA    LUA ("settings={{stringmatch='Programming in Lua'}} "
                             "EUWips={{'{prefix}.101','{prefix}.102'}}      "
                             "EUEips={{'192.168.42.101','192.168.42.102'}}  "
-                            "NLips={{'{prefix}.111', '{prefix}.112'}}  "
-                            "USAips={{'{prefix}.103'}}                     ")
+                            "NLips={{'{prefix}.111', '{prefix}.112'}}      "
+                            "USAips={{'{prefix}.103', '192.168.42.105'}}   ")
 
 usa          IN    LUA    A   ( ";include('config')                         "
                                 "return ifurlup('http://www.lua.org:8080/', "
-                                "{{USAips, EUEips}}, settings)              ")
+                                "USAips, settings)                          ")
+
+usa-ext      IN    LUA    A   ( ";include('config')                         "
+                                "return ifurlup('http://www.lua.org:8080/', "
+                                "{{EUEips, USAips}}, settings)              ")
 
 mix.ifurlup  IN    LUA    A   ("ifurlup('http://www.other.org:8080/ping.json', "
                                "{{ '192.168.42.101', '{prefix}.101' }},        "
                                "{{ stringmatch='pong' }})                      ")
 
-eu-west      IN    LUA    A   ( ";include('config')                         "
-                                "return ifurlup('http://www.lua.org:8080/', "
-                                "{{EUWips, EUEips, USAips}}, settings)      ")
-
 ifurlextup   IN    LUA    A   "ifurlextup({{{{['192.168.0.1']='http://{prefix}.101:8080/404',['192.168.0.2']='http://{prefix}.102:8080/404'}}, {{['192.168.0.3']='http://{prefix}.101:8080/'}}}})"
 
 nl           IN    LUA    A   ( ";include('config')                                "
                                 "return ifportup(8081, NLips) ")
 latlon.geo      IN LUA    TXT "latlon()"
 continent.geo   IN LUA    TXT ";if(continent('NA')) then return 'true' else return 'false' end"
+continent-code.geo   IN LUA    TXT ";return continentCode()"
 asnum.geo       IN LUA    TXT ";if(asnum('4242')) then return 'true' else return 'false' end"
 country.geo     IN LUA    TXT ";if(country('US')) then return 'true' else return 'false' end"
+country-code.geo     IN LUA    TXT ";return countryCode()"
+region.geo      IN LUA    TXT ";if(region('CA')) then return 'true' else return 'false' end"
+region-code.geo      IN LUA    TXT ";return regionCode()"
 latlonloc.geo   IN LUA    TXT "latlonloc()"
 
 true.netmask     IN LUA   TXT   ( ";if(netmask({{ '{prefix}.0/24' }})) "
@@ -130,6 +150,11 @@ resolve          IN    LUA    A   ";local r=resolve('localhost', 1) local t={{}}
 
 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', 'A')[1]"
         """,
         'createforward6.example.org': """
 createforward6.example.org.                 3600 IN SOA  {soa}
@@ -143,6 +168,9 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
 
     @classmethod
     def startResponders(cls):
+        global webserver
+        if webserver: return  # it is already running
+
         webserver = threading.Thread(name='HTTP Listener',
                                      target=cls.HTTPResponder,
                                      args=[8080]
@@ -183,6 +211,18 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
         self.assertRcodeEqual(res, dns.rcode.NOERROR)
         self.assertAnyRRsetInAnswer(res, expected)
 
+    def testPickRandomTxt(self):
+        """
+        Basic pickrandom() test with a set of TXT records
+        """
+        expected = [dns.rrset.from_text('rand-txt.example.org.', 0, dns.rdataclass.IN, 'TXT', 'bob'),
+                    dns.rrset.from_text('rand-txt.example.org.', 0, dns.rdataclass.IN, 'TXT', 'alice')]
+        query = dns.message.make_query('rand-txt.example.org', 'TXT')
+
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertAnyRRsetInAnswer(res, expected)
+
     def testBogusV6PickRandom(self):
         """
         Test a bogus AAAA pickrandom() record  with a set of v4 addr
@@ -215,6 +255,22 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
         res = self.sendUDPQuery(query)
         self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
 
+    def testPickRandomSampleTxt(self):
+        """
+        Basic pickrandomsample() test with a set of TXT records
+        """
+        expected = [dns.rrset.from_text('randn-txt.example.org.', 0, dns.rdataclass.IN, 'TXT', 'bob', 'alice'),
+                    dns.rrset.from_text('randn-txt.example.org.', 0, dns.rdataclass.IN, 'TXT', 'bob', 'john'),
+                    dns.rrset.from_text('randn-txt.example.org.', 0, dns.rdataclass.IN, 'TXT', 'alice', 'bob'),
+                    dns.rrset.from_text('randn-txt.example.org.', 0, dns.rdataclass.IN, 'TXT', 'alice', 'john'),
+                    dns.rrset.from_text('randn-txt.example.org.', 0, dns.rdataclass.IN, 'TXT', 'john', 'bob'),
+                    dns.rrset.from_text('randn-txt.example.org.', 0, dns.rdataclass.IN, 'TXT', 'john', 'alice')]
+        query = dns.message.make_query('randn-txt.example.org', 'TXT')
+
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertIn(res.answer[0], expected)
+
     def testWRandom(self):
         """
         Basic pickwrandom() test with a set of A records
@@ -229,6 +285,18 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
         self.assertRcodeEqual(res, dns.rcode.NOERROR)
         self.assertAnyRRsetInAnswer(res, expected)
 
+    def testWRandomTxt(self):
+        """
+        Basic pickwrandom() test with a set of TXT records
+        """
+        expected = [dns.rrset.from_text('wrand-txt.example.org.', 0, dns.rdataclass.IN, 'TXT', 'bob'),
+                    dns.rrset.from_text('wrand-txt.example.org.', 0, dns.rdataclass.IN, 'TXT', 'alice')]
+        query = dns.message.make_query('wrand-txt.example.org', 'TXT')
+
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertAnyRRsetInAnswer(res, expected)
+
     def testIfportup(self):
         """
         Basic ifportup() test
@@ -266,6 +334,33 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
         self.assertRcodeEqual(res, dns.rcode.NOERROR)
         self.assertAnyRRsetInAnswer(res, expected)
 
+    def testIfportupWithSomeDownMultiset(self):
+        """
+        Basic ifportup() test with some ports DOWN from multiple sets
+        """
+        query = dns.message.make_query('multi.ifportup.example.org', 'A')
+        expected = [
+            dns.rrset.from_text('multi.ifportup.example.org.', 0, dns.rdataclass.IN, 'A',
+                                '192.168.42.21'),
+            dns.rrset.from_text('multi.ifportup.example.org.', 0, dns.rdataclass.IN, 'A',
+                                '192.168.42.23'),
+            dns.rrset.from_text('multi.ifportup.example.org.', 0, dns.rdataclass.IN, 'A',
+                                '{prefix}.102'.format(prefix=self._PREFIX)),
+            dns.rrset.from_text('multi.ifportup.example.org.', 0, dns.rdataclass.IN, 'A',
+                                '{prefix}.101'.format(prefix=self._PREFIX))
+        ]
+
+        # we first expect any of the IPs as no check has been performed yet
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertAnyRRsetInAnswer(res, expected)
+
+        # An ip is up in 2 sets, but we expect only the one from the middle set
+        expected = [expected[2]]
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertAnyRRsetInAnswer(res, expected)
+
     def testIfportupWithAllDown(self):
         """
         Basic ifportup() test with all ports DOWN
@@ -298,7 +393,7 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
         res = self.sendUDPQuery(query)
         self.assertRcodeEqual(res, dns.rcode.NOERROR)
 
-        time.sleep(2)
+        time.sleep(3)
         res = self.sendUDPQuery(query)
         self.assertRcodeEqual(res, dns.rcode.NOERROR)
         self.assertEqual(res.answer, expected)
@@ -310,7 +405,7 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
         reachable = [
             '{prefix}.103'.format(prefix=self._PREFIX)
         ]
-        unreachable = ['192.168.42.101', '192.168.42.102']
+        unreachable = ['192.168.42.105']
         ips = reachable + unreachable
         all_rrs = []
         reachable_rrs = []
@@ -325,8 +420,36 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
         self.assertRcodeEqual(res, dns.rcode.NOERROR)
         self.assertAnyRRsetInAnswer(res, all_rrs)
 
-        # the timeout in the LUA health checker is 1 second, so we make sure to wait slightly longer here
-        time.sleep(2)
+        # the timeout in the LUA health checker is 2 second, so we make sure to wait slightly longer here
+        time.sleep(3)
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertAnyRRsetInAnswer(res, reachable_rrs)
+
+    def testIfurlupMultiSet(self):
+        """
+        Basic ifurlup() test with mutiple sets
+        """
+        reachable = [
+            '{prefix}.103'.format(prefix=self._PREFIX)
+        ]
+        unreachable = ['192.168.42.101', '192.168.42.102', '192.168.42.105']
+        ips = reachable + unreachable
+        all_rrs = []
+        reachable_rrs = []
+        for ip in ips:
+            rr = dns.rrset.from_text('usa-ext.example.org.', 0, dns.rdataclass.IN, 'A', ip)
+            all_rrs.append(rr)
+            if ip in reachable:
+                reachable_rrs.append(rr)
+
+        query = dns.message.make_query('usa-ext.example.org', 'A')
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertAnyRRsetInAnswer(res, all_rrs)
+
+        # the timeout in the LUA health checker is 2 second, so we make sure to wait slightly longer here
+        time.sleep(3)
         res = self.sendUDPQuery(query)
         self.assertRcodeEqual(res, dns.rcode.NOERROR)
         self.assertAnyRRsetInAnswer(res, reachable_rrs)
@@ -368,7 +491,7 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
         self.assertRcodeEqual(res, dns.rcode.NOERROR)
         self.assertAnyRRsetInAnswer(res, all_rrs)
 
-        time.sleep(2)
+        time.sleep(3)
         res = self.sendUDPQuery(query)
         self.assertRcodeEqual(res, dns.rcode.NOERROR)
         self.assertAnyRRsetInAnswer(res, reachable_rrs)
@@ -475,6 +598,63 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
             self.assertRcodeEqual(res, dns.rcode.NOERROR)
             self.assertRRsetInAnswer(res, expected)
 
+    def testCountryCode(self):
+        """
+        Basic countryCode() test
+        """
+        queries = [
+            ('1.1.1.0', 24,  '"au"'),
+            ('1.2.3.0', 24,  '"us"'),
+            ('17.1.0.0', 16, '"--"')
+        ]
+        name = 'country-code.geo.example.org.'
+        for (subnet, mask, txt) in queries:
+            ecso = clientsubnetoption.ClientSubnetOption(subnet, mask)
+            query = dns.message.make_query(name, 'TXT', 'IN', use_edns=True, payload=4096, options=[ecso])
+            expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'TXT', txt)
+
+            res = self.sendUDPQuery(query)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expected)
+
+    def testRegion(self):
+        """
+        Basic region() test
+        """
+        queries = [
+            ('1.1.1.0', 24,  '"false"'),
+            ('1.2.3.0', 24,  '"true"'),
+            ('17.1.0.0', 16, '"false"')
+        ]
+        name = 'region.geo.example.org.'
+        for (subnet, mask, txt) in queries:
+            ecso = clientsubnetoption.ClientSubnetOption(subnet, mask)
+            query = dns.message.make_query(name, 'TXT', 'IN', use_edns=True, payload=4096, options=[ecso])
+            expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'TXT', txt)
+
+            res = self.sendUDPQuery(query)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expected)
+
+    def testRegionCode(self):
+        """
+        Basic regionCode() test
+        """
+        queries = [
+            ('1.1.1.0', 24,  '"--"'),
+            ('1.2.3.0', 24,  '"ca"'),
+            ('17.1.0.0', 16, '"--"')
+        ]
+        name = 'region-code.geo.example.org.'
+        for (subnet, mask, txt) in queries:
+            ecso = clientsubnetoption.ClientSubnetOption(subnet, mask)
+            query = dns.message.make_query(name, 'TXT', 'IN', use_edns=True, payload=4096, options=[ecso])
+            expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'TXT', txt)
+
+            res = self.sendUDPQuery(query)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expected)
+
     def testContinent(self):
         """
         Basic continent() test
@@ -494,6 +674,25 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
             self.assertRcodeEqual(res, dns.rcode.NOERROR)
             self.assertRRsetInAnswer(res, expected)
 
+    def testContinentCode(self):
+        """
+        Basic continentCode() test
+        """
+        queries = [
+            ('1.1.1.0', 24,  '"oc"'),
+            ('1.2.3.0', 24,  '"na"'),
+            ('17.1.0.0', 16, '"--"')
+        ]
+        name = 'continent-code.geo.example.org.'
+        for (subnet, mask, txt) in queries:
+            ecso = clientsubnetoption.ClientSubnetOption(subnet, mask)
+            query = dns.message.make_query(name, 'TXT', 'IN', use_edns=True, payload=4096, options=[ecso])
+            expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'TXT', txt)
+
+            res = self.sendUDPQuery(query)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expected)
+
     def testClosest(self):
         """
         Basic pickclosest() test
@@ -513,6 +712,17 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
             self.assertRcodeEqual(res, dns.rcode.NOERROR)
             self.assertRRsetInAnswer(res, expected)
 
+    def testAll(self):
+        """
+        Basic all() test
+        """
+        expected = [dns.rrset.from_text('all.example.org.', 0, dns.rdataclass.IN, dns.rdatatype.A, '1.2.3.4', '4.3.2.1')]
+        query = dns.message.make_query('all.example.org.', 'A')
+
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertEqual(res.answer, expected)
+
     def testNetmask(self):
         """
         Basic netmask() test
@@ -569,14 +779,109 @@ 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() and pickchashed() test with a set of A records
+        As the `bestwho` is hashed, we should always get the same answer
+        """
+        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)
+                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, query['expected'])
+
+    def testCWHashedTxt(self):
         """
-        Basic pickwhashed() test with a set of A records
+        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.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-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])
+
+    def testHashed(self):
+        """
+        Basic pickhashed() test with a set of A records
+        As the `bestwho` is hashed, we should always get the same answer
+        """
+        expected = [dns.rrset.from_text('hashed.example.org.', 0, dns.rdataclass.IN, 'A', '1.2.3.4'),
+                    dns.rrset.from_text('hashed.example.org.', 0, dns.rdataclass.IN, 'A', '4.3.2.1')]
+        query = dns.message.make_query('hashed.example.org', 'A')
+
+        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 testHashedV6(self):
+        """
+        Basic pickhashed() test with a set of AAAA records
+        As the `bestwho` is hashed, we should always get the same answer
+        """
+        expected = [dns.rrset.from_text('hashed-v6.example.org.', 0, dns.rdataclass.IN, 'AAAA', '2001:db8:a0b:12f0::1'),
+                    dns.rrset.from_text('hashed-v6.example.org.', 0, dns.rdataclass.IN, 'AAAA', 'fe80::2a1:9bff:fe9b:f268')]
+        query = dns.message.make_query('hashed-v6.example.org', 'AAAA')
+
+        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 testHashedTXT(self):
+        """
+        Basic pickhashed() test with a set of TXT records
+        As the `bestwho` is hashed, we should always get the same answer
+        """
+        expected = [dns.rrset.from_text('hashed-txt.example.org.', 0, dns.rdataclass.IN, 'TXT', 'bob'),
+                    dns.rrset.from_text('hashed-txt.example.org.', 0, dns.rdataclass.IN, 'TXT', 'alice')]
+        query = dns.message.make_query('hashed-txt.example.org', 'TXT')
 
         first = self.sendUDPQuery(query)
         self.assertRcodeEqual(first, dns.rcode.NOERROR)
@@ -687,9 +992,12 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
                 "1-2-3-4.foo.bar.baz.quux": "0.0.0.0",
                 "ip-1-2-3-4": "1.2.3.4",
                 "ip-is-here-for-you-1-2-3-4": "1.2.3.4",
+                "40414243": "64.65.66.67",
+                "p40414243": "64.65.66.67",
                 "ip40414243": "64.65.66.67",
-                "ipp40414243": "0.0.0.0",
+                "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, {
@@ -698,6 +1006,8 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
             }),
             ".createforward6.example.org." : (dns.rdatatype.AAAA, {
                 "2001--db8" : "2001::db8",
+                "20010002000300040005000600070db8" : "2001:2:3:4:5:6:7:db8",
+                "blabla20010002000300040005000600070db8" : "2001:2:3:4:5:6:7:db8",
                 "4000-db8--1" : "fe80::1"   # filtered, with fallback address override
             }),
             ".createreverse6.example.org." : (dns.rdatatype.PTR, {
@@ -721,6 +1031,72 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
                 self.assertRcodeEqual(res, dns.rcode.NOERROR)
                 self.assertEqual(res.answer, response.answer)
 
+    def _getCounter(self, tcp=False):
+        """
+        Helper function for shared/non-shared testing
+        """
+        name = 'counter.example.org.'
+
+        query = dns.message.make_query(name, 'TXT')
+        responses = []
+
+        sender = self.sendTCPQuery if tcp else self.sendUDPQuery
+
+        for i in range(50):
+            res = sender(query)
+            responses.append(res.answer[0][0])
+
+        return(responses)
+
+    def testCounter(self):
+        """
+        Test non-shared behaviour
+        """
+
+        resUDP = set(self._getCounter(tcp=False))
+        resTCP = set(self._getCounter(tcp=True))
+
+        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
+edns-subnet-processing=yes
+launch=bind geoip
+any-to-tcp=no
+enable-lua-records=shared
+lua-health-checks-interval=1
+"""
+
+    def testCounter(self):
+        """
+        Test shared behaviour
+        """
+
+        resUDP = set(self._getCounter(tcp=False))
+        resTCP = set(self._getCounter(tcp=True))
+
+        self.assertEqual(len(resUDP), 50)
+        self.assertEqual(len(resTCP), 50)
 
 if __name__ == '__main__':
     unittest.main()
index 2d2b7ad575e37e3bc9883eb97126819a4ad61af3..60127f22ffe5833ecd4ba036d739a78802354bd5 100644 (file)
@@ -15,6 +15,7 @@ class TestProxyProtocolLuaRecords(AuthTest):
 launch=bind
 any-to-tcp=no
 proxy-protocol-from=127.0.0.1
+enable-lua-records
 edns-subnet-processing=yes
 """
 
@@ -141,7 +142,7 @@ options {
 
                 namedconf.write("""
         zone "%s" {
-            type slave;
+            type secondary;
             file "%s.zone";
             masters { %s; };
         };""" % (zone, zonename, cls._zones[zone]))
diff --git a/regression-tests.auth-py/test_XFRIncomplete.py b/regression-tests.auth-py/test_XFRIncomplete.py
new file mode 100644 (file)
index 0000000..3db145e
--- /dev/null
@@ -0,0 +1,207 @@
+import dns
+import json
+import os
+import requests
+import socket
+import struct
+import sys
+import threading
+import time
+
+from authtests import AuthTest
+
+class BadXFRServer(object):
+
+    def __init__(self, port):
+        self._currentSerial = 0
+        self._targetSerial = 1
+        self._serverPort = port
+        listener = threading.Thread(name='XFR Listener', target=self._listener, args=[])
+        listener.setDaemon(True)
+        listener.start()
+
+    def getCurrentSerial(self):
+        return self._currentSerial
+
+    def moveToSerial(self, newSerial):
+        if newSerial == self._currentSerial or newSerial == self._targetSerial:
+            return False
+
+        #if newSerial != self._currentSerial + 1:
+        #    raise AssertionError("Asking the XFR server to serve serial %d, already serving %d" % (newSerial, self._currentSerial))
+        self._targetSerial = newSerial
+        print("moveToSerial %d" % newSerial, file=sys.stderr)
+        return True
+
+    def _getAnswer(self, message):
+
+        response = dns.message.make_response(message)
+        records = []
+
+        if message.question[0].rdtype == dns.rdatatype.AXFR:
+            if self._currentSerial != 0:
+                print('Received an AXFR query but IXFR expected because the current serial is %d' % (self._currentSerial))
+                return (None, self._currentSerial)
+
+            newSerial = self._targetSerial
+            records = [
+                dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
+                dns.rrset.from_text('a.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1'),
+                dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial)
+                ]
+
+        elif message.question[0].rdtype == dns.rdatatype.IXFR:
+            oldSerial = message.authority[0][0].serial
+
+            newSerial = self._targetSerial
+            if newSerial == 2:
+                records = [
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % oldSerial),
+                    # no deletion
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
+                    dns.rrset.from_text('b.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1'),
+                    ]
+            elif newSerial == 3:
+                records = [
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
+                    dns.rrset.from_text('a.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1'),
+                    ]
+
+        response.answer = records
+        return (newSerial, response)
+
+    def _connectionHandler(self, conn):
+        data = None
+        while True:
+            print("Reading from connection...", file=sys.stderr)
+            data = conn.recv(2)
+            if not data:
+                break
+            (datalen,) = struct.unpack("!H", data)
+            data = conn.recv(datalen)
+            print("Received request with len %d" % datalen, file=sys.stderr)
+            if not data:
+                break
+
+            message = dns.message.from_wire(data)
+            if len(message.question) != 1:
+                print('Invalid query, qdcount is %d' % (len(message.question)), file=sys.stderr)
+                break
+            if not message.question[0].rdtype in [dns.rdatatype.AXFR, dns.rdatatype.IXFR]:
+                print('Invalid query, qtype is %d' % (message.question.rdtype), file=sys.stderr)
+                break
+            print(message, file=sys.stderr)
+            (serial, answer) = self._getAnswer(message)
+            if not answer:
+                print('Unable to get a response for %s %d' % (message.question[0].name, message.question[0].rdtype), file=sys.stderr)
+                break
+
+            wire = answer.to_wire()
+            conn.send(struct.pack("!H", len(wire)))
+            conn.send(wire)
+            print("_currentSerial to %d" % serial, file=sys.stderr)
+            self._currentSerial = serial
+            break
+
+        print("_connectionHandler: stop", file=sys.stderr)
+        conn.close()
+
+    def _listener(self):
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+        try:
+            sock.bind(("127.0.0.1", self._serverPort))
+        except socket.error as e:
+            print("Error binding in the IXFR listener: %s" % str(e))
+            sys.exit(1)
+
+        sock.listen(100)
+        while True:
+            try:
+                (conn, _) = sock.accept()
+                print("New connection", file=sys.stderr)
+                thread = threading.Thread(name='IXFR Connection Handler',
+                                      target=self._connectionHandler,
+                                      args=[conn])
+                thread.setDaemon(True)
+                thread.start()
+
+            except socket.error as e:
+                print('Error in IXFR socket: %s' % str(e))
+                sock.close()
+
+badxfrServerPort = 4251
+badxfrServer = BadXFRServer(badxfrServerPort)
+
+class XFRIncompleteAuthTest(AuthTest):
+    """
+    This test makes sure that we correctly detect incomplete RPZ zones via AXFR then IXFR
+    """
+
+    global badxfrServerPort
+    _config_template = """
+launch=gsqlite3 bind
+gsqlite3-database=configs/auth/powerdns.sqlite
+gsqlite3-dnssec
+secondary
+cache-ttl=0
+query-cache-ttl=0
+domain-metadata-cache-ttl=0
+negquery-cache-ttl=0
+xfr-cycle-interval=1
+#loglevel=9
+#axfr-fetch-timeout=20
+"""
+
+    @classmethod
+    def setUpClass(cls):
+        super(XFRIncompleteAuthTest, cls).setUpClass()
+        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):
+        global badxfrServer
+
+        badxfrServer.moveToSerial(serial)
+
+        attempts = 0
+        while attempts < timeout:
+            currentSerial = badxfrServer.getCurrentSerial()
+            if currentSerial > serial:
+                raise AssertionError("Expected serial %d, got %d" % (serial, currentSerial))
+            if currentSerial == serial:
+                badxfrServer.moveToSerial(serial+1)
+                return
+
+            attempts = attempts + 1
+            time.sleep(1)
+
+        raise AssertionError("Waited %d seconds for the serial to be updated to %d but the serial is still %d" % (timeout, serial, currentSerial))
+
+    def checkZone(self):
+        query = dns.message.make_query('zone.rpz.', 'SOA')
+        res = self.sendUDPQuery(query) # , count=len(expected))
+
+        expected = [dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. 1 3600 3600 3600 1')]
+        self.assertEqual(res.answer, expected)
+
+    def doRetrieve(self):
+        os.system("$PDNSCONTROL --socket-dir=configs/auth retrieve zone.rpz.")
+
+    def testXFR(self):
+        # self.waitForTCPSocket("127.0.0.1", self._wsPort)
+        # First zone
+        self.doRetrieve()
+        self.waitUntilCorrectSerialIsLoaded(1)
+        self.checkZone()
+
+        # second zone, should fail, incomplete IXFR
+        self.doRetrieve()
+        self.waitUntilCorrectSerialIsLoaded(2)
+        self.checkZone()
+
+        # third zone, should fail, incomplete AXFR
+        self.doRetrieve()
+        self.waitUntilCorrectSerialIsLoaded(3)
+        self.checkZone()
index aaff017f1fc6a1e3a698e97c596e72b59358c6be..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/Makefile b/regression-tests.dnsdist/Makefile
new file mode 100644 (file)
index 0000000..84286d7
--- /dev/null
@@ -0,0 +1,15 @@
+clean-certs:
+       rm -f ca.key ca.pem ca.srl server.csr server.key server.pem server.chain server.ocsp
+clean-configs:
+       rm -rf configs/*
+certs:
+       # Generate a new CA
+       openssl req -new -x509 -days 1 -extensions v3_ca -keyout ca.key -out ca.pem -nodes -config configCA.conf
+       # Generate a new server certificate request
+       openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr -config configServer.conf
+       # Sign the server cert
+       openssl x509 -req -days 1 -CA ca.pem -CAkey ca.key -CAcreateserial -in server.csr -out server.pem -extfile configServer.conf -extensions v3_req
+       # Generate a chain
+       cat server.pem ca.pem > server.chain
+       # Generate a password-protected PKCS12 file
+       openssl pkcs12 -export -passout pass:passw0rd -clcerts -in server.pem -CAfile ca.pem -inkey server.key -out server.p12
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)
diff --git a/regression-tests.dnsdist/dnsdistdohtests.py b/regression-tests.dnsdist/dnsdistdohtests.py
new file mode 100644 (file)
index 0000000..57f0c54
--- /dev/null
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+import base64
+import dns
+import os
+import unittest
+
+from dnsdisttests import DNSDistTest
+
+import pycurl
+from io import BytesIO
+
+@unittest.skipIf('SKIP_DOH_TESTS' in os.environ, 'DNS over HTTPS tests are disabled')
+class DNSDistDOHTest(DNSDistTest):
+
+    def getHeaderValue(self, name):
+        for header in self._response_headers.decode().splitlines(False):
+            values = header.split(':')
+            key = values[0]
+            if key.lower() == name.lower():
+                return values[1].strip()
+        return None
+
+    def checkHasHeader(self, name, value):
+        got = self.getHeaderValue(name)
+        self.assertEqual(got, value)
+
+    def checkNoHeader(self, name):
+        self.checkHasHeader(name, None)
+
+    @classmethod
+    def setUpClass(cls):
+
+        # for some reason, @unittest.skipIf() is not applied to derived classes with some versions of Python
+        if 'SKIP_DOH_TESTS' in os.environ:
+            raise unittest.SkipTest('DNS over HTTPS tests are disabled')
+
+        cls.startResponders()
+        cls.startDNSDist()
+        cls.setUpSockets()
+
+        print("Launching tests..")
index 5da02e030b2c8a1906e304c7f3d327e61394155a..c829e398cd4e742f64c3add8ba74652464f8554e 100644 (file)
@@ -1,6 +1,8 @@
 #!/usr/bin/env python2
 
+import base64
 import copy
+import errno
 import os
 import socket
 import ssl
@@ -23,6 +25,12 @@ import h2.connection
 import h2.events
 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
 
@@ -37,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):
     """
@@ -47,47 +72,74 @@ 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
-    _dnsdistStartupDelay = 2.0
     _dnsdist = None
     _responsesCounter = {}
     _config_template = """
     """
     _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
+    _backgroundThreads = {}
+    _UDPResponder = None
+    _TCPResponder = None
+    _extraStartupSleep = 0
+    _dnsDistPort = pickAvailablePort()
+    _consolePort = pickAvailablePort()
+    _testServerPort = pickAvailablePort()
+
+    @classmethod
+    def waitForTCPSocket(cls, ipaddress, port):
+        for try_number in range(0, 20):
+            try:
+                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+                sock.settimeout(1.0)
+                sock.connect((ipaddress, port))
+                sock.close()
+                return
+            except Exception as err:
+                if err.errno != errno.ECONNREFUSED:
+                    print(f'Error occurred: {try_number} {err}', file=sys.stderr)
+            time.sleep(0.1)
+       # We assume the dnsdist instance does not listen. That's fine.
 
     @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('')")
 
@@ -99,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])
@@ -115,22 +173,24 @@ 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:
           cls._dnsdist = subprocess.Popen(dnsdistcmd, close_fds=True, stdout=fdLog, stderr=fdLog)
 
-        if 'DNSDIST_FAST_TESTS' in os.environ:
-            delay = 0.5
+        if cls._alternateListeningAddr and cls._alternateListeningPort:
+            cls.waitForTCPSocket(cls._alternateListeningAddr, cls._alternateListeningPort)
         else:
-            delay = cls._dnsdistStartupDelay
-
-        time.sleep(delay)
+            cls.waitForTCPSocket(cls._dnsDistListeningAddr, cls._dnsDistPort)
 
         if cls._dnsdist.poll() is not None:
-            cls._dnsdist.kill()
-            sys.exit(cls._dnsdist.returncode)
+            print(f"\n*** startDNSDist log for {logFile} ***")
+            with open(logFile, 'r') as fdLog:
+                print(fdLog.read())
+            print(f"*** End startDNSDist log for {logFile} ***")
+            raise AssertionError('%s failed (%d)' % (dnsdistcmd, cls._dnsdist.returncode))
+        time.sleep(cls._extraStartupSleep)
 
     @classmethod
     def setUpSockets(cls):
@@ -139,6 +199,29 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         cls._sock.settimeout(2.0)
         cls._sock.connect(("127.0.0.1", cls._dnsDistPort))
 
+    @classmethod
+    def killProcess(cls, p):
+        # Don't try to kill it if it's already dead
+        if p.poll() is not None:
+            return
+        try:
+            p.terminate()
+            for count in range(20):
+                x = p.poll()
+                if x is not None:
+                    break
+                time.sleep(0.1)
+            if x is None:
+                print("kill...", p, file=sys.stderr)
+                p.kill()
+                p.wait()
+        except OSError as e:
+            # There is a race-condition with the poll() and
+            # kill() statements, when the process is dead on the
+            # kill(), this is fine
+            if e.errno != errno.ESRCH:
+                raise
+
     @classmethod
     def setUpClass(cls):
 
@@ -150,24 +233,18 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
 
     @classmethod
     def tearDownClass(cls):
-        if 'DNSDIST_FAST_TESTS' in os.environ:
-            delay = 0.1
-        else:
-            delay = 1.0
-        if cls._dnsdist:
-            cls._dnsdist.terminate()
-            if cls._dnsdist.poll() is None:
-                time.sleep(delay)
-                if cls._dnsdist.poll() is None:
-                    cls._dnsdist.kill()
-                cls._dnsdist.wait()
+        cls._sock.close()
+        # tell the background threads to stop, if any
+        for backgroundThread in cls._backgroundThreads:
+            cls._backgroundThreads[backgroundThread] = False
+        cls.killProcess(cls._dnsdist)
 
     @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):
@@ -201,6 +278,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
 
     @classmethod
     def UDPResponder(cls, port, fromQueue, toQueue, trailingDataResponse=False, callback=None):
+        cls._backgroundThreads[threading.get_native_id()] = True
         # trailingDataResponse=True means "ignore trailing data".
         # Other values are either False (meaning "raise an exception")
         # or are interpreted as a response RCODE for queries with trailing data.
@@ -210,8 +288,17 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
         sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
         sock.bind(("127.0.0.1", port))
+        sock.settimeout(0.5)
         while True:
-            data, addr = sock.recvfrom(4096)
+            try:
+              data, addr = sock.recvfrom(4096)
+            except socket.timeout:
+              if cls._backgroundThreads.get(threading.get_native_id(), False) == False:
+                del cls._backgroundThreads[threading.get_native_id()]
+                break
+              else:
+                continue
+
             forceRcode = None
             try:
                 request = dns.message.from_wire(data, ignore_trailing=ignoreTrailing)
@@ -227,6 +314,8 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
             if callback:
               wire = callback(request)
             else:
+              if request.edns > 1:
+                forceRcode = dns.rcode.BADVERS
               response = cls._getResponse(request, fromQueue, toQueue, synthesize=forceRcode)
               if response:
                 wire = response.to_wire()
@@ -234,15 +323,18 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
             if not wire:
               continue
 
-            sock.settimeout(2.0)
             sock.sendto(wire, addr)
-            sock.settimeout(None)
+
         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
@@ -262,6 +354,8 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
       if callback:
         wire = callback(request)
       else:
+        if request.edns > 1:
+          forceRcode = dns.rcode.BADVERS
         response = cls._getResponse(request, fromQueue, toQueue, synthesize=forceRcode)
         if response:
           wire = response.to_wire(max_size=65535)
@@ -270,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:
@@ -297,7 +397,8 @@ 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")
         # or are interpreted as a response RCODE for queries with trailing data.
@@ -313,6 +414,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
             sys.exit(1)
 
         sock.listen(100)
+        sock.settimeout(0.5)
         if tlsContext:
           sock = tlsContext.wrap_socket(sock, server_side=True)
 
@@ -323,16 +425,22 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
               continue
             except ConnectionResetError:
               continue
+            except socket.timeout:
+              if cls._backgroundThreads.get(threading.get_native_id(), False) == False:
+                 del cls._backgroundThreads[threading.get_native_id()]
+                 break
+              else:
+                continue
 
             conn.settimeout(5.0)
             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()
 
@@ -346,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 = {}
 
@@ -376,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
 
@@ -433,6 +548,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
 
     @classmethod
     def DOHResponder(cls, port, fromQueue, toQueue, trailingDataResponse=False, multipleResponses=False, callback=None, tlsContext=None, useProxyProtocol=False):
+        cls._backgroundThreads[threading.get_native_id()] = True
         # trailingDataResponse=True means "ignore trailing data".
         # Other values are either False (meaning "raise an exception")
         # or are interpreted as a response RCODE for queries with trailing data.
@@ -448,6 +564,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
             sys.exit(1)
 
         sock.listen(100)
+        sock.settimeout(0.5)
         if tlsContext:
             sock = tlsContext.wrap_socket(sock, server_side=True)
 
@@ -460,12 +577,18 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
                 continue
             except ConnectionResetError:
               continue
+            except socket.timeout:
+                if cls._backgroundThreads.get(threading.get_native_id(), False) == False:
+                    del cls._backgroundThreads[threading.get_native_id()]
+                    break
+                else:
+                    continue
 
             conn.settimeout(5.0)
             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()
@@ -511,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:
@@ -520,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)
@@ -542,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:
@@ -556,24 +680,35 @@ 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:
             print("queue empty")
             return message
 
+    @classmethod
+    def sendDOTQuery(cls, port, serverName, query, response, caFile, useQueue=True):
+        conn = cls.openTLSConnection(port, serverName, caFile)
+        cls.sendTCPQueryOverConnection(conn, query, response=response)
+        if useQueue:
+          return cls.recvTCPResponseOverConnection(conn, useQueue=useQueue)
+        return None, cls.recvTCPResponseOverConnection(conn, useQueue=useQueue)
+
     @classmethod
     def sendTCPQuery(cls, query, response, useQueue=True, timeout=2.0, rawQuery=False):
         message = None
         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:
@@ -584,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:
@@ -682,7 +816,7 @@ 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):
         ourNonce = libnacl.utils.rand_nonce()
         theirNonce = None
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -712,6 +846,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         (responseLen,) = struct.unpack("!I", data)
         data = sock.recv(responseLen)
         response = cls._decryptConsole(data, readingNonce)
+        sock.close()
         return response
 
     def compareOptions(self, a, b):
@@ -784,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)
@@ -801,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)
@@ -843,3 +979,187 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         proxy.values.sort()
         values.sort()
         self.assertEqual(proxy.values, values)
+
+    @classmethod
+    def getDOHGetURL(cls, baseurl, query, rawQuery=False):
+        if rawQuery:
+            wire = query
+        else:
+            wire = query.to_wire()
+        param = base64.urlsafe_b64encode(wire).decode('UTF8').rstrip('=')
+        return baseurl + "?dns=" + param
+
+    @classmethod
+    def openDOHConnection(cls, port, caFile, timeout=2.0):
+        conn = pycurl.Curl()
+        conn.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_2)
+
+        conn.setopt(pycurl.HTTPHEADER, ["Content-type: application/dns-message",
+                                         "Accept: application/dns-message"])
+        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, conn=None):
+        url = cls.getDOHGetURL(baseurl, query, rawQuery)
+
+        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)
+
+        if response:
+            if toQueue:
+                toQueue.put(response, True, timeout)
+            else:
+                cls._toResponderQueue.put(response, True, timeout)
+
+        receivedQuery = None
+        message = None
+        cls._response_headers = ''
+        data = conn.perform_rb()
+        cls._rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+        if cls._rcode == 200 and not rawResponse:
+            message = dns.message.from_wire(data)
+        elif rawResponse:
+            message = data
+
+        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)
+
+        cls._response_headers = response_headers.getvalue()
+        return (receivedQuery, message)
+
+    @classmethod
+    def sendDOHPostQuery(cls, port, servername, baseurl, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False, rawResponse=False, customHeaders=[], useHTTPS=True):
+        url = baseurl
+        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)])
+        # 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)
+
+        conn.setopt(pycurl.HTTPHEADER, customHeaders)
+        conn.setopt(pycurl.HEADERFUNCTION, response_headers.write)
+        conn.setopt(pycurl.POST, True)
+        data = query
+        if not rawQuery:
+            data = data.to_wire()
+
+        conn.setopt(pycurl.POSTFIELDS, data)
+
+        if response:
+            cls._toResponderQueue.put(response, True, timeout)
+
+        receivedQuery = None
+        message = None
+        cls._response_headers = ''
+        data = conn.perform_rb()
+        cls._rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+        if cls._rcode == 200 and not rawResponse:
+            message = dns.message.from_wire(data)
+        elif rawResponse:
+            message = data
+
+        if useQueue and not cls._fromResponderQueue.empty():
+            receivedQuery = cls._fromResponderQueue.get(True, timeout)
+
+        cls._response_headers = response_headers.getvalue()
+        return (receivedQuery, message)
+
+    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 7fd3da7a79ad65f19bd5771e351f92c6010afacc..13ce6de1840cb2b70a864b7be811c6a5bc446cea 100644 (file)
@@ -1,11 +1,15 @@
 dnspython>=2.2.0
-nose>=1.3.7
+pytest
+pytest-xdist
 libnacl>=1.4.3,<1.7
 requests>=2.1.0
 protobuf>=3.0
+pyasn1==0.4.8
 pysnmp>=4.3.4
 future>=0.17.1
 pycurl>=7.43.0
 lmdb>=0.95
 cdbx==0.1.2
 h2>=4.0.0
+aioquic
+async_timeout
diff --git a/regression-tests.dnsdist/resolv.conf.sample b/regression-tests.dnsdist/resolv.conf.sample
new file mode 100644 (file)
index 0000000..3d768f7
--- /dev/null
@@ -0,0 +1,8 @@
+# this line should be ignored
+; that one as well
+  # even with leading spaces
+       ; or a tab
+# nameserver line, IPv4, space-separated
+nameserver 9.9.9.9
+# nameserver line, IPv6, tab-separated
+nameserver     2620:fe::fe
index 2a6799b282be6ea69ec5e94f396a0069cc301529..23e6e54e3354865e1c19fdb0db7eb13a66f2fac8 100755 (executable)
@@ -1,6 +1,8 @@
 #!/usr/bin/env bash
 set -e
 
+export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python
+
 if [ ! -d .venv ]; then
   python3 -m venv .venv
 fi
@@ -37,33 +39,23 @@ if [ "${PDNS_DEBUG}" = "YES" ]; then
   set -x
 fi
 
-rm -f ca.key ca.pem ca.srl server.csr server.key server.pem server.chain server.ocsp
-rm -rf configs/*
-
-# Generate a new CA
-openssl req -new -x509 -days 1 -extensions v3_ca -keyout ca.key -out ca.pem -nodes -config configCA.conf
-# Generate a new server certificate request
-openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr -config configServer.conf
-# Sign the server cert
-openssl x509 -req -days 1 -CA ca.pem -CAkey ca.key -CAcreateserial -in server.csr -out server.pem -extfile configServer.conf -extensions v3_req
-# Generate a chain
-cat server.pem ca.pem > server.chain
-# Generate a password-protected PKCS12 file
-openssl pkcs12 -export -passout pass:passw0rd -clcerts -in server.pem -CAfile ca.pem -inkey server.key -out server.p12
+make clean-certs
+make clean-configs
+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}"
 
-rm -f ca.key ca.pem ca.srl server.csr server.key server.pem server.chain server.ocsp
+make clean-certs
index b0926f676644bc99d1c9415bd01fd0e6789f6d2a..e9d12e4d4974ec0ed393e9336e05bb43f9bbfae2 100644 (file)
@@ -1 +1 @@
-addAction(makeRule("includedir.advanced.tests.powerdns.com."), AllowAction())
+addAction(SuffixMatchNodeRule("includedir.advanced.tests.powerdns.com."), AllowAction())
index 98141fdc0de68fa396e3bc66086025ffc7e06d7d..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'
@@ -23,6 +24,35 @@ class APITestsBase(DNSDistTest):
     webserver("127.0.0.1:%s")
     setWebserverConfig({password="%s", apiKey="%s"})
     """
+    _expectedMetrics = ['responses', 'servfail-responses', 'queries', 'acl-drops',
+                        'frontend-noerror', 'frontend-nxdomain', 'frontend-servfail',
+                        'rule-drop', 'rule-nxdomain', 'rule-refused', 'self-answered', 'downstream-timeouts',
+                        'downstream-send-errors', 'trunc-failures', 'no-policy', 'latency0-1',
+                        'latency1-10', 'latency10-50', 'latency50-100', 'latency100-1000',
+                        'latency-slow', 'latency-sum', 'latency-count', 'latency-avg100', 'latency-avg1000',
+                        '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', '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', '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
+
+    @classmethod
+    def setUpClass(cls):
+        cls.startResponders()
+        cls.startDNSDist()
+        cls.setUpSockets()
+        cls.waitForTCPSocket('127.0.0.1', cls._webServerPort)
+        print("Launching tests..")
 
 class TestAPIBasics(APITestsBase):
 
@@ -114,32 +144,39 @@ class TestAPIBasics(APITestsBase):
         for server in content['servers']:
             for key in ['id', 'latency', 'name', 'weight', 'outstanding', 'qpsLimit',
                         'reuseds', 'state', 'address', 'pools', 'qps', 'queries', 'order', 'sendErrors',
-                        'dropRate', 'responses', 'tcpDiedSendingQuery', 'tcpDiedReadingResponse',
+                        'dropRate', 'responses', 'nonCompliantResponses', 'tcpDiedSendingQuery', 'tcpDiedReadingResponse',
                         'tcpGaveUp', 'tcpReadTimeouts', 'tcpWriteTimeouts', 'tcpCurrentConnections',
                         'tcpNewConnections', 'tcpReusedConnections', 'tlsResumptions', 'tcpAvgQueriesPerConnection',
-                        'tcpAvgConnectionDuration']:
+                        'tcpAvgConnectionDuration', 'tcpLatency', 'protocol', 'healthCheckFailures', 'healthCheckFailuresParsing', 'healthCheckFailuresTimeout', 'healthCheckFailuresNetwork', 'healthCheckFailuresMismatch', 'healthCheckFailuresInvalid']:
                 self.assertIn(key, server)
 
             for key in ['id', 'latency', 'weight', 'outstanding', 'qpsLimit', 'reuseds',
-                        'qps', 'queries', 'order']:
+                        'qps', 'queries', 'order', 'tcpLatency', 'responses', 'nonCompliantResponses']:
                 self.assertTrue(server[key] >= 0)
 
             self.assertTrue(server['state'] in ['up', 'down', 'UP', 'DOWN'])
 
         for frontend in content['frontends']:
-            for key in ['id', 'address', 'udp', 'tcp', 'type', 'queries']:
+            for key in ['id', 'address', 'udp', 'tcp', 'type', 'queries', 'nonCompliantQueries']:
                 self.assertIn(key, frontend)
 
-            for key in ['id', 'queries']:
+            for key in ['id', 'queries', 'nonCompliantQueries']:
                 self.assertTrue(frontend[key] >= 0)
 
         for pool in content['pools']:
-            for key in ['id', 'name', 'cacheSize', 'cacheEntries', 'cacheHits', 'cacheMisses', 'cacheDeferredInserts', 'cacheDeferredLookups', 'cacheLookupCollisions', 'cacheInsertCollisions', 'cacheTTLTooShorts']:
+            for key in ['id', 'name', 'cacheSize', 'cacheEntries', 'cacheHits', 'cacheMisses', 'cacheDeferredInserts', 'cacheDeferredLookups', 'cacheLookupCollisions', 'cacheInsertCollisions', 'cacheTTLTooShorts', 'cacheCleanupCount']:
                 self.assertIn(key, pool)
 
-            for key in ['id', 'cacheSize', 'cacheEntries', 'cacheHits', 'cacheMisses', 'cacheDeferredInserts', 'cacheDeferredLookups', 'cacheLookupCollisions', 'cacheInsertCollisions', 'cacheTTLTooShorts']:
+            for key in ['id', 'cacheSize', 'cacheEntries', 'cacheHits', 'cacheMisses', 'cacheDeferredInserts', 'cacheDeferredLookups', 'cacheLookupCollisions', 'cacheInsertCollisions', 'cacheTTLTooShorts', 'cacheCleanupCount']:
                 self.assertTrue(pool[key] >= 0)
 
+        stats = content['statistics']
+        for key in self._expectedMetrics:
+            self.assertIn(key, stats)
+            self.assertTrue(stats[key] >= 0)
+        for key in stats:
+            self.assertIn(key, self._expectedMetrics)
+
     def testServersLocalhostPool(self):
         """
         API: /api/v1/servers/localhost/pool?name=mypool
@@ -164,14 +201,14 @@ class TestAPIBasics(APITestsBase):
         for server in content['servers']:
             for key in ['id', 'latency', 'name', 'weight', 'outstanding', 'qpsLimit',
                         'reuseds', 'state', 'address', 'pools', 'qps', 'queries', 'order', 'sendErrors',
-                        'dropRate', 'responses', 'tcpDiedSendingQuery', 'tcpDiedReadingResponse',
+                        'dropRate', 'responses', 'nonCompliantResponses', 'tcpDiedSendingQuery', 'tcpDiedReadingResponse',
                         'tcpGaveUp', 'tcpReadTimeouts', 'tcpWriteTimeouts', 'tcpCurrentConnections',
                         'tcpNewConnections', 'tcpReusedConnections', 'tcpAvgQueriesPerConnection',
-                        'tcpAvgConnectionDuration']:
+                        'tcpAvgConnectionDuration', 'tcpLatency', 'protocol']:
                 self.assertIn(key, server)
 
             for key in ['id', 'latency', 'weight', 'outstanding', 'qpsLimit', 'reuseds',
-                        'qps', 'queries', 'order']:
+                        'qps', 'queries', 'order', 'tcpLatency', 'responses', 'nonCompliantResponses']:
                 self.assertTrue(server[key] >= 0)
 
             self.assertTrue(server['state'] in ['up', 'down', 'UP', 'DOWN'])
@@ -274,28 +311,12 @@ class TestAPIBasics(APITestsBase):
             self.assertEqual(entry['type'], 'StatisticItem')
             values[entry['name']] = entry['value']
 
-        expected = ['responses', 'servfail-responses', 'queries', 'acl-drops',
-                    'frontend-noerror', 'frontend-nxdomain', 'frontend-servfail',
-                    'rule-drop', 'rule-nxdomain', 'rule-refused', 'self-answered', 'downstream-timeouts',
-                    'downstream-send-errors', 'trunc-failures', 'no-policy', 'latency0-1',
-                    'latency1-10', 'latency10-50', 'latency50-100', 'latency100-1000',
-                    'latency-slow', 'latency-sum', 'latency-count', 'latency-avg100', 'latency-avg1000',
-                    'latency-avg10000', 'latency-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',
-                    'outgoing-doh-query-pipe-full', 'tcp-query-pipe-full', 'tcp-cross-protocol-query-pipe-full',
-                    'tcp-cross-protocol-response-pipe-full']
-
-        for key in expected:
+        for key in self._expectedMetrics:
             self.assertIn(key, values)
             self.assertTrue(values[key] >= 0)
 
         for key in values:
-            self.assertIn(key, expected)
+            self.assertIn(key, self._expectedMetrics)
 
     def testJsonstatStats(self):
         """
@@ -309,19 +330,7 @@ class TestAPIBasics(APITestsBase):
         self.assertTrue(r.json())
         content = r.json()
 
-        expected = ['responses', 'servfail-responses', 'queries', 'acl-drops',
-                    'frontend-noerror', 'frontend-nxdomain', 'frontend-servfail',
-                    'rule-drop', 'rule-nxdomain', 'rule-refused', 'rule-truncated', 'self-answered', 'downstream-timeouts',
-                    'downstream-send-errors', 'trunc-failures', 'no-policy', 'latency0-1',
-                    'latency1-10', 'latency10-50', 'latency50-100', 'latency100-1000',
-                    'latency-slow', 'latency-avg100', 'latency-avg1000', 'latency-avg10000',
-                    'latency-avg1000000', 'uptime', 'real-memory-usage', 'noncompliant-queries',
-                    'noncompliant-responses', 'rdqueries', 'empty-queries', 'cache-hits',
-                    'cache-misses', 'cpu-user-msec', 'cpu-sys-msec', 'fd-usage', 'dyn-blocked',
-                    'dyn-block-nmg-size', 'packetcache-hits', 'packetcache-misses', 'over-capacity-drops',
-                    'too-old-drops', 'proxy-protocol-invalid', 'doh-query-pipe-full', 'doh-response-pipe-full']
-
-        for key in expected:
+        for key in self._expectedMetrics:
             self.assertIn(key, content)
             self.assertTrue(content[key] >= 0)
 
@@ -344,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 = """
@@ -367,6 +429,7 @@ class TestAPIServerDown(APITestsBase):
         content = r.json()
 
         self.assertEqual(content['servers'][0]['latency'], None)
+        self.assertEqual(content['servers'][0]['tcpLatency'], None)
 
 class TestAPIWritable(APITestsBase):
     __test__ = True
@@ -643,6 +706,66 @@ class TestAPIACL(APITestsBase):
         self.assertTrue(r)
         self.assertEqual(r.status_code, 200)
 
+class TestAPIWithoutAuthentication(APITestsBase):
+    __test__ = True
+    _apiPath = '/api/v1/servers/localhost/config'
+    # paths accessible using basic auth only (list not exhaustive)
+    _basicOnlyPath = '/'
+    _config_params = ['_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed']
+    _config_template = """
+    setACL({"127.0.0.1/32", "::1/128"})
+    newServer({address="127.0.0.1:%s"})
+    webserver("127.0.0.1:%s")
+    setWebserverConfig({password="%s", apiRequiresAuthentication=false })
+    """
+
+    def testAuth(self):
+        """
+        API: API do not require authentication
+        """
+
+        for path in [self._apiPath]:
+            url = 'http://127.0.0.1:' + str(self._webServerPort) + path
+
+            r = requests.get(url, timeout=self._webTimeout)
+            self.assertTrue(r)
+            self.assertEqual(r.status_code, 200)
+
+        # these should still require basic authentication
+        for path in [self._basicOnlyPath]:
+            url = 'http://127.0.0.1:' + str(self._webServerPort) + path
+
+            r = requests.get(url, timeout=self._webTimeout)
+            self.assertEqual(r.status_code, 401)
+
+            r = requests.get(url, auth=('whatever', self._webServerBasicAuthPassword), timeout=self._webTimeout)
+            self.assertTrue(r)
+            self.assertEqual(r.status_code, 200)
+
+class TestDashboardWithoutAuthentication(APITestsBase):
+    __test__ = True
+    _basicPath = '/'
+    _config_params = ['_testServerPort', '_webServerPort']
+    _config_template = """
+    setACL({"127.0.0.1/32", "::1/128"})
+    newServer({address="127.0.0.1:%d"})
+    webserver("127.0.0.1:%d")
+    setWebserverConfig({ dashboardRequiresAuthentication=false })
+    """
+    _verboseMode=True
+
+    def testDashboard(self):
+        """
+        API: Dashboard do not require authentication
+        """
+
+        for path in [self._basicPath]:
+            url = 'http://127.0.0.1:' + str(self._webServerPort) + path
+
+            r = requests.get(url, timeout=self._webTimeout)
+            self.assertTrue(r)
+            self.assertEqual(r.status_code, 200)
+
 class TestCustomLuaEndpoint(APITestsBase):
     __test__ = True
     _config_template = """
@@ -710,6 +833,14 @@ class TestWebConcurrentConnections(APITestsBase):
     setWebserverConfig({password="%s", apiKey="%s", maxConcurrentConnections=%d})
     """
 
+    @classmethod
+    def setUpClass(cls):
+        cls.startResponders()
+        cls.startDNSDist()
+        cls.setUpSockets()
+        # do no check if the web server socket is up, because this
+        # might mess up the concurrent connections counter
+
     def testConcurrentConnections(self):
         """
         Web: Concurrent connections
@@ -735,3 +866,36 @@ class TestWebConcurrentConnections(APITestsBase):
         r = requests.get(url, auth=('whatever', self._webServerBasicAuthPassword), timeout=self._webTimeout)
         self.assertTrue(r)
         self.assertEqual(r.status_code, 200)
+
+class TestAPICustomStatistics(APITestsBase):
+    __test__ = True
+    _maxConns = 2
+
+    _config_params = ['_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+    webserver("127.0.0.1:%s")
+    declareMetric("my-custom-metric", "counter", "Number of statistics")
+    declareMetric("my-other-metric", "counter", "Another number of statistics")
+    declareMetric("my-gauge", "gauge", "Current memory usage")
+    setWebserverConfig({password="%s", apiKey="%s"})
+    """
+
+    def testCustomStats(self):
+        """
+        API: /jsonstat?command=stats
+        Test custom statistics are exposed
+        """
+        headers = {'x-api-key': self._webServerAPIKey}
+        url = 'http://127.0.0.1:' + str(self._webServerPort) + '/jsonstat?command=stats'
+        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()
+
+        expected = ['my-custom-metric', 'my-other-metric', 'my-gauge']
+
+        for key in expected:
+            self.assertIn(key, content)
+            self.assertTrue(content[key] >= 0)
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 2425c736c38bfc28dde760e087e43f4ef255bf04..babf7399b3516fd88b112a01c7d8e033246a8a33 100644 (file)
@@ -1,6 +1,7 @@
 #!/usr/bin/env python
 import base64
 import os
+import socket
 import time
 import unittest
 import dns
@@ -266,9 +267,9 @@ class TestAdvancedGetLocalPortOnAnyBind(DNSDistTest):
       return DNSAction.Spoof, "port-was-"..port..".local-port-any.advanced.tests.powerdns.com."
     end
     addAction("local-port-any.advanced.tests.powerdns.com.", LuaAction(answerBasedOnLocalPort))
-    newServer{address="127.0.0.1:%s"}
+    newServer{address="127.0.0.1:%d"}
     """
-    _dnsDistListeningAddr = "0.0.0.0"
+    _dnsDistListeningAddr = '0.0.0.0'
 
     def testAdvancedGetLocalPortOnAnyBind(self):
         """
@@ -304,8 +305,13 @@ class TestAdvancedGetLocalAddressOnAnyBind(DNSDistTest):
     end
     addAction("local-address-any.advanced.tests.powerdns.com.", LuaAction(answerBasedOnLocalAddress))
     newServer{address="127.0.0.1:%s"}
+    addLocal('0.0.0.0:%d')
+    addLocal('[::]:%d')
     """
-    _dnsDistListeningAddr = "0.0.0.0"
+    _config_params = ['_testServerPort', '_dnsDistPort', '_dnsDistPort']
+    _acl = ['127.0.0.1/32', '::1/128']
+    _skipListeningOnCL = True
+    _verboseMode = True
 
     def testAdvancedGetLocalAddressOnAnyBind(self):
         """
@@ -329,6 +335,137 @@ class TestAdvancedGetLocalAddressOnAnyBind(DNSDistTest):
             (_, receivedResponse) = sender(query, response=None, useQueue=False)
             self.assertEqual(receivedResponse, response)
 
+        # now a bit more tricky, UDP-only IPv4
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.CNAME,
+                                    '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(2.0)
+        sock.connect(('127.0.0.2', self._dnsDistPort))
+        try:
+            query = query.to_wire()
+            sock.send(query)
+            (data, remote) = sock.recvfrom(4096)
+            self.assertEqual(remote[0], '127.0.0.2')
+        except socket.timeout:
+            data = None
+
+        self.assertTrue(data)
+        receivedResponse = dns.message.from_wire(data)
+        self.assertEqual(receivedResponse, response)
+
+    def testAdvancedCheckSourceAddrOnAnyBind(self):
+        """
+        Advanced: Check the source address on responses for an ANY bind
+        """
+        name = 'source-addr-any.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        # dnsdist set RA = RD for spoofed responses
+        query.flags &= ~dns.flags.RD
+
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.42')
+        response.answer.append(rrset)
+
+        # a bit more tricky, UDP-only IPv4
+        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        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.assertEqual(remote[0], '127.0.0.2')
+        except socket.timeout:
+            data = None
+
+        self.assertTrue(data)
+        receivedResponse = dns.message.from_wire(data)
+        receivedQuery = self._fromResponderQueue.get(True, 1.0)
+        receivedQuery.id = query.id
+        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(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.assertEqual(remote[0], '::1')
+        except socket.timeout:
+            data = None
+
+        self.assertTrue(data)
+        receivedResponse = dns.message.from_wire(data)
+        receivedQuery = self._fromResponderQueue.get(True, 1.0)
+        receivedQuery.id = query.id
+        self.assertEqual(receivedQuery, query)
+        self.assertEqual(receivedResponse, response)
+
+class TestAdvancedGetLocalAddressOnNonDefaultLoopbackBind(DNSDistTest):
+    # this one is tricky: on the loopback interface we cannot harvest the destination
+    # address, so we exercise a different code path when we bind on a different address
+    # than the default 127.0.0.1 one
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+    addLocal('127.0.1.19:%d')
+    """
+    _config_params = ['_testServerPort', '_dnsDistPort']
+    _acl = ['127.0.0.1/32']
+    _skipListeningOnCL = True
+    _alternateListeningAddr = '127.0.1.19'
+    _alternateListeningPort = DNSDistTest._dnsDistPort
+
+    def testAdvancedCheckSourceAddrOnNonDefaultLoopbackBind(self):
+        """
+        Advanced: Check the source address used to reply on a non-default loopback bind
+        """
+        name = 'source-addr-non-default-loopback.advanced.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.42')
+        response.answer.append(rrset)
+
+        # a bit more tricky, UDP-only IPv4
+        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        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.assertEqual(remote[0], '127.0.1.19')
+        except socket.timeout:
+            data = None
+
+        self.assertTrue(data)
+        receivedResponse = dns.message.from_wire(data)
+        receivedQuery = self._fromResponderQueue.get(True, 1.0)
+        receivedQuery.id = query.id
+        self.assertEqual(receivedQuery, query)
+        self.assertEqual(receivedResponse, response)
+
 class TestAdvancedAllowHeaderOnly(DNSDistTest):
 
     _config_template = """
@@ -454,3 +591,322 @@ class TestProtocols(DNSDistTest):
         receivedQuery.id = query.id
         self.assertEqual(receivedQuery, query)
         self.assertEqual(receivedResponse, response)
+
+class TestCustomMetrics(DNSDistTest):
+    _config_template = """
+    function custommetrics(dq)
+      initialCounter = 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 + 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 work fine") then
+        return DNSAction.None
+      end
+      return DNSAction.Spoof, '1.2.3.4'
+    end
+
+    declareMetric("my-custom-counter", "counter", "Number of tests run")
+    declareMetric("my-custom-gauge", "gauge", "Temperature of the tests")
+    addAction("declare.metric.advanced.tests.powerdns.com.", LuaAction(declareNewMetric))
+    addAction("operations.metric.advanced.tests.powerdns.com.", LuaAction(custommetrics))
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testDeclareAfterConfig(self):
+        """
+        Advanced: Test custom metric declaration after config done
+        """
+        name = 'declare.metric.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            receivedQuery.id = query.id
+            self.assertEqual(receivedQuery, query)
+            self.assertEqual(receivedResponse, response)
+
+    def testMetricOperations(self):
+        """
+        Advanced: Test basic operations on custom metrics
+        """
+        name = 'operations.metric.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        # dnsdist set RA = RD for spoofed responses
+        query.flags &= ~dns.flags.RD
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '4.3.2.1')
+        response.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertEqual(receivedResponse, response)
+
+class TestDNSQuestionTime(DNSDistTest):
+    _config_template = """
+    local queryTime = nil
+
+    function luaquery(dq)
+      if queryTime then
+        errlog('Error, the time variable is already set')
+        return DNSAction.Drop
+      end
+      queryTime = dq:getQueryTime()
+      local currentTime = getCurrentTime()
+      if queryTime.tv_sec > currentTime.tv_sec then
+        errlog('Error, query time is higher than current time')
+        return DNSAction.Drop
+      end
+      if queryTime.tv_sec == currentTime.tv_sec and queryTime.tv_nsec > currentTime.tv_nsec then
+        errlog('Error, query time NS is higher than current time')
+        return DNSAction.Drop
+      end
+      return DNSAction.None
+    end
+
+    function luaresponse(dr)
+      if queryTime == nil then
+        errlog('Error, the time variable is NOT set')
+        return DNSAction.Drop
+      end
+      local currentTime        = getCurrentTime()
+      local queryTimeFromResponse = dr:getQueryTime()
+      if queryTime.tv_sec ~= queryTimeFromResponse.tv_sec or queryTime.tv_nsec ~= queryTimeFromResponse.tv_nsec then
+        errlog('Error, the query time in the response does NOT match the one from the query')
+        return DNSAction.Drop
+      end
+      if queryTime.tv_sec > currentTime.tv_sec then
+        errlog('Error, query time is higher than current time')
+        return DNSAction.Drop
+      end
+      if queryTime.tv_sec == currentTime.tv_sec and queryTime.tv_nsec > currentTime.tv_nsec then
+        errlog('Error, query time (NS) is higher than current time')
+        return DNSAction.Drop
+      end
+
+      queryTime = nil
+      return DNSAction.None
+    end
+
+    addAction(AllRule(), LuaAction(luaquery))
+    addResponseAction(AllRule(), LuaResponseAction(luaresponse))
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testQueryTime(self):
+        """
+        Advanced: Test query time
+        """
+        name = 'query.time.advanced.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,
+                                    '4.3.2.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(receivedQuery, query)
+            self.assertEqual(receivedResponse, response)
+
+class TestChangeName(DNSDistTest):
+    _config_template = """
+    local tagName = 'initial-name'
+    function luaChangeNamequery(dq)
+      dq:setTag(tagName, dq.qname:toString())
+      if not dq:changeName(newDNSName('changeName.advanced.tests.dnsdist.org')) then
+        errlog('Error rebasing the query')
+        return DNSAction.Drop
+      end
+      return DNSAction.None
+    end
+
+    function luaChangeNameresponse(dr)
+      local initialName = dr:getTag(tagName)
+      if not dr:changeName(newDNSName(initialName)) then
+        errlog('Error rebasing the response')
+        return DNSAction.Drop
+      end
+      return DNSAction.None
+    end
+
+    addAction('changeName.advanced.tests.powerdns.com', LuaAction(luaChangeNamequery))
+    addResponseAction('changeName.advanced.tests.dnsdist.org', LuaResponseAction(luaChangeNameresponse))
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testChangeName(self):
+        """
+        Advanced: ChangeName the query name
+        """
+        name = 'changeName.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+
+        changedName = 'changeName.advanced.tests.dnsdist.org.'
+        changedQuery = dns.message.make_query(changedName, 'A', 'IN')
+        changedQuery.id = query.id
+
+        response = dns.message.make_response(changedQuery)
+        rrset = dns.rrset.from_text(changedName,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '4.3.2.1')
+        response.answer.append(rrset)
+        rrset = dns.rrset.from_text('sub.sub2.changeName.advanced.tests.dnsdist.org.',
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.TXT,
+                                    'This text contains sub.sub2.changeName.advanced.tests.dnsdist.org.')
+        response.additional.append(rrset)
+
+        expectedResponse = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '4.3.2.1')
+        expectedResponse.answer.append(rrset)
+        # we only rewrite records if the owner name matches the new target, nothing else
+        rrset = dns.rrset.from_text('sub.sub2.changeName.advanced.tests.dnsdist.org.',
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.TXT,
+                                    'This text contains sub.sub2.changeName.advanced.tests.dnsdist.org.')
+        expectedResponse.additional.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            receivedQuery.id = query.id
+            self.assertEqual(receivedQuery, changedQuery)
+            self.assertEqual(receivedResponse, expectedResponse)
+
+class TestFlagsOnTimeout(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")
+    -- this server is not going to answer, resulting in a timeout
+    newServer{address="192.0.2.1:%s"}:setUp()
+    """
+
+    def testFlags(self):
+        """
+        Advanced: Test that we record the correct incoming flags on a timeout
+        """
+        name = 'timeout-flags.advanced.tests.powerdns.com.'
+
+        # first with RD=1
+        query = dns.message.make_query(name, 'A', 'IN')
+        query.id = 42
+        query.flags |= dns.flags.RD
+
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertFalse(receivedResponse)
+
+        # then with RD=0
+        query = dns.message.make_query(name, 'A', 'IN')
+        query.id = 84
+        query.flags &= ~dns.flags.RD
+
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertFalse(receivedResponse)
+
+        # make sure that the timeouts have been detected and recorded
+        for _ in range(6):
+            content = self.sendConsoleCommand("grepq('')")
+            lines = content.splitlines()
+            if len(lines) == 5:
+                break
+            # and otherwise sleep for a short while
+            time.sleep(1)
+
+        print(lines)
+        self.assertEqual(len(lines), 5)
+        # header line
+        self.assertIn('TC RD AA', lines[0])
+
+        queries = {}
+        timeouts = {}
+
+        for line in lines[1:]:
+            self.assertIn('DoUDP', line)
+            if 'T.O' in line:
+                queryID = int(line.split()[4])
+                timeouts[queryID] = line
+            else:
+                queryID = int(line.split()[3])
+                queries[queryID] = line
+            if queryID == 42:
+                self.assertIn('RD', line)
+            else:
+                self.assertNotIn('RD', line)
+
+        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)
diff --git a/regression-tests.dnsdist/test_Async.py b/regression-tests.dnsdist/test_Async.py
new file mode 100644 (file)
index 0000000..eda4a59
--- /dev/null
@@ -0,0 +1,631 @@
+#!/usr/bin/env python
+
+import os
+import socket
+import sys
+import threading
+import unittest
+import dns
+import dns.message
+import doqclient
+
+from dnsdisttests import DNSDistTest, pickAvailablePort
+
+def AsyncResponder(listenPath, responsePath):
+    # Make sure the socket does not already exist
+    try:
+        os.unlink(listenPath)
+    except OSError:
+        if os.path.exists(listenPath):
+            raise
+
+    sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
+    try:
+        sock.bind(listenPath)
+    except socket.error as e:
+        print("Error binding in the Asynchronous responder: %s" % str(e))
+        sys.exit(1)
+
+    while True:
+        data, addr = sock.recvfrom(65535)
+        print("Got message [%d] '%s' from %s" % (len(data), data, addr))
+        if not data:
+            break
+
+        request = dns.message.from_wire(data)
+        reply = str(request.id) + ' '
+        if str(request.question[0].name).startswith('accept-then-refuse'):
+            if request.flags & dns.flags.QR:
+                reply = reply + 'refuse'
+            else:
+                reply = reply + 'accept'
+        elif str(request.question[0].name).startswith('accept-then-drop'):
+            if request.flags & dns.flags.QR:
+                reply = reply + 'drop'
+            else:
+                reply = reply + 'accept'
+        elif str(request.question[0].name).startswith('accept-then-custom'):
+            if request.flags & dns.flags.QR:
+                reply = reply + 'custom'
+            else:
+                reply = reply + 'accept'
+        elif str(request.question[0].name).startswith('timeout-then-accept'):
+            if request.flags & dns.flags.QR:
+                reply = reply + 'accept'
+            else:
+                # no response
+                continue
+        elif str(request.question[0].name).startswith('accept-then-timeout'):
+            if request.flags & dns.flags.QR:
+                # no response
+                continue
+            else:
+                reply = reply + 'accept'
+        elif str(request.question[0].name).startswith('accept'):
+            reply = reply + 'accept'
+        elif str(request.question[0].name).startswith('refuse'):
+            reply = reply + 'refuse'
+        elif str(request.question[0].name).startswith('drop'):
+            reply = reply + 'drop'
+        elif str(request.question[0].name).startswith('custom'):
+            reply = reply + 'custom'
+        elif str(request.question[0].name).startswith('timeout'):
+            # no response
+            continue
+        else:
+            reply = reply + 'invalid'
+
+        remote = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
+        remote.connect(responsePath)
+        remote.send(reply.encode())
+        print("Sent [%d] '%s' to %s" % (len(reply), reply, responsePath))
+
+    sock.close()
+
+asyncResponderSocketPath = '/tmp/async-responder.sock'
+dnsdistSocketPath = '/tmp/dnsdist.sock'
+asyncResponder = threading.Thread(name='Asynchronous Responder', target=AsyncResponder, args=[asyncResponderSocketPath, dnsdistSocketPath])
+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
+        """
+        for name in ['accept.async.tests.powerdns.com.', 'accept.tcp-only.async.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)
+
+            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)
+
+    def testPassCached(self):
+        """
+        Async: Accept (cached)
+        """
+        name = 'accept.cache.async.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)
+
+        for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
+            sender = getattr(self, method)
+            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)
+
+    def testTimeoutThenAccept(self):
+        """
+        Async: Timeout then accept
+        """
+        for name in ['timeout-then-accept.async.tests.powerdns.com.', 'timeout-then-accept.tcp-only.async.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)
+
+            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)
+
+    def testAcceptThenTimeout(self):
+        """
+        Async: Accept then timeout
+        """
+        for name in ['accept-then-timeout.async.tests.powerdns.com.', 'accept-then-timeout.tcp-only.async.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)
+
+            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)
+
+    def testAcceptThenRefuse(self):
+        """
+        Async: Accept then refuse
+        """
+        for name in ['accept-then-refuse.async.tests.powerdns.com.', 'accept-then-refuse.tcp-only.async.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)
+            expectedResponse.flags |= dns.flags.RA
+            expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+            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)
+
+    def testAcceptThenCustom(self):
+        """
+        Async: Accept then custom
+        """
+        for name in ['accept-then-custom.async.tests.powerdns.com.', 'accept-then-custom.tcp-only.async.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)
+
+            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", "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)
+
+    def testAcceptThenDrop(self):
+        """
+        Async: Accept then drop
+        """
+        for name in ['accept-then-drop.async.tests.powerdns.com.', 'accept-then-drop.tcp-only.async.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)
+
+            for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
+                sender = getattr(self, method)
+                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)
+
+    def testRefused(self):
+        """
+        Async: Refused
+        """
+        name = 'refused.async.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags |= dns.flags.RA
+        expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+        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)
+
+    def testDrop(self):
+        """
+        Async: Drop
+        """
+        name = 'drop.async.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+
+        for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
+            sender = getattr(self, method)
+            try:
+                (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            except doqclient.StreamResetError:
+                receivedResponse = None
+            self.assertEqual(receivedResponse, None)
+
+    def testCustom(self):
+        """
+        Async: Custom answer
+        """
+        name = 'custom.async.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags |= dns.flags.RA
+        expectedResponse.set_rcode(dns.rcode.FORMERR)
+
+        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)
+
+    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
+
+        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)
+
+            # first response is a TC=1
+            tcResponse = dns.message.make_response(query)
+            tcResponse.flags |= dns.flags.TC
+            self._toResponderQueue.put(tcResponse, True, 2.0)
+
+            # 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:%d", pool={'', 'cache'}}
+    newServer{address="127.0.0.1:%d", pool="tcp-only", tcpOnly=true }
+
+    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
+
+    local filteringTagName = 'filtering'
+    local filteringTagValue = 'pass'
+    local asyncID = 0
+
+    pc = newPacketCache(100)
+    getPool('cache'):setCache(pc)
+
+    local asyncObjectsMap = {}
+
+    function gotAsyncResponse(endpointID, message, from)
+
+      print('Got async response '..message)
+      local parts = {}
+      for part in message:gmatch("%%S+") do table.insert(parts, part) end
+      if #parts ~= 2 then
+        print('Invalid message')
+        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)
+        return
+      end
+      if parts[2] == 'refuse' then
+        print('refusing')
+        C.dnsdist_ffi_set_rcode_from_async(asyncID, queryID, DNSRCode.REFUSED, true)
+        return
+      end
+      if parts[2] == 'drop' then
+        print('dropping')
+        C.dnsdist_ffi_drop_from_async(asyncID, queryID)
+        return
+      end
+      if parts[2] == 'custom' then
+        print('sending a custom response')
+        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
+
+    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
+
+      local queryPtr = C.dnsdist_ffi_dnsquestion_get_header(dq)
+      local querySize = C.dnsdist_ffi_dnsquestion_get_len(dq)
+
+      -- we need to take a copy, as we can no longer touch that data after calling set_async
+      local buffer = ffi.string(queryPtr, querySize)
+
+      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
+    end
+
+    function passResponseToAsyncFilter(dr)
+      print('in passResponseToAsyncFilter')
+      local timeout = 500 -- 500 ms
+
+      local responsePtr = C.dnsdist_ffi_dnsquestion_get_header(dr)
+      local responseSize = C.dnsdist_ffi_dnsquestion_get_len(dr)
+
+      -- we need to take a copy, as we can no longer touch that data after calling set_async
+      local buffer = ffi.string(responsePtr, responseSize)
+
+      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
+    end
+
+    -- this only matters for tests actually reaching the backend
+    addAction('tcp-only.async.tests.powerdns.com', PoolAction('tcp-only', false))
+    addAction('cache.async.tests.powerdns.com', PoolAction('cache', false))
+    addAction(AllRule(), LuaFFIAction(passQueryToAsyncFilter))
+    addCacheHitResponseAction(AllRule(), LuaFFIResponseAction(passResponseToAsyncFilter))
+    addResponseAction(AllRule(), LuaFFIResponseAction(passResponseToAsyncFilter))
+    """
+    _asyncResponderSocketPath = asyncResponderSocketPath
+    _dnsdistSocketPath = 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):
+    _config_template = """
+    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:%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'
+    local asyncID = 0
+
+    pc = newPacketCache(100)
+    getPool('cache'):setCache(pc)
+
+    function gotAsyncResponse(endpointID, message, from)
+
+      print('Got async response '..message)
+      local parts = {}
+      for part in message:gmatch("%%S+") do
+        table.insert(parts, part)
+      end
+      if #parts ~= 2 then
+        print('Invalid message')
+        return
+      end
+      local queryID = tonumber(parts[1])
+      local asyncObject = getAsynchronousObject(asyncID, queryID)
+      if parts[2] == 'accept' then
+        print('accepting')
+        local dq = asyncObject:getDQ()
+        dq:setTag(filteringTagName, filteringTagValue)
+        asyncObject:resume()
+        return
+      end
+      if parts[2] == 'refuse' then
+        print('refusing')
+        local dq = asyncObject:getDQ()
+        asyncObject:setRCode(DNSRCode.REFUSED, true)
+        asyncObject:resume()
+        return
+      end
+      if parts[2] == 'drop' then
+        print('dropping')
+        asyncObject:drop()
+        return
+      end
+      if parts[2] == 'custom' then
+        print('sending a custom response')
+        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
+
+    asyncResponderEndpoint = newNetworkEndpoint('%s')
+    listener = newNetworkListener()
+    listener:addUnixListeningEndpoint('%s', 0, gotAsyncResponse)
+    listener:start()
+
+    function passQueryToAsyncFilter(dq)
+      print('in passQueryToAsyncFilter')
+      local timeout = 500 -- 500 ms
+
+      local buffer = dq:getContent()
+      local id = dq.dh:getID()
+      dq:suspend(asyncID, id, timeout)
+      asyncResponderEndpoint:send(buffer)
+
+      return DNSAction.Allow
+    end
+
+    function passResponseToAsyncFilter(dr)
+      print('in passResponseToAsyncFilter')
+      local timeout = 500 -- 500 ms
+
+      local buffer = dr:getContent()
+      local id = dr.dh:getID()
+      dr:suspend(asyncID, id, timeout)
+      asyncResponderEndpoint:send(buffer)
+
+      return DNSResponseAction.Allow
+    end
+
+    -- this only matters for tests actually reaching the backend
+    addAction('tcp-only.async.tests.powerdns.com', PoolAction('tcp-only', false))
+    addAction('cache.async.tests.powerdns.com', PoolAction('cache', false))
+    addAction(AllRule(), LuaAction(passQueryToAsyncFilter))
+    addCacheHitResponseAction(AllRule(), LuaResponseAction(passResponseToAsyncFilter))
+    addResponseAction(AllRule(), LuaResponseAction(passResponseToAsyncFilter))
+    """
+    _asyncResponderSocketPath = asyncResponderSocketPath
+    _dnsdistSocketPath = dnsdistSocketPath
+    _config_params = ['_testServerPort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_dohWithH2OServerPort', '_serverCert', '_serverKey', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_doqServerPort', '_serverCert', '_serverKey', '_asyncResponderSocketPath', '_dnsdistSocketPath']
+    _verboseMode = True
index 5ce70a3fe756a2fb902091a51ea32e6c9f07de9f..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
@@ -15,11 +16,22 @@ class TestBackendDiscovery(DNSDistTest):
     _svcUpgradeDoTBackendDifferentAddrPort1 = 10604
     _svcUpgradeDoTBackendDifferentAddrPort2 = 10605
     _svcUpgradeDoTUnreachableBackendPort = 10606
+    _svcBrokenDNSResponseBackendPort = 10607
+    _svcUpgradeDoHBackendWithoutPathPort = 10608
+    _connectionRefusedBackendPort = 10609
+    _eofBackendPort = 10610
+    _servfailBackendPort = 10611
+    _wrongNameBackendPort = 10612
+    _wrongIDBackendPort = 10613
+    _tooManyQuestionsBackendPort = 10614
+    _badQNameBackendPort = 10615
+    _svcUpgradeDoTNoPortBackendPort = 10616
+    _svcUpgradeDoHNoPortBackendPort = 10617
     _upgradedBackendsPool = 'upgraded'
 
     _consoleKey = DNSDistTest.generateConsoleKey()
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
-    _config_params = ['_consoleKeyB64', '_consolePort', '_noSVCBackendPort', '_svcNoUpgradeBackendPort', '_svcUpgradeDoTBackendPort', '_upgradedBackendsPool', '_svcUpgradeDoHBackendPort', '_svcUpgradeDoTBackendDifferentAddrPort1', '_svcUpgradeDoTBackendDifferentAddrPort2', '_svcUpgradeDoTUnreachableBackendPort']
+    _config_params = ['_consoleKeyB64', '_consolePort', '_noSVCBackendPort', '_svcNoUpgradeBackendPort', '_svcUpgradeDoTBackendPort', '_upgradedBackendsPool', '_svcUpgradeDoHBackendPort', '_svcUpgradeDoTBackendDifferentAddrPort1', '_svcUpgradeDoTBackendDifferentAddrPort2', '_svcUpgradeDoTUnreachableBackendPort', '_svcBrokenDNSResponseBackendPort', '_svcUpgradeDoHBackendWithoutPathPort', '_connectionRefusedBackendPort', '_eofBackendPort', '_servfailBackendPort', '_wrongNameBackendPort', '_wrongIDBackendPort', '_tooManyQuestionsBackendPort', '_badQNameBackendPort', '_svcUpgradeDoTNoPortBackendPort', '_svcUpgradeDoHNoPortBackendPort']
     _config_template = """
     setKey("%s")
     controlSocket("127.0.0.1:%d")
@@ -33,10 +45,10 @@ class TestBackendDiscovery(DNSDistTest):
     newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
 
     -- SVCB upgrade to DoT, same address, keep the backend, different pool
-    newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradePool='%s', autoUpgradeKeep=true}:setUp()
+    newServer{address="127.0.0.1:%s", caStore='ca.pem', pool={'', 'another-pool'}, autoUpgrade=true, autoUpgradePool='%s', autoUpgradeKeep=true, source='127.0.0.1@lo'}:setUp()
 
     -- SVCB upgrade to DoH, same address, do not keep the backend, same pool
-    newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
+    newServer{address="127.0.0.1:%s", caStore='ca.pem', pool={'another-pool'}, autoUpgrade=true, autoUpgradeKeep=false}:setUp()
 
     -- SVCB upgrade to DoT, different address, certificate is valid for the initial address
     newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
@@ -46,6 +58,39 @@ class TestBackendDiscovery(DNSDistTest):
 
     -- SVCB upgrade to DoT but upgraded port is not reachable
     newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
+
+    -- The SVCB response is not valid
+    newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
+
+    -- SVCB upgrade to DoH except the path is not specified
+    newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
+
+    -- Connection refused
+    newServer({address="127.0.0.1:%s", caStore='ca.pem', pool={"", "other-pool"}, autoUpgrade=true, source='127.0.0.1@lo'}):setUp()
+
+    -- EOF
+    newServer({address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true}):setUp()
+
+    -- ServFail
+    newServer({address="127.0.0.1:%s", autoUpgrade=true}):setUp()
+
+    -- Wrong name
+    newServer({address="127.0.0.1:%s", autoUpgrade=true}):setUp()
+
+    -- Wrong ID
+    newServer({address="127.0.0.1:%s", autoUpgrade=true}):setUp()
+
+    -- Too many questions
+    newServer({address="127.0.0.1:%s", autoUpgrade=true}):setUp()
+
+    -- Bad QName
+    newServer({address="127.0.0.1:%s", autoUpgrade=true}):setUp()
+
+    -- SVCB upgrade to DoT, same address, no port specified via SVCB
+    newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
+
+    -- SVCB upgrade to DoH, same address, no port specified via SVCB
+    newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
     """
     _verboseMode = True
 
@@ -70,6 +115,33 @@ class TestBackendDiscovery(DNSDistTest):
                                     dns.rdatatype.SVCB,
                                     '1 tls.tests.dnsdist.org. alpn="dot" port=10652 ipv4hint=127.0.0.1')
         response.answer.append(rrset)
+        # add a useless A record for good measure
+        rrset = dns.rrset.from_text(request.question[0].name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        response.answer.append(rrset)
+        # plus more useless records in authority
+        rrset = dns.rrset.from_text(request.question[0].name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        response.authority.append(rrset)
+        # and finally valid, albeit useless, hints
+        rrset = dns.rrset.from_text('tls.tests.dnsdist.org.',
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        response.additional.append(rrset)
+        rrset = dns.rrset.from_text('tls.tests.dnsdist.org.',
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '::1')
+        response.additional.append(rrset)
         return response.to_wire()
 
     def UpgradeDoHCallback(request):
@@ -112,57 +184,171 @@ class TestBackendDiscovery(DNSDistTest):
         response.answer.append(rrset)
         return response.to_wire()
 
+    def BrokenResponseCallback(request):
+        response = dns.message.make_response(request)
+        response.use_edns(edns=False)
+        response.question = []
+        return response.to_wire()
+
+    def UpgradeDoHMissingPathCallback(request):
+        response = dns.message.make_response(request)
+        rrset = dns.rrset.from_text(request.question[0].name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.SVCB,
+                                    '1 tls.tests.dnsdist.org. alpn="h2" port=10653 ipv4hint=127.0.0.1')
+        response.answer.append(rrset)
+        return response.to_wire()
+
+    def EOFCallback(request):
+        return None
+
+    def ServFailCallback(request):
+        response = dns.message.make_response(request)
+        response.set_rcode(dns.rcode.SERVFAIL)
+        return response.to_wire()
+
+    def WrongNameCallback(request):
+        query = dns.message.make_query('not-the-right-one.', dns.rdatatype.SVCB)
+        response = dns.message.make_response(query)
+        response.id = request.id
+        return response.to_wire()
+
+    def WrongIDCallback(request):
+        response = dns.message.make_response(request)
+        response.id = request.id ^ 42
+        return response.to_wire()
+
+    def WrongIDCallback(request):
+        response = dns.message.make_response(request)
+        response.id = request.id ^ 42
+        return response.to_wire()
+
+    def TooManyQuestionsCallback(request):
+        response = dns.message.make_response(request)
+        response.question.append(response.question[0])
+        return response.to_wire()
+
+    def BadQNameCallback(request):
+        response = dns.message.make_response(request)
+        wire = bytearray(response.to_wire())
+        # mess up the first label length
+        wire[12] = 0xFF
+        return wire
+
+    def UpgradeDoTNoPortCallback(request):
+        response = dns.message.make_response(request)
+        rrset = dns.rrset.from_text(request.question[0].name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.SVCB,
+                                    '1 tls.tests.dnsdist.org. alpn="dot" ipv4hint=127.0.0.1')
+        response.answer.append(rrset)
+        return response.to_wire()
+
+    def UpgradeDoHNoPortCallback(request):
+        response = dns.message.make_response(request)
+        rrset = dns.rrset.from_text(request.question[0].name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.SVCB,
+                                    '1 tls.tests.dnsdist.org. alpn="h2" ipv4hint=127.0.0.1 key7="/dns-query{?dns}"')
+        response.answer.append(rrset)
+        return response.to_wire()
+
     @classmethod
     def startResponders(cls):
         tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
         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.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.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.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.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.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.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.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.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.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.daemon = True
+        TCPUpgradeToDoHNoPortResponder.start()
+
+
     def checkBackendsUpgraded(self):
         output = self.sendConsoleCommand('showServers()')
         print(output)
@@ -172,23 +358,41 @@ class TestBackendDiscovery(DNSDistTest):
             if line.startswith('#') or line.startswith('All'):
                 continue
             tokens = line.split()
-            self.assertTrue(len(tokens) == 12 or len(tokens) == 13)
-            self.assertEquals(tokens[2], 'UP')
+            self.assertTrue(len(tokens) == 13 or len(tokens) == 14)
+            if tokens[1] == '127.0.0.1:10652':
+                # 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.assertEqual(tokens[2], 'up')
+            else:
+                self.assertEqual(tokens[2], 'UP')
             pool = ''
-            if len(tokens) == 13:
-                pool = tokens[12]
+            if len(tokens) == 14:
+                pool = tokens[13]
             backends[tokens[1]] = pool
 
         expected = {
             '127.0.0.1:10600': '',
             '127.0.0.1:10601': '',
-            '127.0.0.1:10602': '',
+            '127.0.0.1:10602': 'another-pool',
             # 10603 has been upgraded to 10653 and removed
             # 10604 has been upgraded to 10654 and removed
             '127.0.0.2:10605': '',
             '127.0.0.1:10606': '',
+            '127.0.0.1:10607': '',
+            '127.0.0.1:10608': '',
+            '127.0.0.1:10609': 'other-pool',
+            '127.0.0.1:10610': '',
+            '127.0.0.1:10611': '',
+            '127.0.0.1:10612': '',
+            '127.0.0.1:10613': '',
+            '127.0.0.1:10614': '',
+            '127.0.0.1:10615': '',
+            # these two are not upgraded because there is no backend listening on the default ports (443 and 853)
+            '127.0.0.1:10616': '',
+            '127.0.0.1:10617': '',
             '127.0.0.1:10652': 'upgraded',
-            '127.0.0.1:10653': '',
+            '127.0.0.1:10653': 'another-pool',
             '127.0.0.2:10654': ''
         }
         print(backends)
@@ -198,7 +402,6 @@ class TestBackendDiscovery(DNSDistTest):
         """
         Backend Discovery: Upgrade
         """
-
         # enough time for discovery to happen
         # 5s is not enough with TSAN
         time.sleep(10)
@@ -206,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 2ed764ce86b08b7dfb58feee9d75486f278a7ebc..2b4f0a06ad98416272af2bcc58d89da41b074571 100644 (file)
@@ -15,6 +15,7 @@ def writeCDB(fname, variant=1):
     cdb.add(b'this is the value of the qname tag', b'this is the value of the second tag')
     cdb.commit().close()
     os.rename(fname+'.tmp', fname)
+    cdb.close()
 
 @unittest.skipIf('SKIP_CDB_TESTS' in os.environ, 'CDB tests are disabled')
 class CDBTest(DNSDistTest):
@@ -184,6 +185,9 @@ class TestCDBReload(CDBTest):
             self.assertEqual(expectedResponse, receivedResponse)
 
         # write a new CDB which has no entry for 127.0.0.1
+        # first ensure that the mtime will change after writing
+        # the new version
+        time.sleep(1)
         writeCDB(self._cdbFileName, 2)
         # wait long enough for the CDB database to be reloaded
         time.sleep(self._cdbRefreshDelay + 1)
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"}
     """
 
diff --git a/regression-tests.dnsdist/test_CacheInsertedResponses.py b/regression-tests.dnsdist/test_CacheInsertedResponses.py
new file mode 100644 (file)
index 0000000..a50d01b
--- /dev/null
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+import base64
+import time
+import dns
+from dnsdisttests import DNSDistTest
+
+class TestCacheInsertedResponses(DNSDistTest):
+
+    capTTLMax = 3600
+    capTTLMin = 60
+    _config_template = """
+    pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
+    getPool(""):setCache(pc)
+    addCacheInsertedResponseAction(SuffixMatchNodeRule("cacheinsertedresponses.tests.powerdns.com."), LimitTTLResponseAction(%d, %d))
+    newServer{address="127.0.0.1:%s"}
+    """
+    _config_params = ['capTTLMax', 'capTTLMin', '_testServerPort']
+
+    def testTTLSetAfterInsertion(self):
+        """
+        CacheInsertedResponse: Check that the TTL is capped after inserting into the cache
+        """
+        initialTTL = 86400
+        name = 'reduce-ttl-after-insertion.cacheinsertedresponses.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AAAA', 'IN')
+
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    initialTTL,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '::1')
+        response.answer.append(rrset)
+
+        responseOnMiss = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    self.capTTLMax,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '::1')
+        responseOnMiss.answer.append(rrset)
+
+        # first query to fill the cache
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(receivedResponse, responseOnMiss)
+        self.assertLessEqual(receivedResponse.answer[0].ttl, self.capTTLMax)
+
+        # now the result should be cached
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertEqual(receivedResponse, response)
+        self.assertGreater(receivedResponse.answer[0].ttl, self.capTTLMax)
+        self.assertLessEqual(receivedResponse.answer[0].ttl, initialTTL)
+
+    def testTTLRaisedAfterInsertion(self):
+        """
+        CacheInsertedResponse: Check that the TTL can be raised after inserting into the cache
+        """
+        initialTTL = 0
+        name = 'raise-ttl-after-insertion.cacheinsertedresponses.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AAAA', 'IN')
+
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    initialTTL,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '::1')
+        response.answer.append(rrset)
+
+        responseOnMiss = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    self.capTTLMax,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '::1')
+        responseOnMiss.answer.append(rrset)
+
+        # first query to fill the cache
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(receivedResponse, responseOnMiss)
+        self.assertGreater(receivedResponse.answer[0].ttl, initialTTL)
+        self.assertLessEqual(receivedResponse.answer[0].ttl, self.capTTLMin)
+
+        # the result should NOT have been cached
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(receivedResponse, responseOnMiss)
+        self.assertGreater(receivedResponse.answer[0].ttl, initialTTL)
+        self.assertLessEqual(receivedResponse.answer[0].ttl, self.capTTLMin)
+
index 587b0f0b3df94ae04e3aab7d6e0b662b440c0341..f9421812b36327221cd91170712f331a85c287fd 100644 (file)
@@ -4,15 +4,16 @@ import time
 import dns
 import clientsubnetoption
 import cookiesoption
-from dnsdisttests import DNSDistTest
+import requests
+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, ""
@@ -233,6 +234,80 @@ class TestCaching(DNSDistTest):
             value = self._responsesCounter[key]
             self.assertEqual(value, numberOfQueries)
 
+    def testAXFRResponse(self):
+        """
+        Cache: AXFR should not be cached
+
+        dnsdist should not cache responses to AXFR queries.
+        """
+        name = 'axfr.cache.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AXFR', 'IN')
+        response = dns.message.make_response(query)
+        soa = dns.rrset.from_text(name,
+                                  60,
+                                  dns.rdataclass.IN,
+                                  dns.rdatatype.SOA,
+                                  'ns.' + name + ' hostmaster.' + name + ' 1 3600 3600 3600 60')
+        response.answer.append(soa)
+        response.answer.append(dns.rrset.from_text(name,
+                                                   60,
+                                                   dns.rdataclass.IN,
+                                                   dns.rdatatype.A,
+                                                   '192.0.2.1'))
+        response.answer.append(soa)
+        numberOfQueries = 5
+
+        for _ in range(numberOfQueries):
+            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(receivedResponse, response)
+
+        for key in self._responsesCounter:
+            value = self._responsesCounter[key]
+            self.assertEqual(value, numberOfQueries)
+
+    def testIXFRResponse(self):
+        """
+        Cache: IXFR should not be cached
+
+        dnsdist should not cache responses to IXFR queries.
+        """
+        name = 'ixfr.cache.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'IXFR', 'IN')
+        response = dns.message.make_response(query)
+        soa = dns.rrset.from_text(name,
+                                  60,
+                                  dns.rdataclass.IN,
+                                  dns.rdatatype.SOA,
+                                  'ns.' + name + ' hostmaster.' + name + ' 1 3600 3600 3600 60')
+        response.answer.append(soa)
+        response.answer.append(dns.rrset.from_text(name,
+                                                   60,
+                                                   dns.rdataclass.IN,
+                                                   dns.rdatatype.A,
+                                                   '192.0.2.1'))
+        response.answer.append(soa)
+        numberOfQueries = 5
+
+        for _ in range(numberOfQueries):
+            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(receivedResponse, response)
+
+        for key in self._responsesCounter:
+            value = self._responsesCounter[key]
+            self.assertEqual(value, numberOfQueries)
+
     def testCacheExpiration(self):
         """
         Cache: Cache expiration
@@ -849,6 +924,7 @@ class TestCachingHashingCookies(DNSDistTest):
 
 class TestTempFailureCacheTTLAction(DNSDistTest):
 
+    _extraStartupSleep = 1
     _config_template = """
     pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
     getPool(""):setCache(pc)
@@ -1212,7 +1288,7 @@ class TestCachingStaleExpungePrevented(DNSDistTest):
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
     _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort']
     _config_template = """
-    pc = newPacketCache(100, {maxTTL=86400, minTTL=1, temporaryFailureTTL=0, staleTTL=60, dontAge=false, numberOfShards=1, deferrableInsertLock=true, maxNegativeTTL=3600, ecsParsing=false, keepStaleData=true})
+    pc = newPacketCache(100, {maxTTL=86400, minTTL=1, temporaryFailureTTL=0, staleTTL=60, dontAge=false, numberOfShards=1, deferrableInsertLock=true, maxNegativeTTL=3600, parseECS=false, keepStaleData=true})
     getPool(""):setCache(pc)
     setStaleCacheEntriesTTL(600)
     -- try to remove all expired entries
@@ -2706,3 +2782,199 @@ class TestCachingBackendSettingRD(DNSDistTest):
             self.assertTrue(receivedQuery)
             self.assertTrue(receivedResponse)
             self.assertEqual(receivedResponse, expectedResponse)
+
+class TestAPICache(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='
+    _config_params = ['_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+    webserver("127.0.0.1:%s")
+    setWebserverConfig({password="%s", apiKey="%s"})
+    pc = newPacketCache(100)
+    getPool(""):setCache(pc)
+    getPool("pool-with-cache"):setCache(pc)
+    getPool("pool-without-cache")
+    """
+
+    def testCacheClearingViaAPI(self):
+        """
+        Cache: Clear cache via API
+        """
+        headers = {'x-api-key': self._webServerAPIKey}
+        url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/cache'
+        name = 'cache-api.cache.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AAAA', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '::1')
+        response.answer.append(rrset)
+
+        # first query to fill the cache
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(receivedResponse, response)
+
+        # second query should be a hit
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertEqual(receivedResponse, response)
+
+        # GET should on the cache API should yield a 400
+        r = requests.get(url + '?pool=pool-without-cache&name=cache-api.cache.tests.powerdns.com.&type=AAAA', headers=headers, timeout=self._webTimeout)
+        self.assertEqual(r.status_code, 400)
+
+        # different pool
+        r = requests.delete(url + '?pool=pool-without-cache&name=cache-api.cache.tests.powerdns.com.&type=AAAA', headers=headers, timeout=self._webTimeout)
+        self.assertEqual(r.status_code, 404)
+
+        # no 'pool'
+        r = requests.delete(url + '?name=cache-api.cache.tests.powerdns.com.&type=AAAA', headers=headers, timeout=self._webTimeout)
+        self.assertEqual(r.status_code, 400)
+
+        # no 'name'
+        r = requests.delete(url + '?pool=pool-without-cache&type=AAAA', headers=headers, timeout=self._webTimeout)
+        self.assertEqual(r.status_code, 400)
+
+        # invalid name (label is too long)
+        r = requests.delete(url + '?pool=&name=' + 'a'*65, headers=headers, timeout=self._webTimeout)
+        self.assertEqual(r.status_code, 400)
+
+        # different name
+        r = requests.delete(url + '?pool=&name=not-cache-api.cache.tests.powerdns.com.', headers=headers, timeout=self._webTimeout)
+        self.assertTrue(r)
+        self.assertEqual(r.status_code, 200)
+        content = r.json()
+        self.assertIn('count', content)
+        self.assertEqual(int(content['count']), 0)
+
+        # different type
+        r = requests.delete(url + '?pool=&name=cache-api.cache.tests.powerdns.com.&type=A', headers=headers, timeout=self._webTimeout)
+        self.assertTrue(r)
+        self.assertEqual(r.status_code, 200)
+        content = r.json()
+        self.assertIn('count', content)
+        self.assertEqual(int(content['count']), 0)
+
+        # should still be a hit
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertEqual(receivedResponse, response)
+
+        # remove
+        r = requests.delete(url + '?pool=&name=cache-api.cache.tests.powerdns.com.&type=AAAA', headers=headers, timeout=self._webTimeout)
+        self.assertTrue(r)
+        self.assertEqual(r.status_code, 200)
+        content = r.json()
+        self.assertIn('count', content)
+        self.assertEqual(int(content['count']), 1)
+
+        # should be a miss
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(receivedResponse, response)
+
+        # remove all types
+        r = requests.delete(url + '?pool=&name=cache-api.cache.tests.powerdns.com.', headers=headers, timeout=self._webTimeout)
+        self.assertTrue(r)
+        self.assertEqual(r.status_code, 200)
+        content = r.json()
+        self.assertIn('count', content)
+        self.assertEqual(int(content['count']), 1)
+
+        # should be a miss
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(receivedResponse, response)
+
+        # suffix removal
+        r = requests.delete(url + '?pool=&name=cache.tests.powerdns.com.&suffix=true', headers=headers, timeout=self._webTimeout)
+        self.assertTrue(r)
+        self.assertEqual(r.status_code, 200)
+        content = r.json()
+        self.assertIn('count', content)
+        self.assertEqual(int(content['count']), 1)
+
+        # should be a miss
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        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 924abf47ad70a4131cba85ce9c677530eb5e02e4..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,13 +64,20 @@ 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):
+        try:
+            float(num)
+            return True
+        except ValueError:
+            return False
+
     def testCarbon(self):
         """
         Carbon: send data to 2 carbon servers
@@ -94,7 +101,7 @@ class TestCarbon(DNSDistTest):
             self.assertTrue(line.startswith(expectedStart))
             parts = line.split(b' ')
             self.assertEqual(len(parts), 3)
-            self.assertTrue(parts[1].isdigit())
+            self.assertTrue(self.isfloat(parts[1]))
             self.assertTrue(parts[2].isdigit())
             self.assertTrue(int(parts[2]) <= int(after))
 
@@ -105,7 +112,7 @@ class TestCarbon(DNSDistTest):
             self.assertTrue(line.startswith(expectedStart))
             parts = line.split(b' ')
             self.assertEqual(len(parts), 3)
-            self.assertTrue(parts[1].isdigit())
+            self.assertTrue(self.isfloat(parts[1]))
             self.assertTrue(parts[2].isdigit())
             self.assertTrue(int(parts[2]) <= int(after))
 
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 15cc8dc08b5777008c3fe68228b9f792d4fbc2c5..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')
@@ -29,8 +28,6 @@ class DNSCryptTest(DNSDistTest):
     _resolverCertificateValidFrom = int(time.time() - 60)
     _resolverCertificateValidUntil = int(time.time() + 7200)
 
-    _dnsdistStartupDelay = 10
-
     def doDNSCryptQuery(self, client, query, response, tcp):
         self._toResponderQueue.put(response)
         data = client.query(query.to_wire(), tcp=tcp)
@@ -78,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)
@@ -100,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)
@@ -132,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()
@@ -250,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)
@@ -261,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)
@@ -284,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)
@@ -335,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
@@ -343,6 +340,7 @@ class TestDNSCryptAutomaticRotation(DNSCryptTest):
         last = now
       end
     end
+    addMaintenanceCallback(reloadCallback)
     """
 
     _config_params = ['_consoleKeyB64', '_consolePort', '_resolverCertificateSerial', '_resolverCertificateValidFrom', '_resolverCertificateValidUntil', '_dnsDistPortDNSCrypt', '_providerName', '_testServerPort', '_resolverCertificateSerial']
@@ -351,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()
diff --git a/regression-tests.dnsdist/test_DNSParser.py b/regression-tests.dnsdist/test_DNSParser.py
new file mode 100644 (file)
index 0000000..1a14821
--- /dev/null
@@ -0,0 +1,146 @@
+#!/usr/bin/env python
+import unittest
+import dns
+from dnsdisttests import DNSDistTest
+
+class TestDNSParser(DNSDistTest):
+
+    _verboseMode = True
+    _config_template = """
+  function checkQueryPacket(dq)
+    local packet = dq:getContent()
+    if #packet ~= 41 then
+      return DNSAction.Spoof, #packet..".invalid.query.size."
+    end
+
+    local overlay = newDNSPacketOverlay(packet)
+    if overlay.qname:toString() ~= "powerdns.com." then
+      return DNSAction.Spoof, overlay.qname:toString().."invalid.query.qname."
+    end
+    if overlay.qtype ~= DNSQType.A then
+      return DNSAction.Spoof, overlay.qtype..".invalid.query.qtype."
+    end
+    if overlay.qclass ~= DNSClass.IN then
+      return DNSAction.Spoof, overlay.qclass..".invalid.query.qclass."
+    end
+    local count = overlay:getRecordsCountInSection(0)
+    if count ~= 0 then
+      return DNSAction.Spoof, count..".invalid.query.count.in.q."
+    end
+    count = overlay:getRecordsCountInSection(1)
+    if count ~= 0 then
+      return DNSAction.Spoof, count..".invalid.query.count.in.a."
+    end
+    count = overlay:getRecordsCountInSection(2)
+    if count ~= 0 then
+      return DNSAction.Spoof, count..".invalid.query.count.in.auth."
+    end
+    count = overlay:getRecordsCountInSection(3)
+    -- for OPT
+    if count ~= 1 then
+      return DNSAction.Spoof, count..".invalid.query.count.in.add."
+    end
+    return DNSAction.None
+  end
+
+  function checkResponsePacket(dq)
+    local packet = dq:getContent()
+    if #packet ~= 57 then
+      print(#packet..".invalid.size.")
+      return DNSResponseAction.ServFail
+    end
+
+    local overlay = newDNSPacketOverlay(packet)
+    if overlay.qname:toString() ~= "powerdns.com." then
+      print(overlay.qname:toString().."invalid.qname.")
+      return DNSResponseAction.ServFail
+    end
+    if overlay.qtype ~= DNSQType.A then
+      print(overlay.qtype..".invalid.qtype.")
+      return DNSResponseAction.ServFail
+    end
+    if overlay.qclass ~= DNSClass.IN then
+      print(overlay.qclass..".invalid.qclass.")
+      return DNSResponseAction.ServFail
+    end
+    local count = overlay:getRecordsCountInSection(0)
+    if count ~= 0 then
+      print(count..".invalid.count.in.q.")
+      return DNSResponseAction.ServFail
+    end
+    count = overlay:getRecordsCountInSection(1)
+    if count ~= 1 then
+      print(count..".invalid.count.in.a.")
+      return DNSResponseAction.ServFail
+    end
+    count = overlay:getRecordsCountInSection(2)
+    if count ~= 0 then
+      print(count..".invalid.count.in.auth.")
+      return DNSResponseAction.ServFail
+    end
+    count = overlay:getRecordsCountInSection(3)
+    -- for OPT
+    if count ~= 1 then
+      print(count..".invalid.count.in.add.")
+      return DNSResponseAction.ServFail
+    end
+    local record = overlay:getRecord(0)
+    if record.name:toString() ~= "powerdns.com." then
+      print(record.name:toString()..".invalid.name.")
+      return DNSResponseAction.ServFail
+    end
+    if record.type ~= DNSQType.A then
+      print(record.type..".invalid.type.")
+      return DNSResponseAction.ServFail
+    end
+    if record.class ~= DNSClass.IN then
+      print(record.class..".invalid.class.")
+      return DNSResponseAction.ServFail
+    end
+    if record.ttl ~= 3600 then
+      print(record.ttl..".invalid.ttl.")
+      return DNSResponseAction.ServFail
+    end
+    if record.place ~= 1 then
+      print(record.place..".invalid.place.")
+      return DNSResponseAction.ServFail
+    end
+    if record.contentLength ~= 4 then
+      print(record.contentLength..".invalid.contentLength.")
+      return DNSResponseAction.ServFail
+    end
+    if record.contentOffset ~= 42 then
+      print(record.contentOffset..".invalid.contentOffset.")
+      return DNSResponseAction.ServFail
+    end
+    return DNSAction.None
+  end
+
+  addAction(AllRule(), LuaAction(checkQueryPacket))
+  addResponseAction(AllRule(), LuaResponseAction(checkResponsePacket))
+  newServer{address="127.0.0.1:%s"}
+    """
+
+    def testQuestionAndResponse(self):
+        """
+        DNS Parser: basic checks
+        """
+        name = 'powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        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)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            print(receivedResponse)
+            self.assertTrue(receivedQuery)
+            self.assertTrue(receivedResponse)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(receivedResponse, response)
index ee99e21d5672f4302ed3aa396383a342882669ec..7fc186d982d47eb2580f1262bea8c45d32e1b4a9 100644 (file)
 #!/usr/bin/env python
+
 import base64
 import dns
 import os
-import re
 import time
 import unittest
 import clientsubnetoption
-from dnsdisttests import DNSDistTest
+
+from dnsdistdohtests import DNSDistDOHTest
+from dnsdisttests import pickAvailablePort
 
 import pycurl
 from io import BytesIO
 
-@unittest.skipIf('SKIP_DOH_TESTS' in os.environ, 'DNS over HTTPS tests are disabled')
-class DNSDistDOHTest(DNSDistTest):
-
-    @classmethod
-    def getDOHGetURL(cls, baseurl, query, rawQuery=False):
-        if rawQuery:
-            wire = query
-        else:
-            wire = query.to_wire()
-        param = base64.urlsafe_b64encode(wire).decode('UTF8').rstrip('=')
-        return baseurl + "?dns=" + param
-
-    @classmethod
-    def openDOHConnection(cls, port, caFile, timeout=2.0):
-        conn = pycurl.Curl()
-        conn.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_2)
-
-        conn.setopt(pycurl.HTTPHEADER, ["Content-type: application/dns-message",
-                                         "Accept: application/dns-message"])
-        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):
-        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 useHTTPS:
-            conn.setopt(pycurl.SSL_VERIFYPEER, 1)
-            conn.setopt(pycurl.SSL_VERIFYHOST, 2)
-            if caFile:
-                conn.setopt(pycurl.CAINFO, caFile)
-
-        conn.setopt(pycurl.HTTPHEADER, customHeaders)
-        conn.setopt(pycurl.HEADERFUNCTION, response_headers.write)
-
-        if response:
-            cls._toResponderQueue.put(response, True, timeout)
-
-        receivedQuery = None
-        message = None
-        cls._response_headers = ''
-        data = conn.perform_rb()
-        cls._rcode = conn.getinfo(pycurl.RESPONSE_CODE)
-        if cls._rcode == 200 and not rawResponse:
-            message = dns.message.from_wire(data)
-        elif rawResponse:
-            message = data
-
-        if useQueue and not cls._fromResponderQueue.empty():
-            receivedQuery = cls._fromResponderQueue.get(True, timeout)
-
-        cls._response_headers = response_headers.getvalue()
-        return (receivedQuery, message)
-
-    @classmethod
-    def sendDOHPostQuery(cls, port, servername, baseurl, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False, rawResponse=False, customHeaders=[], useHTTPS=True):
-        url = baseurl
-        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 useHTTPS:
-            conn.setopt(pycurl.SSL_VERIFYPEER, 1)
-            conn.setopt(pycurl.SSL_VERIFYHOST, 2)
-            if caFile:
-                conn.setopt(pycurl.CAINFO, caFile)
-
-        conn.setopt(pycurl.HTTPHEADER, customHeaders)
-        conn.setopt(pycurl.HEADERFUNCTION, response_headers.write)
-        conn.setopt(pycurl.POST, True)
-        data = query
-        if not rawQuery:
-            data = data.to_wire()
-
-        conn.setopt(pycurl.POSTFIELDS, data)
-
-        if response:
-            cls._toResponderQueue.put(response, True, timeout)
-
-        receivedQuery = None
-        message = None
-        cls._response_headers = ''
-        data = conn.perform_rb()
-        cls._rcode = conn.getinfo(pycurl.RESPONSE_CODE)
-        if cls._rcode == 200 and not rawResponse:
-            message = dns.message.from_wire(data)
-        elif rawResponse:
-            message = data
-
-        if useQueue and not cls._fromResponderQueue.empty():
-            receivedQuery = cls._fromResponderQueue.get(True, timeout)
-
-        cls._response_headers = response_headers.getvalue()
-        return (receivedQuery, message)
-
-    def getHeaderValue(self, name):
-        for header in self._response_headers.decode().splitlines(False):
-            values = header.split(':')
-            key = values[0]
-            if key.lower() == name.lower():
-                return values[1].strip()
-        return None
-
-    def checkHasHeader(self, name, value):
-        got = self.getHeaderValue(name)
-        self.assertEqual(got, value)
-
-    def checkNoHeader(self, name):
-        self.checkHasHeader(name, None)
-
-    @classmethod
-    def setUpClass(cls):
-
-        # for some reason, @unittest.skipIf() is not applied to derived classes with some versions of Python
-        if 'SKIP_DOH_TESTS' in os.environ:
-            raise unittest.SkipTest('DNS over HTTPS tests are disabled')
-
-        cls.startResponders()
-        cls.startDNSDist()
-        cls.setUpSockets()
-
-        print("Launching tests..")
-
-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"}})
-    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))
@@ -163,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
@@ -182,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):
         """
@@ -361,9 +237,174 @@ 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 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)
@@ -394,13 +435,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)
@@ -408,7 +479,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):
         """
@@ -648,22 +719,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):
         """
@@ -699,27 +775,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):
         """
@@ -801,19 +883,21 @@ class TestDOHAddingECS(DNSDistDOHTest):
         self.checkQueryEDNSWithECS(expectedQuery, receivedQuery)
         self.checkResponseEDNSWithECS(response, receivedResponse)
 
-class TestDOHOverHTTP(DNSDistDOHTest):
+class TestDoHAddingECSNGHTTP2(DOHAddingECSTests, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
 
-    _dohServerPort = 8480
+class TestDoHAddingECSH2O(DOHAddingECSTests, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+
+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):
         """
@@ -866,23 +950,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):
         """
@@ -1016,7 +1112,7 @@ class TestDOHWithCache(DNSDistDOHTest):
         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 dnsdist over UDP
+        # first query, received by the responder over UDP
         self.assertTrue(receivedQuery)
         receivedQuery.id = expectedQuery.id
         self.assertEqual(expectedQuery, receivedQuery)
@@ -1026,7 +1122,7 @@ class TestDOHWithCache(DNSDistDOHTest):
         self.assertTrue(receivedResponse)
         self.assertEqual(response, receivedResponse)
 
-        # check the second query, received by dnsdist over TCP
+        # check the second query, received by the responder over TCP
         receivedQuery = self._fromResponderQueue.get(True, 2.0)
         self.assertTrue(receivedQuery)
         receivedQuery.id = expectedQuery.id
@@ -1075,20 +1171,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):
         """
@@ -1116,20 +1219,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"}})
+    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")
 
@@ -1162,7 +1270,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):
         """
@@ -1178,21 +1286,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):
         """
@@ -1239,23 +1355,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 TestDOHForwardedForNoTrusted(DNSDistDOHTest):
+class TestDOHForwardedForH2O(DOHForwardedFor, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+
+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):
         """
@@ -1274,32 +1396,44 @@ 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 TestDOHFrontendLimits(DNSDistDOHTest):
+class TestDOHForwardedForNoTrustedNGHTTP2(DOHForwardedForNoTrusted, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+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
 
     def testTCPConnsPerDOHFrontend(self):
         """
@@ -1311,7 +1445,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)
 
@@ -1344,12 +1481,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))
@@ -1363,9 +1506,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):
         """
@@ -1385,23 +1528,29 @@ class TestProtocols(DNSDistDOHTest):
         self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
         self.assertEqual(response, receivedResponse)
 
-class TestDOHWithPCKS12Cert(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 PCKS12 file configured
+        DoH: Test Simple DOH Query with a password protected PKCS12 file configured
         """
         name = 'simple.doh.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
@@ -1421,3 +1570,110 @@ class TestDOHWithPCKS12Cert(DNSDistDOHTest):
         self.assertTrue(receivedResponse)
         receivedQuery.id = expectedQuery.id
         self.assertEqual(expectedQuery, receivedQuery)
+
+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 = 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", { "/" }, {library='%s'})
+    """
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
+
+    def testDOHTCPOnly(self):
+        """
+        DoH: Test a DoH query forwarded to a TCP-only server
+        """
+        name = 'tcponly.doh.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        query.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)
+
+        (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEqual(receivedQuery, query)
+        self.assertEqual(receivedResponse, response)
+
+class TestDOHForwardedToTCPOnlyNGHTTP2(DOHForwardedToTCPOnly, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestDOHForwardedToTCPOnlyH2O(DOHForwardedToTCPOnly, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+
+class DOHLimits(object):
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _dohServerPort = pickAvailablePort()
+    _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
+    _serverKey = 'server.key'
+    _serverCert = 'server.chain'
+    _maxTCPConnsPerClient = 3
+    _config_template = """
+    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', '_dohLibrary', '_maxTCPConnsPerClient']
+
+    def testConnsPerClient(self):
+        """
+        DoH Limits: Maximum number of conns per client
+        """
+        name = 'maxconnsperclient.doh.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        url = self.getDOHGetURL(self._dohBaseURL, query)
+        conns = []
+
+        for idx in range(self._maxTCPConnsPerClient + 1):
+            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)
+            conns.append(conn)
+
+        count = 0
+        failed = 0
+        for conn in conns:
+            try:
+                data = conn.perform_rb()
+                rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+                count = count + 1
+            except:
+                failed = failed + 1
+
+        for conn in conns:
+            conn.close()
+
+        # wait a bit to be sure that dnsdist closed the connections
+        # and decremented the counters on its side, otherwise subsequent
+        # connections will be dropped
+        time.sleep(1)
+
+        self.assertEqual(count, self._maxTCPConnsPerClient)
+        self.assertEqual(failed, 1)
+
+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 788a0b17d7341b4fc3ca93f1b2c5af8eb69b2e51..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 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,
@@ -1047,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
@@ -1056,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,
@@ -1236,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:
@@ -1249,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 0c7a5faf4f8b127d28128f387e84a051d6f54126..7a5c6b7e0c06f79af6ccd676e7d103970a1f9044 100644 (file)
@@ -1,26 +1,48 @@
 #!/usr/bin/env python
 import base64
+import requests
+import ssl
+import threading
 import time
 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):
         """
@@ -29,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()")
@@ -62,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()
     """
@@ -83,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()
     """
@@ -103,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'}
     """
 
@@ -125,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.'}
     """
 
@@ -146,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)
@@ -175,3 +214,187 @@ class TestHealthCheckCustomFunction(HealthCheckTest):
         time.sleep(1.5)
         self.assertGreater(TestHealthCheckCustomFunction._healthCheckCounter, before)
         self.assertEqual(self.getBackendStatus(), 'up')
+
+_do53HealthCheckQueries = 0
+_dotHealthCheckQueries = 0
+_dohHealthCheckQueries = 0
+
+class TestLazyHealthChecks(HealthCheckTest):
+    _extraStartupSleep = 1
+    _do53Port = pickAvailablePort()
+    _dotPort = pickAvailablePort()
+    _dohPort = pickAvailablePort()
+
+    _consoleKey = DNSDistTest.generateConsoleKey()
+    _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+    _config_params = ['_consoleKeyB64', '_consolePort', '_do53Port', '_dotPort', '_dohPort']
+    _config_template = """
+    setKey("%s")
+    controlSocket("127.0.0.1:%d")
+
+    newServer{address="127.0.0.1:%s", healthCheckMode='lazy', checkInterval=1, lazyHealthCheckFailedInterval=1, lazyHealthCheckThreshold=10, lazyHealthCheckSampleSize=100,  lazyHealthCheckMinSampleCount=10, lazyHealthCheckMode='TimeoutOrServFail', pool=''}
+
+    newServer{address="127.0.0.1:%s", tls='openssl', caStore='ca.pem', healthCheckMode='lazy', checkInterval=1, lazyHealthCheckFailedInterval=1, lazyHealthCheckThreshold=10, lazyHealthCheckSampleSize=100,  lazyHealthCheckMinSampleCount=10, lazyHealthCheckMode='TimeoutOrServFail', pool='dot'}
+    addAction('dot.lazy.test.powerdns.com.', PoolAction('dot'))
+
+    newServer{address="127.0.0.1:%s", tls='openssl', dohPath='/dns-query', caStore='ca.pem', healthCheckMode='lazy', checkInterval=1, lazyHealthCheckFailedInterval=1, lazyHealthCheckThreshold=10, lazyHealthCheckSampleSize=100,  lazyHealthCheckMinSampleCount=10, lazyHealthCheckMode='TimeoutOrServFail', pool='doh'}
+    addAction('doh.lazy.test.powerdns.com.', PoolAction('doh'))
+    """
+    _verboseMode = True
+
+    @staticmethod
+    def HandleDNSQuery(request):
+        response = dns.message.make_response(request)
+        if str(request.question[0].name).startswith('server-failure'):
+            response.set_rcode(dns.rcode.SERVFAIL)
+        return response.to_wire()
+
+    @classmethod
+    def Do53Callback(cls, request):
+        global _do53HealthCheckQueries
+        if str(request.question[0].name).startswith('a.root-servers.net'):
+            _do53HealthCheckQueries = _do53HealthCheckQueries + 1
+            response = dns.message.make_response(request)
+            return response.to_wire()
+        return cls.HandleDNSQuery(request)
+
+    @classmethod
+    def DoTCallback(cls, request):
+        global _dotHealthCheckQueries
+        if str(request.question[0].name).startswith('a.root-servers.net'):
+            _dotHealthCheckQueries = _dotHealthCheckQueries + 1
+            response = dns.message.make_response(request)
+            return response.to_wire()
+        return cls.HandleDNSQuery(request)
+
+    @classmethod
+    def DoHCallback(cls, request, requestHeaders, fromQueue, toQueue):
+        global _dohHealthCheckQueries
+        if str(request.question[0].name).startswith('a.root-servers.net'):
+            _dohHealthCheckQueries = _dohHealthCheckQueries + 1
+            response = dns.message.make_response(request)
+            return 200, response.to_wire()
+        return 200, cls.HandleDNSQuery(request)
+
+    @classmethod
+    def startResponders(cls):
+        tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+        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.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.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.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.daemon = True
+        DoHResponder.start()
+
+    def testDo53Lazy(self):
+        """
+        Lazy Healthchecks: Do53
+        """
+        # there is one initial query on startup
+        self.assertEqual(_do53HealthCheckQueries, 1)
+        time.sleep(1)
+        self.assertEqual(_do53HealthCheckQueries, 1)
+
+        name = 'do53.lazy.test.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        failedQuery = dns.message.make_query('server-failure.do53.lazy.test.powerdns.com.', 'A', 'IN')
+        failedResponse = dns.message.make_response(failedQuery)
+        failedResponse.set_rcode(dns.rcode.SERVFAIL)
+
+        # send a few valid queries
+        for _ in range(5):
+            for method in ("sendUDPQuery", "sendTCPQuery"):
+                sender = getattr(self, method)
+                (_, receivedResponse) = sender(query, response=None, useQueue=False)
+                self.assertEqual(receivedResponse, response)
+
+        self.assertEqual(_do53HealthCheckQueries, 1)
+
+        # we need at least 10 samples, and 10 percent of them failing, so two failing queries should be enough
+        for _ in range(2):
+            (_, receivedResponse) = self.sendUDPQuery(failedQuery, response=None, useQueue=False)
+            self.assertEqual(receivedResponse, failedResponse)
+
+        time.sleep(1.5)
+        self.assertEqual(_do53HealthCheckQueries, 2)
+        self.assertEqual(self.getBackendStatus(), 'up')
+
+    def testDoTLazy(self):
+        """
+        Lazy Healthchecks: DoT
+        """
+        # there is one initial query on startup
+        self.assertEqual(_dotHealthCheckQueries, 1)
+        time.sleep(1)
+        self.assertEqual(_dotHealthCheckQueries, 1)
+
+        name = 'dot.lazy.test.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        failedQuery = dns.message.make_query('server-failure.dot.lazy.test.powerdns.com.', 'A', 'IN')
+        failedResponse = dns.message.make_response(failedQuery)
+        failedResponse.set_rcode(dns.rcode.SERVFAIL)
+
+        # send a few valid queries
+        for _ in range(5):
+            for method in ("sendUDPQuery", "sendTCPQuery"):
+                sender = getattr(self, method)
+                (_, receivedResponse) = sender(query, response=None, useQueue=False)
+                self.assertEqual(receivedResponse, response)
+
+        self.assertEqual(_dotHealthCheckQueries, 1)
+
+        # we need at least 10 samples, and 10 percent of them failing, so two failing queries should be enough
+        for _ in range(2):
+            (_, receivedResponse) = self.sendUDPQuery(failedQuery, response=None, useQueue=False)
+            self.assertEqual(receivedResponse, failedResponse)
+
+        time.sleep(1.5)
+        self.assertEqual(_dotHealthCheckQueries, 2)
+        self.assertEqual(self.getBackendStatus(), 'up')
+
+    def testDoHLazy(self):
+        """
+        Lazy Healthchecks: DoH
+        """
+        # there is one initial query on startup
+        self.assertEqual(_dohHealthCheckQueries, 1)
+        time.sleep(1)
+        self.assertEqual(_dohHealthCheckQueries, 1)
+
+        name = 'doh.lazy.test.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        failedQuery = dns.message.make_query('server-failure.doh.lazy.test.powerdns.com.', 'A', 'IN')
+        failedResponse = dns.message.make_response(failedQuery)
+        failedResponse.set_rcode(dns.rcode.SERVFAIL)
+
+        # send a few valid queries
+        for _ in range(5):
+            for method in ("sendUDPQuery", "sendTCPQuery"):
+                sender = getattr(self, method)
+                (_, receivedResponse) = sender(query, response=None, useQueue=False)
+                self.assertEqual(receivedResponse, response)
+
+        self.assertEqual(_dohHealthCheckQueries, 1)
+
+        # we need at least 10 samples, and 10 percent of them failing, so two failing queries should be enough
+        for _ in range(2):
+            (_, receivedResponse) = self.sendUDPQuery(failedQuery, response=None, useQueue=False)
+            self.assertEqual(receivedResponse, failedResponse)
+
+        time.sleep(1.5)
+        self.assertEqual(_dohHealthCheckQueries, 2)
+        self.assertEqual(self.getBackendStatus(), 'up')
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)
diff --git a/regression-tests.dnsdist/test_Metrics.py b/regression-tests.dnsdist/test_Metrics.py
new file mode 100644 (file)
index 0000000..bc7faf6
--- /dev/null
@@ -0,0 +1,187 @@
+#!/usr/bin/env python
+
+import os
+import requests
+import socket
+import threading
+import unittest
+import dns
+from dnsdisttests import DNSDistTest, pickAvailablePort
+
+class RuleMetricsTest(object):
+
+    _config_template = """
+    addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl" })
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/"})
+
+    newServer{address="127.0.0.1:%s", pool={'', 'cache'}}
+    webserver("127.0.0.1:%s")
+    setWebserverConfig({apiKey="%s"})
+
+    addAction('rcode-nxdomain.metrics.tests.powerdns.com', RCodeAction(DNSRCode.NXDOMAIN))
+    addAction('rcode-refused.metrics.tests.powerdns.com', RCodeAction(DNSRCode.REFUSED))
+    addAction('rcode-servfail.metrics.tests.powerdns.com', RCodeAction(DNSRCode.SERVFAIL))
+
+    pc = newPacketCache(100)
+    getPool('cache'):setCache(pc)
+    addAction('cache.metrics.tests.powerdns.com', PoolAction('cache'))
+    """
+    _webTimeout = 2.0
+    _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 = pickAvailablePort()
+    _dohServerPort = pickAvailablePort()
+    _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
+    _config_params = ['_tlsServerPort', '_serverCert', '_serverKey', '_dohServerPort', '_serverCert', '_serverKey', '_testServerPort', '_webServerPort', '_webServerAPIKeyHashed']
+
+    @classmethod
+    def setUpClass(cls):
+        cls.startResponders()
+        cls.startDNSDist()
+        cls.setUpSockets()
+        cls.waitForTCPSocket('127.0.0.1', cls._webServerPort)
+        print("Launching tests..")
+
+    def getMetric(self, name):
+        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()
+        stats = content['statistics']
+        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
+        """
+        rcodes = [
+            ( 'nxdomain', dns.rcode.NXDOMAIN ),
+            ( 'refused', dns.rcode.REFUSED ),
+            ( 'servfail', dns.rcode.SERVFAIL )
+        ]
+        servfailBackendResponses = self.getMetric('servfail-responses')
+
+        for (name, rcode) in rcodes:
+            qname = 'rcode-' + name + '.metrics.tests.powerdns.com.'
+            query = dns.message.make_query(qname, 'A', 'IN')
+            # dnsdist set RA = RD for spoofed responses
+            query.flags &= ~dns.flags.RD
+            expectedResponse = dns.message.make_response(query)
+            expectedResponse.set_rcode(rcode)
+
+            ruleMetricBefore = self.getMetric('rule-' + name)
+            if name != 'refused':
+                frontendMetricBefore = self.getMetric('frontend-' + name)
+
+            for method in ("sendUDPQuery", "sendTCPQuery"):
+                sender = getattr(self, method)
+                (_, receivedResponse) = sender(query, response=None, useQueue=False)
+                self.assertEqual(receivedResponse, expectedResponse)
+
+            self.assertEqual(self.getMetric('rule-' + name), ruleMetricBefore + 2)
+            if name != 'refused':
+                self.assertEqual(self.getMetric('frontend-' + name), frontendMetricBefore + 2)
+
+        # 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
+        """
+
+        for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHQueryWrapper"):
+            qname = method + '.cache.metrics.tests.powerdns.com.'
+            query = dns.message.make_query(qname, '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(qname,
+                                        3600,
+                                        dns.rdataclass.IN,
+                                        dns.rdatatype.A,
+                                        '127.0.0.1')
+            response.answer.append(rrset)
+
+            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
+            (receivedQuery, receivedResponse) = sender(query, response)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(receivedResponse, response)
+            # second time, hit
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertEqual(receivedResponse, response)
+
+            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 server failures
+        """
+
+        for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHQueryWrapper"):
+            qname = method + '.servfail.cache.metrics.tests.powerdns.com.'
+            query = dns.message.make_query(qname, 'A', 'IN')
+            # dnsdist set RA = RD for spoofed responses
+            query.flags &= ~dns.flags.RD
+            response = dns.message.make_response(query)
+            response.set_rcode(dns.rcode.SERVFAIL)
+
+            frontendBefore = self.getMetric('frontend-servfail')
+            servfailBefore = self.getMetric('servfail-responses')
+            ruleBefore = self.getMetric('rule-servfail')
+
+            sender = getattr(self, method)
+            # first time, cache miss
+            (receivedQuery, receivedResponse) = sender(query, response)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(receivedResponse, response)
+            # second time, hit
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertEqual(receivedResponse, response)
+
+            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)
+    """
diff --git a/regression-tests.dnsdist/test_NetworkBindings.py b/regression-tests.dnsdist/test_NetworkBindings.py
new file mode 100644 (file)
index 0000000..f90ccd9
--- /dev/null
@@ -0,0 +1,141 @@
+#!/usr/bin/env python
+import unittest
+import os
+import subprocess
+import time
+
+class TestNetworkEndpointConfig(unittest.TestCase):
+
+    def checkDNSDistExitCode(self, configTemplate, expectedCode, clientMode=False, verboseMode=False):
+        conffile = 'configs/dnsdist_TestNetworkEndpointConfig.conf'
+        with open(conffile, 'w') as conf:
+            conf.write("-- Autogenerated by dnsdisttests.py\n")
+            conf.write(configTemplate)
+
+        dnsdistcmd = [os.environ['DNSDISTBIN'], '-C', conffile, '--check-config']
+        if clientMode:
+            dnsdistcmd.append('-c')
+        if verboseMode:
+            dnsdistcmd.append('-v')
+
+        output = None
+        returnCode = None
+        try:
+            output = subprocess.check_output(dnsdistcmd, stderr=subprocess.STDOUT, close_fds=True)
+            returnCode = 0
+        except subprocess.CalledProcessError as exc:
+            output = exc.output
+            returnCode = exc.returncode
+
+        print(output)
+        self.assertEqual(returnCode, expectedCode)
+
+    def testNonExistingEndpoint(self):
+        """
+        NetworkBindings: Non existing endpoint
+        """
+        configTemplate = """
+            newServer{address="127.0.0.1:53"}
+            local endpoint = newNetworkEndpoint('/this/path/does/not/exist')
+            if endpoint == nil then
+              os.exit(1)
+            end
+            if endpoint:isValid() then
+              os.exit(2)
+            end
+            if endpoint:send('test') then
+              os.exit(3)
+            end
+            os.exit(0)
+        """
+
+        self.checkDNSDistExitCode(configTemplate, 0)
+
+    def testClientMode(self):
+        """
+        NetworkBindings: Client mode
+        """
+        configTemplate = """
+            newServer{address="127.0.0.1:53"}
+            local endpoint = newNetworkEndpoint('/this/path/does/not/exist')
+            if endpoint == nil then
+              os.exit(1)
+            end
+            if endpoint:isValid() then
+              os.exit(2)
+            end
+            if endpoint:send('test') then
+              os.exit(3)
+            end
+            local listener = newNetworkListener()
+            if listener == nil then
+              os.exit(4)
+            end
+            local endpointId = 1
+            local function callback(_, _, _)
+            end
+            if listener:addUnixListeningEndpoint('/path', 1, callback) then
+              os.exit(5)
+            end
+            listener:start()
+            os.exit(0)
+        """
+
+        self.checkDNSDistExitCode(configTemplate, 0, clientMode=True)
+
+    def testGetResolvers(self):
+        """
+        NetworkBindings: getResolvers
+        """
+        configTemplate = """
+            newServer{address="127.0.0.1:53"}
+            local resolvers = getResolvers('resolv.conf.sample')
+            if #resolvers ~= 2 then
+              os.exit(1)
+            end
+            if resolvers[1] ~= '9.9.9.9' then
+              os.exit(2)
+            end
+            if resolvers[2] ~= '2620:fe::fe' then
+              os.exit(2)
+            end
+            os.exit(0)
+        """
+
+        self.checkDNSDistExitCode(configTemplate, 0, clientMode=True)
+
+    def testCommunication(self):
+        """
+        NetworkBindings: Communication
+        """
+        configTemplate = """
+            newServer{address="127.0.0.1:53"}
+            local listener = newNetworkListener()
+            if listener == nil then
+              os.exit(1)
+            end
+            local endpointId = 1
+
+            local function callback(id, dgram, _)
+              -- this function will never get called because we are holding the Lua lock
+            end
+
+            if not listener:addUnixListeningEndpoint('/tmp/dnsdist.network-bindings.test', 1, callback) then
+              os.exit(4)
+            end
+            --listener:start()
+
+            local endpoint = newNetworkEndpoint('/tmp/dnsdist.network-bindings.test')
+            if endpoint == nil then
+              os.exit(5)
+            end
+            if not endpoint:isValid() then
+              os.exit(6)
+            end
+            if not endpoint:send('test') then
+              os.exit(7)
+            end
+            os.exit(0)
+        """
+
+        self.checkDNSDistExitCode(configTemplate, 0)
index 6b29e3df554c2b9403bdcbaa0d66d7095d19e615..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,33 +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-ocsp.key'
+    _serverCert = 'server-ocsp.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    # invalid OCSP file!
+    _ocspFile = '/dev/null'
+    _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:%d", "%s", "%s", { "/" }, { ocspResponses={"%s"}, library='nghttp2'})
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { ocspResponses={"%s"}, library='h2o'})
+
+    """
+    _config_params = ['_testServerPort', '_consoleKeyB64', '_consolePort', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_ocspFile', '_dohWithH2OServerPort', '_serverCert', '_serverKey', '_ocspFile']
+
+    def testBrokenOCSPStapling(self):
+        """
+        OCSP Stapling: Broken (DoH)
+        """
+        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")
@@ -119,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()")
 
@@ -134,17 +176,45 @@ class TestOCSPStaplingTLSGnuTLS(DNSDistOCSPStaplingTest):
         self.assertTrue(serialNumber2)
         self.assertNotEqual(serialNumber, serialNumber2)
 
+class TestBrokenOCSPStaplingTLSGnuTLS(DNSDistOCSPStaplingTest):
+
+    _consoleKey = DNSDistTest.generateConsoleKey()
+    _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+    _serverKey = 'server-ocsp.key'
+    _serverCert = 'server-ocsp.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    # invalid OCSP file!
+    _ocspFile = '/dev/null'
+    _tlsServerPort = pickAvailablePort()
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+    setKey("%s")
+    controlSocket("127.0.0.1:%s")
+
+    addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="gnutls", ocspResponses={"%s"}})
+    """
+    _config_params = ['_testServerPort', '_consoleKeyB64', '_consolePort', '_tlsServerPort', '_serverCert', '_serverKey', '_ocspFile']
+
+    def testBrokenOCSPStapling(self):
+        """
+        OCSP Stapling: Broken (GnuTLS)
+        """
+        output = self.checkOCSPStaplingStatus('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert)
+        self.assertNotIn('OCSP Response Status: successful (0x0)', output)
+        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")
@@ -162,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()")
 
@@ -176,3 +246,31 @@ class TestOCSPStaplingTLSOpenSSL(DNSDistOCSPStaplingTest):
         serialNumber2 = self.getOCSPSerial(output)
         self.assertTrue(serialNumber2)
         self.assertNotEqual(serialNumber, serialNumber2)
+
+class TestBrokenOCSPStaplingTLSOpenSSL(DNSDistOCSPStaplingTest):
+
+    _consoleKey = DNSDistTest.generateConsoleKey()
+    _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+    _serverKey = 'server-ocsp.key'
+    _serverCert = 'server-ocsp.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    # invalid OCSP file!
+    _ocspFile = '/dev/null'
+    _tlsServerPort = pickAvailablePort()
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+    setKey("%s")
+    controlSocket("127.0.0.1:%s")
+
+    addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl", ocspResponses={"%s"}})
+    """
+    _config_params = ['_testServerPort', '_consoleKeyB64', '_consolePort', '_tlsServerPort', '_serverCert', '_serverKey', '_ocspFile']
+
+    def testBrokenOCSPStapling(self):
+        """
+        OCSP Stapling: Broken (OpenSSL)
+        """
+        output = self.checkOCSPStaplingStatus('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert)
+        self.assertNotIn('OCSP Response Status: successful (0x0)', output)
+        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 51d6f06a0886aae030132d03a067f5ede0be356b..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'
@@ -19,6 +19,15 @@ class TestPrometheus(DNSDistTest):
     newServer{address="127.0.0.1:%s"}
     webserver("127.0.0.1:%s")
     setWebserverConfig({password="%s", apiKey="%s"})
+    pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
+    getPool(""):setCache(pc)
+
+    -- test custom metrics as well
+    declareMetric('custom-metric1', 'counter', 'Custom counter')
+    incMetric('custom-metric1')
+    declareMetric('custom-metric2', 'gauge', 'Custom gauge')
+    -- and custom names
+    declareMetric('custom-metric3', 'counter', 'Custom counter', 'custom_prometheus_name')
     """
 
     def checkPrometheusContentBasic(self, content):
@@ -33,9 +42,35 @@ class TestPrometheus(DNSDistTest):
             elif not line.startswith('#'):
                 tokens = line.split(' ')
                 self.assertEqual(len(tokens), 2)
-                if not line.startswith('dnsdist_'):
+                if not line.startswith('dnsdist_') and not line.startswith('custom_prometheus_name'):
                     raise AssertionError('Expecting prometheus metric to be prefixed by \'dnsdist_\', got: "%s"' % (line))
 
+    def checkMetric(self, content, name, expectedType, expectedValue):
+        typeFound = False
+        helpFound = False
+        valueFound = False
+        for line in content.splitlines():
+            if name in str(line):
+                tokens = line.split(' ')
+                if line.startswith('# HELP'):
+                    self.assertGreaterEqual(len(tokens), 4)
+                    if tokens[2] == name:
+                        helpFound = True
+                elif line.startswith('# TYPE'):
+                    self.assertEqual(len(tokens), 4)
+                    if tokens[2] == name:
+                        typeFound = True
+                        self.assertEqual(tokens[3], expectedType)
+                elif not line.startswith('#'):
+                    self.assertEqual(len(tokens), 2)
+                    if tokens[0] == name:
+                        valueFound = True
+                        self.assertEqual(int(tokens[1]), expectedValue)
+
+        self.assertTrue(typeFound)
+        self.assertTrue(helpFound)
+        self.assertTrue(valueFound)
+
     def checkPrometheusContentPromtool(self, content):
         output = None
         try:
@@ -64,3 +99,6 @@ class TestPrometheus(DNSDistTest):
         self.assertEqual(r.status_code, 200)
         self.checkPrometheusContentBasic(r.text)
         self.checkPrometheusContentPromtool(r.content)
+        self.checkMetric(r.text, 'dnsdist_custom_metric1', 'counter', 1)
+        self.checkMetric(r.text, 'dnsdist_custom_metric2', 'gauge', 0)
+        self.checkMetric(r.text, 'custom_prometheus_name', 'counter', 0)
index b5384aaf6a05f1f28b4227d8f1ba44a62d450b5a..5f65fd31a9103cd83dcd6f6a34e24a33b78cbb15 100644 (file)
@@ -1,22 +1,26 @@
 #!/usr/bin/env python
+import base64
 import threading
 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
 
     @classmethod
     def ProtobufListener(cls, port):
+        cls._backgroundThreads[threading.get_native_id()] = True
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
         try:
@@ -26,8 +30,17 @@ class DNSDistProtobufTest(DNSDistTest):
             sys.exit(1)
 
         sock.listen(100)
+        sock.settimeout(1.0)
         while True:
-            (conn, _) = sock.accept()
+            try:
+                (conn, _) = sock.accept()
+            except socket.timeout:
+                if cls._backgroundThreads.get(threading.get_native_id(), False) == False:
+                    del cls._backgroundThreads[threading.get_native_id()]
+                    break
+                else:
+                    continue
+
             data = None
             while True:
                 data = conn.recv(2)
@@ -46,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):
@@ -65,14 +78,20 @@ class DNSDistProtobufTest(DNSDistTest):
         msg.ParseFromString(data)
         return msg
 
-    def checkProtobufBase(self, msg, protocol, query, initiator, normalQueryResponse=True):
+    def checkProtobufBase(self, msg, protocol, query, initiator, normalQueryResponse=True, v6=False):
         self.assertTrue(msg)
         self.assertTrue(msg.HasField('timeSec'))
         self.assertTrue(msg.HasField('socketFamily'))
-        self.assertEqual(msg.socketFamily, dnsmessage_pb2.PBDNSMessage.INET)
+        if v6:
+            self.assertEqual(msg.socketFamily, dnsmessage_pb2.PBDNSMessage.INET6)
+        else:
+            self.assertEqual(msg.socketFamily, dnsmessage_pb2.PBDNSMessage.INET)
         self.assertTrue(msg.HasField('from'))
         fromvalue = getattr(msg, 'from')
-        self.assertEqual(socket.inet_ntop(socket.AF_INET, fromvalue), initiator)
+        if v6:
+            self.assertEqual(socket.inet_ntop(socket.AF_INET6, fromvalue), initiator)
+        else:
+            self.assertEqual(socket.inet_ntop(socket.AF_INET, fromvalue), initiator)
         self.assertTrue(msg.HasField('socketProtocol'))
         self.assertEqual(msg.socketProtocol, protocol)
         self.assertTrue(msg.HasField('messageId'))
@@ -82,7 +101,7 @@ class DNSDistProtobufTest(DNSDistTest):
         self.assertTrue(msg.HasField('serverIdentity'))
         self.assertEqual(msg.serverIdentity, self._protobufServerID.encode('utf-8'))
 
-        if normalQueryResponse:
+        if normalQueryResponse and (protocol == dnsmessage_pb2.PBDNSMessage.UDP or protocol == dnsmessage_pb2.PBDNSMessage.TCP):
           # compare inBytes with length of query/response
           self.assertEqual(msg.inBytes, len(query.to_wire()))
         # dnsdist doesn't set the existing EDNS Subnet for now,
@@ -91,13 +110,14 @@ class DNSDistProtobufTest(DNSDistTest):
         # self.assertEqual(len(msg.originalRequestorSubnet), 4)
         # self.assertEqual(socket.inet_ntop(socket.AF_INET, msg.originalRequestorSubnet), '127.0.0.1')
 
-    def checkProtobufQuery(self, msg, protocol, query, qclass, qtype, qname, initiator='127.0.0.1'):
+    def checkProtobufQuery(self, msg, protocol, query, qclass, qtype, qname, initiator='127.0.0.1', v6=False):
         self.assertEqual(msg.type, dnsmessage_pb2.PBDNSMessage.DNSQueryType)
-        self.checkProtobufBase(msg, protocol, query, initiator)
+        self.checkProtobufBase(msg, protocol, query, initiator, v6=v6)
         # dnsdist doesn't fill the responder field for responses
         # because it doesn't keep the information around.
         self.assertTrue(msg.HasField('to'))
-        self.assertEqual(socket.inet_ntop(socket.AF_INET, msg.to), '127.0.0.1')
+        if not v6:
+            self.assertEqual(socket.inet_ntop(socket.AF_INET, msg.to), '127.0.0.1')
         self.assertTrue(msg.HasField('question'))
         self.assertTrue(msg.question.HasField('qClass'))
         self.assertEqual(msg.question.qClass, qclass)
@@ -119,9 +139,9 @@ class DNSDistProtobufTest(DNSDistTest):
         self.assertTrue(msg.HasField('response'))
         self.assertTrue(msg.response.HasField('queryTimeSec'))
 
-    def checkProtobufResponse(self, msg, protocol, response, initiator='127.0.0.1'):
+    def checkProtobufResponse(self, msg, protocol, response, initiator='127.0.0.1', v6=False):
         self.assertEqual(msg.type, dnsmessage_pb2.PBDNSMessage.DNSResponseType)
-        self.checkProtobufBase(msg, protocol, response, initiator)
+        self.checkProtobufBase(msg, protocol, response, initiator, v6=v6)
         self.assertTrue(msg.HasField('response'))
         self.assertTrue(msg.response.HasField('queryTimeSec'))
 
@@ -276,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()
@@ -304,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()
@@ -349,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()
@@ -375,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()
@@ -392,6 +415,313 @@ class TestProtobuf(DNSDistProtobufTest):
             self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 3600)
             self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '127.0.0.1')
 
+class TestProtobufMetaTags(DNSDistProtobufTest):
+    _config_params = ['_testServerPort', '_protobufServerPort']
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+    rl = newRemoteLogger('127.0.0.1:%d')
+
+    addAction(AllRule(), SetTagAction('my-tag-key', 'my-tag-value'))
+    addAction(AllRule(), SetTagAction('my-empty-key', ''))
+    addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='dnsdist-server-1', exportTags='*'}, {b64='b64-content', ['my-tag-export-name']='tag:my-tag-key'}))
+    addResponseAction(AllRule(), SetTagResponseAction('my-tag-key2', 'my-tag-value2'))
+    addResponseAction(AllRule(), RemoteLogResponseAction(rl, nil, false, {serverID='dnsdist-server-1', exportTags='my-empty-key,my-tag-key2'}, {['my-tag-export-name']='tags'}))
+    """
+
+    def testProtobufMeta(self):
+        """
+        Protobuf: Meta values
+        """
+        name = 'meta.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)
+
+        (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)
+        # regular tags
+        self.assertEqual(len(msg.response.tags), 2)
+        self.assertIn('my-tag-key:my-tag-value', msg.response.tags)
+        self.assertIn('my-empty-key', msg.response.tags)
+        # meta tags
+        self.assertEqual(len(msg.meta), 2)
+        tags = {}
+        for entry in msg.meta:
+            tags[entry.key] = entry.value.stringVal
+
+        self.assertIn('b64', tags)
+        self.assertIn('my-tag-export-name', tags)
+
+        b64EncodedQuery = base64.b64encode(query.to_wire()).decode('ascii')
+        self.assertEqual(tags['b64'], [b64EncodedQuery])
+        self.assertEqual(tags['my-tag-export-name'], ['my-tag-value'])
+
+        # check the protobuf message corresponding to the UDP response
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, response)
+        # regular tags
+        self.assertEqual(len(msg.response.tags), 2)
+        self.assertIn('my-tag-key2:my-tag-value2', msg.response.tags)
+        self.assertIn('my-empty-key', msg.response.tags)
+        # meta tags
+        self.assertEqual(len(msg.meta), 1)
+        self.assertEqual(msg.meta[0].key, 'my-tag-export-name')
+        self.assertEqual(len(msg.meta[0].value.stringVal), 3)
+        self.assertIn('my-tag-key:my-tag-value', msg.meta[0].value.stringVal)
+        self.assertIn('my-tag-key2:my-tag-value2', msg.meta[0].value.stringVal)
+        # 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 = 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:%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):
+        """
+        Protobuf: Meta values - DoH
+        """
+        name = 'meta-doh.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 ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper"):
+            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 == "sendUDPQuery":
+                pbMessageType = dnsmessage_pb2.PBDNSMessage.UDP
+            elif method == "sendTCPQuery":
+                pbMessageType = dnsmessage_pb2.PBDNSMessage.TCP
+            elif method == "sendDOTQueryWrapper":
+                pbMessageType = dnsmessage_pb2.PBDNSMessage.DOT
+            elif method == "sendDOHWithNGHTTP2QueryWrapper" or method == "sendDOHWithH2OQueryWrapper":
+                pbMessageType = dnsmessage_pb2.PBDNSMessage.DOH
+
+            self.checkProtobufQuery(msg, pbMessageType, query, dns.rdataclass.IN, dns.rdatatype.A, name)
+            self.assertEqual(len(msg.meta), 5)
+            tags = {}
+            for entry in msg.meta:
+                self.assertEqual(len(entry.value.stringVal), 1)
+                tags[entry.key] = entry.value.stringVal[0]
+
+            self.assertIn('agent', tags)
+            if method == "sendDOHWithNGHTTP2QueryWrapper" or method == "sendDOHWithH2OQueryWrapper":
+                self.assertIn('PycURL', tags['agent'])
+                self.assertIn('host', tags)
+                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()
+            self.checkProtobufResponse(msg, pbMessageType, response)
+            self.assertEqual(len(msg.meta), 5)
+            tags = {}
+            for entry in msg.meta:
+                self.assertEqual(len(entry.value.stringVal), 1)
+                tags[entry.key] = entry.value.stringVal[0]
+
+            self.assertIn('agent', tags)
+            if method == "sendDOHWithNGHTTP2QueryWrapper" or method == "sendDOHWithH2OQueryWrapper":
+                self.assertIn('PycURL', tags['agent'])
+                self.assertIn('host', tags)
+                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')
+
+class TestProtobufMetaProxy(DNSDistProtobufTest):
+
+    _config_params = ['_testServerPort', '_protobufServerPort']
+    _config_template = """
+    setProxyProtocolACL( { "127.0.0.1/32" } )
+
+    newServer{address="127.0.0.1:%d"}
+    rl = newRemoteLogger('127.0.0.1:%d')
+
+    local mytags = {pp='proxy-protocol-values', pp42='proxy-protocol-value:42'}
+    addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='dnsdist-server-1'}, mytags))
+
+    -- proxy protocol values are NOT passed to the response
+    """
+
+    def testProtobufMetaProxy(self):
+        """
+        Protobuf: Meta values - Proxy
+        """
+        name = 'meta-proxy.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)
+
+        destAddr = "2001:db8::9"
+        destPort = 9999
+        srcAddr = "2001:db8::8"
+        srcPort = 8888
+        udpPayload = ProxyProtocol.getPayload(False, False, True, srcAddr, destAddr, srcPort, destPort, [ [ 2, b'foo'], [ 42, b'proxy'] ])
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(udpPayload + query.to_wire(), response, rawQuery=True)
+
+        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, initiator='2001:db8::8', v6=True)
+        self.assertEqual(len(msg.meta), 2)
+        tags = {}
+        for entry in msg.meta:
+            tags[entry.key] = entry.value.stringVal
+
+        self.assertIn('pp42', tags)
+        self.assertEqual(tags['pp42'], ['proxy'])
+        self.assertIn('pp', tags)
+        self.assertEqual(len(tags['pp']), 2)
+        self.assertIn('2:foo', tags['pp'])
+        self.assertIn('42:proxy', tags['pp'])
+
 class TestProtobufIPCipher(DNSDistProtobufTest):
     _config_params = ['_testServerPort', '_protobufServerPort', '_protobufServerID', '_protobufServerID']
     _config_template = """
@@ -434,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()
@@ -462,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()
@@ -480,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 bf073f74ed639de2465a6c678705baaa251e8148..2ed60e08bc9f4351b7a59da384030f4b3f7691a6 100644 (file)
@@ -1,14 +1,18 @@
 #!/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
 try:
@@ -16,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']
@@ -396,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')
@@ -432,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):
         """
@@ -443,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):
@@ -654,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
@@ -720,3 +823,186 @@ class TestProxyProtocolNotExpected(DNSDistTest):
         except socket.timeout:
           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'
+    _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:%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', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_dohWithH2OServerPort', '_serverCert', '_serverKey']
+    _verboseMode = True
+
+    def testTruncation(self):
+        """
+        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.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)
+        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)
+
+        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(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 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())
+
+    def testAddressFamilyMismatch(self):
+        """
+        DOH with IPv6 X-Forwarded-For to an IPv4 endpoint
+        """
+        name = 'x-forwarded-for-af-mismatch.doh.outgoing-proxy-protocol.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)
+
+        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)
+
+          # 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 1fd2f4f054f35bc7303339d79e8887773cc475a7..af16a644bf3bc4527632eb696dd0be4a18d63be0 100644 (file)
@@ -55,6 +55,7 @@ class TestResponseRuleNXDelayed(DNSDistTest):
 
 class TestResponseRuleERCode(DNSDistTest):
 
+    _extraStartupSleep = 1
     _config_template = """
     newServer{address="127.0.0.1:%s"}
     addResponseAction(ERCodeRule(DNSRCode.BADVERS), DelayResponseAction(1000))
@@ -345,6 +346,39 @@ class TestResponseRuleLimitTTL(DNSDistTest):
             self.assertNotEqual(response.answer[0].ttl, receivedResponse.answer[0].ttl)
             self.assertEqual(receivedResponse.answer[0].ttl, self._lowttl)
 
+class TestSetReducedTTL(DNSDistTest):
+
+    _percentage = 42
+    _initialTTL = 100
+    _config_params = ['_percentage', '_testServerPort']
+    _config_template = """
+    addResponseAction(AllRule(), SetReducedTTLResponseAction(%d))
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testLimitTTL(self):
+        """
+        Responses: Reduce TTL to 42%
+        """
+        name = 'reduced-ttl.responses.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    self._initialTTL,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.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)
+            self.assertNotEqual(response.answer[0].ttl, receivedResponse.answer[0].ttl)
+            self.assertEqual(receivedResponse.answer[0].ttl, self._percentage)
+
 class TestResponseLuaActionReturnSyntax(DNSDistTest):
 
     _config_template = """
diff --git a/regression-tests.dnsdist/test_RestartQuery.py b/regression-tests.dnsdist/test_RestartQuery.py
new file mode 100644 (file)
index 0000000..5bc61ff
--- /dev/null
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+import threading
+import clientsubnetoption
+import dns
+from dnsdisttests import DNSDistTest, pickAvailablePort
+
+def servFailResponseCallback(request):
+    response = dns.message.make_response(request)
+    response.set_rcode(dns.rcode.SERVFAIL)
+    return response.to_wire()
+
+def normalResponseCallback(request):
+    response = dns.message.make_response(request)
+    rrset = dns.rrset.from_text(request.question[0].name,
+                                3600,
+                                dns.rdataclass.IN,
+                                dns.rdatatype.A,
+                                '127.0.0.1')
+    response.answer.append(rrset)
+    return response.to_wire()
+
+class TestRestartQuery(DNSDistTest):
+
+    # this test suite uses different responder ports
+    _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()
+
+    function makeQueryRestartable(dq)
+      dq:setRestartable()
+      return DNSAction.None
+    end
+
+    function restartOnServFail(dr)
+      if dr.rcode == DNSRCode.SERVFAIL then
+        dr.pool = 'restarted'
+        dr:restart()
+      end
+
+      return DNSResponseAction.None
+    end
+
+    addAction(AllRule(), LuaAction(makeQueryRestartable))
+    addResponseAction(AllRule(), LuaResponseAction(restartOnServFail))
+    """
+    _config_params = ['_testNormalServerPort', '_testServfailServerPort']
+    _verboseMode = True
+
+    @classmethod
+    def startResponders(cls):
+        print("Launching responders..")
+
+        # servfail
+        cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServfailServerPort, cls._toResponderQueue, cls._fromResponderQueue, False, servFailResponseCallback])
+        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.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.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.daemon = True
+        cls._TCPResponderNormal.start()
+
+    def testRestartingQuery(self):
+        """
+        Restart: ServFail then restarted to a second pool
+        """
+        name = 'restart.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        expectedResponse = dns.message.make_response(query)
+        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(receivedResponse, expectedResponse)
index fbe5a42320e817d08b3f98ae808e3c44b56b7bb3..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):
@@ -735,30 +799,3 @@ class TestRoutingHighValueWRandom(DNSDistTest):
             self.assertEqual(self._responsesCounter['UDP Responder 2'], numberOfQueries - self._responsesCounter['UDP Responder'])
         if 'TCP Responder 2' in self._responsesCounter:
             self.assertEqual(self._responsesCounter['TCP Responder 2'], numberOfQueries - self._responsesCounter['TCP Responder'])
-
-class TestRoutingBadWeightWRandom(DNSDistTest):
-
-    _testServer2Port = 5351
-    _consoleKey = DNSDistTest.generateConsoleKey()
-    _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
-    _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_testServer2Port']
-    _config_template = """
-    setKey("%s")
-    controlSocket("127.0.0.1:%s")
-    setServerPolicy(wrandom)
-    s1 = newServer{address="127.0.0.1:%s", weight=-1}
-    s2 = newServer{address="127.0.0.1:%s", weight=2147483648}
-    """
-    _checkConfigExpectedOutput = b"""Error creating new server: downstream weight value must be greater than 0.
-Error creating new server: downstream weight value must be between 1 and 2147483647
-Configuration 'configs/dnsdist_TestRoutingBadWeightWRandom.conf' OK!
-"""
-
-    def testBadWeightWRandom(self):
-        """
-        Routing: WRandom
-
-        Test that downstreams cannot be added with invalid weights.
-        """
-        # There should be no downstreams
-        self.assertTrue(self.sendConsoleCommand("getServer(0)").startswith("Error"))
index 6468502f0479d4eea628053a9c50aa6b28e8d5b3..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 = """
@@ -1449,6 +1568,98 @@ class TestAdvancedNegativeAndSOA(DNSDistTest):
             (_, receivedResponse) = sender(query, response=None, useQueue=False)
             self.checkMessageEDNSWithoutOptions(expectedResponse, receivedResponse)
 
+
+class TestAdvancedNegativeAndSOAAuthSection(DNSDistTest):
+
+    _selfGeneratedPayloadSize = 1232
+    _config_template = """
+    addAction("nxd.negativeandsoa.advanced.tests.powerdns.com.", NegativeAndSOAAction(true, "auth.", 42, "mname", "rname", 5, 4, 3, 2, 1, { soaInAuthoritySection=true }))
+    addAction("nodata.negativeandsoa.advanced.tests.powerdns.com.", NegativeAndSOAAction(false, "another-auth.", 42, "mname", "rname", 1, 2, 3, 4, 5, { soaInAuthoritySection=true }))
+    setPayloadSizeOnSelfGeneratedAnswers(%d)
+    newServer{address="127.0.0.1:%s"}
+    """
+    _config_params = ['_selfGeneratedPayloadSize', '_testServerPort']
+
+
+    def testAdvancedNegativeAndSOANXD(self):
+        """
+        Advanced: NegativeAndSOAAction NXD
+        """
+        name = 'nxd.negativeandsoa.advanced.tests.powerdns.com.'
+        # no EDNS
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(dns.rcode.NXDOMAIN)
+        soa = dns.rrset.from_text("auth.",
+                                  42,
+                                  dns.rdataclass.IN,
+                                  dns.rdatatype.SOA,
+                                  'mname. rname. 5 4 3 2 1')
+        expectedResponse.authority.append(soa)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.checkMessageNoEDNS(expectedResponse, receivedResponse)
+
+        # withEDNS
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query, our_payload=self._selfGeneratedPayloadSize)
+        expectedResponse.set_rcode(dns.rcode.NXDOMAIN)
+        soa = dns.rrset.from_text("auth.",
+                                  42,
+                                  dns.rdataclass.IN,
+                                  dns.rdatatype.SOA,
+                                  'mname. rname. 5 4 3 2 1')
+        expectedResponse.authority.append(soa)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.checkMessageEDNSWithoutOptions(expectedResponse, receivedResponse)
+
+    def testAdvancedNegativeAndSOANoData(self):
+        """
+        Advanced: NegativeAndSOAAction NoData
+        """
+        name = 'nodata.negativeandsoa.advanced.tests.powerdns.com.'
+        # no EDNS
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(dns.rcode.NOERROR)
+        soa = dns.rrset.from_text("another-auth.",
+                                  42,
+                                  dns.rdataclass.IN,
+                                  dns.rdatatype.SOA,
+                                  'mname. rname. 1 2 3 4 5')
+        expectedResponse.authority.append(soa)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.checkMessageNoEDNS(expectedResponse, receivedResponse)
+
+        # with EDNS
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query, our_payload=self._selfGeneratedPayloadSize)
+        expectedResponse.set_rcode(dns.rcode.NOERROR)
+        soa = dns.rrset.from_text("another-auth.",
+                                  42,
+                                  dns.rdataclass.IN,
+                                  dns.rdatatype.SOA,
+                                  'mname. rname. 1 2 3 4 5')
+        expectedResponse.authority.append(soa)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.checkMessageEDNSWithoutOptions(expectedResponse, receivedResponse)
+
+
 class TestAdvancedLuaRule(DNSDistTest):
 
     _config_template = """
@@ -1564,3 +1775,82 @@ class TestAdvancedSetEDNSOptionAction(DNSDistTest):
             self.assertEqual(expectedQuery, receivedQuery)
             self.checkResponseNoEDNS(response, receivedResponse)
             self.checkQueryEDNS(expectedQuery, receivedQuery)
+
+    def testAdvancedSetEDNSOptionWithDOSet(self):
+        """
+        Advanced: Set EDNS Option (DO bit set)
+        """
+        # check that the DO bit is correctly handled, as we messed that up once
+        name = 'setednsoption-do.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True, want_dnssec=True, payload=4096)
+
+        eco = cookiesoption.CookiesOption(b'deadbeef', b'deadc0de')
+        expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[eco], want_dnssec=True)
+
+        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 = expectedQuery.id
+            self.assertEqual(expectedQuery, receivedQuery)
+            self.checkResponseEDNSWithoutECS(response, receivedResponse)
+            self.checkQueryEDNS(expectedQuery, receivedQuery)
+
+class TestAdvancedLuaGetContent(DNSDistTest):
+
+    _config_template = """
+    function accessContentLua(dq)
+        local expectedSize = 57
+        local content = dq:getContent()
+        if content == nil or #content == 0 then
+            errlog('No content')
+            return DNSAction.Nxdomain, ""
+        end
+        if #content ~= expectedSize then
+            errlog('Invalid content size'..#content)
+            return DNSAction.Nxdomain, ""
+        end
+        -- the qname is right after the header, and we have only the qtype and qclass after that
+        local qname = string.sub(content, 13, -5)
+        local expectedQName = '\\011get-content\\008advanced\\005tests\\008powerdns\\003com\\000'
+        if qname ~= expectedQName then
+            errlog('Invalid qname '..qname..', expecting '..expectedQName)
+            return DNSAction.Nxdomain, ""
+        end
+        return DNSAction.None, ""
+    end
+    addAction(AllRule(), LuaAction(accessContentLua))
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testGetContentViaLua(self):
+        """
+        Advanced: Test getContent() via Lua
+        """
+        name = 'get-content.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AAAA', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '::1')
+        response.answer.append(rrset)
+
+        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(receivedResponse, response)
index 5d6db34cbde45346d9c29a9d5ee2eeed79aa117b..b7738f3302d5d53287ab646274e9614101a3cdfa 100644 (file)
@@ -6,7 +6,8 @@ from pysnmp.hlapi import *
 from dnsdisttests import DNSDistTest
 
 class TestSNMP(DNSDistTest):
-
+    # wait 1s so that the uptime is > 0
+    _extraStartupSleep = 1
     _snmpTimeout = 2.0
     _snmpServer = '127.0.0.1'
     _snmpPort = 161
@@ -19,7 +20,9 @@ class TestSNMP(DNSDistTest):
     _config_template = """
     newServer{address="127.0.0.1:%s", name="servername"}
     snmpAgent(true)
+    setVerboseHealthChecks(true)
     """
+    _verboseMode = True
 
     def _checkStatsValues(self, results, queriesCountersValue):
         for i in list(range(1, 5)) + list(range(6, 20)) + list(range(24, 35)) + [ 35 ] :
@@ -98,8 +101,6 @@ class TestSNMP(DNSDistTest):
         return results
 
     def _checkStats(self, auth, name):
-        # wait 1s so that the uptime is > 0
-        time.sleep(1)
 
         results = self._getSNMPStats(auth)
         self._checkStatsValues(results, self.__class__._queriesSent)
index 2da43c46e28ee7925ccea44d8898cca5edc6965f..9056924231a6f53865801a359074f2d58941df25 100644 (file)
@@ -6,22 +6,22 @@ class TestSVCB(DNSDistTest):
 
     _config_template = """
     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" }, key42="/dns-query{?dns}" })
+                       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, key42="/dns-query{?dns}" })
+                         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" }, key42="/dns-query{?dns}"})
+                                 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 73c01a8ba3f5f8c61c8975eed172b6b945be889d..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'
@@ -30,6 +30,7 @@ class TestBrokenTCPFastOpen(DNSDistTest):
 
     @classmethod
     def BrokenTCPResponder(cls, port):
+        cls._backgroundThreads[threading.get_native_id()] = True
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
         sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
@@ -40,8 +41,17 @@ class TestBrokenTCPFastOpen(DNSDistTest):
             sys.exit(1)
 
         sock.listen(100)
+        sock.settimeout(1.0)
         while True:
-            (conn, _) = sock.accept()
+            try:
+                (conn, _) = sock.accept()
+            except socket.timeout:
+                if cls._backgroundThreads.get(threading.get_native_id(), False) == False:
+                    del cls._backgroundThreads[threading.get_native_id()]
+                    break
+                else:
+                    continue
+
             conn.settimeout(5.0)
             data = conn.recv(2)
             if not data:
@@ -61,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 896a577f6da8f902ed56e67081cebe0175884d82..9803ed550f961300ecfb656ede0eddd6edaa9a65 100644 (file)
@@ -6,13 +6,15 @@ import ssl
 import subprocess
 import time
 import unittest
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
 class TLSTests(object):
 
     def getServerCertificate(self):
         conn = self.openTLSConnection(self._tlsServerPort, self._serverName, self._caCert)
-        return conn.getpeercert()
+        cert = conn.getpeercert()
+        conn.close()
+        return cert
 
     def getTLSProvider(self):
         return self.sendConsoleCommand("getBind(0):getEffectiveTLSProvider()").rstrip()
@@ -56,9 +58,10 @@ 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()
         # open a new connection
         conn = self.openTLSConnection(self._tlsServerPort, self._serverName, self._caCert)
 
@@ -86,6 +89,7 @@ class TLSTests(object):
 
         # and that the serial is different
         self.assertNotEqual(serialNumber, cert['serialNumber'])
+        conn.close()
 
     def testTLKA(self):
         """
@@ -112,6 +116,8 @@ class TLSTests(object):
             self.assertEqual(query, receivedQuery)
             self.assertEqual(response, receivedResponse)
 
+        conn.close()
+
     def testTLSPipelining(self):
         """
         TLS: Several queries over the same connection without waiting for the responses
@@ -139,6 +145,8 @@ class TLSTests(object):
             self.assertEqual(query, receivedQuery)
             self.assertEqual(response, receivedResponse)
 
+        conn.close()
+
     def testTLSSNIRouting(self):
         """
         TLS: SNI Routing
@@ -169,6 +177,7 @@ class TLSTests(object):
         self.assertTrue(receivedResponse)
         self.assertEqual(expectedResponse, receivedResponse)
 
+        conn.close()
         # this one should not
         conn = self.openTLSConnection(self._tlsServerPort, self._serverName, self._caCert)
 
@@ -179,6 +188,7 @@ class TLSTests(object):
         receivedQuery.id = query.id
         self.assertEqual(query, receivedQuery)
         self.assertEqual(response, receivedResponse)
+        conn.close()
 
     def testTLSSNIRoutingAfterResumption(self):
         # we have more complicated tests about session resumption itself,
@@ -255,13 +265,14 @@ class TLSTests(object):
 
 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")
@@ -272,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")
@@ -294,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"}
 
@@ -352,25 +377,27 @@ class TestDOTWithCache(DNSDistTest):
         self.assertEqual(expectedQuery, receivedQuery)
         self.checkQueryNoEDNS(expectedQuery, receivedQuery)
         self.assertEqual(response, receivedResponse)
+        conn.close()
 
         for _ in range(numberOfQueries):
             conn = self.openTLSConnection(self._tlsServerPort, self._serverName, self._caCert)
             self.sendTCPQueryOverConnection(conn, query, response=None)
             receivedResponse = self.recvTCPResponseOverConnection(conn, useQueue=False)
             self.assertEqual(receivedResponse, response)
+            conn.close()
 
 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
@@ -380,6 +407,8 @@ class TestTLSFrontendLimits(DNSDistTest):
     addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl", maxConcurrentTCPConnections=%d })
     """
     _config_params = ['_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_maxTCPConnsPerTLSFrontend']
+    _alternateListeningAddr = '127.0.0.1'
+    _alternateListeningPort = _tlsServerPort
 
     def testTCPConnsPerTLSFrontend(self):
         """
@@ -429,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)
@@ -461,15 +490,16 @@ class TestProtocols(DNSDistTest):
         receivedQuery.id = query.id
         self.assertEqual(query, receivedQuery)
         self.assertEqual(response, receivedResponse)
+        conn.close()
 
 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")
@@ -479,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 0d0f0b16e68b018a58b4ee58accaf33f97f6456c..dbc1dc906fc0bf980bb84aa088b36aefd2c3b9ee 100644 (file)
@@ -1,5 +1,8 @@
 import dns
+import dns.serial
 import time
+import itertools
+import socket
 
 from ixfrdisttests import IXFRDistTest
 from xfrserver.xfrserver import AXFRServer
@@ -21,6 +24,25 @@ $ORIGIN example.
 ns1.example.    4242    A       192.0.2.1
 ns2.example.    4242    A       192.0.2.2
 newrecord.example.        8484    A       192.0.2.42
+""",
+    3: """
+$ORIGIN example.
+@        86400   SOA    foo bar 3 1500 3 4 5
+@        4242    NS     ns1.example.
+@        4242    NS     ns2.example.
+ns1.example.    4242    A       192.0.2.1
+ns2.example.    4242    A       192.0.2.2
+newrecord2.example.        8484    A       192.0.2.42
+""",
+    4: """
+$ORIGIN example.
+@        86400   SOA    foo bar 4 2 3 4 5
+@        4242    NS     ns1.example.
+@        4242    NS     ns2.example.
+ns1.example.    4242    A       192.0.2.1
+ns2.example.    4242    A       192.0.2.2
+newrecord2.example.        8484    A       192.0.2.42
+other.example.  1234    TXT     "foo"
 """
 }
 
@@ -35,10 +57,16 @@ 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
     def setUpClass(cls):
@@ -50,20 +78,37 @@ 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)
+
+            if response_message.rcode() == dns.rcode.REFUSED:
+                return 0
+
+            soa_rrset = response_message.find_rrset(dns.message.ANSWER, dns.name.from_text("example."), dns.rdataclass.IN, dns.rdatatype.SOA)
+            return soa_rrset[0].serial
+
         attempts = 0
         while attempts < timeout:
             print('attempts=%s timeout=%s' % (attempts, timeout))
-            servedSerial = xfrServer.getServedSerial()
+            servedSerial = get_current_serial()
             print('servedSerial=%s' % servedSerial)
             if servedSerial > serial:
                 raise AssertionError("Expected serial %d, got %d" % (serial, servedSerial))
             if servedSerial == serial:
                 self._xfrDone = self._xfrDone + 1
+                self._loaded_serials.append(serial)
                 return
 
             attempts = attempts + 1
@@ -73,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():
@@ -93,14 +138,46 @@ class IXFRDistBasicTest(IXFRDistTest):
     def checkIXFR(self, fromserial, toserial):
         global zones, xfrServer
 
-        ixfr = []
-        soa1 = xfrServer._getSOAForSerial(fromserial)
-        soa2 = xfrServer._getSOAForSerial(toserial)
-        newrecord = [r for r in xfrServer._getRecordsForSerial(toserial) if r.name==dns.name.from_text('newrecord.example.')]
+        soa_requested = xfrServer._getSOAForSerial(fromserial)
+        soa_latest = xfrServer._getSOAForSerial(self._loaded_serials[-1])
+
+        self.assertEqual(soa_latest[0].serial, toserial)
+
         query = dns.message.make_query('example.', 'IXFR')
-        query.authority = [soa1]
+        query.authority = [soa_requested]
+
+        expected = []
+        expected.append([soa_latest]) #latest SOA
+
+        def pairwise(iterable): # itertools.pairwise exists in 3.10, but until then...
+            # pairwise('ABCDEFG') --> AB BC CD DE EF FG
+            a, b = itertools.tee(iterable)
+            next(b, None)
+            return zip(a, b)
+
+        found_starting_version = False
+        for serial_pair in pairwise(self._loaded_serials):
+            if dns.serial.Serial(serial_pair[0]) < dns.serial.Serial(fromserial):
+                continue
+
+            if serial_pair[0] == fromserial:
+                found_starting_version = True
+
+            old_records = [r for r in xfrServer._getRecordsForSerial(serial_pair[0]) if r.rdtype != dns.rdatatype.SOA]
+            new_records = [r for r in xfrServer._getRecordsForSerial(serial_pair[1]) if r.rdtype != dns.rdatatype.SOA]
+            added = [r for r in new_records if r not in old_records]
+            removed = [r for r in old_records if r not in new_records]
+
+            expected.append([xfrServer._getSOAForSerial(serial_pair[0])]) # old SOA
+            if removed: expected.append(removed) # removed records from old SOA (sendTCPQueryMultiResponse skips if empty)
+            expected.append([xfrServer._getSOAForSerial(serial_pair[1])]) # new SOA
+            if added: expected.append(added) # added records in new SOA (sendTCPQueryMultiResponse skips if empty)
+
+        expected.append([soa_latest]) # latest SOA
+
+        if not found_starting_version:
+            raise AssertionError("Did not find zone version with requested serial {fromserial}, impossible to IXFR scenario?")
 
-        expected = [[soa2], [soa1], [soa2], newrecord, [soa2]]
         res = self.sendTCPQueryMultiResponse(query, count=len(expected)+1) # +1 for trailing data check
         answers = [r.answer for r in res]
 
@@ -115,6 +192,7 @@ class IXFRDistBasicTest(IXFRDistTest):
                 pos = pos + 1
             answerPos = answerPos + 1
 
+
     def test_a_XFR(self):
         self.waitUntilCorrectSerialIsLoaded(1)
         self.checkFullZone(1)
@@ -128,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)
@@ -153,3 +232,33 @@ class IXFRDistBasicTest(IXFRDistTest):
 
         response = self.sendUDPQuery(query)
         self.assertEqual(expected, response)
+
+    def test_c_IXFR_multi(self):
+        self.waitUntilCorrectSerialIsLoaded(3)
+        self.checkFullZone(3)
+        self.checkIXFR(2,3)
+        self.checkIXFR(1,3)
+
+        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)
+        self.checkIXFR(1,4)
index f170a6732e87a62b524997f77fb2d6ecf3e4fa45..1be5735c0de87c33abeef4d4a7002789723ca20e 100644 (file)
@@ -1,6 +1,7 @@
 from ixfrdisttests import IXFRDistTest
 import time
 import requests
+import subprocess
 
 xfrServerPort = 4244
 
@@ -26,14 +27,19 @@ 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"]
-    metric_domain_stats = ["ixfrdist_soa_serial", "ixfrdist_soa_checks",
-                           "ixfrdist_soa_checks_failed",
-                           "ixfrdist_soa_inqueries",
-                           "ixfrdist_axfr_inqueries", "ixfrdist_axfr_failures",
-                           "ixfrdist_ixfr_inqueries", "ixfrdist_ixfr_failures"]
+    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_notimp"]
+    metric_domain_stats = ["ixfrdist_soa_serial", "ixfrdist_soa_checks_total",
+                           "ixfrdist_soa_checks_failed_total",
+                           "ixfrdist_soa_inqueries_total",
+                           "ixfrdist_axfr_inqueries_total", "ixfrdist_axfr_failures_total",
+                           "ixfrdist_ixfr_inqueries_total", "ixfrdist_ixfr_failures_total"]
 
     @classmethod
     def setUpClass(cls):
@@ -45,6 +51,24 @@ webserver-address: %s
     def tearDownClass(cls):
         cls.tearDownIXFRDist()
 
+    def checkPrometheusContentPromtool(self, content):
+        output = None
+        try:
+            testcmd = ['promtool', 'check', 'metrics']
+            process = subprocess.Popen(testcmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
+            output = process.communicate(input=content)
+        except subprocess.CalledProcessError as exc:
+            raise AssertionError('%s failed (%d): %s' % (testcmd, process.returncode, process.output))
+
+        # commented out because promtool returns 3 because of the "_total" suffix warnings
+        #if process.returncode != 0:
+        #    raise AssertionError('%s failed (%d): %s' % (testcmd, process.returncode, output))
+
+        for line in output[0].splitlines():
+            if line.endswith(b"should have \"_total\" suffix"):
+                continue
+            raise AssertionError('%s returned an unexpected output. Faulty line is "%s", complete content is "%s"' % (testcmd, line, output))
+
     def test_program_stats_exist(self):
         res = requests.get('http://{}/metrics'.format(self.webserver_address))
         self.assertEqual(res.status_code, 200)
@@ -53,8 +77,13 @@ webserver-address: %s
                 continue
             if "{" in line:
                 continue
-            self.assertIn(line.split(" ")[0],
+            tokens = line.split(" ")
+            self.assertIn(tokens[0],
                           self.metric_prog_stats + self.metric_domain_stats)
+            if tokens[0] == 'ixfrdist_unknown_domain_inqueries_total':
+                self.assertEqual(int(tokens[1]), 0)
+
+        self.checkPrometheusContentPromtool(res.content)
 
     def test_registered(self):
         res = requests.get('http://{}/metrics'.format(self.webserver_address))
@@ -67,7 +96,7 @@ webserver-address: %s
                 continue
             if "{" not in line:
                 continue
-            self.assertIn('{domain=example}', line)
+            self.assertIn('{domain="example"}', line)
             self.assertIn(line.split("{")[0], self.metric_domain_stats)
 
     def test_metrics_have_help(self):
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 cfbd4b0c11a039c83c369162bb0a251251a6765e..540abe8081d0b1c859378309608e7d3a550c896d 100644 (file)
@@ -1,9 +1,9 @@
 1
 2
-0      minimal.com.    IN      CDS     86400   19988 13 4 b6c8e1ae21490b6a8cdf999383247214490a0b501cad0da434940fa61e2532325bee9b3db253905ecb37614cc6058005
-0      minimal.com.    IN      CDS     86400   44631 13 4 1a7a856b72dab4cd8d2173319574e36a91040eff134f6613e0699229013766ea57ffe5720260d5b2da2b1ab19b6bc216
-0      minimal.com.    IN      RRSIG   86400   CDS 13 2 86400 [expiry] [inception] [keytag] minimal.com. ...
-2      .       IN      OPT     32768   
+0      minimal.com.    86400   IN      CDS     19988 13 4 b6c8e1ae21490b6a8cdf999383247214490a0b501cad0da434940fa61e2532325bee9b3db253905ecb37614cc6058005
+0      minimal.com.    86400   IN      CDS     44631 13 4 1a7a856b72dab4cd8d2173319574e36a91040eff134f6613e0699229013766ea57ffe5720260d5b2da2b1ab19b6bc216
+0      minimal.com.    86400   IN      RRSIG   CDS 13 2 86400 [expiry] [inception] [keytag] minimal.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='minimal.com.', qtype=CDS
 minimal.com.   120     IN      NS      ns1.example.com.
@@ -20,17 +20,17 @@ minimal.com.        86400   IN      DNSKEY  257 3 13 ...
 minimal.com.   86400   IN      DNSKEY  257 3 13 ...
 minimal.com.   86400   IN      RRSIG   CDS 13 2 86400 [expiry] [inception] [keytag] minimal.com. ...
 minimal.com.   86400   IN      RRSIG   DNSKEY 13 2 86400 [expiry] [inception] [keytag] minimal.com. ...
-0      minimal.com.    IN      CDS     86400   19988 13 1 99173ff8dd534e1e9092572d4008ccb9381424b3
-0      minimal.com.    IN      CDS     86400   19988 13 2 f317da762321935edfca118cd5dac67b8ef5d8826f63c4399ddf7fbe2415734e
-0      minimal.com.    IN      CDS     86400   44631 13 1 c7e816f8e73d4a4a3b8d06d7376833d525bdd7db
-0      minimal.com.    IN      CDS     86400   44631 13 2 25b688fbaf349a706bf0d8b02ac858f0567cf601ca1a9d7f34bb08c43928093e
-0      minimal.com.    IN      RRSIG   86400   CDS 13 2 86400 [expiry] [inception] [keytag] minimal.com. ...
-2      .       IN      OPT     32768   
+0      minimal.com.    86400   IN      CDS     19988 13 1 99173ff8dd534e1e9092572d4008ccb9381424b3
+0      minimal.com.    86400   IN      CDS     19988 13 2 f317da762321935edfca118cd5dac67b8ef5d8826f63c4399ddf7fbe2415734e
+0      minimal.com.    86400   IN      CDS     44631 13 1 c7e816f8e73d4a4a3b8d06d7376833d525bdd7db
+0      minimal.com.    86400   IN      CDS     44631 13 2 25b688fbaf349a706bf0d8b02ac858f0567cf601ca1a9d7f34bb08c43928093e
+0      minimal.com.    86400   IN      RRSIG   CDS 13 2 86400 [expiry] [inception] [keytag] minimal.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='minimal.com.', qtype=CDS
-0      minimal.com.    IN      CDS     86400   0 0 0 00
-0      minimal.com.    IN      RRSIG   86400   CDS 13 2 86400 [expiry] [inception] [keytag] minimal.com. ...
-2      .       IN      OPT     32768   
+0      minimal.com.    86400   IN      CDS     0 0 0 00
+0      minimal.com.    86400   IN      RRSIG   CDS 13 2 86400 [expiry] [inception] [keytag] minimal.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='minimal.com.', qtype=CDS
 minimal.com.   120     IN      NS      ns1.example.com.
@@ -46,11 +46,11 @@ minimal.com.        86400   IN      DNSKEY  257 3 13 ...
 minimal.com.   86400   IN      DNSKEY  257 3 13 ...
 minimal.com.   86400   IN      RRSIG   CDS 13 2 86400 [expiry] [inception] [keytag] minimal.com. ...
 minimal.com.   86400   IN      RRSIG   DNSKEY 13 2 86400 [expiry] [inception] [keytag] minimal.com. ...
-1      minimal.com.    IN      NSEC    120     minimal.com. NS SOA RRSIG NSEC DNSKEY
-1      minimal.com.    IN      RRSIG   120     NSEC 13 2 120 [expiry] [inception] [keytag] minimal.com. ...
-1      minimal.com.    IN      RRSIG   120     SOA 13 2 120 [expiry] [inception] [keytag] minimal.com. ...
-1      minimal.com.    IN      SOA     120     ns1.example.com. ahu.example.com. 2000081501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      minimal.com.    120     IN      NSEC    minimal.com. NS SOA RRSIG NSEC DNSKEY
+1      minimal.com.    120     IN      RRSIG   NSEC 13 2 120 [expiry] [inception] [keytag] minimal.com. ...
+1      minimal.com.    120     IN      RRSIG   SOA 13 2 120 [expiry] [inception] [keytag] minimal.com. ...
+1      minimal.com.    120     IN      SOA     ns1.example.com. ahu.example.com. 2000081501 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='minimal.com.', qtype=CDS
 minimal.com.   120     IN      NS      ns1.example.com.
@@ -64,10 +64,10 @@ minimal.com.        120     IN      SOA     ns1.example.com. ahu.example.com. 2000081501 28800 7200
 minimal.com.   86400   IN      DNSKEY  257 3 13 ...
 minimal.com.   86400   IN      DNSKEY  257 3 13 ...
 minimal.com.   86400   IN      RRSIG   DNSKEY 13 2 86400 [expiry] [inception] [keytag] minimal.com. ...
-0      minimal.com.    IN      CDS     86400   19988 13 4 b6c8e1ae21490b6a8cdf999383247214490a0b501cad0da434940fa61e2532325bee9b3db253905ecb37614cc6058005
-0      minimal.com.    IN      CDS     86400   44631 13 4 1a7a856b72dab4cd8d2173319574e36a91040eff134f6613e0699229013766ea57ffe5720260d5b2da2b1ab19b6bc216
-0      minimal.com.    IN      RRSIG   86400   CDS 13 2 86400 [expiry] [inception] [keytag] minimal.com. ...
-2      .       IN      OPT     32768   
+0      minimal.com.    86400   IN      CDS     19988 13 4 b6c8e1ae21490b6a8cdf999383247214490a0b501cad0da434940fa61e2532325bee9b3db253905ecb37614cc6058005
+0      minimal.com.    86400   IN      CDS     44631 13 4 1a7a856b72dab4cd8d2173319574e36a91040eff134f6613e0699229013766ea57ffe5720260d5b2da2b1ab19b6bc216
+0      minimal.com.    86400   IN      RRSIG   CDS 13 2 86400 [expiry] [inception] [keytag] minimal.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='minimal.com.', qtype=CDS
 minimal.com.   120     IN      NS      ns1.example.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 0271c8c0de76d7a651e5831a55d2d5ebb5cf68e3..e90206c6e938100b0d455bf27b584ed1693596b7 100644 (file)
@@ -1,4 +1,4 @@
 had non-zero drops
 Reply to question for qname='example.com.', qtype=A
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
-1      example.com.    IN      SOA     3600    ahu.example.com. ns1.example.com. 2008080300 1800 3600 604800 3600
+1      example.com.    3600    IN      SOA     ahu.example.com. ns1.example.com. 2008080300 1800 3600 604800 3600
index c6fd746bcb836f4a9c5aa8c3783b3b44e98f9dff..46db7ad5a14accecba528fd4523c6d3cad5cbab3 100644 (file)
@@ -1,16 +1,16 @@
-0      minimal.com.    IN      SOA     120     ns1.example.com. ahu.example.com. 2000081501 28800 7200 604800 86400
+0      minimal.com.    120     IN      SOA     ns1.example.com. ahu.example.com. 2000081501 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='minimal.com.', qtype=SOA
-0      minimal.com.    IN      SOA     120     ns1.example.com. ahu.example.com. 2000081501 28800 7200 604800 86400
-2      .       IN      OPT     0       
+0      minimal.com.    120     IN      SOA     ns1.example.com. ahu.example.com. 2000081501 28800 7200 604800 86400
+2      .       0       IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='minimal.com.', qtype=SOA
-0      minimal.com.    IN      NS      120     ns1.example.com.
-0      minimal.com.    IN      NS      120     ns2.example.com.
-2      .       IN      OPT     0       
+0      minimal.com.    120     IN      NS      ns1.example.com.
+0      minimal.com.    120     IN      NS      ns2.example.com.
+2      .       0       IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='minimal.com.', qtype=NS
-0      minimal.com.    IN      NS      120     ns1.example.com.
-0      minimal.com.    IN      NS      120     ns2.example.com.
+0      minimal.com.    120     IN      NS      ns1.example.com.
+0      minimal.com.    120     IN      NS      ns2.example.com.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='minimal.com.', qtype=NS
index e94fe49cf82129d2ffd60dfba90586d1b517da22..15e634623d7ccb974a2df262152741f384bf3557 100644 (file)
@@ -9,6 +9,6 @@ options {
 };
 
 zone "minimal.com"{
-       type master;
+       type primary;
        file "./minimal.com";
 };
diff --git a/regression-tests.nobackend/lmdb-metadata-leak/command b/regression-tests.nobackend/lmdb-metadata-leak/command
new file mode 100755 (executable)
index 0000000..c4d9d02
--- /dev/null
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+set -e
+if [ "${PDNS_DEBUG}" = "YES" ]; then
+  set -x
+fi
+
+rootPath=$(readlink -f $(dirname $0))
+
+for random in no yes
+do
+  workdir=$(mktemp -d)
+
+  cat << EOF > "${workdir}/pdns-lmdb.conf"
+  module-dir=../regression-tests/modules
+  launch=lmdb
+  lmdb-filename=${workdir}/pdns.lmdb
+  lmdb-shards=2
+  lmdb-random-ids=$random
+EOF
+
+  echo === random=$random
+
+  echo == creating zone
+  $PDNSUTIL --config-dir="${workdir}" --config-name=lmdb create-zone example.com
+  
+  $PDNSUTIL --config-dir="${workdir}" --config-name=lmdb set-meta example.com FOO BAR
+  $PDNSUTIL --config-dir="${workdir}" --config-name=lmdb show-zone example.com | grep FOO
+  mdb_dump -p -a -n ${workdir}/pdns.lmdb | egrep -o 'FOO[0-9]*' | LC_ALL=C sort
+
+  $PDNSUTIL --config-dir="${workdir}" --config-name=lmdb set-meta example.com FOO2 BAR2
+  $PDNSUTIL --config-dir="${workdir}" --config-name=lmdb show-zone example.com | grep FOO
+  mdb_dump -p -a -n ${workdir}/pdns.lmdb | egrep -o 'FOO[0-9]*' | LC_ALL=C sort
+
+  $PDNSUTIL --config-dir="${workdir}" --config-name=lmdb set-meta example.com FOO2 BAR2
+  $PDNSUTIL --config-dir="${workdir}" --config-name=lmdb show-zone example.com | grep FOO
+  mdb_dump -p -a -n ${workdir}/pdns.lmdb | egrep -o 'FOO[0-9]*' | LC_ALL=C sort
+
+  echo == deleting zone
+  $PDNSUTIL --config-dir="${workdir}" --config-name=lmdb delete-zone example.com
+  mdb_dump -p -a -n ${workdir}/pdns.lmdb | egrep -o 'FOO[0-9]*' | LC_ALL=C sort
+done
\ No newline at end of file
diff --git a/regression-tests.nobackend/lmdb-metadata-leak/description b/regression-tests.nobackend/lmdb-metadata-leak/description
new file mode 100644 (file)
index 0000000..99a9783
--- /dev/null
@@ -0,0 +1,2 @@
+This test verifies that, with the LMDB-backend, pdnsutil set-meta and delete-zone do not leave old unreachable metadata items lying around
+
diff --git a/regression-tests.nobackend/lmdb-metadata-leak/expected_result b/regression-tests.nobackend/lmdb-metadata-leak/expected_result
new file mode 100644 (file)
index 0000000..d1be72c
--- /dev/null
@@ -0,0 +1,32 @@
+=== random=no
+== creating zone
+Set 'example.com' meta FOO = BAR
+       FOO     BAR
+FOO
+Set 'example.com' meta FOO2 = BAR2
+       FOO     BAR
+       FOO2    BAR2
+FOO
+FOO2
+Set 'example.com' meta FOO2 = BAR2
+       FOO     BAR
+       FOO2    BAR2
+FOO
+FOO2
+== deleting zone
+=== random=yes
+== creating zone
+Set 'example.com' meta FOO = BAR
+       FOO     BAR
+FOO
+Set 'example.com' meta FOO2 = BAR2
+       FOO     BAR
+       FOO2    BAR2
+FOO
+FOO2
+Set 'example.com' meta FOO2 = BAR2
+       FOO     BAR
+       FOO2    BAR2
+FOO
+FOO2
+== deleting zone
index b0a962af875c293985e4bba37c3237ed13743c90..cd1779cba2bd360be3ccfb29582db03055cb7940 100644 (file)
@@ -1,7 +1,7 @@
-0      cname.example2.com.     IN      CNAME   3600    www.example.com.
-0      www.example.com.        IN      A       120     127.0.0.1
+0      cname.example2.com.     3600    IN      CNAME   www.example.com.
+0      www.example.com.        120     IN      A       127.0.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cname.example2.com.', qtype=A
-0      www.example.com.        IN      A       120     127.0.0.1
+0      www.example.com.        120     IN      A       127.0.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.example.com.', qtype=A
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 7f9d928e23af1f8051c491436a87a34bc73ba9a2..8fcb78bd54dc8a731225fa688486365e9251c423 100644 (file)
@@ -1,20 +1,20 @@
 Set 'minimal.com' meta SOA-EDIT = INCREMENT-WEEKS
-0      minimal.com.    IN      SOA     120     ns1.example.com. ahu.example.com. 2000083846 28800 7200 604800 86400
+0      minimal.com.    120     IN      SOA     ns1.example.com. ahu.example.com. 2000083846 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='minimal.com.', qtype=SOA
-1      minimal.com.    IN      SOA     120     ns1.example.com. ahu.example.com. 2000083846 28800 7200 604800 86400
+1      minimal.com.    120     IN      SOA     ns1.example.com. ahu.example.com. 2000083846 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='minimal.com.', qtype=MX
-1      minimal.com.    IN      SOA     120     ns1.example.com. ahu.example.com. 2000083846 28800 7200 604800 86400
+1      minimal.com.    120     IN      SOA     ns1.example.com. ahu.example.com. 2000083846 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nx.minimal.com.', qtype=MX
 midnight has passed
-0      minimal.com.    IN      SOA     120     ns1.example.com. ahu.example.com. 2000083847 28800 7200 604800 86400
+0      minimal.com.    120     IN      SOA     ns1.example.com. ahu.example.com. 2000083847 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='minimal.com.', qtype=SOA
-1      minimal.com.    IN      SOA     120     ns1.example.com. ahu.example.com. 2000083847 28800 7200 604800 86400
+1      minimal.com.    120     IN      SOA     ns1.example.com. ahu.example.com. 2000083847 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='minimal.com.', qtype=MX
-1      minimal.com.    IN      SOA     120     ns1.example.com. ahu.example.com. 2000083847 28800 7200 604800 86400
+1      minimal.com.    120     IN      SOA     ns1.example.com. ahu.example.com. 2000083847 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nx.minimal.com.', qtype=MX
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 81e2dcd5161143abd1f719c8ab3833e9ade22dbf..0139d4987db3e9a62a3163f9ef05302e1032e8e8 100755 (executable)
@@ -8,4 +8,5 @@ for zone in `cat ../regression-tests/named.conf | grep 'zone ' | cut -f 2 -d \"`
 do
        ${MD5SUM} ../regression-tests/zones/$zone
 done
+${MD5SUM} ../modules/tinydnsbackend/data
 ${MD5SUM} ../modules/tinydnsbackend/data.cdb
index 22cb2e036f74e2ed32a296bd30bad10f785d1238..29e484430fb70a07131f8f07401e94b3ed5cba4d 100644 (file)
@@ -1,10 +1,10 @@
-229dad9ea0464a429685d3dda8a8e9ef  ../regression-tests/zones/example.com
-fe49d2784b1bcc3b91ddd5619f0b6cc1  ../regression-tests/zones/test.com
-f0df67fa656d33fd85098cbe43893395  ../regression-tests/zones/test.dyndns
+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
 e7c0fd528e8aaedb1ea3b6daaead4de2  ../regression-tests/zones/wtest.com
 42b442de632686e94bde75acf66cf524  ../regression-tests/zones/nztest.com
-b06133eb32c5bdf346223563501ba8f8  ../regression-tests/zones/dnssec-parent.com
+7f79c98efdb1d3d2318ac666d2fb5642  ../regression-tests/zones/dnssec-parent.com
 e9be89b6e5e0da8910c69e46f35d20ab  ../regression-tests/zones/insecure.dnssec-parent.com
 6510bf48aa3ca3501b73a1f510852a34  ../regression-tests/zones/delegated.dnssec-parent.com
 a63dc120391d9df0003f2ec4f461a6af  ../regression-tests/zones/secure-delegated.dnssec-parent.com
@@ -15,4 +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
-31595b9c5e078fa22dd1716a34ca1323  ../modules/tinydnsbackend/data.cdb
+e85d67cb577cf1de3127e439e7529311  ../modules/tinydnsbackend/data
+7715e725358f969aa92e46ae9be0c464  ../modules/tinydnsbackend/data.cdb
index 8d9c261f5706e4a3ca1d2d589cff469a376f4d64..554e99feccd17f4a1e043bc4e9321ba7eb7e2409 100644 (file)
@@ -1,4 +1,4 @@
 example.       86400   IN      NS      ns.example.
 example.       86400   IN      SOA     ns.example. admin.example. 2018031900 1800 900 604800 86400
-example.       86400   IN      ZONEMD  2018031900 1 1 8ee54f64ce0d57fd70e1a4811a9ca9e849e2e50cb598edf3ba9c2a58625335c1f966835f0d4338d9f78f557227d63bf6
+example.       86400   IN      TYPE63  \# 54 7848b91c01018ee54f64ce0d57fd70e1a4811a9ca9e849e2e50cb598edf3ba9c2a58625335c1f966835f0d4338d9f78f557227d63bf6
 ns.example.    3600    IN      A       127.0.0.1
index 6b3b04a72714a92dc5954083bac8af460aeffba2..6ccca4215601b38ceebe63ff96f2db58790a19eb 100644 (file)
@@ -20,7 +20,7 @@
 .      86400   IN      NSEC    aaa. NS SOA RRSIG NSEC DNSKEY TYPE63 
 .      86400   IN      DNSKEY  256 3 8 AwEAAZTGv2wmrvnhZ1f4Adpwhz6Z0BbbmlCBf/MN58G2nF82w5vZZ7I0QSkKKk4CGCclh+SeGg6QBvKpizswrJvm5egaFiUQFzvIUvBmCm0oWCIYmu7ewsjGmIqgwlUvGL9xPPE3PQs5s++ZazVtUb+5xsxHS/qp2NT+bp0C5tRnWr7fNc1TFQgXFM69+jNKxVGO/JQSN5xguoBdwCelWEQcllAzCvNKRkilnDMDSXipiPzqTcM/DnSifRskaDLY9cy82044MA4eEfI2DumLfRDXI8IuJLbhYA9Enl044RIeBRRoDyR+ipx9yd6nizGzme2+ze3H9tRVAMbB9z5S+zcjmHU= ;{id = 17913 (zsk), size = 2048b}
 .      86400   IN      DNSKEY  257 3 8 AwEAAbP1gbbCHbgEmVs/RAhiW6ohuVICDnOEEZ7U4DcFctfjbWFG+Wuh8KQe6ukgKALFy129NfPa7e0IjMQRQ3DN3DqQqV0cLRx1ESfug2R8ya3JIsBVR6SAkX/+x55/g27lMTtc5zhGBCAQvZip0LpnDYU3F+IvC+GR065Wn25GjspN0xjSqr37ch0qOwXCMNLY6rBkHkUylajDg9g8s1SBq0gkCWXd7ZJjX5gHn1VjCh3W6amUCAJ3B/qEiEYUjwLAQa1S+XQzxLTExE3UJjgzs/El0Y0IUOO/X/hzZHwU1AiH4/nSAfBVfOY6kAUQk9xrm5Pb+h4DL45kzd6vRyhLobs= ;{id = 21544 (ksk), size = 2048b}
-.      86400   IN      ZONEMD  2021051901 1 1 ce4da51b3dd9166e24cbedd141aee69279389402a132b555a5419ecdca473579f3f5bb2f7671e454db6ac42aa96a4d70
+.      86400   IN      TYPE63  \# 54 7876cdfd0101ce4da51b3dd9166e24cbedd141aee69279389402a132b555a5419ecdca473579f3f5bb2f7671e454db6ac42aa96a4d70
 aaa.   172800  IN      NS      ns1.dns.nic.aaa.
 aaa.   172800  IN      NS      ns2.dns.nic.aaa.
 aaa.   172800  IN      NS      ns3.dns.nic.aaa.
index 9464b298a7090daec2dcbc84d45e192abed92012..7d925f92c9a69aee23d7eaf973c8c0debc4989fc 100644 (file)
@@ -19,7 +19,7 @@ arpa. 86400   IN      RRSIG   TYPE63 8 1 86400 20210727155612 20210629155612 29094 arpa.
 arpa.  86400   IN      NSEC    AS112.ARPA. NS SOA RRSIG NSEC DNSKEY TYPE63 
 arpa.  86400   IN      DNSKEY  256 3 8 AwEAAdMaRW2okM0GrfInisiH9HWsqokdnmeXnJjKUwVQ8dy5sxm0DyCtzNapj54SF4ofgJxYufQCzYoe3Y3WsB6dKW15pTvu6ggqwuTTxvAnkMSHAlMGBE0sybRBIM38WswPcjAXmpITj7Zvgm8qh80dcusK5vwqJhb2CDWHRezUwiIB ;{id = 29094 (zsk), size = 1024b}
 arpa.  86400   IN      DNSKEY  257 3 8 AwEAAdQP1t2ookuQYFNUNGDmLHcoA6LFSImvULaUgChKiIO6Vv5yDyHB0Ng6ZkfHM0586cLcbXNBLj/9u5A4vqzOFj8phzW4WLZREZBLYMcuHhvQdqzuDJ0J5mxmLLis5eNaCwukVm6Zpf/otzCJsx9LyrhQBTyx6FF+h7dbSCvjh7tD ;{id = 18949 (ksk), size = 1024b}
-arpa.  86400   IN      ZONEMD  2021062901 1 1 578c4c70a9b75af08eb159293188cf6659db2339cfed605e2272f23465d4f1d733c07263860ad66725ee53cbc5f69fc3
+arpa.  86400   IN      TYPE63  \# 54 7876f8f50101578c4c70a9b75af08eb159293188cf6659db2339cfed605e2272f23465d4f1d733c07263860ad66725ee53cbc5f69fc3
 as112.arpa.    172800  IN      NS      a.iana-servers.net.
 as112.arpa.    172800  IN      NS      b.iana-servers.net.
 as112.arpa.    172800  IN      NS      c.iana-servers.net.
index 325c06eee7c7bb3b530b48cf6325a6a35ed8215f..6a10fe9c494231c980dfc5b167637da66a1f823d 100644 (file)
@@ -19,7 +19,7 @@ arpa. 86400   IN      RRSIG   TYPE63 8 1 86400 20210616170429 20210519170429 29094 arpa.
 arpa.  86400   IN      DNSKEY  256 3 8 AwEAAdMaRW2okM0GrfInisiH9HWsqokdnmeXnJjKUwVQ8dy5sxm0DyCtzNapj54SF4ofgJxYufQCzYoe3Y3WsB6dKW15pTvu6ggqwuTTxvAnkMSHAlMGBE0sybRBIM38WswPcjAXmpITj7Zvgm8qh80dcusK5vwqJhb2CDWHRezUwiIB ;{id = 29094 (zsk), size = 1024b}
 arpa.  86400   IN      DNSKEY  257 3 8 AwEAAdQP1t2ookuQYFNUNGDmLHcoA6LFSImvULaUgChKiIO6Vv5yDyHB0Ng6ZkfHM0586cLcbXNBLj/9u5A4vqzOFj8phzW4WLZREZBLYMcuHhvQdqzuDJ0J5mxmLLis5eNaCwukVm6Zpf/otzCJsx9LyrhQBTyx6FF+h7dbSCvjh7tD ;{id = 18949 (ksk), size = 1024b}
 arpa.  3600    IN      NSEC3PARAM      1 0 1 - 
-arpa.  86400   IN      ZONEMD  2021051902 1 1 9a84145013e13e3de2328868888c65aa46b7381213990f83d496c642d2324029cc852e09bffa38afd8e9197977776591
+arpa.  86400   IN      TYPE63  \# 54 7876cdfe01019a84145013e13e3de2328868888c65aa46b7381213990f83d496c642d2324029cc852e09bffa38afd8e9197977776591
 0js82oec35lbbc4hl35476cm5icacksf.arpa. 86400   IN      RRSIG   NSEC3 8 2 86400 20210616170429 20210519170429 29094 arpa. PRVkH4+Nm17QlFgwFLnoqwaiIwWZ4pvscanHdMb6HOKkSxwtDoWAGhZubvYGt/Je735nQkGQPPXW2tkMkJa3D7e6RkX/8AoxcqqXOimC6BlG6LuSL4rSousDlbrulyh87qgIHXkUtrHyYUNAMZMKOjMHo7t5IxwjBO0SGADoglk=
 0js82oec35lbbc4hl35476cm5icacksf.arpa. 86400 IN NSEC3 1 0 1 - 2UB8EN7BK0T6DENIGO3I729IVQVME3VE NS
 2ub8en7bk0t6denigo3i729ivqvme3ve.arpa. 86400   IN      RRSIG   NSEC3 8 2 86400 20210616170429 20210519170429 29094 arpa. JsSiqDiPs0juQxKEcCKTFvKXzUdvIvCILEzcN79+qAxaiQuulHUxTSMDvrsxm83m9juvoOUYtBlPyZdI9erAfiEkpF71ZIl8iP7AKGgqTeV1C4SHnf2KsFi69qimdLbWeIfFGYEq+54Vj5vF1SrRounvj63avhI/Zf0tTWz11+4=
index c417f8617eeae7d842544f7a98d8904c13a72aed..61c8c15f7e21d77b21b0020c42efb40f8c191f19 100644 (file)
@@ -19,7 +19,7 @@ arpa. 86400 IN RRSIG TYPE63 8 1 86400 20210616170629 20210519170629 29094 ARPA.
 arpa.  86400   IN      NSEC    as112.arpa. NS SOA RRSIG NSEC DNSKEY TYPE63 
 arpa.  86400   IN      DNSKEY  256 3 8 AwEAAdMaRW2okM0GrfInisiH9HWsqokdnmeXnJjKUwVQ8dy5sxm0DyCtzNapj54SF4ofgJxYufQCzYoe3Y3WsB6dKW15pTvu6ggqwuTTxvAnkMSHAlMGBE0sybRBIM38WswPcjAXmpITj7Zvgm8qh80dcusK5vwqJhb2CDWHRezUwiIB ;{id = 29094 (zsk), size = 1024b}
 arpa.  86400   IN      DNSKEY  257 3 8 AwEAAdQP1t2ookuQYFNUNGDmLHcoA6LFSImvULaUgChKiIO6Vv5yDyHB0Ng6ZkfHM0586cLcbXNBLj/9u5A4vqzOFj8phzW4WLZREZBLYMcuHhvQdqzuDJ0J5mxmLLis5eNaCwukVm6Zpf/otzCJsx9LyrhQBTyx6FF+h7dbSCvjh7tD ;{id = 18949 (ksk), size = 1024b}
-arpa.  86400   IN      ZONEMD  2021051902 1 1 db114bb13809ed81b8fa94f3024996b646b2762805676b0f6a5569b96196f1307d7a9cde68c4eea614a15c59c2332532
+arpa.  86400   IN      TYPE63  \# 54 7876cdfe0101db114bb13809ed81b8fa94f3024996b646b2762805676b0f6a5569b96196f1307d7a9cde68c4eea614a15c59c2332532
 as112.arpa.    172800  IN      NS      a.iana-servers.net.
 as112.arpa.    172800  IN      NS      b.iana-servers.net.
 as112.arpa.    172800  IN      NS      c.iana-servers.net.
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 8907fcdfc6bdf477b92cd0c1c42d7efec3d88478..c19426965e0b892733af3bd26912b31667576584 100644 (file)
@@ -39,7 +39,6 @@ class RecursorTest(AssertEqualDNSMessageMixin, unittest.TestCase):
 
     _confdir = 'recursor'
 
-    _recursorStartupDelay = 2.0
     _recursorPort = 5300
 
     _recursor = None
@@ -51,14 +50,36 @@ daemon=no
 trace=yes
 dont-query=
 local-address=127.0.0.1
-packetcache-ttl=0
-packetcache-servfail-ttl=0
+packetcache-ttl=15
+packetcache-servfail-ttl=15
 max-cache-ttl=15
-threads=1
+threads=2
 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 = """
 """
@@ -163,6 +184,8 @@ secure.example.          3600 IN NS   ns.secure.example.
 ns.secure.example.       3600 IN A    {prefix}.9
 secure.example.          3600 IN MX   10 mx1.secure.example.
 secure.example.          3600 IN MX   20 mx2.secure.example.
+sub.secure.example.      3600 IN MX   10 mx1.secure.example.
+sub.secure.example.      3600 IN MX   20 mx2.secure.example.
 
 naptr.secure.example.    60   IN NAPTR   10 10 "a" "X" "A" s1.secure.example.
 naptr.secure.example.    60   IN NAPTR   10 10 "s" "Y" "B" service1.secure.example.
@@ -299,7 +322,9 @@ node1.undelegated.insecure.example.  3600 IN A    192.0.2.22
 delay1.example.                       3600 IN SOA  {soa}
 delay1.example.                       3600 IN NS n1.delay1.example.
 ns1.delay1.example.                   3600 IN A    {prefix}.16
+8.delay1.example.                     3600 IN A    192.0.2.100
 *.delay1.example.                     0    LUA TXT ";" "local socket=require('socket')" "socket.sleep(tonumber(qname:getRawLabels()[1])/10)" "return 'a'"
+*.delay1.example.                     0    LUA AAAA ";" "local socket=require('socket')" "socket.sleep(tonumber(qname:getRawLabels()[1])/10)" "return '1::2'"
         """,
         
         'delay2.example': """
@@ -617,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
@@ -645,11 +708,11 @@ distributor-threads={threads}""".format(confdir=confdir,
             raise AssertionError('%s failed (%d)' % (recursorcmd, cls._recursor.returncode))
 
     @classmethod
-    def wipeRecursorCache(cls, confdir):
+    def wipeRecursorCache(cls, confdir, name='.$'):
         rec_controlCmd = [os.environ['RECCONTROL'],
                           '--config-dir=%s' % confdir,
                           'wipe-cache',
-                          '.$']
+                          name]
         try:
             subprocess.check_output(rec_controlCmd, stderr=subprocess.STDOUT)
         except subprocess.CalledProcessError as e:
index 4f5afe9d85890b50c8196ee090d7e3889d5c7f06..1ec32b533fc0eb64d6e3c67d56aa4aca6c58a2f0 100644 (file)
@@ -1,7 +1,8 @@
 dnspython>=1.11
-nose>=1.3.7
+pytest
 protobuf>=2.5; sys_platform != 'darwin'
 protobuf>=3.0; sys_platform == 'darwin'
+pyasn1==0.4.8
 pysnmp>=4.3.4
 requests>=2.1.0
 Twisted>0.15.0
index d570c5849729eb94c1ec96e36ebd46f37536d6d8..12b6946e7e0cff4cc5ac6d37bf84d4391b8b011b 100755 (executable)
@@ -1,5 +1,7 @@
 #!/bin/sh
 
+export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python
+
 if [ ! -d .venv ]; then
   python3 -m venv .venv
 fi
@@ -57,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 4fcd3f29ba74c245a9e44f2c88fe341fd90fa08d..88be1855002ef54d3e294fc7cf692df3a3c45847 100644 (file)
@@ -3,6 +3,8 @@ from recursortests import RecursorTest
 import os
 import requests
 import subprocess
+import time
+import extendederrors
 
 class AggressiveNSECCacheBase(RecursorTest):
     __test__ = False
@@ -10,20 +12,26 @@ class AggressiveNSECCacheBase(RecursorTest):
     _wsTimeout = 10
     _wsPassword = 'secretpassword'
     _apiKey = 'secretapikey'
+    #_recursorStartupDelay = 4.0
     _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
     webserver-password=%s
     api-key=%s
+    devonly-regression-test-mode
+    extended-resolution-errors=yes
     """ % (_wsPort, _wsPassword, _apiKey)
 
     @classmethod
-    def setUp(cls):
+    def wipe(cls):
         confdir = os.path.join('configs', cls._confdir)
-        cls.wipeRecursorCache(confdir)
+        # Only wipe examples, as wiping the root triggers root NS refreshes
+        cls.wipeRecursorCache(confdir, "example$")
 
     def getMetric(self, name):
         headers = {'x-api-key': self._apiKey}
@@ -40,9 +48,31 @@ class AggressiveNSECCacheBase(RecursorTest):
 
         self.assertTrue(False)
 
+    def testNoEDE(self):
+        # This isn't an aggresive cache check, but the strcuture is very similar to the others,
+        # so letys place it here.
+        # It test the issue that an intermediate EDE does not get reported with the final answer
+        # https://github.com/PowerDNS/pdns/pull/12694
+        self.wipe()
+
+        res = self.sendQuery('host1.sub.secure.example.', 'TXT')
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertAnswerEmpty(res)
+        self.assertAuthorityHasSOA(res)
+        self.assertMessageIsAuthenticated(res)
+        self.assertEqual(res.edns, 0)
+        self.assertEqual(len(res.options), 0)
+
+        res = self.sendQuery('host1.sub.secure.example.', 'A')
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertMessageIsAuthenticated(res)
+        self.assertEqual(res.edns, 0)
+        self.assertEqual(len(res.options), 0)
+
     def testNoData(self):
+        self.wipe()
 
-        # first we query a non-existent type, to get the NSEC in our cache
+        # first we query a nonexistent type, to get the NSEC in our cache
         entries = self.getMetric('aggressive-nsec-cache-entries')
         res = self.sendQuery('host1.secure.example.', 'TXT')
         self.assertRcodeEqual(res, dns.rcode.NOERROR)
@@ -62,6 +92,10 @@ class AggressiveNSECCacheBase(RecursorTest):
         self.assertMessageIsAuthenticated(res)
         self.assertEqual(nbQueries, self.getMetric('all-outqueries'))
         self.assertEqual(self.getMetric('aggressive-nsec-cache-entries'), entries)
+        self.assertEqual(res.edns, 0)
+        self.assertEqual(len(res.options), 1)
+        self.assertEqual(res.options[0].otype, 15)
+        self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(29, b'Result synthesized from aggressive NSEC cache (RFC8198)'))
 
 class AggressiveNSECCacheNSEC(AggressiveNSECCacheBase):
     _confdir = 'AggressiveNSECCacheNSEC'
@@ -70,8 +104,9 @@ class AggressiveNSECCacheNSEC(AggressiveNSECCacheBase):
     # we can't use the same tests for NSEC and NSEC3 because the hashed NSEC3s
     # do not deny the same names than the non-hashed NSECs do
     def testNXD(self):
+        self.wipe()
 
-        # first we query a non-existent name, to get the needed NSECs (name + widcard) in our cache
+        # first we query a nonexistent name, to get the needed NSECs (name + widcard) in our cache
         entries = self.getMetric('aggressive-nsec-cache-entries')
         hits = self.getMetric('aggressive-nsec-cache-nsec-hits')
         res = self.sendQuery('host2.secure.example.', 'TXT')
@@ -95,10 +130,15 @@ class AggressiveNSECCacheNSEC(AggressiveNSECCacheBase):
         self.assertEqual(nbQueries, self.getMetric('all-outqueries'))
         self.assertEqual(self.getMetric('aggressive-nsec-cache-entries'), entries)
         self.assertGreater(self.getMetric('aggressive-nsec-cache-nsec-hits'), hits)
+        self.assertEqual(res.edns, 0)
+        self.assertEqual(len(res.options), 1)
+        self.assertEqual(res.options[0].otype, 15)
+        self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(29, b'Result synthesized from aggressive NSEC cache (RFC8198)'))
 
     def testWildcard(self):
+        self.wipe()
 
-        # first we query a non-existent name, but for which a wildcard matches,
+        # first we query a nonexistent name, but for which a wildcard matches,
         # to get the NSEC in our cache
         res = self.sendQuery('test1.wildcard.secure.example.', 'A')
         expected = dns.rrset.from_text('test1.wildcard.secure.example.', 0, dns.rdataclass.IN, 'A', '{prefix}.10'.format(prefix=self._PREFIX))
@@ -117,6 +157,10 @@ class AggressiveNSECCacheNSEC(AggressiveNSECCacheBase):
         self.assertMessageIsAuthenticated(res)
         self.assertEqual(nbQueries, self.getMetric('all-outqueries'))
         self.assertGreater(self.getMetric('aggressive-nsec-cache-nsec-wc-hits'), hits)
+        self.assertEqual(res.edns, 0)
+        self.assertEqual(len(res.options), 1)
+        self.assertEqual(res.options[0].otype, 15)
+        self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(29, b'Result synthesized from aggressive NSEC cache (RFC8198)'))
 
         # now we ask for a type that does not exist at the wildcard
         hits = self.getMetric('aggressive-nsec-cache-nsec-hits')
@@ -128,6 +172,10 @@ class AggressiveNSECCacheNSEC(AggressiveNSECCacheBase):
         self.assertMessageIsAuthenticated(res)
         self.assertEqual(nbQueries, self.getMetric('all-outqueries'))
         self.assertGreater(self.getMetric('aggressive-nsec-cache-nsec-hits'), hits)
+        self.assertEqual(res.edns, 0)
+        self.assertEqual(len(res.options), 1)
+        self.assertEqual(res.options[0].otype, 15)
+        self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(29, b'Result synthesized from aggressive NSEC cache (RFC8198)'))
 
         # we can also ask a different type, for a different name that is covered
         # by the NSEC and matches the wildcard (but the type does not exist)
@@ -140,10 +188,21 @@ class AggressiveNSECCacheNSEC(AggressiveNSECCacheBase):
         self.assertMessageIsAuthenticated(res)
         self.assertEqual(nbQueries, self.getMetric('all-outqueries'))
         self.assertGreater(self.getMetric('aggressive-nsec-cache-nsec-hits'), hits)
+        self.assertEqual(res.edns, 0)
+        self.assertEqual(len(res.options), 1)
+        self.assertEqual(res.options[0].otype, 15)
+        self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(29, b'Result synthesized from aggressive NSEC cache (RFC8198)'))
 
     def test_Bogus(self):
+        self.wipe()
+
+        # query a name in example to fill the aggressive negcache
+        res = self.sendQuery('example.', 'A')
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertAnswerEmpty(res)
+        self.assertEqual(1, self.getMetric('aggressive-nsec-cache-entries'))
+
         # query a name in a Bogus zone
-        entries = self.getMetric('aggressive-nsec-cache-entries')
         res = self.sendQuery('ted1.bogus.example.', 'A')
         self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
         self.assertAnswerEmpty(res)
@@ -167,8 +226,14 @@ class AggressiveNSECCacheNSEC(AggressiveNSECCacheBase):
         self.assertAnswerEmpty(res)
         self.assertAuthorityHasSOA(res)
         self.assertGreater(self.getMetric('all-outqueries'), nbQueries)
-        # we will accept a NSEC for root, which is secure..
-        self.assertEqual(entries + 1, self.getMetric('aggressive-nsec-cache-entries'))
+
+        # Check that we stil have one aggressive cache entry
+        self.assertEqual(1, self.getMetric('aggressive-nsec-cache-entries'))
+        print(res.options)
+        self.assertEqual(res.edns, 0)
+        self.assertEqual(len(res.options), 1)
+        self.assertEqual(res.options[0].otype, 15)
+        self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(9, b''))
 
 class AggressiveNSECCacheNSEC3(AggressiveNSECCacheBase):
     _confdir = 'AggressiveNSECCacheNSEC3'
@@ -219,8 +284,9 @@ class AggressiveNSECCacheNSEC3(AggressiveNSECCacheBase):
             raise AssertionError('%s failed (%d): %s' % (pdnsutilCmd, e.returncode, e.output))
 
     def testNXD(self):
+        self.wipe()
 
-        # first we query a non-existent name, to get the needed NSEC3s in our cache
+        # first we query a nonexistent name, to get the needed NSEC3s in our cache
         res = self.sendQuery('host2.secure.example.', 'TXT')
         self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
         self.assertAnswerEmpty(res)
@@ -236,8 +302,13 @@ class AggressiveNSECCacheNSEC3(AggressiveNSECCacheBase):
         self.assertAuthorityHasSOA(res)
         self.assertMessageIsAuthenticated(res)
         self.assertEqual(nbQueries, self.getMetric('all-outqueries'))
+        self.assertEqual(res.edns, 0)
+        self.assertEqual(len(res.options), 1)
+        self.assertEqual(res.options[0].otype, 15)
+        self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(29, b'Result synthesized from aggressive NSEC cache (RFC8198)'))
 
     def testWildcard(self):
+        self.wipe()
 
         # first let's get the SOA and wildcard NSEC in our cache by asking a name that matches the wildcard
         # but a type that does not exist
@@ -247,7 +318,7 @@ class AggressiveNSECCacheNSEC3(AggressiveNSECCacheBase):
         self.assertAuthorityHasSOA(res)
         self.assertMessageIsAuthenticated(res)
 
-        # we query a non-existent name, but for which a wildcard matches,
+        # we query a nonexistent name, but for which a wildcard matches,
         # to get the NSEC3 in our cache
         res = self.sendQuery('test5.wildcard.secure.example.', 'A')
         expected = dns.rrset.from_text('test5.wildcard.secure.example.', 0, dns.rdataclass.IN, 'A', '{prefix}.10'.format(prefix=self._PREFIX))
@@ -264,6 +335,10 @@ class AggressiveNSECCacheNSEC3(AggressiveNSECCacheBase):
         self.assertMatchingRRSIGInAnswer(res, expected)
         self.assertMessageIsAuthenticated(res)
         self.assertEqual(nbQueries, self.getMetric('all-outqueries'))
+        self.assertEqual(res.edns, 0)
+        self.assertEqual(len(res.options), 1)
+        self.assertEqual(res.options[0].otype, 15)
+        self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(29, b'Result synthesized from aggressive NSEC cache (RFC8198)'))
 
         # now we ask for a type that does not exist at the wildcard
         nbQueries = self.getMetric('all-outqueries')
@@ -273,6 +348,10 @@ class AggressiveNSECCacheNSEC3(AggressiveNSECCacheBase):
         self.assertAuthorityHasSOA(res)
         self.assertMessageIsAuthenticated(res)
         self.assertEqual(nbQueries, self.getMetric('all-outqueries'))
+        self.assertEqual(res.edns, 0)
+        self.assertEqual(len(res.options), 1)
+        self.assertEqual(res.options[0].otype, 15)
+        self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(29, b'Result synthesized from aggressive NSEC cache (RFC8198)'))
 
         # we can also ask a different type, for a different name that is covered
         # by the NSEC3s and matches the wildcard (but the type does not exist)
@@ -283,8 +362,14 @@ class AggressiveNSECCacheNSEC3(AggressiveNSECCacheBase):
         self.assertAuthorityHasSOA(res)
         self.assertMessageIsAuthenticated(res)
         self.assertEqual(nbQueries, self.getMetric('all-outqueries'))
+        self.assertEqual(res.edns, 0)
+        self.assertEqual(len(res.options), 1)
+        self.assertEqual(res.options[0].otype, 15)
+        self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(29, b'Result synthesized from aggressive NSEC cache (RFC8198)'))
 
     def test_OptOut(self):
+        self.wipe()
+
         # query a name in an opt-out zone
         res = self.sendQuery('ns2.optout.example.', 'A')
         self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
@@ -298,3 +383,5 @@ class AggressiveNSECCacheNSEC3(AggressiveNSECCacheBase):
         self.assertAnswerEmpty(res)
         self.assertAuthorityHasSOA(res)
         self.assertGreater(self.getMetric('all-outqueries'), nbQueries)
+        self.assertEqual(res.edns, 0)
+        self.assertEqual(len(res.options), 0)
diff --git a/regression-tests.recursor-dnssec/test_AnyBind.py b/regression-tests.recursor-dnssec/test_AnyBind.py
new file mode 100644 (file)
index 0000000..f1e9ab6
--- /dev/null
@@ -0,0 +1,41 @@
+import dns
+import os
+import socket
+from recursortests import RecursorTest
+
+class testAnyBind(RecursorTest):
+    _confdir = 'AnyBind'
+
+    _config_template = """dnssec=validate
+    local-address=0.0.0.0
+auth-zones=authzone.example=configs/%s/authzone.zone""" % _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(testAnyBind, cls).generateRecursorConfig(confdir)
+
+    @classmethod
+    def setUpSockets(cls):
+        print("Setting up UDP socket..")
+        cls._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        cls._sock.settimeout(2.0)
+        cls._sock.connect(("127.0.0.2", cls._recursorPort))
+
+    def testA(self):
+        """Test to see if we get a reply from 127.0.0.2 if rec is bound to ANY address"""
+        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)
+
index 4fb601cb55b2e19114e2e31b2a4ad548983817dd..abf6fdb23ac20f726ab1a04693af5514869bbadc 100644 (file)
@@ -5,11 +5,6 @@ from recursortests import RecursorTest
 
 class DNS64RecursorTest(RecursorTest):
 
-    _auth_zones = {
-        '8': {'threads': 1,
-              'zones': ['ROOT']}
-    }
-
     _confdir = 'DNS64'
     _config_template = """
     auth-zones=example.dns64=configs/%s/example.dns64.zone
@@ -19,6 +14,16 @@ class DNS64RecursorTest(RecursorTest):
     dns64-prefix=64:ff9b::/96
     """ % (_confdir, _confdir, _confdir)
 
+    _lua_dns_script_file = """
+      function nodata(dq)
+        if dq.qtype == pdns.AAAA and dq.qname:equal("formerr.example.dns64") then
+          dq.rcode = pdns.FORMERR
+          return true
+        end
+        return false
+       end
+    """
+
     @classmethod
     def generateRecursorConfig(cls, confdir):
         authzonepath = os.path.join(confdir, 'example.dns64.zone')
@@ -30,6 +35,7 @@ www 3600 IN TXT "does exist"
 aaaa 3600 IN AAAA 2001:db8::1
 cname 3600 IN CNAME cname2.example.dns64.
 cname2 3600 IN CNAME www.example.dns64.
+formerr 3600 IN A 192.0.2.43
 """.format(soa=cls._SOA))
 
         authzonepath = os.path.join(confdir, 'in-addr.arpa.zone')
@@ -123,6 +129,28 @@ cname2 3600 IN CNAME www.example.dns64.
             self.assertRcodeEqual(res, dns.rcode.NOERROR)
             self.assertRRsetInAnswer(res, expected)
 
+    # If the AAAA is handled by Lua code, we should not get a dns64 result
+    def testFormerr(self):
+        qname = 'formerr.example.dns64'
+
+        query = dns.message.make_query(qname, 'AAAA', want_dnssec=True)
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query)
+            self.assertRcodeEqual(res, dns.rcode.FORMERR)
+
+    # If the AAAA times out, we still should get a dns64 result
+    def testTimeout(self):
+        qname = '8.delay1.example.'
+        expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'AAAA', '64:ff9b::c000:264')
+
+        query = dns.message.make_query(qname, 'AAAA', want_dnssec=True)
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expected)
+
     # there is a TXT record, we should get it
     def testExistingTXT(self):
         qname = 'www.example.dns64.'
index d619bc6547ef8a0799f1ea1a583c6391f8c813f4..d4e5e9d72b435ad9d85a63808a0b30f9c639bb3c 100644 (file)
@@ -23,10 +23,10 @@ daemon=no
 trace=yes
 dont-query=
 local-address=127.0.0.1
-packetcache-ttl=0
-packetcache-servfail-ttl=0
+packetcache-ttl=15
+packetcache-servfail-ttl=15
 max-cache-ttl=600
-threads=1
+threads=2
 loglevel=9
 disable-syslog=yes
 log-common-errors=yes
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)
diff --git a/regression-tests.recursor-dnssec/test_LockedCache.py b/regression-tests.recursor-dnssec/test_LockedCache.py
new file mode 100644 (file)
index 0000000..9a58d97
--- /dev/null
@@ -0,0 +1,105 @@
+import dns
+import os
+import subprocess
+import time
+
+from recursortests import RecursorTest
+
+class testLockedCache(RecursorTest):
+    """
+    Test that a locked cached entry is *not* updated by the same additional encountered in a second query
+    """
+    _confdir = 'LockedCache'
+
+    _config_template = """
+    dnssec=validate
+    record-cache-locked-ttl-perc=100
+    """
+
+    def getCacheTTL(self):
+        rec_controlCmd = [os.environ['RECCONTROL'],
+                          '--config-dir=%s' % 'configs/' + self._confdir,
+                          'dump-cache',
+                          '-']
+        try:
+            ret = subprocess.check_output(rec_controlCmd, stderr=subprocess.STDOUT, text=True)
+            for i in ret.splitlines():
+                pieces = i.split(' ')
+                print(pieces)
+                if pieces[0] == 'mx1.secure.example.' and pieces[4] == 'A':
+                    return pieces[2]
+            raise AssertionError("Cache Line not found");
+
+        except subprocess.CalledProcessError as e:
+            print(e.output)
+            raise
+
+    def testMX(self):
+        expected1 = dns.rrset.from_text('secure.example.', 0, dns.rdataclass.IN, 'MX', '10 mx1.secure.example.', '20 mx2.secure.example.')
+        expected2 = dns.rrset.from_text('sub.secure.example.', 0, dns.rdataclass.IN, 'MX', '10 mx1.secure.example.', '20 mx2.secure.example.')
+        query1 = dns.message.make_query('secure.example', 'MX', want_dnssec=True)
+        query1.flags |= dns.flags.AD
+        query2 = dns.message.make_query('sub.secure.example', 'MX', want_dnssec=True)
+        query2.flags |= dns.flags.AD
+
+        res = self.sendUDPQuery(query1)
+        self.assertMessageIsAuthenticated(res)
+        self.assertRRsetInAnswer(res, expected1)
+        self.assertMatchingRRSIGInAnswer(res, expected1)
+        ttl1 = self.getCacheTTL()
+        time.sleep(2)
+        res = self.sendUDPQuery(query2)
+        self.assertMessageIsAuthenticated(res)
+        self.assertRRsetInAnswer(res, expected2)
+        self.assertMatchingRRSIGInAnswer(res, expected2)
+        ttl2 = self.getCacheTTL()
+        self.assertGreater(ttl1, ttl2)
+
+class testNotLockedCache(RecursorTest):
+    """
+    Test that a not locked cached entry *is* updated by the same additional encountered in a second query
+    """
+    _confdir = 'NotLockedCache'
+
+    _config_template = """
+    dnssec=validate
+    """
+
+    def getCacheTTL(self):
+        rec_controlCmd = [os.environ['RECCONTROL'],
+                          '--config-dir=%s' % 'configs/' + self._confdir,
+                          'dump-cache',
+                          '-']
+        try:
+            ret = subprocess.check_output(rec_controlCmd, stderr=subprocess.STDOUT, text=True)
+            for i in ret.splitlines():
+                pieces = i.split(' ')
+                print(pieces)
+                if pieces[0] == 'mx1.secure.example.' and pieces[4] == 'A':
+                    return int(pieces[2])
+            return -1
+
+        except subprocess.CalledProcessError as e:
+            print(e.output)
+            raise
+
+    def testMX(self):
+        expected1 = dns.rrset.from_text('secure.example.', 0, dns.rdataclass.IN, 'MX', '10 mx1.secure.example.', '20 mx2.secure.example.')
+        expected2 = dns.rrset.from_text('sub.secure.example.', 0, dns.rdataclass.IN, 'MX', '10 mx1.secure.example.', '20 mx2.secure.example.')
+        query1 = dns.message.make_query('secure.example', 'MX', want_dnssec=True)
+        query1.flags |= dns.flags.AD
+        query2 = dns.message.make_query('sub.secure.example', 'MX', want_dnssec=True)
+        query2.flags |= dns.flags.AD
+
+        res = self.sendUDPQuery(query1)
+        self.assertMessageIsAuthenticated(res)
+        self.assertRRsetInAnswer(res, expected1)
+        self.assertMatchingRRSIGInAnswer(res, expected1)
+        ttl1 = self.getCacheTTL()
+        time.sleep(2)
+        res = self.sendUDPQuery(query2)
+        self.assertMessageIsAuthenticated(res)
+        self.assertRRsetInAnswer(res, expected2)
+        self.assertMatchingRRSIGInAnswer(res, expected2)
+        ttl2 = self.getCacheTTL()
+        self.assertAlmostEqual(ttl1, ttl2, delta=1)
index 9e04d0a9a9c387c8a3280f1e81beb2164368050e..dad74051f5d687abc1ccdbefa5e2be0fc2bab903 100644 (file)
@@ -99,6 +99,13 @@ class GettagRecursorTest(RecursorTest):
         return true
       end
 
+      local tm = os.time()
+      if dq.queryTime.tv_sec < tm - 1 or dq.queryTime.tv_sec > tm + 1 then
+        pdnslog("queryTime is wrong")
+        dq.rcode = pdns.REFUSED
+        return true
+      end
+
       if dq.qtype == pdns.A then
         dq:addAnswer(pdns.A, '192.0.2.1')
       elseif dq.qtype == pdns.AAAA then
@@ -233,6 +240,10 @@ class UDPHooksResponder(DatagramProtocol):
             answer = dns.rrset.from_text('postresolve.luahooks.example.', 3600, dns.rdataclass.IN, 'A', '192.0.2.1')
             response.answer.append(answer)
 
+        elif request.question[0].name == dns.name.from_text('preresolve.luahooks.example.'):
+            answer = dns.rrset.from_text('preresolve.luahooks.example.', 3600, dns.rdataclass.IN, 'A', '192.0.2.2')
+            response.answer.append(answer)
+
         self.transport.write(response.to_wire(), address)
 
 class LuaHooksRecursorTest(RecursorTest):
@@ -256,6 +267,29 @@ quiet=no
       return true
     end
 
+    function preresolve(dq)
+      -- test both preresolve hooking and udpQueryResponse
+      if dq.qtype == pdns.A and dq.qname == newDN("preresolve.luahooks.example.") then
+        dq.followupFunction = "udpQueryResponse"
+        dq.udpCallback = "gotUdpResponse"
+        dq.udpQueryDest = newCA("%s.23:53")
+        -- synthesize query, responder should answer with regular answer
+        dq.udpQuery = "\\254\\255\\001\\000\\000\\001\\000\\000\\000\\000\\000\\000\\npreresolve\\008luahooks\\007example\\000\\000\\001\\000\\001"
+        return true
+      end
+      return false
+    end
+
+    function gotUdpResponse(dq)
+      dq.followupFunction = ""
+      if string.len(dq.udpAnswer) == 61 then
+        dq:addAnswer(pdns.A, "192.0.2.2")
+        return true;
+      end
+      dq:addAnswer(pdns.A, "0.0.0.0")
+      return true
+    end
+
     function nodata(dq)
       if dq.qtype == pdns.AAAA and dq.qname == newDN("nodata.luahooks.example.") then
         dq:addAnswer(pdns.AAAA, "2001:DB8::1")
@@ -300,7 +334,7 @@ quiet=no
       return false
     end
 
-    """ % (os.environ['PREFIX'], os.environ['PREFIX'])
+    """ % (os.environ['PREFIX'], os.environ['PREFIX'], os.environ['PREFIX'])
 
     @classmethod
     def startResponders(cls):
@@ -377,6 +411,15 @@ quiet=no
             self.assertRRsetInAnswer(res, expected)
             self.assertEqual(res.answer[0].ttl, 1)
 
+    def testPreResolve(self):
+        expected = dns.rrset.from_text('preresolve.luahooks.example.', 1, dns.rdataclass.IN, 'A', '192.0.2.2')
+        query = dns.message.make_query('preresolve.luahooks.example.', 'A', 'IN')
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expected)
+
     def testIPFilterHeader(self):
         query = dns.message.make_query('ipfilter.luahooks.example.', 'A', 'IN')
         query.flags |= dns.flags.AD
@@ -536,6 +579,7 @@ class PDNSRandomTest(RecursorTest):
     function preresolve (dq)
       dq.rcode = pdns.NOERROR
       dq:addAnswer(pdns.TXT, pdnsrandom())
+      dq.variable = true
       return true
     end
     """
@@ -962,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 6ff0ea346ff6b5ac1977fbd51a6d56fdfa082c47..622ad37ec6c2e469caa0e8040f65b7ccfa6fdf8b 100644 (file)
@@ -70,6 +70,7 @@ f 3600 IN CNAME f            ; CNAME loop: dirty trick to get a ServFail in an a
         self.assertTrue(foundMisses)
 
     def testNotify(self):
+        self.waitForTCPSocket("127.0.0.1", self._wsPort)
         # first query
         qname = 'a.example.'
         query = dns.message.make_query(qname, 'A', want_dnssec=True)
@@ -102,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 35b6039558be149ce85e8508ddd9a958fd42cdb1..ba8c102ddde4b97e407817d65d22cdcc976817c4 100644 (file)
@@ -21,6 +21,7 @@ class PacketCacheRecursorTest(RecursorTest):
     _apiKey = 'secretapikey'
     _config_template = """
     packetcache-ttl=10
+    packetcache-negative-ttl=8
     packetcache-servfail-ttl=5
     auth-zones=example=configs/%s/example.zone
     webserver=yes
@@ -46,6 +47,7 @@ f 3600 IN CNAME f            ; CNAME loop: dirty trick to get a ServFail in an a
         super(PacketCacheRecursorTest, cls).generateRecursorConfig(confdir)
 
     def checkPacketCacheMetrics(self, expectedHits, expectedMisses):
+        self.waitForTCPSocket("127.0.0.1", self._wsPort)
         headers = {'x-api-key': self._apiKey}
         url = 'http://127.0.0.1:' + str(self._wsPort) + '/api/v1/servers/localhost/statistics'
         r = requests.get(url, headers=headers, timeout=self._wsTimeout)
@@ -67,6 +69,7 @@ f 3600 IN CNAME f            ; CNAME loop: dirty trick to get a ServFail in an a
         self.assertTrue(foundMisses)
 
     def testPacketCache(self):
+        self.waitForTCPSocket("127.0.0.1", self._wsPort)
         # first query, no cookie
         qname = 'a.example.'
         query = dns.message.make_query(qname, 'A', want_dnssec=True)
@@ -140,13 +143,13 @@ f 3600 IN CNAME f            ; CNAME loop: dirty trick to get a ServFail in an a
         self.assertRRsetInAnswer(res, expected)
         self.checkPacketCacheMetrics(6, 4)
 
-        # NXDomain should get default packetcache TTL (10)
+        # NXDomain should get negative packetcache TTL (8)
         query = dns.message.make_query('nxdomain.example.', 'A', want_dnssec=True)
         res = self.sendUDPQuery(query)
         self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
         self.checkPacketCacheMetrics(6, 5)
 
-        # NoData should get default packetcache TTL (10)
+        # NoData should get negative packetcache TTL (8)
         query = dns.message.make_query('a.example.', 'AAAA', want_dnssec=True)
         res = self.sendUDPQuery(query)
         self.assertRcodeEqual(res, dns.rcode.NOERROR)
@@ -165,8 +168,8 @@ f 3600 IN CNAME f            ; CNAME loop: dirty trick to get a ServFail in an a
         try:
             ret = subprocess.check_output(rec_controlCmd, stderr=subprocess.STDOUT)
             self.assertTrue((b"a.example. 10 A  ; tag 0 udp\n" in ret) or (b"a.example. 9 A  ; tag 0 udp\n" in ret))
-            self.assertTrue((b"nxdomain.example. 10 A  ; tag 0 udp\n" in ret) or (b"nxdomain.example. 9 A  ; tag 0 udp\n" in ret))
-            self.assertTrue((b"a.example. 10 AAAA  ; tag 0 udp\n" in ret) or (b"a.example. 9 AAAA  ; tag 0 udp\n" in ret))
+            self.assertTrue((b"nxdomain.example. 8 A  ; tag 0 udp\n" in ret) or (b"nxdomain.example. 7 A  ; tag 0 udp\n" in ret))
+            self.assertTrue((b"a.example. 8 AAAA  ; tag 0 udp\n" in ret) or (b"a.example. 7 AAAA  ; tag 0 udp\n" in ret))
             self.assertTrue((b"f.example. 5 A  ; tag 0 udp\n" in ret) or (b"f.example. 4 A  ; tag 0 udp\n" in ret))
 
         except subprocess.CalledProcessError as e:
index 92f5155102a439f8800e9f3512a17a15c80552ec..953a9ce20ead2a57aa4516deb8597f2b72ee935a 100644 (file)
@@ -6,6 +6,7 @@ import struct
 import sys
 import threading
 import time
+import clientsubnetoption
 
 # Python2/3 compatibility hacks
 try:
@@ -93,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())
@@ -117,9 +118,14 @@ class TestRecursorProtobuf(RecursorTest):
           if oldmsg is not None:
             self.assertEqual(msg, oldmsg)
 
-        print(msg)
+        #print(msg)
         return msg
 
+    def emptyProtoBufQueue(self):
+        for param in protobufServersParameters:
+            while not param.queue.empty():
+                param.queue.get(False)
+
     def checkNoRemainingMessage(self):
         for param in protobufServersParameters:
           self.assertTrue(param.queue.empty())
@@ -154,7 +160,7 @@ class TestRecursorProtobuf(RecursorTest):
             self.assertEqual(len(msg.originalRequestorSubnet), 4)
             self.assertEqual(socket.inet_ntop(socket.AF_INET, msg.originalRequestorSubnet), '127.0.0.1')
 
-    def checkOutgoingProtobufBase(self, msg, protocol, query, initiator, length=None):
+    def checkOutgoingProtobufBase(self, msg, protocol, query, initiator, length=None, expectedECS=None):
         self.assertTrue(msg)
         self.assertTrue(msg.HasField('timeSec'))
         self.assertTrue(msg.HasField('socketFamily'))
@@ -171,6 +177,11 @@ class TestRecursorProtobuf(RecursorTest):
         else:
           # compare inBytes with length of query/response
           self.assertEqual(msg.inBytes, len(query.to_wire()))
+        if expectedECS is not None:
+            self.assertTrue(msg.HasField('originalRequestorSubnet'))
+            # v4 only for now
+            self.assertEqual(len(msg.originalRequestorSubnet), 4)
+            self.assertEqual(socket.inet_ntop(socket.AF_INET, msg.originalRequestorSubnet), expectedECS)
 
     def checkProtobufQuery(self, msg, protocol, query, qclass, qtype, qname, initiator='127.0.0.1', to='127.0.0.1'):
         self.assertEqual(msg.type, dnsmessage_pb2.PBDNSMessage.DNSQueryType)
@@ -221,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'))
@@ -242,9 +253,9 @@ class TestRecursorProtobuf(RecursorTest):
             for s in m.value.stringVal :
               self.assertTrue(s in metas[m.key]['stringVal'])
 
-    def checkProtobufOutgoingQuery(self, msg, protocol, query, qclass, qtype, qname, initiator='127.0.0.1', length=None):
+    def checkProtobufOutgoingQuery(self, msg, protocol, query, qclass, qtype, qname, initiator='127.0.0.1', length=None, expectedECS=None):
         self.assertEqual(msg.type, dnsmessage_pb2.PBDNSMessage.DNSOutgoingQueryType)
-        self.checkOutgoingProtobufBase(msg, protocol, query, initiator, length=length)
+        self.checkOutgoingProtobufBase(msg, protocol, query, initiator, length=length, expectedECS=expectedECS)
         self.assertTrue(msg.HasField('to'))
         self.assertTrue(msg.HasField('question'))
         self.assertTrue(msg.question.HasField('qClass'))
@@ -266,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')))
@@ -278,9 +289,7 @@ class TestRecursorProtobuf(RecursorTest):
         super(TestRecursorProtobuf, self).setUp()
         # Make sure the queue is empty, in case
         # a previous test failed
-        for param in protobufServersParameters:
-            while not param.queue.empty():
-                param.queue.get(False)
+        self.emptyProtoBufQueue()
         # wait long enough to be sure that the housekeeping has
         # prime the root NS
         time.sleep(1)
@@ -368,6 +377,82 @@ auth-zones=example=configs/%s/example.zone""" % _confdir
         self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
         self.checkNoRemainingMessage()
 
+class ProtobufProxyMappingTest(TestRecursorProtobuf):
+    """
+    This test makes sure that we correctly export queries and response over protobuf with a proxyMapping
+    """
+
+    _confdir = 'ProtobufProxyMappingTest'
+    _config_template = """
+    auth-zones=example=configs/%s/example.zone
+    allow-from=3.4.5.0/24
+    """ % _confdir
+
+    _lua_config_file = """
+    addProxyMapping("127.0.0.1/24", "3.4.5.6:99")
+    protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
+    """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
+
+    def testA(self):
+        name = 'a.example.'
+        expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
+        query = dns.message.make_query(name, 'A', want_dnssec=True)
+        query.flags |= dns.flags.CD
+        res = self.sendUDPQuery(query)
+
+        self.assertRRsetInAnswer(res, expected)
+
+        # check the protobuf messages corresponding to the UDP query and answer
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
+        # then the response
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '127.0.0.1')
+        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.42')
+        self.checkNoRemainingMessage()
+
+class ProtobufProxyMappingLogMappedTest(TestRecursorProtobuf):
+    """
+    This test makes sure that we correctly export queries and response over protobuf.
+    """
+
+    _confdir = 'ProtobufProxyMappingLogMappedTest'
+    _config_template = """
+    auth-zones=example=configs/%s/example.zone
+    allow-from=3.4.5.0/0"
+    """ % _confdir
+
+    _lua_config_file = """
+    addProxyMapping("127.0.0.1/24", "3.4.5.6:99")
+    protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logMappedFrom = true })
+    """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
+
+    def testA(self):
+        name = 'a.example.'
+        expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
+        query = dns.message.make_query(name, 'A', want_dnssec=True)
+        query.flags |= dns.flags.CD
+        res = self.sendUDPQuery(query)
+
+        self.assertRRsetInAnswer(res, expected)
+
+        # check the protobuf messages corresponding to the UDP query and answer
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name, '3.4.5.6')
+        # then the response
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '3.4.5.6')
+        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.42')
+        self.checkNoRemainingMessage()
+
 class ProtobufProxyTest(TestRecursorProtobuf):
     """
     This test makes sure that we correctly export addresses over protobuf when the proxy protocol is used.
@@ -402,6 +487,85 @@ allow-from=127.0.0.1,6.6.6.6
         self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
         self.checkNoRemainingMessage()
 
+class ProtobufProxyWithProxyByTableTest(TestRecursorProtobuf):
+    """
+    This test makes sure that we correctly export addresses over protobuf when the proxy protocol and a proxy table mapping is used
+    """
+
+    _confdir = 'ProtobufProxyWithProxyByTable'
+    _config_template = """
+auth-zones=example=configs/%s/example.zone
+proxy-protocol-from=127.0.0.1/32
+allow-from=3.4.5.6
+""" % _confdir
+
+    _lua_config_file = """
+    addProxyMapping("6.6.6.6/24", "3.4.5.6:99")
+    protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
+    """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
+
+    def testA(self):
+        name = 'a.example.'
+        expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
+        query = dns.message.make_query(name, 'A', want_dnssec=True)
+        query.flags |= dns.flags.CD
+        res = self.sendUDPQueryWithProxyProtocol(query, False, '6.6.6.6', '7.7.7.7', 666, 777)
+
+        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, '6.6.6.6', '7.7.7.7')
+        # then the response
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '6.6.6.6')
+        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.42')
+        self.checkNoRemainingMessage()
+
+class ProtobufProxyWithProxyByTableLogMappedTest(TestRecursorProtobuf):
+    """
+    This test makes sure that we correctly export addresses over protobuf when the proxy protocol and a proxy table mapping is used
+    """
+
+    _confdir = 'ProtobufProxyWithProxyByTableLogMapped'
+    _config_template = """
+auth-zones=example=configs/%s/example.zone
+proxy-protocol-from=127.0.0.1/32
+allow-from=3.4.5.6
+""" % _confdir
+
+    _lua_config_file = """
+    addProxyMapping("6.6.6.6/24", "3.4.5.6:99")
+    protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logMappedFrom = true })
+    """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
+
+    def testA(self):
+        name = 'a.example.'
+        expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
+        query = dns.message.make_query(name, 'A', want_dnssec=True)
+        query.flags |= dns.flags.CD
+        res = self.sendUDPQueryWithProxyProtocol(query, False, '6.6.6.6', '7.7.7.7', 666, 777)
+
+        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, '3.4.5.6', '7.7.7.7')
+        # then the response
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '3.4.5.6')
+        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.42')
+        self.checkNoRemainingMessage()
+
+
 class OutgoingProtobufDefaultTest(TestRecursorProtobuf):
     """
     This test makes sure that we correctly export outgoing queries over protobuf.
@@ -414,17 +578,25 @@ class OutgoingProtobufDefaultTest(TestRecursorProtobuf):
     # Switch off QName Minimization, it generates much more protobuf messages
     # (or make the test much more smart!)
     qname-minimization=no
+    max-cache-ttl=600
+    loglevel=9
 """
     _lua_config_file = """
     outgoingProtobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
     """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
 
     def testA(self):
+        # There is a race in priming (having the . DNSKEY in cache in particular) and this code.
+        # So make sure we have the . DNSKEY in cache
+        query = dns.message.make_query('.', 'A', want_dnssec=True)
+        query.flags |= dns.flags.RD
+        res = self.sendUDPQuery(query)
+        time.sleep(1)
+        self.emptyProtoBufQueue()
+
         name = 'host1.secure.example.'
         expected = list()
 
-        # the root DNSKEY has been learned with priming the root NS already
-        # ('.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 201),
         for qname, qtype, proto, responseSize in [
                 ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 248),
                 ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 221),
@@ -460,6 +632,111 @@ class OutgoingProtobufDefaultTest(TestRecursorProtobuf):
 
         self.checkNoRemainingMessage()
 
+class OutgoingProtobufWithECSMappingTest(TestRecursorProtobuf):
+    """
+    This test makes sure that we correctly export outgoing queries over protobuf.
+    It must be improved and setup env so we can check for incoming responses, but makes sure for now
+    that the recursor at least connects to the protobuf server.
+    """
+
+    _confdir = 'OutgoingProtobuffWithECSMapping'
+    _config_template = """
+    # Switch off QName Minimization, it generates much more protobuf messages
+    # (or make the test much more smart!)
+    qname-minimization=no
+    edns-subnet-allow-list=example
+    allow-from=1.2.3.4/32
+    # this is to not let . queries interfere
+    max-cache-ttl=600
+    loglevel=9
+"""
+    _lua_config_file = """
+    outgoingProtobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
+    addProxyMapping("127.0.0.0/8", "1.2.3.4", { "host1.secure.example." })
+    """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
+
+    def testA(self):
+        # There is a race in priming (having the . DNSKEY in cache in particular) and this code.
+        # So make sure we have the . DNSKEY in cache
+        query = dns.message.make_query('.', 'A', want_dnssec=True)
+        query.flags |= dns.flags.RD
+        res = self.sendUDPQuery(query)
+        time.sleep(1)
+        self.emptyProtoBufQueue()
+
+        name = 'host1.secure.example.'
+        expected = list()
+
+        for qname, qtype, proto, responseSize, ecs in [
+                ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 248, "1.2.3.0"),
+                ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 221, "1.2.3.0"),
+                ('example.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 219, "1.2.3.0"),
+                ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 175, "1.2.3.0"),
+                ('secure.example.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 233, "1.2.3.0"),
+        ]:
+            if not qname:
+                expected.append((None, None, None, None, None, None, None))
+                continue
+            ecso = clientsubnetoption.ClientSubnetOption('9.10.11.12', 24)
+            query = dns.message.make_query(qname, qtype, use_edns=True, want_dnssec=True, options=[ecso], payload=512)
+            resp = dns.message.make_response(query)
+            expected.append((
+                qname, qtype, query, resp, proto, responseSize, ecs
+            ))
+
+        query = dns.message.make_query(name, 'A', want_dnssec=True)
+        query.flags |= dns.flags.RD
+        res = self.sendUDPQuery(query)
+
+        for qname, qtype, qry, ans, proto, responseSize, ecs in expected:
+            if not qname:
+                self.getFirstProtobufMessage()
+                self.getFirstProtobufMessage()
+                continue
+
+            msg = self.getFirstProtobufMessage()
+            self.checkProtobufOutgoingQuery(msg, proto, qry, dns.rdataclass.IN, qtype, qname, "127.0.0.1", None, ecs)
+            # Check the answer
+            msg = self.getFirstProtobufMessage()
+            self.checkProtobufIncomingResponse(msg, proto, ans, length=responseSize)
+
+        self.checkNoRemainingMessage()
+
+        # this query should use the unmapped ECS
+        name = 'mx1.secure.example.'
+        expected = list()
+
+        for qname, qtype, proto, responseSize, ecs in [
+                ('mx1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 173, "127.0.0.1"),
+        ]:
+            if not qname:
+                expected.append((None, None, None, None, None, None, None))
+                continue
+            ecso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 32)
+            query = dns.message.make_query(qname, qtype, use_edns=True, want_dnssec=True, options=[ecso], payload=512)
+            resp = dns.message.make_response(query)
+            expected.append((
+                qname, qtype, query, resp, proto, responseSize, ecs
+            ))
+
+        query = dns.message.make_query(name, 'A', want_dnssec=True)
+        query.flags |= dns.flags.RD
+        res = self.sendUDPQuery(query)
+
+        for qname, qtype, qry, ans, proto, responseSize, ecs in expected:
+            if not qname:
+                self.getFirstProtobufMessage()
+                self.getFirstProtobufMessage()
+                continue
+
+            msg = self.getFirstProtobufMessage()
+            self.checkProtobufOutgoingQuery(msg, proto, qry, dns.rdataclass.IN, qtype, qname, "127.0.0.1", None, ecs)
+            # Check the answer
+            msg = self.getFirstProtobufMessage()
+            self.checkProtobufIncomingResponse(msg, proto, ans, length=responseSize)
+
+        self.checkNoRemainingMessage()
+
 class OutgoingProtobufNoQueriesTest(TestRecursorProtobuf):
     """
     This test makes sure that we correctly export incoming responses but not outgoing queries over protobuf.
@@ -471,12 +748,23 @@ class OutgoingProtobufNoQueriesTest(TestRecursorProtobuf):
     _config_template = """
     # Switch off QName Minimization, it generates much more protobuf messages
     # (or make the test much more smart!)
-    qname-minimization=no"""
+    qname-minimization=no
+    max-cache-ttl=600
+    loglevel=9
+"""
     _lua_config_file = """
     outgoingProtobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=false, logResponses=true })
     """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
 
     def testA(self):
+        # There is a race in priming (having the . DNSKEY in cache in particular) and this code.
+        # So make sure we have the . DNSKEY in cache
+        query = dns.message.make_query('.', 'A', want_dnssec=True)
+        query.flags |= dns.flags.RD
+        res = self.sendUDPQuery(query)
+        time.sleep(1)
+        self.emptyProtoBufQueue()
+
         name = 'host1.secure.example.'
         expected = list()
         # the root DNSKEY has been learned with priming the root NS already
@@ -669,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_ProxyByTable.py b/regression-tests.recursor-dnssec/test_ProxyByTable.py
new file mode 100644 (file)
index 0000000..b804001
--- /dev/null
@@ -0,0 +1,44 @@
+import dns
+import os
+from recursortests import RecursorTest
+
+class testProxyByTable(RecursorTest):
+    """
+    This test makes sure that we correctly use the proxy-mapped address during the ACL check
+    """
+    _confdir = 'ProxyByTable'
+
+    _config_template = """dnssec=validate
+    auth-zones=authzone.example=configs/%s/authzone.zone
+    allow-from=3.4.5.0/24
+    """ % _confdir
+
+    _lua_config_file = """
+    addProxyMapping("127.0.0.0/24", "3.4.5.6:99")
+    """
+
+    @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(testProxyByTable, cls).generateRecursorConfig(confdir)
+
+
+    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
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query)
+
+            self.assertMessageIsAuthenticated(res)
+            self.assertRRsetInAnswer(res, expected)
+            self.assertMatchingRRSIGInAnswer(res, expected)
+
+
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 d940d45c6a07a4f35dfe1bf11d37788c536670ce..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
@@ -366,6 +399,9 @@ webserver-port=%d
 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
 
@@ -403,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.')
@@ -484,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.')
@@ -492,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.')
@@ -507,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.')
 
@@ -518,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
@@ -554,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.')
@@ -562,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):
@@ -678,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):
diff --git a/regression-tests.recursor-dnssec/test_RPZIncomplete.py b/regression-tests.recursor-dnssec/test_RPZIncomplete.py
new file mode 100644 (file)
index 0000000..b5a0e8a
--- /dev/null
@@ -0,0 +1,241 @@
+import dns
+import json
+import os
+import requests
+import socket
+import struct
+import sys
+import threading
+import time
+
+from recursortests import RecursorTest
+
+class BadRPZServer(object):
+
+    def __init__(self, port):
+        self._currentSerial = 0
+        self._targetSerial = 1
+        self._serverPort = port
+        listener = threading.Thread(name='RPZ Listener', target=self._listener, args=[])
+        listener.setDaemon(True)
+        listener.start()
+
+    def getCurrentSerial(self):
+        return self._currentSerial
+
+    def moveToSerial(self, newSerial):
+        if newSerial == self._currentSerial:
+            return False
+
+        #if newSerial != self._currentSerial + 1:
+        #    raise AssertionError("Asking the RPZ server to serve serial %d, already serving %d" % (newSerial, self._currentSerial))
+        self._targetSerial = newSerial
+        return True
+
+    def _getAnswer(self, message):
+
+        response = dns.message.make_response(message)
+        records = []
+
+        if message.question[0].rdtype == dns.rdatatype.AXFR:
+            if self._currentSerial != 0:
+                print('Received an AXFR query but IXFR expected because the current serial is %d' % (self._currentSerial))
+                return (None, self._currentSerial)
+
+            newSerial = self._targetSerial
+            records = [
+                dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
+                dns.rrset.from_text('a.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1'),
+                dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial)
+                ]
+
+        elif message.question[0].rdtype == dns.rdatatype.IXFR:
+            oldSerial = message.authority[0][0].serial
+
+            newSerial = self._targetSerial
+            if newSerial == 2:
+                records = [
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % oldSerial),
+                    # no deletion
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
+                    dns.rrset.from_text('b.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1'),
+                    ]
+            elif newSerial == 3:
+                records = [
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
+                    dns.rrset.from_text('a.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1'),
+                    ]
+
+        response.answer = records
+        return (newSerial, response)
+
+    def _connectionHandler(self, conn):
+        data = None
+        while True:
+            data = conn.recv(2)
+            if not data:
+                break
+            (datalen,) = struct.unpack("!H", data)
+            data = conn.recv(datalen)
+            if not data:
+                break
+
+            message = dns.message.from_wire(data)
+            if len(message.question) != 1:
+                print('Invalid RPZ query, qdcount is %d' % (len(message.question)), file=sys.stderr)
+                break
+            if not message.question[0].rdtype in [dns.rdatatype.AXFR, dns.rdatatype.IXFR]:
+                print('Invalid RPZ query, qtype is %d' % (message.question.rdtype), file=sys.stderr)
+                break
+            (serial, answer) = self._getAnswer(message)
+            if not answer:
+                print('Unable to get a response for %s %d' % (message.question[0].name, message.question[0].rdtype), file=sys.stderr)
+                break
+
+            wire = answer.to_wire()
+            conn.send(struct.pack("!H", len(wire)))
+            conn.send(wire)
+            self._currentSerial = serial
+            break
+
+        conn.close()
+
+    def _listener(self):
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+        try:
+            sock.bind(("127.0.0.1", self._serverPort))
+        except socket.error as e:
+            print("Error binding in the RPZ listener: %s" % str(e))
+            sys.exit(1)
+
+        sock.listen(100)
+        while True:
+            try:
+                (conn, _) = sock.accept()
+                thread = threading.Thread(name='RPZ Connection Handler',
+                                      target=self._connectionHandler,
+                                      args=[conn])
+                thread.setDaemon(True)
+                thread.start()
+
+            except socket.error as e:
+                print('Error in RPZ socket: %s' % str(e))
+                sock.close()
+
+class RPZIncompleteRecursorTest(RecursorTest):
+    _wsPort = 8042
+    _wsTimeout = 2
+    _wsPassword = 'secretpassword'
+    _apiKey = 'secretapikey'
+    _confdir = 'RPZIncomplete'
+    _auth_zones = {
+        '8': {'threads': 1,
+              'zones': ['ROOT']},
+        '10': {'threads': 1,
+               'zones': ['example']},
+    }
+
+    _config_template = """
+auth-zones=example=configs/%s/example.zone
+webserver=yes
+webserver-port=%d
+webserver-address=127.0.0.1
+webserver-password=%s
+api-key=%s
+log-rpz-changes=yes
+""" % (_confdir, _wsPort, _wsPassword, _apiKey)
+
+    def checkRPZStats(self, serial, recordsCount, fullXFRCount, totalXFRCount, failedXFRCount):
+        headers = {'x-api-key': self._apiKey}
+        url = 'http://127.0.0.1:' + str(self._wsPort) + '/api/v1/servers/localhost/rpzstatistics'
+        r = requests.get(url, headers=headers, timeout=self._wsTimeout)
+        self.assertTrue(r)
+        self.assertEqual(r.status_code, 200)
+        self.assertTrue(r.json())
+        content = r.json()
+        self.assertIn('zone.rpz.', content)
+        zone = content['zone.rpz.']
+        for key in ['last_update', 'records', 'serial', 'transfers_failed', 'transfers_full', 'transfers_success']:
+            self.assertIn(key, zone)
+
+        self.assertEqual(zone['serial'], serial)
+        self.assertEqual(zone['records'], recordsCount)
+        self.assertEqual(zone['transfers_full'], fullXFRCount)
+        self.assertEqual(zone['transfers_success'], totalXFRCount)
+        self.assertEqual(zone['transfers_failed'], failedXFRCount)
+
+badrpzServerPort = 4251
+badrpzServer = BadRPZServer(badrpzServerPort)
+
+class RPZXFRIncompleteRecursorTest(RPZIncompleteRecursorTest):
+    """
+    This test makes sure that we correctly detect incomplete RPZ zones via AXFR then IXFR
+    """
+
+    global badrpzServerPort
+    _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 })
+    """ % (badrpzServerPort)
+    _confdir = 'RPZXFRIncomplete'
+    _wsPort = 8042
+    _wsTimeout = 2
+    _wsPassword = 'secretpassword'
+    _apiKey = 'secretapikey'
+    _config_template = """
+auth-zones=example=configs/%s/example.zone
+webserver=yes
+webserver-port=%d
+webserver-address=127.0.0.1
+webserver-password=%s
+api-key=%s
+""" % (_confdir, _wsPort, _wsPassword, _apiKey)
+
+    @classmethod
+    def generateRecursorConfig(cls, confdir):
+        authzonepath = os.path.join(confdir, 'example.zone')
+        with open(authzonepath, 'w') as authzone:
+            authzone.write("""$ORIGIN example.
+@ 3600 IN SOA {soa}
+a 3600 IN A 192.0.2.42
+b 3600 IN A 192.0.2.42
+c 3600 IN A 192.0.2.42
+d 3600 IN A 192.0.2.42
+e 3600 IN A 192.0.2.42
+""".format(soa=cls._SOA))
+        super(RPZIncompleteRecursorTest, cls).generateRecursorConfig(confdir)
+
+    def waitUntilCorrectSerialIsLoaded(self, serial, timeout=5):
+        global badrpzServer
+
+        badrpzServer.moveToSerial(serial)
+
+        attempts = 0
+        while attempts < timeout:
+            currentSerial = badrpzServer.getCurrentSerial()
+            if currentSerial > serial:
+                raise AssertionError("Expected serial %d, got %d" % (serial, currentSerial))
+            if currentSerial == serial:
+                return
+
+            attempts = attempts + 1
+            time.sleep(1)
+
+        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):
+        self.waitForTCPSocket("127.0.0.1", self._wsPort)
+        # First zone
+        self.waitUntilCorrectSerialIsLoaded(1)
+        self.checkRPZStats(1, 1, 1, 1, 1) # failure count includes a port 9999 attempt
+
+        # second zone, should fail, incomplete IXFR
+        self.waitUntilCorrectSerialIsLoaded(2)
+        self.checkRPZStats(1, 1, 1, 1, 3)
+
+        # third zone, should fail, incomplete AXFR
+        self.waitUntilCorrectSerialIsLoaded(3)
+        self.checkRPZStats(1, 1, 1, 1, 5)
+
index bb6aeea5ded14087bb5cfbd61afca38e5b926eeb..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
@@ -26,7 +27,7 @@ except NameError:
     pass
 
 
-def checkDnstapBase(testinstance, dnstap, protocol, initiator, responder):
+def checkDnstapBase(testinstance, dnstap, protocol, initiator, responder, response_port=53):
     testinstance.assertTrue(dnstap)
     testinstance.assertTrue(dnstap.HasField('identity'))
     #testinstance.assertEqual(dnstap.identity, b'a.server')
@@ -48,7 +49,7 @@ def checkDnstapBase(testinstance, dnstap, protocol, initiator, responder):
     testinstance.assertTrue(dnstap.message.HasField('response_address'))
     testinstance.assertEqual(socket.inet_ntop(socket.AF_INET, dnstap.message.response_address), responder)
     testinstance.assertTrue(dnstap.message.HasField('response_port'))
-    testinstance.assertEqual(dnstap.message.response_port, 53)
+    testinstance.assertEqual(dnstap.message.response_port, response_port)
 
 
 def checkDnstapQuery(testinstance, dnstap, protocol, initiator, responder):
@@ -66,6 +67,28 @@ def checkDnstapQuery(testinstance, dnstap, protocol, initiator, responder):
     #wire_message = dns.message.from_wire(dnstap.message.query_message)
     #testinstance.assertEqual(wire_message, query)
 
+def checkDnstapNOD(testinstance, dnstap, protocol, initiator, responder, response_port, query_zone):
+    testinstance.assertEqual(dnstap.message.type, dnstap_pb2.Message.CLIENT_QUERY)
+    checkDnstapBase(testinstance, dnstap, protocol, initiator, responder, response_port)
+
+    testinstance.assertTrue(dnstap.message.HasField('query_time_sec'))
+    testinstance.assertTrue(dnstap.message.HasField('query_time_nsec'))
+
+    testinstance.assertTrue(dnstap.message.HasField('query_zone'))
+    testinstance.assertEqual(dns.name.from_wire(dnstap.message.query_zone, 0)[0].to_text(), query_zone)
+
+def checkDnstapUDR(testinstance, dnstap, protocol, initiator, responder, response_port, query_zone):
+    testinstance.assertEqual(dnstap.message.type, dnstap_pb2.Message.RESOLVER_RESPONSE)
+    checkDnstapBase(testinstance, dnstap, protocol, initiator, responder, response_port)
+
+    testinstance.assertTrue(dnstap.message.HasField('query_time_sec'))
+    testinstance.assertTrue(dnstap.message.HasField('query_time_nsec'))
+
+    testinstance.assertTrue(dnstap.message.HasField('query_zone'))
+    testinstance.assertEqual(dns.name.from_wire(dnstap.message.query_zone, 0)[0].to_text(), query_zone)
+
+    testinstance.assertTrue(dnstap.message.HasField('response_message'))
+    wire_message = dns.message.from_wire(dnstap.message.response_message)
 
 def checkDnstapExtra(testinstance, dnstap, expected):
     testinstance.assertTrue(dnstap.HasField('extra'))
@@ -164,13 +187,13 @@ 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)
             except exception as e:
                 sys.stderr.write("Unexpected socket error %s\n" % str(e))
-                sys.exit(1)                
+                sys.exit(1)
         conn.close()
 
     @classmethod
@@ -194,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
@@ -282,7 +305,7 @@ dnstapFrameStreamServer({"%s"})
         query.flags |= dns.flags.RD
         res = self.sendUDPQuery(query)
         self.assertNotEqual(res, None)
-        
+
         # check the dnstap message corresponding to the UDP query
         dnstap = self.getFirstDnstap()
 
@@ -291,11 +314,6 @@ dnstapFrameStreamServer({"%s"})
         checkDnstapNoExtra(self, dnstap)
 
 class DNSTapLogNoQueriesTest(TestRecursorDNSTap):
-    """
-    This test makes sure that we correctly export outgoing queries over DNSTap.
-    It must be improved and setup env so we can check for incoming responses, but makes sure for now
-    that the recursor at least connects to the DNSTap server.
-    """
 
     _confdir = 'DNSTapLogNoQueries'
     _config_template = """
@@ -313,3 +331,141 @@ dnstapFrameStreamServer({"%s"}, {logQueries=false})
 
         # We don't expect anything
         self.assertTrue(DNSTapServerParameters.queue.empty())
+
+class DNSTapLogNODTest(TestRecursorDNSTap):
+    """
+    This test makes sure that we correctly export outgoing queries over DNSTap.
+    It must be improved and setup env so we can check for incoming responses, but makes sure for now
+    that the recursor at least connects to the DNSTap server.
+    """
+
+    _confdir = 'DNSTapLogNODQueries'
+    _config_template = """
+new-domain-tracking=yes
+new-domain-history-dir=configs/%s/nod
+unique-response-tracking=yes
+unique-response-history-dir=configs/%s/udr
+auth-zones=example=configs/%s/example.zone""" % (_confdir, _confdir, _confdir)
+    _lua_config_file = """
+dnstapNODFrameStreamServer({"%s"})
+    """ % (DNSTapServerParameters.path)
+
+    @classmethod
+    def generateRecursorConfig(cls, confdir):
+        for directory in ["nod", "udr"]:
+            path = os.path.join('configs', cls._confdir, directory)
+            cls.createConfigDir(path)
+        super(DNSTapLogNODTest, cls).generateRecursorConfig(confdir)
+
+    def getFirstDnstap(self):
+        try:
+            data = DNSTapServerParameters.queue.get(True, timeout=2.0)
+        except:
+            data = False
+        self.assertTrue(data)
+        dnstap = dnstap_pb2.Dnstap()
+        dnstap.ParseFromString(data)
+        return dnstap
+
+    def testA(self):
+        name = 'www.example.org.'
+        query = dns.message.make_query(name, 'A', want_dnssec=True)
+        query.flags |= dns.flags.RD
+        res = self.sendUDPQuery(query)
+        self.assertNotEqual(res, None)
+
+        # check the dnstap message corresponding to the UDP query
+        dnstap = self.getFirstDnstap()
+
+        checkDnstapNOD(self, dnstap, dnstap_pb2.UDP, '127.0.0.1', '127.0.0.1', 5300, name)
+        # We don't expect a response
+        checkDnstapNoExtra(self, dnstap)
+
+class DNSTapLogUDRTest(TestRecursorDNSTap):
+
+    _confdir = 'DNSTapLogUDRResponses'
+    _config_template = """
+new-domain-tracking=yes
+new-domain-history-dir=configs/%s/nod
+unique-response-tracking=yes
+unique-response-history-dir=configs/%s/udr
+auth-zones=example=configs/%s/example.zone""" % (_confdir, _confdir, _confdir)
+    _lua_config_file = """
+dnstapNODFrameStreamServer({"%s"}, {logNODs=false, logUDRs=true})
+    """ % (DNSTapServerParameters.path)
+
+    @classmethod
+    def generateRecursorConfig(cls, confdir):
+        for directory in ["nod", "udr"]:
+            path = os.path.join('configs', cls._confdir, directory)
+            cls.createConfigDir(path)
+        super(DNSTapLogUDRTest, cls).generateRecursorConfig(confdir)
+
+    def getFirstDnstap(self):
+        try:
+            data = DNSTapServerParameters.queue.get(True, timeout=2.0)
+        except:
+            data = False
+        self.assertTrue(data)
+        dnstap = dnstap_pb2.Dnstap()
+        dnstap.ParseFromString(data)
+        return dnstap
+
+    def testA(self):
+        name = 'types.example.'
+        query = dns.message.make_query(name, 'A', want_dnssec=True)
+        query.flags |= dns.flags.RD
+        res = self.sendUDPQuery(query)
+        self.assertNotEqual(res, None)
+
+        # check the dnstap message corresponding to the UDP query
+        dnstap = self.getFirstDnstap()
+
+        checkDnstapUDR(self, dnstap, dnstap_pb2.UDP, '127.0.0.1', '127.0.0.1', 5300, name)
+        # We don't expect a rpasesponse
+        checkDnstapNoExtra(self, dnstap)
+
+class DNSTapLogNODUDRTest(TestRecursorDNSTap):
+
+    _confdir = 'DNSTapLogNODUDRs'
+    _config_template = """
+new-domain-tracking=yes
+new-domain-history-dir=configs/%s/nod
+unique-response-tracking=yes
+unique-response-history-dir=configs/%s/udr
+auth-zones=example=configs/%s/example.zone""" % (_confdir, _confdir, _confdir)
+    _lua_config_file = """
+dnstapNODFrameStreamServer({"%s"}, {logNODs=true, logUDRs=true})
+    """ % (DNSTapServerParameters.path)
+
+    @classmethod
+    def generateRecursorConfig(cls, confdir):
+        for directory in ["nod", "udr"]:
+            path = os.path.join('configs', cls._confdir, directory)
+            cls.createConfigDir(path)
+        super(DNSTapLogNODUDRTest, cls).generateRecursorConfig(confdir)
+
+    def getFirstDnstap(self):
+        try:
+            data = DNSTapServerParameters.queue.get(True, timeout=2.0)
+        except:
+            data = False
+        self.assertTrue(data)
+        dnstap = dnstap_pb2.Dnstap()
+        dnstap.ParseFromString(data)
+        return dnstap
+
+    def testA(self):
+        name = 'types.example.'
+        query = dns.message.make_query(name, 'A', want_dnssec=True)
+        query.flags |= dns.flags.RD
+        res = self.sendUDPQuery(query)
+        self.assertNotEqual(res, None)
+
+        dnstap = self.getFirstDnstap()
+        checkDnstapUDR(self, dnstap, dnstap_pb2.UDP, '127.0.0.1', '127.0.0.1', 5300, name)
+
+        dnstap = self.getFirstDnstap()
+        checkDnstapNOD(self, dnstap, dnstap_pb2.UDP, '127.0.0.1', '127.0.0.1', 5300, name)
+
+        checkDnstapNoExtra(self, dnstap)
index 62f6537e2c559f3831894feb83a360e95f29cb09..cb1133641b3e8114101e74931a407c23fd6a929c 100644 (file)
@@ -2,6 +2,8 @@ import dns
 import requests
 import socket
 import time
+import extendederrors
+
 from recursortests import RecursorTest
 
 class RootNXTrustRecursorTest(RecursorTest):
@@ -46,6 +48,8 @@ webserver-port=%d
 webserver-address=127.0.0.1
 webserver-password=%s
 api-key=%s
+devonly-regression-test-mode
+extended-resolution-errors
 """ % (_wsPort, _wsPassword, _apiKey)
 
     def testRootNXTrust(self):
@@ -71,7 +75,7 @@ api-key=%s
 
         # then query nx2.example.
         before = after
-        query = dns.message.make_query('www2.nx-example.', 'A')
+        query = dns.message.make_query('www2.nx-example.', 'A', use_edns=True)
         res = self.sendUDPQuery(query)
 
         self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
@@ -79,6 +83,8 @@ api-key=%s
 
         after = self.getOutgoingQueriesCount()
         self.assertEqual(after, before + 1)
+        self.assertEqual(res.edns, 0)
+        self.assertEqual(len(res.options), 0)
 
 class testRootNXTrustEnabled(RootNXTrustRecursorTest):
     _confdir = 'RootNXTrustEnabled'
@@ -94,6 +100,8 @@ webserver-port=%d
 webserver-address=127.0.0.1
 webserver-password=%s
 api-key=%s
+devonly-regression-test-mode
+extended-resolution-errors
 """ % (_wsPort, _wsPassword, _apiKey)
 
     def testRootNXTrust(self):
@@ -119,7 +127,7 @@ api-key=%s
 
         # then query nx2.example.
         before = after
-        query = dns.message.make_query('www2.nx-example.', 'A')
+        query = dns.message.make_query('www2.nx-example.', 'A', use_edns=True)
         res = self.sendUDPQuery(query)
 
         self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
@@ -127,3 +135,7 @@ api-key=%s
 
         after = self.getOutgoingQueriesCount()
         self.assertEqual(after, before)
+        self.assertEqual(res.edns, 0)
+        self.assertEqual(len(res.options), 1)
+        self.assertEqual(res.options[0].otype, 15)
+        self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(29, b'Result synthesized by root-nx-trust'))
index 5b7b1b82ff554188fa867437a7440a7f4e2ad92c..6d54618ae641f7aa1465d00bf2c13b025072a56e 100644 (file)
@@ -22,10 +22,10 @@ daemon=no
 trace=yes
 dont-query=
 local-address=127.0.0.1
-packetcache-ttl=0
-packetcache-servfail-ttl=0
+packetcache-ttl=15
+packetcache-servfail-ttl=15
 max-cache-ttl=600
-threads=1
+threads=2
 loglevel=9
 disable-syslog=yes
 log-common-errors=yes
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 0994a47ec9c0e4709a6e970aa16ddf93f6a867d3..fcc915ab53d1abcb8dcc6bfd723ecc325075b787 100644 (file)
@@ -12,6 +12,7 @@ class testSimpleDoT(RecursorTest):
     _config_template = """
 dnssec=validate
 dot-to-auth-names=powerdns.com
+devonly-regression-test-mode
     """
 
     _roothints = None
@@ -29,9 +30,9 @@ dot-to-auth-names=powerdns.com
         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)
index 95e2fbca04033713058343eace766212d809c412..d839d43084ecec58e43d1ff642611ced797c72ed 100644 (file)
@@ -12,6 +12,7 @@ class testSimpleForwardOverDoT(RecursorTest):
     _config_template = """
 dnssec=validate
 forward-zones-recurse=.=9.9.9.9:853
+devonly-regression-test-mode
     """
 
     @classmethod
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_ZTC.py b/regression-tests.recursor-dnssec/test_ZTC.py
new file mode 100644 (file)
index 0000000..a801a25
--- /dev/null
@@ -0,0 +1,31 @@
+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
+"""
+
+    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 8d454a0989c3eafa347aa855e9527e40fb15b349..c40ee802f566e447b6b39b1068cf244a14781d03 100644 (file)
@@ -1,4 +1,4 @@
-0      net.    IN      DS      86400   35886 8 2 7862b27f5f516ebe19680444d4ce5e762981931842c465f00236401d8bd973ee
-2      .       IN      OPT     32768   
+0      net.    86400   IN      DS      35886 8 2 7862b27f5f516ebe19680444d4ce5e762981931842c465f00236401d8bd973ee
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='net.', qtype=DS
index 3b95c8b98e3c4687ee9eeb14237f80a981614c89..445bc1250864e67ab0eafbf80885cbbb965d5504 100644 (file)
@@ -1,5 +1,5 @@
-0      net.    IN      DS      86400   35886 8 2 7862b27f5f516ebe19680444d4ce5e762981931842c465f00236401d8bd973ee
-0      net.    IN      RRSIG   86400   DS 13 1 86400 [expiry] [inception] [keytag] . ...
-2      .       IN      OPT     32768   
+0      net.    86400   IN      DS      35886 8 2 7862b27f5f516ebe19680444d4ce5e762981931842c465f00236401d8bd973ee
+0      net.    86400   IN      RRSIG   DS 13 1 86400 [expiry] [inception] [keytag] . ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='net.', qtype=DS
index e8cd4fe499af1e40b4545675883184fcc033666b..0118e37872b738dc646671d0daae10a4e1fe61d1 100644 (file)
@@ -1,6 +1,6 @@
-1      net.    IN      NS      172800  a.gtld-servers.net.
-2      .       IN      OPT     32768   
-2      a.gtld-servers.net.     IN      A       172800  192.5.6.30
-2      a.gtld-servers.net.     IN      AAAA    172800  2001:503:a83e::2:30
+1      net.    172800  IN      NS      a.gtld-servers.net.
+2      .       32768   IN      OPT     
+2      a.gtld-servers.net.     172800  IN      A       192.5.6.30
+2      a.gtld-servers.net.     172800  IN      AAAA    2001:503:a83e::2:30
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='net.', qtype=NS
index 34b452e7499fe6931a9ff6d6c5fbdd824609a8f4..0a1e68a10f3a4d1bc9cfcc6410a51d2c4149d620 100644 (file)
@@ -1,8 +1,8 @@
-1      net.    IN      DS      86400   35886 8 2 7862b27f5f516ebe19680444d4ce5e762981931842c465f00236401d8bd973ee
-1      net.    IN      NS      172800  a.gtld-servers.net.
-1      net.    IN      RRSIG   86400   DS 13 1 86400 [expiry] [inception] [keytag] . ...
-2      .       IN      OPT     32768   
-2      a.gtld-servers.net.     IN      A       172800  192.5.6.30
-2      a.gtld-servers.net.     IN      AAAA    172800  2001:503:a83e::2:30
+1      net.    172800  IN      NS      a.gtld-servers.net.
+1      net.    86400   IN      DS      35886 8 2 7862b27f5f516ebe19680444d4ce5e762981931842c465f00236401d8bd973ee
+1      net.    86400   IN      RRSIG   DS 13 1 86400 [expiry] [inception] [keytag] . ...
+2      .       32768   IN      OPT     
+2      a.gtld-servers.net.     172800  IN      A       192.5.6.30
+2      a.gtld-servers.net.     172800  IN      AAAA    2001:503:a83e::2:30
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='net.', qtype=NS
index 7f0ccacb4b821ca6a5c210fa0b0812c0e127d7c6..7dd425415e45943359ac6bec256e954b02a95607 100644 (file)
@@ -1,5 +1,5 @@
-0      .       IN      NS      518400  a.root-servers.net.
-2      a.root-servers.net.     IN      A       518400  198.41.0.4
-2      a.root-servers.net.     IN      AAAA    518400  2001:503:ba3e::2:30
+0      .       518400  IN      NS      a.root-servers.net.
+2      a.root-servers.net.     518400  IN      A       198.41.0.4
+2      a.root-servers.net.     518400  IN      AAAA    2001:503:ba3e::2:30
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='.', qtype=NS
index c6b505054f7dc6b2a44ae5d2a348c96486bd2949..f362c5a21f8eae6e3fd9cd420059a5ee08eb7a61 100644 (file)
@@ -1,5 +1,5 @@
-1      uk.     IN      NS      172800  nsa.nic.uk.
-2      nsa.nic.uk.     IN      A       172800  156.154.100.3
-2      nsa.nic.uk.     IN      AAAA    172800  2001:502:ad09::3
+1      uk.     172800  IN      NS      nsa.nic.uk.
+2      nsa.nic.uk.     172800  IN      A       156.154.100.3
+2      nsa.nic.uk.     172800  IN      AAAA    2001:502:ad09::3
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='co.uk.', qtype=DS
index f714a769c8f374ea14e4a7113a5ded831a1e86c1..6fed85b3b1ebb5cdec88976b142eeb595807697a 100644 (file)
@@ -1,6 +1,6 @@
-1      uk.     IN      DS      86400   43876 8 2 a107ed2ac1bd14d924173bc7e827a1153582072394f9272ba37e2353bc659603
-1      uk.     IN      NS      172800  nsa.nic.uk.
-2      nsa.nic.uk.     IN      A       172800  156.154.100.3
-2      nsa.nic.uk.     IN      AAAA    172800  2001:502:ad09::3
+1      uk.     172800  IN      NS      nsa.nic.uk.
+1      uk.     86400   IN      DS      43876 8 2 a107ed2ac1bd14d924173bc7e827a1153582072394f9272ba37e2353bc659603
+2      nsa.nic.uk.     172800  IN      A       156.154.100.3
+2      nsa.nic.uk.     172800  IN      AAAA    2001:502:ad09::3
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='co.uk.', qtype=DS
index c6b505054f7dc6b2a44ae5d2a348c96486bd2949..f362c5a21f8eae6e3fd9cd420059a5ee08eb7a61 100644 (file)
@@ -1,5 +1,5 @@
-1      uk.     IN      NS      172800  nsa.nic.uk.
-2      nsa.nic.uk.     IN      A       172800  156.154.100.3
-2      nsa.nic.uk.     IN      AAAA    172800  2001:502:ad09::3
+1      uk.     172800  IN      NS      nsa.nic.uk.
+2      nsa.nic.uk.     172800  IN      A       156.154.100.3
+2      nsa.nic.uk.     172800  IN      AAAA    2001:502:ad09::3
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='co.uk.', qtype=DS
index f714a769c8f374ea14e4a7113a5ded831a1e86c1..6fed85b3b1ebb5cdec88976b142eeb595807697a 100644 (file)
@@ -1,6 +1,6 @@
-1      uk.     IN      DS      86400   43876 8 2 a107ed2ac1bd14d924173bc7e827a1153582072394f9272ba37e2353bc659603
-1      uk.     IN      NS      172800  nsa.nic.uk.
-2      nsa.nic.uk.     IN      A       172800  156.154.100.3
-2      nsa.nic.uk.     IN      AAAA    172800  2001:502:ad09::3
+1      uk.     172800  IN      NS      nsa.nic.uk.
+1      uk.     86400   IN      DS      43876 8 2 a107ed2ac1bd14d924173bc7e827a1153582072394f9272ba37e2353bc659603
+2      nsa.nic.uk.     172800  IN      A       156.154.100.3
+2      nsa.nic.uk.     172800  IN      AAAA    2001:502:ad09::3
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='co.uk.', qtype=DS
index 8bff8257849d4154451ec15f2acd93d67cb9a9b3..907c5c0986c87e476a8b0e7eab5e63bfee2ce2e6 100644 (file)
@@ -1,5 +1,5 @@
-1      uk.     IN      NS      172800  nsa.nic.uk.
-2      nsa.nic.uk.     IN      A       172800  156.154.100.3
-2      nsa.nic.uk.     IN      AAAA    172800  2001:502:ad09::3
+1      uk.     172800  IN      NS      nsa.nic.uk.
+2      nsa.nic.uk.     172800  IN      A       156.154.100.3
+2      nsa.nic.uk.     172800  IN      AAAA    2001:502:ad09::3
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='barney.advsys.co.uk.', qtype=DS
index 07271b403738dfe78b56cae4c1277532fba5fc3d..febe97a6a3959c7379323688a94d7a051431db1c 100644 (file)
@@ -1,6 +1,6 @@
-1      uk.     IN      DS      86400   43876 8 2 a107ed2ac1bd14d924173bc7e827a1153582072394f9272ba37e2353bc659603
-1      uk.     IN      NS      172800  nsa.nic.uk.
-2      nsa.nic.uk.     IN      A       172800  156.154.100.3
-2      nsa.nic.uk.     IN      AAAA    172800  2001:502:ad09::3
+1      uk.     172800  IN      NS      nsa.nic.uk.
+1      uk.     86400   IN      DS      43876 8 2 a107ed2ac1bd14d924173bc7e827a1153582072394f9272ba37e2353bc659603
+2      nsa.nic.uk.     172800  IN      A       156.154.100.3
+2      nsa.nic.uk.     172800  IN      AAAA    2001:502:ad09::3
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='barney.advsys.co.uk.', qtype=DS
index 270b1028e9cce570113fabecf038e2b37e056ffe..63f7999854c5146161e7ad8b8a9c9556f93d8b92 100644 (file)
@@ -1,3 +1,3 @@
-1      .       IN      SOA     86400   a.root-servers.net. nstld.verisign-grs.com. 2016021600 1800 900 604800 86400
+1      .       86400   IN      SOA     a.root-servers.net. nstld.verisign-grs.com. 2016021600 1800 900 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='com.', qtype=NS
index 87f4a76710279ab9a846a7130f0e5e41d0da356c..862272b24dc882487bae4f105cd92df0c24ee417 100644 (file)
@@ -1,6 +1,6 @@
-1      net.    IN      NS      172800  a.gtld-servers.net.
-2      .       IN      OPT     32768   
-2      a.gtld-servers.net.     IN      A       172800  192.5.6.30
-2      a.gtld-servers.net.     IN      AAAA    172800  2001:503:a83e::2:30
+1      net.    172800  IN      NS      a.gtld-servers.net.
+2      .       32768   IN      OPT     
+2      a.gtld-servers.net.     172800  IN      A       192.5.6.30
+2      a.gtld-servers.net.     172800  IN      AAAA    2001:503:a83e::2:30
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='some-host.domain.net.', qtype=A
index abd2224cebd06d9bf740f627e1bb6da106bbd262..32c9d2702980bbd9f451f699907935dea409d56d 100644 (file)
@@ -1,8 +1,8 @@
-1      net.    IN      DS      86400   35886 8 2 7862b27f5f516ebe19680444d4ce5e762981931842c465f00236401d8bd973ee
-1      net.    IN      NS      172800  a.gtld-servers.net.
-1      net.    IN      RRSIG   86400   DS 13 1 86400 [expiry] [inception] [keytag] . ...
-2      .       IN      OPT     32768   
-2      a.gtld-servers.net.     IN      A       172800  192.5.6.30
-2      a.gtld-servers.net.     IN      AAAA    172800  2001:503:a83e::2:30
+1      net.    172800  IN      NS      a.gtld-servers.net.
+1      net.    86400   IN      DS      35886 8 2 7862b27f5f516ebe19680444d4ce5e762981931842c465f00236401d8bd973ee
+1      net.    86400   IN      RRSIG   DS 13 1 86400 [expiry] [inception] [keytag] . ...
+2      .       32768   IN      OPT     
+2      a.gtld-servers.net.     172800  IN      A       192.5.6.30
+2      a.gtld-servers.net.     172800  IN      AAAA    2001:503:a83e::2:30
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='some-host.domain.net.', qtype=A
index 0289965f21a055d7f729c2049171b8c9385b7ddb..3f29b7520efed749f88b0bdf236735b59ea85277 100644 (file)
@@ -1,3 +1,4 @@
+/*.log
 /*.xml
 /*.tar
 /K*
index 034bd9b6886f7826fcc26e21b7122b29d288b5e5..c5ca8f158b1b2e3af1379b3a53e99fe0fe72d008 100644 (file)
@@ -7,7 +7,7 @@ We need very recent versions of:
 
  * validns (http://www.validns.net/)
  * ldns-verify-zone (part of ldns)
- * jdnssec-verifyzone (http://www.verisignlabs.com/dnssec-tools/)
+ * jdnssec-verifyzone (https://github.com/dblacka/jdnssec-tools)
  * named-checkzone (part of BIND9)
  * unbound-host (part of unbound)
  * drill (part of ldns)
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 876edaeb38326c2030034edce27904ec27467d6c..2018929324075cb97c80557617d88374161b1409 100644 (file)
@@ -76,22 +76,42 @@ 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.    IN      A       30      $geoipregionip
-2      .       IN      OPT     0       AAgACAABIBgBAQEB
+0      www.geo.example.com.    30      IN      A       $geoipregionip
+2      .       0       IN      OPT     AAgACAABIBgBAQEB
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.geo.example.com.', qtype=A
 EOF
                 cat > $testsdir/region-cname-resolution/expected_result <<EOF
-0      indirect.geo.example.com.       IN      CNAME   30      $geoipregion.elsewhere.example.com.
-2      .       IN      OPT     0       AAgACAABIBgBAQEB
+0      indirect.geo.example.com.       30      IN      CNAME   $geoipregion.elsewhere.example.com.
+2      .       0       IN      OPT     AAgACAABIBgBAQEB
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='indirect.geo.example.com.', qtype=A
 EOF
                 cat > $testsdir/text-interpolation/expected_result <<EOF
-0      continent.geo.example.com.      IN      TXT     30      "Your continent is na"
-2      .       IN      OPT     0       AAgACAABIBgBAgME
+0      continent.geo.example.com.      30      IN      TXT     "Your continent is na"
+2      .       0       IN      OPT     AAgACAABIBgBAgME
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='continent.geo.example.com.', qtype=TXT
 EOF
index 8a31c1c242d26bbce7505eb2717574bca90775f1..5b2ed60fc252afad205864b4b1497d056a6b0611 100644 (file)
@@ -28,6 +28,24 @@ any-to-tcp=no
 zone-cache-refresh-interval=0
 __EOF__
 
+               # setup catalog zone
+
+               if ! $PDNSUTIL --config-dir=. --config-name=gmysql list-all-zones | grep '^.$' # detect root tests
+               then
+                       for zone in $(grep 'zone ' named.conf  | cut -f2 -d\")
+                       do
+                               $PDNSUTIL --config-dir=. --config-name=gmysql set-kind $zone master
+                               $PDNSUTIL --config-dir=. --config-name=gmysql set-catalog $zone catalog.invalid
+                       done
+
+                       $PDNSUTIL --config-dir=. --config-name=gmysql load-zone catalog.invalid zones/catalog.invalid
+                       $PDNSUTIL --config-dir=. --config-name=gmysql set-kind catalog.invalid producer
+
+                       $PDNSUTIL --config-dir=. --config-name=gmysql set-option test.com producer coo other-catalog.invalid
+                       $PDNSUTIL --config-dir=. --config-name=gmysql set-option test.com producer unique 123
+                       $PDNSUTIL --config-dir=. --config-name=gmysql set-option tsig.com producer group pdns-group-x pdns-group-y
+               fi
+
                gsql_master gmysql dyndns
                ;;
 
index e4dd2e64173a485f8e2c1839e8dfea7702381369..1d9ee2bdbd22ad82204e9425c216f2edfdc8493e 100644 (file)
@@ -27,42 +27,79 @@ __EOF__
                echo "gmysql-dnssec" >> pdns-gmysql2.conf
        fi
 
+       zones=0
        for zone in $(grep 'zone ' named.conf  | cut -f2 -d\" | perl -e 'print reverse <STDIN>')
        do
-               mysql --user="$GMYSQL2USER" --password="$GMYSQL2PASSWD" --host="$GMYSQL2HOST" \
-                       "$GMYSQL2DB" -e "INSERT INTO domains (name, type, master) VALUES('$zone','SLAVE','127.0.0.1:$port')"
+               zones=$((zones+1))
+               if [ "$zone" = "example.com" ]; then
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 create-secondary-zone $zone 127.0.0.1:$port
+               fi
+               if [ "$zone" = "test.com" ]; then
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 create-secondary-zone $zone 127.0.0.1:$port
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 set-catalog $zone other-catalog.invalid
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 set-option $zone consumer coo catalog.invalid
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 set-option $zone consumer unique 42
+               fi
                if [ "$zone" = "tsig.com" ]; then
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 create-secondary-zone $zone 127.0.0.2:$port
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 set-catalog $zone catalog.invalid
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 set-option $zone consumer unique $($SAXFR 127.0.0.1 $port catalog.invalid | grep $zone | grep PTR | cut -d'.' -f1)
                        $PDNSUTIL --config-dir=. --config-name=gmysql2 import-tsig-key test $ALGORITHM $KEY
-                       $PDNSUTIL --config-dir=. --config-name=gmysql2 activate-tsig-key tsig.com test slave
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 activate-tsig-key tsig.com test secondary
                fi
                if [ "$zone" = "stest.com" ]; then
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 create-secondary-zone $zone 127.0.0.1:$port
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 set-catalog $zone other-catalog.invalid
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 set-option $zone consumer coo catalog.invalid
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 set-option $zone consumer unique $($SAXFR 127.0.0.1 $port catalog.invalid | grep $zone | grep PTR | cut -d'.' -f1)
                        if [[ $skipreasons != *nolua* ]]; then
                                $PDNSUTIL --config-dir=. --config-name=gmysql2 set-meta stest.com AXFR-SOURCE 127.0.0.2
                        fi
                fi
+               if [ "$zone" = "wtest.com" ]; then
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 create-secondary-zone $zone 127.0.0.1:$port
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 set-catalog $zone catalog.invalid
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 set-option $zone consumer unique 42
+               fi
+               if [ "$zone" = "." ]; then
+                       $PDNSUTIL --config-dir=. --config-name=gmysql2 create-secondary-zone $zone 127.0.0.1:$port
+               fi
        done
 
+       # setup catalog zone
+       if [ $zones -ne 1 ] # detect root tests
+       then
+               zones=$((zones+1))
+               $PDNSUTIL --config-dir=. --config-name=gmysql2 create-secondary-zone catalog.invalid 127.0.0.1:$port
+               $PDNSUTIL --config-dir=. --config-name=gmysql2 set-kind catalog.invalid consumer
+
+               $PDNSUTIL --config-dir=. --config-name=gmysql2 create-secondary-zone remove.invalid 127.0.0.1:$port
+               $PDNSUTIL --config-dir=. --config-name=gmysql2 set-catalog remove.invalid catalog.invalid
+       fi
+
        port=$((port+100))
 
        $RUNWRAPPER $PDNS2 --daemon=no --local-port=$port --config-dir=. \
                --config-name=gmysql2 --socket-dir=./ --no-shuffle \
-               --slave --retrieval-threads=4 \
-               --slave-cycle-interval=300 --dname-processing &
+               --secondary --xfr-cycle-interval=15 --dname-processing &
+
+       sleep 1
+       $PDNSCONTROL --config-name=gmysql2 --socket-dir=. --no-config retrieve catalog.invalid
 
-       echo 'waiting for zones to be slaved'
+       echo 'waiting for zones to be fetched'
        loopcount=0
        while [ $loopcount -lt 30 ]
        do
                sleep 5
-               todo=$(mysql --user="$GMYSQL2USER" --password="$GMYSQL2PASSWD" --host="$GMYSQL2HOST" \
-                       "$GMYSQL2DB" -ss -e 'SELECT COUNT(id) FROM domains WHERE last_check IS NULL')
-               if [ $todo = 0 ]
+               present=$(mysql --user="$GMYSQL2USER" --password="$GMYSQL2PASSWD" --host="$GMYSQL2HOST" \
+                       "$GMYSQL2DB" -ss -e "SELECT COUNT(DISTINCT(name)) FROM records WHERE type='SOA'")
+               if [ $present -eq $zones ]
                then
                        break
                fi
                let loopcount=loopcount+1
        done
-       if [ $todo -ne 0 ]
+       if [ $present -ne $zones ]
        then
                echo "AXFR FAILED" >> failed_tests
                exit
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 3efedc66a79e6aa0664cca4082cd00609105e44b..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,9 +41,11 @@ 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 id,name,master,last_check,notified_serial,type from domains where type='MASTER'
-godbc-info-all-slaves-query=select id,name,master,last_check from domains where type='SLAVE'
-godbc-info-zone-query=select id,name,master,last_check,notified_serial,type,account from domains where name=?
+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=?
 godbc-insert-comment-query=INSERT INTO comments (domain_id, name, type, modified_at, account, comment) VALUES (?, ?, ?, ?, ?, ?)
 godbc-insert-empty-non-terminal-order-query=insert into records (type,domain_id,disabled,name,ordername,auth,ttl,prio,content) values (null,?,0,?,?,?,null,null,null)
 godbc-insert-record-query=insert into records (content,ttl,prio,type,domain_id,disabled,name,ordername,auth) values (?,?,?,?,?,?,?,?,?)
@@ -60,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 77a5dbe3b31fb5ab73f6ffd8b083a17a8093384c..c1bf9346b205b1cd6517b965c24947e4816eedda 100644 (file)
@@ -47,10 +47,27 @@ __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
 
+        # setup catalog zone
+
+        if ! $PDNSUTIL --config-dir=. --config-name=lmdb list-all-zones | grep '^.$' # detect root tests
+        then
+            for zone in $(grep 'zone ' named.conf  | cut -f2 -d\" | grep -v '^nztest.com$')
+            do
+                $PDNSUTIL --config-dir=. --config-name=lmdb set-kind $zone master
+                $PDNSUTIL --config-dir=. --config-name=lmdb set-catalog $zone catalog.invalid
+            done
+
+            $PDNSUTIL --config-dir=. --config-name=lmdb load-zone catalog.invalid zones/catalog.invalid
+            $PDNSUTIL --config-dir=. --config-name=lmdb set-kind catalog.invalid producer
+
+            $PDNSUTIL --config-dir=. --config-name=lmdb set-options-json test.com '{"producer":{"coo":"other-catalog.invalid","unique":"123"}}'
+            $PDNSUTIL --config-dir=. --config-name=lmdb set-options-json tsig.com '{"producer":{"group":["pdns-group-x","pdns-group-y"]}}'
+        fi
+
         $RUNWRAPPER $PDNS --daemon=no --local-address=$address --local-port=$port --config-dir=. \
             --config-name=lmdb --socket-dir=./ --no-shuffle \
             --dnsupdate=no \
index 992a8bcf4a83deecef0d2c3c79ad202b430ef9da..697ee7aa8f5f789f7441dc3af887c434e3a0a4cf 100644 (file)
@@ -9,26 +9,61 @@ __EOF__
        zones=0
        for zone in $(grep 'zone ' named.conf  | cut -f2 -d\" | grep -v '^nztest.com$' | perl -e 'print reverse <STDIN>')
        do
-               let zones=zones+1
-               $PDNSUTIL --config-dir=. --config-name=lmdb2 create-slave-zone $zone 127.0.0.1:$port
+               zones=$((zones+1))
+               if [ "$zone" = "example.com" ]; then
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 create-secondary-zone $zone 127.0.0.1:$port
+               fi
+               if [ "$zone" = "test.com" ]; then
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 create-secondary-zone $zone 127.0.0.1:$port
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 set-catalog $zone other-catalog.invalid
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 set-options-json $zone '{"consumer":{"coo":"catalog.invalid","unique":"42"}}'
+               fi
                if [ "$zone" = "tsig.com" ]; then
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 create-secondary-zone $zone 127.0.0.2:$port
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 set-catalog $zone catalog.invalid
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 set-options-json $zone "{\"consumer\":{\"unique\":\"$($SAXFR 127.0.0.1 $port catalog.invalid | grep $zone | grep PTR | cut -d'.' -f1)\"}}"
                        $PDNSUTIL --config-dir=. --config-name=lmdb2 import-tsig-key test $ALGORITHM $KEY
-                       $PDNSUTIL --config-dir=. --config-name=lmdb2 activate-tsig-key tsig.com test slave
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 activate-tsig-key tsig.com test secondary
                fi
                if [ "$zone" = "stest.com" ]; then
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 create-secondary-zone $zone 127.0.0.1:$port
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 set-catalog $zone other-catalog.invalid
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 set-options-json $zone "{\"consumer\":{\"coo\":\"catalog.invalid\",\"unique\":\"$($SAXFR 127.0.0.1 $port catalog.invalid | grep $zone | grep PTR | cut -d'.' -f1)\"}}"
                        if [[ $skipreasons != *nolua* ]]; then
                                $PDNSUTIL --config-dir=. --config-name=lmdb2 set-meta stest.com AXFR-SOURCE 127.0.0.2
                        fi
                fi
+               if [ "$zone" = "wtest.com" ]; then
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 create-secondary-zone $zone 127.0.0.1:$port
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 set-catalog $zone catalog.invalid
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 set-options-json $zone '{"consumer":{"unique":"42"}}'
+               fi
+               if [ "$zone" = "." ]; then
+                       $PDNSUTIL --config-dir=. --config-name=lmdb2 create-secondary-zone $zone 127.0.0.1:$port
+               fi
        done
 
+       # setup catalog zone
+       if [ $zones -ne 1 ] # detect root tests
+       then
+               zones=$((zones+1))
+               $PDNSUTIL --config-dir=. --config-name=lmdb2 create-secondary-zone catalog.invalid 127.0.0.1:$port
+               $PDNSUTIL --config-dir=. --config-name=lmdb2 set-kind catalog.invalid consumer
+
+               $PDNSUTIL --config-dir=. --config-name=lmdb2 create-secondary-zone remove.invalid 127.0.0.1:$port
+               $PDNSUTIL --config-dir=. --config-name=lmdb2 set-catalog remove.invalid catalog.invalid
+       fi
+
        port=$((port+100))
 
        $RUNWRAPPER $PDNS2 --daemon=no --local-port=$port --config-dir=. \
                --config-name=lmdb2 --socket-dir=./ --no-shuffle \
-               --slave --dname-processing --api --api-key=secret &
+               --secondary --xfr-cycle-interval=15 --dname-processing --api --api-key=secret &
+
+       sleep 1
+       $PDNSCONTROL --config-name=lmdb2 --socket-dir=. --no-config retrieve catalog.invalid
 
-       echo 'waiting for zones to be slaved'
+       echo 'waiting for zones to be fetched'
        loopcount=0
        while [ $loopcount -lt 30 ]
        do
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 abf63f40bb0eb14817a2b341d9910eade5c6f33e..ec58cfe90b40dfe1da5da2313997eeaa72dcdf8b 100755 (executable)
@@ -31,7 +31,7 @@ rm -f recursor.pid pdns_recursor.pid
 <measurement><name>system CPU seconds</name><value>%S</value></measurement>
 <measurement><name>wallclock seconds</name><value>%e</value></measurement>
 <measurement><name>%% CPU used</name><value>%P</value></measurement>
-'         ${RECURSOR} --daemon=no --local-port=$port --socket-dir=./ --trace=$TRACE --config-dir=. --max-mthreads=$mthreads --query-local-address="0.0.0.0${QLA6}" --threads=$threads --record-cache-shards=$shards --disable-packetcache --refresh-on-ttl-perc=10 --dnssec=validate > recursor.log 2>&1 &
+'         ${RECURSOR} --daemon=no --local-port=$port --socket-dir=./ --trace=$TRACE --config-dir=. --max-mthreads=$mthreads --query-local-address="0.0.0.0${QLA6}" --threads=$threads --record-cache-shards=$shards --refresh-on-ttl-perc=10 --dnssec=validate --pdns-distributes-queries --reuseport=no > recursor.log 2>&1 &
 sleep 3
 if [ ! -e pdns_recursor.pid ]; then
         cat recursor.log
@@ -44,10 +44,11 @@ fi
 echo
 echo === First run with limit=$limit threads=$threads mthreads=$mthreads shards=$shards ===
 ${DNSBULKTEST} --www=false -qe 127.0.0.1 $port $limit < ${CSV} > bulktest.results
-echo
 kill -USR1 $(cat pdns_recursor.pid) || true
 ${RECCONTROL} --timeout=20 --socket-dir=. --config-dir=. get-all || true
 
+sleep 5
+
 # rerun 1 with hot cache
 echo
 echo === Second run with limit=$limit threads=$threads mthreads=$mthreads shards=$shards ===
@@ -55,6 +56,8 @@ ${DNSBULKTEST} --www=false -qe 127.0.0.1 $port $limit < ${CSV} > bulktest.result
 kill -USR1 $(cat pdns_recursor.pid) || true
 ${RECCONTROL} --timeout=20 --socket-dir=. --config-dir=. get-all || true
 
+sleep 5
+
 # rerun 2 with hot cache
 echo
 echo === Third run with limit=$limit threads=$threads mthreads=$mthreads shards=$shards ===
@@ -82,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 )
index 0bb54176b7f5cbf2bd870c0e27b6452a2ff2c100..048b3b17db7fb93b5912288cb5435afb61cdf8e1 100644 (file)
@@ -3,3 +3,4 @@ real_result
 *.out
 start
 step.*
+verify-dnssec-zone/allow-missing
index c30991725bc0a89ae19a7a6ae74fde09622e0ffa..848392213b81c4922721603180bb3f6a884f7c71 100644 (file)
@@ -1,4 +1,4 @@
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='host-255.test.dyndns.', qtype=ANY
 Answer:
@@ -7,7 +7,7 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      host-1.test.dyndns.     IN      A       3600    127.0.0.101
+0      host-1.test.dyndns.     3600    IN      A       127.0.0.101
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='host-1.test.dyndns.', qtype=ANY
 Answer:
@@ -16,10 +16,10 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      host-1.test.dyndns.     IN      A       3600    127.0.0.101
+0      host-1.test.dyndns.     3600    IN      A       127.0.0.101
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='host-1.test.dyndns.', qtype=A
-0      host-2.test.dyndns.     IN      A       3600    127.0.0.102
+0      host-2.test.dyndns.     3600    IN      A       127.0.0.102
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='host-2.test.dyndns.', qtype=A
 Answer:
@@ -28,7 +28,7 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      host-2.test.dyndns.     IN      A       3600    127.0.0.102
+0      host-2.test.dyndns.     3600    IN      A       127.0.0.102
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='host-2.test.dyndns.', qtype=A
 Answer:
@@ -37,7 +37,7 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      host-2.test.dyndns.     IN      A       3600    127.0.0.102
+0      host-2.test.dyndns.     3600    IN      A       127.0.0.102
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='host-2.test.dyndns.', qtype=A
 Answer:
index d60d96e51291089eed60596269b5e07ac2046275..cc8dfbac36ffacdada0084013faf05bdce8f1545 100644 (file)
@@ -1,6 +1,6 @@
-0      multi.test.dyndns.      IN      A       3600    127.0.0.1
-0      multi.test.dyndns.      IN      A       3600    127.0.0.2
-0      multi.test.dyndns.      IN      A       3600    127.0.0.3
+0      multi.test.dyndns.      3600    IN      A       127.0.0.1
+0      multi.test.dyndns.      3600    IN      A       127.0.0.2
+0      multi.test.dyndns.      3600    IN      A       127.0.0.3
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='multi.test.dyndns.', qtype=ANY
 Answer:
index 371bdf9fdecf625e3b039fd9b98832b62bea7253..1161f4a3c9682c3ccc360b8cacce372e0384f4ca 100644 (file)
@@ -1,5 +1,5 @@
 * Check if there is nothing there
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cname-and-other.test.dyndns.', qtype=ANY
 * Try to add 2 records that are not allowed together (should lead to FORMERR)
@@ -10,10 +10,10 @@ Answer:
 ;test.dyndns.                  IN      SOA
 
 * check that indeed nothing was added
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cname-and-other.test.dyndns.', qtype=A
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cname-and-other.test.dyndns.', qtype=CNAME
 * Add a record that will result in an ENT being inserted at the upcoming CNAME
@@ -45,7 +45,7 @@ Answer:
 ;test.dyndns.                  IN      SOA
 
 * Check that we only still have the CNAME
-0      cname-and-other.test.dyndns.    IN      CNAME   3600    powerdns-cname.example.
+0      cname-and-other.test.dyndns.    3600    IN      CNAME   powerdns-cname.example.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cname-and-other.test.dyndns.', qtype=A
 * Delete the CNAME and add the A record in one go
@@ -56,10 +56,10 @@ Answer:
 ;test.dyndns.                  IN      SOA
 
 * Check that we have only the A-record
-0      cname-and-other.test.dyndns.    IN      A       3600    192.0.2.1
+0      cname-and-other.test.dyndns.    3600    IN      A       192.0.2.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cname-and-other.test.dyndns.', qtype=A
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cname-and-other.test.dyndns.', qtype=CNAME
 * Attempt to add a CNAME (should be REFUSED)
@@ -70,7 +70,7 @@ Answer:
 ;test.dyndns.                  IN      SOA
 
 * check that we have only the A-record
-0      cname-and-other.test.dyndns.    IN      A       3600    192.0.2.1
+0      cname-and-other.test.dyndns.    3600    IN      A       192.0.2.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cname-and-other.test.dyndns.', qtype=ANY
 * Clean up
index 4ed8fac755900d9a4f94119774d4a148c24b3364..ed67693edeba34dda30022deeedb8ef59da6eda7 100644 (file)
@@ -11,7 +11,7 @@ Answer:
 
 
 == Verify PTR presence
-0      ptr1.test.dyndns.       IN      PTR     3600    host-2.test.dyndns.
+0      ptr1.test.dyndns.       3600    IN      PTR     host-2.test.dyndns.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='ptr1.test.dyndns.', qtype=PTR
 
@@ -24,7 +24,7 @@ Answer:
 
 
 == Verify that we have one PTR
-0      ptr1.test.dyndns.       IN      PTR     3600    host-2.test.dyndns.
+0      ptr1.test.dyndns.       3600    IN      PTR     host-2.test.dyndns.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='ptr1.test.dyndns.', qtype=PTR
 
index 1dd2dfb7dc81fb06d06c06becab25049268f07cf..f76b19aa41dd72169dfdcf97d7d9e74afe36a2d6 100644 (file)
@@ -1,5 +1,5 @@
 * Check that the name cannot be found
-1      sub.test.dyndns.        IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      sub.test.dyndns.        3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='occluded.sub.test.dyndns.', qtype=ANY
 * Create the occluded name in the parent zone
@@ -10,7 +10,7 @@ Answer:
 ;test.dyndns.                  IN      SOA
 
 * Check that the name cannot be found
-1      sub.test.dyndns.        IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      sub.test.dyndns.        3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='occluded.sub.test.dyndns.', qtype=ANY
 * Create a record in the child zone
@@ -21,7 +21,7 @@ Answer:
 ;sub.test.dyndns.              IN      SOA
 
 * Check that the child zone record is visible
-0      occluded.sub.test.dyndns.       IN      CNAME   3600    child.
+0      occluded.sub.test.dyndns.       3600    IN      CNAME   child.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='occluded.sub.test.dyndns.', qtype=A
 * Remove child record
@@ -32,7 +32,7 @@ Answer:
 ;sub.test.dyndns.              IN      SOA
 
 * Check that the occluded name is invisible again
-1      sub.test.dyndns.        IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      sub.test.dyndns.        3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='occluded.sub.test.dyndns.', qtype=ANY
 * Remove parent record
index d796a2b134e952781c07750453b60e06e68d5a8f..3cb0761168cdfb3196399a5da8395a2c35d4d61a 100644 (file)
@@ -1,4 +1,4 @@
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='dhcpd-host.test.dyndns.', qtype=ANY
 Answer:
@@ -7,8 +7,8 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      dhcpd-host.test.dyndns. IN      A       60      127.0.0.1
-0      dhcpd-host.test.dyndns. IN      TXT     60      "318188eb1a97d43928ccf8494d4a910c8a"
+0      dhcpd-host.test.dyndns. 60      IN      A       127.0.0.1
+0      dhcpd-host.test.dyndns. 60      IN      TXT     "318188eb1a97d43928ccf8494d4a910c8a"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='dhcpd-host.test.dyndns.', qtype=ANY
 Answer:
@@ -17,7 +17,7 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      dhcpd-host.test.dyndns. IN      TXT     60      "318188eb1a97d43928ccf8494d4a910c8a"
+0      dhcpd-host.test.dyndns. 60      IN      TXT     "318188eb1a97d43928ccf8494d4a910c8a"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='dhcpd-host.test.dyndns.', qtype=ANY
 Answer:
@@ -26,6 +26,6 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='dhcpd-host.test.dyndns.', qtype=ANY
index 2dd91c78723629dab4fec1c82e20c012d3f5151c..c2250b7cb39a37adc83aa2647ab2fee8772aaee7 100644 (file)
@@ -1,4 +1,4 @@
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cname3.test.dyndns.', qtype=CNAME
 Answer:
@@ -7,7 +7,7 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      cname3.test.dyndns.     IN      CNAME   3600    cname1.test.dyndns.
+0      cname3.test.dyndns.     3600    IN      CNAME   cname1.test.dyndns.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cname3.test.dyndns.', qtype=CNAME
 Answer:
@@ -16,6 +16,6 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cname3.test.dyndns.', qtype=CNAME
index c25d426d076b41753a8f9269afdb74bf0cfa3db4..52eb1ebbd2e52c6f7adec5028379fb40909062c2 100644 (file)
@@ -1,7 +1,7 @@
-0      test.dyndns.    IN      MX      3600    10 host-1.test.dyndns.
-0      test.dyndns.    IN      MX      3600    20 host-2.test.dyndns.
-2      host-1.test.dyndns.     IN      A       3600    127.0.0.101
-2      host-2.test.dyndns.     IN      A       3600    127.0.0.102
+0      test.dyndns.    3600    IN      MX      10 host-1.test.dyndns.
+0      test.dyndns.    3600    IN      MX      20 host-2.test.dyndns.
+2      host-1.test.dyndns.     3600    IN      A       127.0.0.101
+2      host-2.test.dyndns.     3600    IN      A       127.0.0.102
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=MX
 Answer:
@@ -10,12 +10,12 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      MX      3000    10 host-1.test.dyndns.
-0      test.dyndns.    IN      MX      3000    20 host-2.test.dyndns.
-0      test.dyndns.    IN      MX      3000    30 host-3.test.dyndns.
-2      host-1.test.dyndns.     IN      A       3600    127.0.0.101
-2      host-2.test.dyndns.     IN      A       3600    127.0.0.102
-2      host-3.test.dyndns.     IN      A       3600    127.0.0.103
+0      test.dyndns.    3000    IN      MX      10 host-1.test.dyndns.
+0      test.dyndns.    3000    IN      MX      20 host-2.test.dyndns.
+0      test.dyndns.    3000    IN      MX      30 host-3.test.dyndns.
+2      host-1.test.dyndns.     3600    IN      A       127.0.0.101
+2      host-2.test.dyndns.     3600    IN      A       127.0.0.102
+2      host-3.test.dyndns.     3600    IN      A       127.0.0.103
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=MX
 Answer:
@@ -24,9 +24,9 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      MX      3000    10 host-1.test.dyndns.
-0      test.dyndns.    IN      MX      3000    20 host-2.test.dyndns.
-2      host-1.test.dyndns.     IN      A       3600    127.0.0.101
-2      host-2.test.dyndns.     IN      A       3600    127.0.0.102
+0      test.dyndns.    3000    IN      MX      10 host-1.test.dyndns.
+0      test.dyndns.    3000    IN      MX      20 host-2.test.dyndns.
+2      host-1.test.dyndns.     3600    IN      A       127.0.0.101
+2      host-2.test.dyndns.     3600    IN      A       127.0.0.102
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=MX
diff --git a/regression-tests/tests/1dyndns-update-add-delete-txt/command b/regression-tests/tests/1dyndns-update-add-delete-txt/command
new file mode 100755 (executable)
index 0000000..4607742
--- /dev/null
@@ -0,0 +1,148 @@
+#!/bin/sh -x
+
+## add + delete with explicit data
+
+# check all MX records
+cleandig test.dyndns TXT hidesoadetails
+
+# add mx record
+cleannsupdate <<!
+server $nameserver $port
+zone test.dyndns
+update add test.dyndns. 3000 TXT 30 host-3.test.dyndns
+send
+answer
+!
+
+# check if record was really added
+cleandig test.dyndns TXT hidesoadetails
+
+# delete the just added record
+cleannsupdate <<!
+server $nameserver $port
+zone test.dyndns
+update delete test.dyndns. 3000 TXT 30 host-3.test.dyndns
+send
+answer
+!
+
+# check if the record was deleted.
+cleandig test.dyndns TXT hidesoadetails
+
+
+## add with explicit data (single string), remove whole RRset
+
+# add mx record
+cleannsupdate <<!
+server $nameserver $port
+zone test.dyndns
+update add test.dyndns. 3000 TXT "the quick brown fox"
+send
+answer
+!
+
+# check if record was really added
+cleandig test.dyndns TXT hidesoadetails
+
+# delete the just added record
+cleannsupdate <<!
+server $nameserver $port
+zone test.dyndns
+update delete test.dyndns. 3000 TXT
+send
+answer
+!
+# check if the record was deleted.
+cleandig test.dyndns TXT hidesoadetails
+
+## add with explicit data (single string), remove whole RRset
+
+# add mx record
+cleannsupdate <<!
+server $nameserver $port
+zone test.dyndns
+update add test.dyndns. 3000 TXT "the quick brown fox" "jumps over the lazy dog"
+send
+answer
+!
+
+# check if record was really added
+cleandig test.dyndns TXT hidesoadetails
+
+# delete the just added record
+cleannsupdate <<!
+server $nameserver $port
+zone test.dyndns
+update delete test.dyndns. 3000 TXT
+send
+answer
+!
+# check if the record was deleted.
+cleandig test.dyndns TXT hidesoadetails
+
+## add with explicit data (multiple strings, total over 255), remove whole RRset
+
+# add mx record
+cleannsupdate <<!
+server $nameserver $port
+zone test.dyndns
+update add test.dyndns. 3000 TXT "Now, why the chicken might have done it, I just couldn't say" "But if I'm gonna make it home, this is the only way" "Five lanes of traffic, an embankment, then a stream" "It's like it’s some kind of amphibianic fever dream" "The total lack of law enforcement on this road's a crime" "I'd write a letter, but I simply haven't got the time"
+send
+answer
+!
+
+# check if record was really added
+cleandig test.dyndns TXT hidesoadetails
+
+# delete the just added record
+cleannsupdate <<!
+server $nameserver $port
+zone test.dyndns
+update delete test.dyndns. 3000 TXT
+send
+answer
+!
+# check if the record was deleted.
+cleandig test.dyndns TXT hidesoadetails
+
+## add with explicit data (multiple strings, total over 255), remove whole RRset by content
+
+# add mx record
+cleannsupdate <<!
+server $nameserver $port
+zone test.dyndns
+update add test.dyndns. 3000 TXT "Now, why the chicken might have done it, I just couldn't say" "But if I'm gonna make it home, this is the only way" "Five lanes of traffic, an embankment, then a stream" "It's like it’s some kind of amphibianic fever dream" "The total lack of law enforcement on this road's a crime" "I'd write a letter, but I simply haven't got the time"
+send
+answer
+!
+
+# check if record was really added
+cleandig test.dyndns TXT hidesoadetails
+
+# delete the just added record
+cleannsupdate <<!
+server $nameserver $port
+zone test.dyndns
+update delete test.dyndns. 3000 TXT
+send
+answer
+!
+# check if the record was deleted.
+cleandig test.dyndns TXT hidesoadetails
+
+## now remove the overly long entry that was autosplit
+
+# check that it exists now
+cleandig xautosplit.test.dyndns TXT hidesoadetails
+
+# delete it
+cleannsupdate <<!
+server $nameserver $port
+zone test.dyndns
+update delete xautosplit.test.dyndns. TXT "They fixed up the corner store like it was a nightclub - It's permanently disco - Everyone is dressed so oddly I can't recognize them - I can't tell the staff from the customers - Baby check this out, I've got something to say - Man, it's so loud in here " "- When they stop the drum machine and I can think again - I'll remember what it was"
+send
+answer
+!
+
+# check that it is gone
+cleandig xautosplit.test.dyndns TXT hidesoadetails
diff --git a/regression-tests/tests/1dyndns-update-add-delete-txt/description b/regression-tests/tests/1dyndns-update-add-delete-txt/description
new file mode 100644 (file)
index 0000000..9956f4b
--- /dev/null
@@ -0,0 +1,2 @@
+A test to see if RFC2136 add and delete record works properly on a TXT records,
+including TXT records containing multiple strings.
diff --git a/regression-tests/tests/1dyndns-update-add-delete-txt/expected_result b/regression-tests/tests/1dyndns-update-add-delete-txt/expected_result
new file mode 100644 (file)
index 0000000..9c63d64
--- /dev/null
@@ -0,0 +1,105 @@
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='test.dyndns.', qtype=TXT
+Answer:
+;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: [id]
+;; flags: qr aa; ZONE: 1, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
+;; ZONE SECTION:
+;test.dyndns.                  IN      SOA
+
+0      test.dyndns.    3000    IN      TXT     "30" "host-3.test.dyndns"
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='test.dyndns.', qtype=TXT
+Answer:
+;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: [id]
+;; flags: qr aa; ZONE: 1, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
+;; ZONE SECTION:
+;test.dyndns.                  IN      SOA
+
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='test.dyndns.', qtype=TXT
+Answer:
+;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: [id]
+;; flags: qr aa; ZONE: 1, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
+;; ZONE SECTION:
+;test.dyndns.                  IN      SOA
+
+0      test.dyndns.    3000    IN      TXT     "the quick brown fox"
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='test.dyndns.', qtype=TXT
+Answer:
+;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: [id]
+;; flags: qr aa; ZONE: 1, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
+;; ZONE SECTION:
+;test.dyndns.                  IN      SOA
+
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='test.dyndns.', qtype=TXT
+Answer:
+;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: [id]
+;; flags: qr aa; ZONE: 1, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
+;; ZONE SECTION:
+;test.dyndns.                  IN      SOA
+
+0      test.dyndns.    3000    IN      TXT     "the quick brown fox" "jumps over the lazy dog"
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='test.dyndns.', qtype=TXT
+Answer:
+;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: [id]
+;; flags: qr aa; ZONE: 1, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
+;; ZONE SECTION:
+;test.dyndns.                  IN      SOA
+
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='test.dyndns.', qtype=TXT
+Answer:
+;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: [id]
+;; flags: qr aa; ZONE: 1, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
+;; ZONE SECTION:
+;test.dyndns.                  IN      SOA
+
+0      test.dyndns.    3000    IN      TXT     "Now, why the chicken might have done it, I just couldn't say" "But if I'm gonna make it home, this is the only way" "Five lanes of traffic, an embankment, then a stream" "It's like it\226\128\153s some kind of amphibianic fever dream" "The total lack of law enforcement on this road's a crime" "I'd write a letter, but I simply haven't got the time"
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='test.dyndns.', qtype=TXT
+Answer:
+;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: [id]
+;; flags: qr aa; ZONE: 1, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
+;; ZONE SECTION:
+;test.dyndns.                  IN      SOA
+
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='test.dyndns.', qtype=TXT
+Answer:
+;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: [id]
+;; flags: qr aa; ZONE: 1, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
+;; ZONE SECTION:
+;test.dyndns.                  IN      SOA
+
+0      test.dyndns.    3000    IN      TXT     "Now, why the chicken might have done it, I just couldn't say" "But if I'm gonna make it home, this is the only way" "Five lanes of traffic, an embankment, then a stream" "It's like it\226\128\153s some kind of amphibianic fever dream" "The total lack of law enforcement on this road's a crime" "I'd write a letter, but I simply haven't got the time"
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='test.dyndns.', qtype=TXT
+Answer:
+;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: [id]
+;; flags: qr aa; ZONE: 1, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
+;; ZONE SECTION:
+;test.dyndns.                  IN      SOA
+
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='test.dyndns.', qtype=TXT
+0      xautosplit.test.dyndns. 3600    IN      TXT     "They fixed up the corner store like it was a nightclub - It's permanently disco - Everyone is dressed so oddly I can't recognize them - I can't tell the staff from the customers - Baby check this out, I've got something to say - Man, it's so loud in here " "- When they stop the drum machine and I can think again - I'll remember what it was"
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='xautosplit.test.dyndns.', qtype=TXT
+Answer:
+;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: [id]
+;; flags: qr aa; ZONE: 1, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
+;; ZONE SECTION:
+;test.dyndns.                  IN      SOA
+
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='xautosplit.test.dyndns.', qtype=TXT
diff --git a/regression-tests/tests/1dyndns-update-add-delete-txt/skip.nodyndns b/regression-tests/tests/1dyndns-update-add-delete-txt/skip.nodyndns
new file mode 100644 (file)
index 0000000..81c071b
--- /dev/null
@@ -0,0 +1 @@
+Skip this test if the backend does not support dyndns/rfc2136
index 48a2468c2cb4ec2db73abc643f417b51fa954379..75d1235d07100fb58d74d5a588cc4cbdd165a5ab 100644 (file)
@@ -1,4 +1,4 @@
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='abc.add-delete.test.dyndns.', qtype=ANY
 Answer:
@@ -7,10 +7,10 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      abc.add-delete.test.dyndns.     IN      TXT     3600    "Wildcard with non-empty terminal"
+0      abc.add-delete.test.dyndns.     3600    IN      TXT     "Wildcard with non-empty terminal"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='abc.add-delete.test.dyndns.', qtype=ANY
-0      abc1.add-delete.test.dyndns.    IN      TXT     3600    "Wildcard with non-empty terminal"
+0      abc1.add-delete.test.dyndns.    3600    IN      TXT     "Wildcard with non-empty terminal"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='abc1.add-delete.test.dyndns.', qtype=ANY
 Answer:
@@ -19,6 +19,6 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='abc.add-delete.test.dyndns.', qtype=ANY
index a8fbee2c1eac1fddd7ae66d7c1a6ed9e120c2f9b..a44dd27ed992c0a308c1ca8c830dd40ca1c0a92d 100644 (file)
@@ -1,7 +1,7 @@
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='host-invalid.test.dyndns.', qtype=ANY
 RCODE: FORMERR
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='host-invalid.test.dyndns.', qtype=ANY
index 4be40cdf4f69e92669fdfa9275861e499ffe8b82..1625dd9255b0b2f9b2ae71250a1ef7b3fdc259c4 100644 (file)
@@ -1,45 +1,45 @@
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
 Answer:
@@ -54,50 +54,50 @@ Check if delegate and glue are added correctly.
 > ns1.c.host.test.dyndns       A       0       192.168.0.1     3600
 --- End: diff start step.1 ---
 
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      c.host.test.dyndns.     IN      NS      3600    ns1.c.host.test.dyndns.
-2      .       IN      OPT     32768   
-2      ns1.c.host.test.dyndns. IN      A       3600    192.168.0.1
+1      c.host.test.dyndns.     3600    IN      NS      ns1.c.host.test.dyndns.
+2      .       32768   IN      OPT     
+2      ns1.c.host.test.dyndns. 3600    IN      A       192.168.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      c.host.test.dyndns.     IN      NS      3600    ns1.c.host.test.dyndns.
-2      .       IN      OPT     32768   
-2      ns1.c.host.test.dyndns. IN      A       3600    192.168.0.1
+1      c.host.test.dyndns.     3600    IN      NS      ns1.c.host.test.dyndns.
+2      .       32768   IN      OPT     
+2      ns1.c.host.test.dyndns. 3600    IN      A       192.168.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
 Answer:
@@ -112,48 +112,48 @@ Check if delegate is deleted and glue auth=1
 > ns1.c.host.test.dyndns       A       0       192.168.0.1     3600
 --- End: diff start step.2 ---
 
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='c.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
 Answer:
@@ -167,47 +167,47 @@ Check if we are back to normal
 no difference
 --- End: diff start step.3 ---
 
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
index 9ff75d98228ca379adf0e61453da54705a697333..a056be27cf28647f46a82c5b922c58fa703a88aa 100644 (file)
@@ -1,92 +1,92 @@
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-0      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+0      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      NSEC    3600    host-1.test.dyndns. A RRSIG NSEC
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-0      e.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      NSEC    host-1.test.dyndns. A RRSIG NSEC
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+0      e.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      e.host.test.dyndns.     IN      NSEC    3600    host-1.test.dyndns. A RRSIG NSEC
-1      e.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      e.host.test.dyndns.     3600    IN      NSEC    host-1.test.dyndns. A RRSIG NSEC
+1      e.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      e.host.test.dyndns.     IN      NSEC    3600    host-1.test.dyndns. A RRSIG NSEC
-1      e.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      e.host.test.dyndns.     3600    IN      NSEC    host-1.test.dyndns. A RRSIG NSEC
+1      e.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
 Answer:
@@ -101,91 +101,91 @@ Check if delegate and glue are added correctly.
 > ns1.c.host.test.dyndns       A       0       192.168.0.1     3600    NULL    0
 --- End: diff start step.1 ---
 
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      NSEC    3600    c.host.test.dyndns. A RRSIG NSEC
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-0      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      NSEC    c.host.test.dyndns. A RRSIG NSEC
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+0      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    c.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    c.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      c.host.test.dyndns.     IN      NS      3600    ns1.c.host.test.dyndns.
-1      c.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. NS RRSIG NSEC
-1      c.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
-2      ns1.c.host.test.dyndns. IN      A       3600    192.168.0.1
+1      c.host.test.dyndns.     3600    IN      NS      ns1.c.host.test.dyndns.
+1      c.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. NS RRSIG NSEC
+1      c.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
+2      ns1.c.host.test.dyndns. 3600    IN      A       192.168.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      c.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. NS RRSIG NSEC
-1      c.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      c.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. NS RRSIG NSEC
+1      c.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      NSEC    3600    host-1.test.dyndns. A RRSIG NSEC
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-0      e.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      NSEC    host-1.test.dyndns. A RRSIG NSEC
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+0      e.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      e.host.test.dyndns.     IN      NSEC    3600    host-1.test.dyndns. A RRSIG NSEC
-1      e.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      e.host.test.dyndns.     3600    IN      NSEC    host-1.test.dyndns. A RRSIG NSEC
+1      e.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    c.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    c.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    c.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    c.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      c.host.test.dyndns.     IN      NS      3600    ns1.c.host.test.dyndns.
-1      c.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. NS RRSIG NSEC
-1      c.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
-2      ns1.c.host.test.dyndns. IN      A       3600    192.168.0.1
+1      c.host.test.dyndns.     3600    IN      NS      ns1.c.host.test.dyndns.
+1      c.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. NS RRSIG NSEC
+1      c.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
+2      ns1.c.host.test.dyndns. 3600    IN      A       192.168.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      c.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. NS RRSIG NSEC
-1      c.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      c.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. NS RRSIG NSEC
+1      c.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      e.host.test.dyndns.     IN      NSEC    3600    host-1.test.dyndns. A RRSIG NSEC
-1      e.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      e.host.test.dyndns.     3600    IN      NSEC    host-1.test.dyndns. A RRSIG NSEC
+1      e.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
 Answer:
@@ -200,91 +200,91 @@ Check if delegate is deleted and glue auth=1
 > ns1.c.host.test.dyndns       A       0       192.168.0.1     3600    'host c ns1'    1
 --- End: diff start step.2 ---
 
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      NSEC    3600    ns1.c.host.test.dyndns. A RRSIG NSEC
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-0      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      NSEC    ns1.c.host.test.dyndns. A RRSIG NSEC
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+0      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    ns1.c.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    ns1.c.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    ns1.c.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    ns1.c.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='c.host.test.dyndns.', qtype=ANY
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      ns1.c.host.test.dyndns. IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      ns1.c.host.test.dyndns. IN      RRSIG   3600    NSEC 13 5 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      ns1.c.host.test.dyndns. 3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      ns1.c.host.test.dyndns. 3600    IN      RRSIG   NSEC 13 5 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      NSEC    3600    host-1.test.dyndns. A RRSIG NSEC
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-0      e.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      NSEC    host-1.test.dyndns. A RRSIG NSEC
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+0      e.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      e.host.test.dyndns.     IN      NSEC    3600    host-1.test.dyndns. A RRSIG NSEC
-1      e.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      e.host.test.dyndns.     3600    IN      NSEC    host-1.test.dyndns. A RRSIG NSEC
+1      e.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    ns1.c.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    ns1.c.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    ns1.c.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    ns1.c.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    ns1.c.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    ns1.c.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      ns1.c.host.test.dyndns. IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      ns1.c.host.test.dyndns. IN      RRSIG   3600    NSEC 13 5 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      ns1.c.host.test.dyndns. 3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      ns1.c.host.test.dyndns. 3600    IN      RRSIG   NSEC 13 5 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      e.host.test.dyndns.     IN      NSEC    3600    host-1.test.dyndns. A RRSIG NSEC
-1      e.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      e.host.test.dyndns.     3600    IN      NSEC    host-1.test.dyndns. A RRSIG NSEC
+1      e.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
 Answer:
@@ -298,94 +298,94 @@ Check if we are back to normal
 no difference
 --- End: diff start step.3 ---
 
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-0      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+0      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      NSEC    3600    host-1.test.dyndns. A RRSIG NSEC
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-0      e.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      NSEC    host-1.test.dyndns. A RRSIG NSEC
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+0      e.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      e.host.test.dyndns.     IN      NSEC    3600    host-1.test.dyndns. A RRSIG NSEC
-1      e.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      e.host.test.dyndns.     3600    IN      NSEC    host-1.test.dyndns. A RRSIG NSEC
+1      e.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      e.host.test.dyndns.     IN      NSEC    3600    host-1.test.dyndns. A RRSIG NSEC
-1      e.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      e.host.test.dyndns.     3600    IN      NSEC    host-1.test.dyndns. A RRSIG NSEC
+1      e.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
index 8d68f6461c21576dfabaff26d143220a5b400633..5a0a642037da0974d5cecf9a483b6b852ddfb8f0 100644 (file)
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8V0C5RPU3BNVS3KLJ997NS1CMB4ID023
-1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8V0C5RPU3BNVS3KLJ997NS1CMB4ID023
+1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd TL50O2E6J8AJ79R56OLQQ5H6R0H7HVLT
-1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd TL50O2E6J8AJ79R56OLQQ5H6R0H7HVLT
+1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 9ESKCI2JT1GR4EB97DR5MG4A1NNJN5HL
-1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR7 A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd ECLK033HJV6D7GBOLF2UAMVD7A9HJULJ
-1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 9ESKCI2JT1GR4EB97DR5MG4A1NNJN5HL
+1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR7 A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd ECLK033HJV6D7GBOLF2UAMVD7A9HJULJ
+1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8V0C5RPU3BNVS3KLJ997NS1CMB4ID023
-1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8V0C5RPU3BNVS3KLJ997NS1CMB4ID023
+1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8TI9TGH8D81FRR5VR4O1O5BVKO42V9DR A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      bq8ul1d9pir8bm0bq7t9ov5ckk8ghkhd.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd BQ8UL1D9PIR8BM0BQ7T9OV5CKK8GHKHF
-1      bq8ul1d9pir8bm0bq7t9ov5ckk8ghkhd.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      uel5ohqcmefoo28jdmrcmq1ugmlo0poc.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd UEL5OHQCMEFOO28JDMRCMQ1UGMLO0POE
-1      uel5ohqcmefoo28jdmrcmq1ugmlo0poc.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8TI9TGH8D81FRR5VR4O1O5BVKO42V9DR A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      bq8ul1d9pir8bm0bq7t9ov5ckk8ghkhd.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd BQ8UL1D9PIR8BM0BQ7T9OV5CKK8GHKHF
+1      bq8ul1d9pir8bm0bq7t9ov5ckk8ghkhd.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      uel5ohqcmefoo28jdmrcmq1ugmlo0poc.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd UEL5OHQCMEFOO28JDMRCMQ1UGMLO0POE
+1      uel5ohqcmefoo28jdmrcmq1ugmlo0poc.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
 Answer:
@@ -119,105 +119,105 @@ Check if delegate and glue are added correctly.
 > ns1.c.host.test.dyndns       A       0       192.168.0.1     3600    NULL    0
 --- End: diff start step.1 ---
 
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8V0C5RPU3BNVS3KLJ997NS1CMB4ID023
-1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8V0C5RPU3BNVS3KLJ997NS1CMB4ID023
+1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      c.host.test.dyndns.     IN      NS      3600    ns1.c.host.test.dyndns.
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ NS
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
-2      ns1.c.host.test.dyndns. IN      A       3600    192.168.0.1
+1      c.host.test.dyndns.     3600    IN      NS      ns1.c.host.test.dyndns.
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ NS
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
+2      ns1.c.host.test.dyndns. 3600    IN      A       192.168.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd TL50O2E6J8AJ79R56OLQQ5H6R0H7HVLT
-1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd TL50O2E6J8AJ79R56OLQQ5H6R0H7HVLT
+1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 9ESKCI2JT1GR4EB97DR5MG4A1NNJN5HL
-1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR7 A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd ECLK033HJV6D7GBOLF2UAMVD7A9HJULJ
-1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 9ESKCI2JT1GR4EB97DR5MG4A1NNJN5HL
+1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR7 A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd ECLK033HJV6D7GBOLF2UAMVD7A9HJULJ
+1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8V0C5RPU3BNVS3KLJ997NS1CMB4ID023
-1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8V0C5RPU3BNVS3KLJ997NS1CMB4ID023
+1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      c.host.test.dyndns.     IN      NS      3600    ns1.c.host.test.dyndns.
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ NS
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
-2      ns1.c.host.test.dyndns. IN      A       3600    192.168.0.1
+1      c.host.test.dyndns.     3600    IN      NS      ns1.c.host.test.dyndns.
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ NS
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
+2      ns1.c.host.test.dyndns. 3600    IN      A       192.168.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8TI9TGH8D81FRR5VR4O1O5BVKO42V9DR A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      bq8ul1d9pir8bm0bq7t9ov5ckk8ghkhd.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd BQ8UL1D9PIR8BM0BQ7T9OV5CKK8GHKHF
-1      bq8ul1d9pir8bm0bq7t9ov5ckk8ghkhd.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      uel5ohqcmefoo28jdmrcmq1ugmlo0poc.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd UEL5OHQCMEFOO28JDMRCMQ1UGMLO0POE
-1      uel5ohqcmefoo28jdmrcmq1ugmlo0poc.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8TI9TGH8D81FRR5VR4O1O5BVKO42V9DR A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      bq8ul1d9pir8bm0bq7t9ov5ckk8ghkhd.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd BQ8UL1D9PIR8BM0BQ7T9OV5CKK8GHKHF
+1      bq8ul1d9pir8bm0bq7t9ov5ckk8ghkhd.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      uel5ohqcmefoo28jdmrcmq1ugmlo0poc.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd UEL5OHQCMEFOO28JDMRCMQ1UGMLO0POE
+1      uel5ohqcmefoo28jdmrcmq1ugmlo0poc.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
 Answer:
@@ -232,109 +232,109 @@ Check if delegate is deleted and glue auth=1
 > ns1.c.host.test.dyndns       A       0       192.168.0.1     3600    NULL    1
 --- End: diff start step.2 ---
 
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8V0C5RPU3BNVS3KLJ997NS1CMB4ID023
-1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8V0C5RPU3BNVS3KLJ997NS1CMB4ID023
+1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='c.host.test.dyndns.', qtype=ANY
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd TL50O2E6J8AJ79R56OLQQ5H6R0H7HVLT
-1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd TL50O2E6J8AJ79R56OLQQ5H6R0H7HVLT
+1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 9ESKCI2JT1GR4EB97DR5MG4A1NNJN5HL
-1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR7 A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd ECLK033HJV6D7GBOLF2UAMVD7A9HJULJ
-1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 9ESKCI2JT1GR4EB97DR5MG4A1NNJN5HL
+1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR7 A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd ECLK033HJV6D7GBOLF2UAMVD7A9HJULJ
+1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8V0C5RPU3BNVS3KLJ997NS1CMB4ID023
-1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8V0C5RPU3BNVS3KLJ997NS1CMB4ID023
+1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      pjn3mi4alcs21beld7nveruudmhh8t4l.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd PJN3MI4ALCS21BELD7NVERUUDMHH8T4N
-1      pjn3mi4alcs21beld7nveruudmhh8t4l.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      rnplq1876o9vq8qr599rlr4ism2beht4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd RNPLQ1876O9VQ8QR599RLR4ISM2BEHT6
-1      rnplq1876o9vq8qr599rlr4ism2beht4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      pjn3mi4alcs21beld7nveruudmhh8t4l.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd PJN3MI4ALCS21BELD7NVERUUDMHH8T4N
+1      pjn3mi4alcs21beld7nveruudmhh8t4l.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      rnplq1876o9vq8qr599rlr4ism2beht4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd RNPLQ1876O9VQ8QR599RLR4ISM2BEHT6
+1      rnplq1876o9vq8qr599rlr4ism2beht4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8TI9TGH8D81FRR5VR4O1O5BVKO42V9DR A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      bq8ul1d9pir8bm0bq7t9ov5ckk8ghkhd.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd BQ8UL1D9PIR8BM0BQ7T9OV5CKK8GHKHF
-1      bq8ul1d9pir8bm0bq7t9ov5ckk8ghkhd.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      uel5ohqcmefoo28jdmrcmq1ugmlo0poc.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd UEL5OHQCMEFOO28JDMRCMQ1UGMLO0POE
-1      uel5ohqcmefoo28jdmrcmq1ugmlo0poc.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8TI9TGH8D81FRR5VR4O1O5BVKO42V9DR A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      bq8ul1d9pir8bm0bq7t9ov5ckk8ghkhd.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd BQ8UL1D9PIR8BM0BQ7T9OV5CKK8GHKHF
+1      bq8ul1d9pir8bm0bq7t9ov5ckk8ghkhd.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      uel5ohqcmefoo28jdmrcmq1ugmlo0poc.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd UEL5OHQCMEFOO28JDMRCMQ1UGMLO0POE
+1      uel5ohqcmefoo28jdmrcmq1ugmlo0poc.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
 Answer:
@@ -348,112 +348,112 @@ Check if we are back to normal
 no difference
 --- End: diff start step.3 ---
 
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8V0C5RPU3BNVS3KLJ997NS1CMB4ID023
-1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8V0C5RPU3BNVS3KLJ997NS1CMB4ID023
+1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd TL50O2E6J8AJ79R56OLQQ5H6R0H7HVLT
-1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd TL50O2E6J8AJ79R56OLQQ5H6R0H7HVLT
+1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 9ESKCI2JT1GR4EB97DR5MG4A1NNJN5HL
-1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR7 A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd ECLK033HJV6D7GBOLF2UAMVD7A9HJULJ
-1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 9ESKCI2JT1GR4EB97DR5MG4A1NNJN5HL
+1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR7 A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd ECLK033HJV6D7GBOLF2UAMVD7A9HJULJ
+1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8V0C5RPU3BNVS3KLJ997NS1CMB4ID023
-1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8V0C5RPU3BNVS3KLJ997NS1CMB4ID023
+1      8v0c5rpu3bnvs3klj997ns1cmb4id021.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8TI9TGH8D81FRR5VR4O1O5BVKO42V9DR A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      bq8ul1d9pir8bm0bq7t9ov5ckk8ghkhd.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd BQ8UL1D9PIR8BM0BQ7T9OV5CKK8GHKHF
-1      bq8ul1d9pir8bm0bq7t9ov5ckk8ghkhd.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      uel5ohqcmefoo28jdmrcmq1ugmlo0poc.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd UEL5OHQCMEFOO28JDMRCMQ1UGMLO0POE
-1      uel5ohqcmefoo28jdmrcmq1ugmlo0poc.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8TI9TGH8D81FRR5VR4O1O5BVKO42V9DR A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      bq8ul1d9pir8bm0bq7t9ov5ckk8ghkhd.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd BQ8UL1D9PIR8BM0BQ7T9OV5CKK8GHKHF
+1      bq8ul1d9pir8bm0bq7t9ov5ckk8ghkhd.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      uel5ohqcmefoo28jdmrcmq1ugmlo0poc.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd UEL5OHQCMEFOO28JDMRCMQ1UGMLO0POE
+1      uel5ohqcmefoo28jdmrcmq1ugmlo0poc.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
index f660f13025ffe115684cf55959026bf505bb03f9..fd45ed9181eb4ceb9ccfbae720187a730df91db3 100644 (file)
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 2GP5RDNJOQ5OOSPC5O1IH9LALI101DI8 NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 2GP5RDNJOQ5OOSPC5O1IH9LALI101DI8 NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
 Answer:
@@ -111,97 +111,97 @@ Check if delegate and glue are added correctly.
 > ns1.c.host.test.dyndns       A       0       192.168.0.1     3600    NULL    0
 --- End: diff start step.1 ---
 
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      c.host.test.dyndns.     IN      NS      3600    ns1.c.host.test.dyndns.
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P NS
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
-2      ns1.c.host.test.dyndns. IN      A       3600    192.168.0.1
+1      c.host.test.dyndns.     3600    IN      NS      ns1.c.host.test.dyndns.
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P NS
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
+2      ns1.c.host.test.dyndns. 3600    IN      A       192.168.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MI A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MI A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      c.host.test.dyndns.     IN      NS      3600    ns1.c.host.test.dyndns.
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P NS
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
-2      ns1.c.host.test.dyndns. IN      A       3600    192.168.0.1
+1      c.host.test.dyndns.     3600    IN      NS      ns1.c.host.test.dyndns.
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P NS
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
+2      ns1.c.host.test.dyndns. 3600    IN      A       192.168.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 2GP5RDNJOQ5OOSPC5O1IH9LALI101DI8 NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 2GP5RDNJOQ5OOSPC5O1IH9LALI101DI8 NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
 Answer:
@@ -216,101 +216,101 @@ Check if delegate is deleted and glue auth=1
 > ns1.c.host.test.dyndns       A       0       192.168.0.1     3600    '6ovod1m5kossaiiqprlaf42prqrkeaq7'      1
 --- End: diff start step.2 ---
 
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MI A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MI A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      ncja3un028k84h59aoloj6bh06s80071.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd Q75PNOE7PB74PND6OGN44T5BTUURBHRF NS
-1      ncja3un028k84h59aoloj6bh06s80071.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      r9s1cj8dkmnmenjn95sti8nhh9utpq9k.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd S30OPRHQREKH5SUH6L530KD668ELK9OS
-1      r9s1cj8dkmnmenjn95sti8nhh9utpq9k.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      ncja3un028k84h59aoloj6bh06s80071.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd Q75PNOE7PB74PND6OGN44T5BTUURBHRF NS
+1      ncja3un028k84h59aoloj6bh06s80071.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      r9s1cj8dkmnmenjn95sti8nhh9utpq9k.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd S30OPRHQREKH5SUH6L530KD668ELK9OS
+1      r9s1cj8dkmnmenjn95sti8nhh9utpq9k.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 2GP5RDNJOQ5OOSPC5O1IH9LALI101DI8 NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 2GP5RDNJOQ5OOSPC5O1IH9LALI101DI8 NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
 Answer:
@@ -324,104 +324,104 @@ Check if we are back to normal
 no difference
 --- End: diff start step.3 ---
 
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 2GP5RDNJOQ5OOSPC5O1IH9LALI101DI8 NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 2GP5RDNJOQ5OOSPC5O1IH9LALI101DI8 NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
index c71ff6458f02f6640aa82e596710a74230605a6a..bd91850cfaf63ea9c798188f1c1146371b15a509 100644 (file)
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 2GP5RDNJOQ5OOSPC5O1IH9LALI101DI8 NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 2GP5RDNJOQ5OOSPC5O1IH9LALI101DI8 NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
 Answer:
@@ -111,101 +111,101 @@ Check if delegate and glue are added correctly.
 > ns1.c.host.test.dyndns       A       0       192.168.0.1     3600    NULL    0
 --- End: diff start step.1 ---
 
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      c.host.test.dyndns.     IN      NS      3600    ns1.c.host.test.dyndns.
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
-2      ns1.c.host.test.dyndns. IN      A       3600    192.168.0.1
+1      c.host.test.dyndns.     3600    IN      NS      ns1.c.host.test.dyndns.
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
+2      ns1.c.host.test.dyndns. 3600    IN      A       192.168.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      c.host.test.dyndns.     IN      NS      3600    ns1.c.host.test.dyndns.
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
-2      ns1.c.host.test.dyndns. IN      A       3600    192.168.0.1
+1      c.host.test.dyndns.     3600    IN      NS      ns1.c.host.test.dyndns.
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
+2      ns1.c.host.test.dyndns. 3600    IN      A       192.168.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 2GP5RDNJOQ5OOSPC5O1IH9LALI101DI8 NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 2GP5RDNJOQ5OOSPC5O1IH9LALI101DI8 NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
 Answer:
@@ -220,101 +220,101 @@ Check if delegate is deleted and glue auth=1
 > ns1.c.host.test.dyndns       A       0       192.168.0.1     3600    '6ovod1m5kossaiiqprlaf42prqrkeaq7'      1
 --- End: diff start step.2 ---
 
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MI A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MI A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P
-1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lresbbp3lv8blgj9fsgtdmm4q7vj3d6j.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd Q75PNOE7PB74PND6OGN44T5BTUURBHRF A RRSIG
-1      lresbbp3lv8blgj9fsgtdmm4q7vj3d6j.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      r9s1cj8dkmnmenjn95sti8nhh9utpq9k.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd S30OPRHQREKH5SUH6L530KD668ELK9OS
-1      r9s1cj8dkmnmenjn95sti8nhh9utpq9k.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P
+1      fgun0ru4oe3g76tr551hg97mpu37b6mi.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lresbbp3lv8blgj9fsgtdmm4q7vj3d6j.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd Q75PNOE7PB74PND6OGN44T5BTUURBHRF A RRSIG
+1      lresbbp3lv8blgj9fsgtdmm4q7vj3d6j.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      r9s1cj8dkmnmenjn95sti8nhh9utpq9k.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd S30OPRHQREKH5SUH6L530KD668ELK9OS
+1      r9s1cj8dkmnmenjn95sti8nhh9utpq9k.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6OVOD1M5KOSSAIIQPRLAF42PRQRKEAQ7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 2GP5RDNJOQ5OOSPC5O1IH9LALI101DI8 NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 2GP5RDNJOQ5OOSPC5O1IH9LALI101DI8 NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
 Answer:
@@ -328,104 +328,104 @@ Check if we are back to normal
 no difference
 --- End: diff start step.3 ---
 
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.b.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.d.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 2GP5RDNJOQ5OOSPC5O1IH9LALI101DI8 NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 2GP5RDNJOQ5OOSPC5O1IH9LALI101DI8 NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      u36f0tjooqv1kspatto6qns0vap731v2.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.e.host.test.dyndns.', qtype=ANY
index 56a3093e13777cfb67a40da18ab4230e3d0bf0b2..8aec65d66eaeec5dc58c1898af29b41dfa0f120f 100644 (file)
@@ -1,5 +1,5 @@
-0      delete-add.test.dyndns. IN      A       3600    127.0.0.108
-0      delete-add.test.dyndns. IN      TXT     3600    "Should be gone after a while"
+0      delete-add.test.dyndns. 3600    IN      A       127.0.0.108
+0      delete-add.test.dyndns. 3600    IN      TXT     "Should be gone after a while"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='delete-add.test.dyndns.', qtype=ANY
 Answer:
@@ -8,7 +8,7 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      delete-add.test.dyndns. IN      TXT     3600    "Should be gone after a while"
+0      delete-add.test.dyndns. 3600    IN      TXT     "Should be gone after a while"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='delete-add.test.dyndns.', qtype=ANY
 Answer:
@@ -17,7 +17,7 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      delete-add.test.dyndns. IN      A       3600    127.0.0.108
-0      delete-add.test.dyndns. IN      TXT     3600    "Should be gone after a while"
+0      delete-add.test.dyndns. 3600    IN      A       127.0.0.108
+0      delete-add.test.dyndns. 3600    IN      TXT     "Should be gone after a while"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='delete-add.test.dyndns.', qtype=ANY
index 49fca9bdf057d09f5f529cf2daa31b9efda135fd..6793e8627780effe9f1d0bce57960555fddac80d 100644 (file)
@@ -1,5 +1,5 @@
-0      delete-add.test.dyndns. IN      A       3600    127.0.0.108
-0      delete-add.test.dyndns. IN      TXT     3600    "Should be gone after a while"
+0      delete-add.test.dyndns. 3600    IN      A       127.0.0.108
+0      delete-add.test.dyndns. 3600    IN      TXT     "Should be gone after a while"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='delete-add.test.dyndns.', qtype=ANY
 Answer:
@@ -8,7 +8,7 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='delete-add.test.dyndns.', qtype=ANY
 Answer:
@@ -17,7 +17,7 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      delete-add.test.dyndns. IN      A       3600    127.0.0.108
-0      delete-add.test.dyndns. IN      TXT     3600    "Should be gone after a while"
+0      delete-add.test.dyndns. 3600    IN      A       127.0.0.108
+0      delete-add.test.dyndns. 3600    IN      TXT     "Should be gone after a while"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='delete-add.test.dyndns.', qtype=ANY
index d31c55603b0c6587735a86d44ba40dabdae9ec54..e0af37dcb593fc0ac233e783db9823622460dce1 100644 (file)
@@ -1,7 +1,7 @@
-0      test.dyndns.    IN      MX      3000    10 host-1.test.dyndns.
-0      test.dyndns.    IN      MX      3000    20 host-2.test.dyndns.
-2      host-1.test.dyndns.     IN      A       3600    127.0.0.101
-2      host-2.test.dyndns.     IN      A       3600    127.0.0.102
+0      test.dyndns.    3000    IN      MX      10 host-1.test.dyndns.
+0      test.dyndns.    3000    IN      MX      20 host-2.test.dyndns.
+2      host-1.test.dyndns.     3600    IN      A       127.0.0.101
+2      host-2.test.dyndns.     3600    IN      A       127.0.0.102
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=MX
 Answer:
@@ -10,11 +10,11 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      MX      3600    10 host-1.test.dyndns.
-0      test.dyndns.    IN      MX      3600    100 mx1.google.com.
-0      test.dyndns.    IN      MX      3600    20 host-2.test.dyndns.
-2      host-1.test.dyndns.     IN      A       3600    127.0.0.101
-2      host-2.test.dyndns.     IN      A       3600    127.0.0.102
+0      test.dyndns.    3600    IN      MX      10 host-1.test.dyndns.
+0      test.dyndns.    3600    IN      MX      100 mx1.google.com.
+0      test.dyndns.    3600    IN      MX      20 host-2.test.dyndns.
+2      host-1.test.dyndns.     3600    IN      A       127.0.0.101
+2      host-2.test.dyndns.     3600    IN      A       127.0.0.102
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=MX
 Answer:
@@ -23,11 +23,11 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      MX      3600    10 host-1.test.dyndns.
-0      test.dyndns.    IN      MX      3600    100 mx1.google.com.
-0      test.dyndns.    IN      MX      3600    20 host-2.test.dyndns.
-2      host-1.test.dyndns.     IN      A       3600    127.0.0.101
-2      host-2.test.dyndns.     IN      A       3600    127.0.0.102
+0      test.dyndns.    3600    IN      MX      10 host-1.test.dyndns.
+0      test.dyndns.    3600    IN      MX      100 mx1.google.com.
+0      test.dyndns.    3600    IN      MX      20 host-2.test.dyndns.
+2      host-1.test.dyndns.     3600    IN      A       127.0.0.101
+2      host-2.test.dyndns.     3600    IN      A       127.0.0.102
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=MX
 Answer:
@@ -36,9 +36,9 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      MX      3600    10 host-1.test.dyndns.
-0      test.dyndns.    IN      MX      3600    20 host-2.test.dyndns.
-2      host-1.test.dyndns.     IN      A       3600    127.0.0.101
-2      host-2.test.dyndns.     IN      A       3600    127.0.0.102
+0      test.dyndns.    3600    IN      MX      10 host-1.test.dyndns.
+0      test.dyndns.    3600    IN      MX      20 host-2.test.dyndns.
+2      host-1.test.dyndns.     3600    IN      A       127.0.0.101
+2      host-2.test.dyndns.     3600    IN      A       127.0.0.102
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=MX
index 6dc5c5df119734f53356e30354deaa1f2a538e97..150d71aa69d851077989db36bed26d54069ba68f 100644 (file)
@@ -1,7 +1,7 @@
-0      test.dyndns.    IN      NS      3600    ns1.test.dyndns.
-0      test.dyndns.    IN      NS      3600    ns2.test.dyndns.
-2      ns1.test.dyndns.        IN      A       3600    127.0.0.1
-2      ns2.test.dyndns.        IN      A       3600    127.0.0.2
+0      test.dyndns.    3600    IN      NS      ns1.test.dyndns.
+0      test.dyndns.    3600    IN      NS      ns2.test.dyndns.
+2      ns1.test.dyndns.        3600    IN      A       127.0.0.1
+2      ns2.test.dyndns.        3600    IN      A       127.0.0.2
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=NS
 Answer:
@@ -10,10 +10,10 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      NS      3600    ns1.test.dyndns.
-0      test.dyndns.    IN      NS      3600    ns2.test.dyndns.
-2      ns1.test.dyndns.        IN      A       3600    127.0.0.1
-2      ns2.test.dyndns.        IN      A       3600    127.0.0.2
+0      test.dyndns.    3600    IN      NS      ns1.test.dyndns.
+0      test.dyndns.    3600    IN      NS      ns2.test.dyndns.
+2      ns1.test.dyndns.        3600    IN      A       127.0.0.1
+2      ns2.test.dyndns.        3600    IN      A       127.0.0.2
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=NS
 Answer:
@@ -22,10 +22,10 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      NS      3600    ns1.test.dyndns.
-0      test.dyndns.    IN      NS      3600    ns2.test.dyndns.
-2      ns1.test.dyndns.        IN      A       3600    127.0.0.1
-2      ns2.test.dyndns.        IN      A       3600    127.0.0.2
+0      test.dyndns.    3600    IN      NS      ns1.test.dyndns.
+0      test.dyndns.    3600    IN      NS      ns2.test.dyndns.
+2      ns1.test.dyndns.        3600    IN      A       127.0.0.1
+2      ns2.test.dyndns.        3600    IN      A       127.0.0.2
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=NS
 Answer:
@@ -34,8 +34,8 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      NS      3600    ns2.test.dyndns.
-2      ns2.test.dyndns.        IN      A       3600    127.0.0.2
+0      test.dyndns.    3600    IN      NS      ns2.test.dyndns.
+2      ns2.test.dyndns.        3600    IN      A       127.0.0.2
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=NS
 Answer:
@@ -44,9 +44,9 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      NS      3600    ns1.test.dyndns.
-0      test.dyndns.    IN      NS      3600    ns2.test.dyndns.
-2      ns1.test.dyndns.        IN      A       3600    127.0.0.1
-2      ns2.test.dyndns.        IN      A       3600    127.0.0.2
+0      test.dyndns.    3600    IN      NS      ns1.test.dyndns.
+0      test.dyndns.    3600    IN      NS      ns2.test.dyndns.
+2      ns1.test.dyndns.        3600    IN      A       127.0.0.1
+2      ns2.test.dyndns.        3600    IN      A       127.0.0.2
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=NS
index 7650ccb5f9cc3bb5f22a54936960f5e14bdeb0b3..6ac536d770e155c2e236b2533405f7ef9a2a44f2 100644 (file)
@@ -1,4 +1,4 @@
-0      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+0      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=SOA
 Answer:
@@ -7,6 +7,6 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+0      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=SOA
index 60994ece8c9d528434e16873cd5b52ea04b61eeb..4b6e44b9dcc800044a6e6e677641de5c95d16c57 100644 (file)
@@ -1,33 +1,33 @@
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='host.test.dyndns.', qtype=ANY
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.c.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
 Answer:
@@ -36,36 +36,36 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='host.test.dyndns.', qtype=ANY
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.c.host.test.dyndns.', qtype=ANY
-0      d.host.test.dyndns.     IN      A       3600    127.0.0.1
-2      .       IN      OPT     32768   
+0      d.host.test.dyndns.     3600    IN      A       127.0.0.1
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
 Answer:
@@ -74,35 +74,35 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='host.test.dyndns.', qtype=ANY
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.c.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
index 4f17ee70a963267701749b6dfed257ead7a41ad6..8355330d42c04034b974f5737f492da4128df651 100644 (file)
@@ -1,65 +1,65 @@
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='host.test.dyndns.', qtype=ANY
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-0      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+0      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.c.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      NSEC    3600    host-1.test.dyndns. A RRSIG NSEC
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-0      e.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      NSEC    host-1.test.dyndns. A RRSIG NSEC
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+0      e.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      e.host.test.dyndns.     IN      NSEC    3600    host-1.test.dyndns. A RRSIG NSEC
-1      e.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      e.host.test.dyndns.     3600    IN      NSEC    host-1.test.dyndns. A RRSIG NSEC
+1      e.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
 Answer:
@@ -68,66 +68,66 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='host.test.dyndns.', qtype=ANY
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      NSEC    3600    d.host.test.dyndns. A RRSIG NSEC
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-0      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      NSEC    d.host.test.dyndns. A RRSIG NSEC
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+0      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    d.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    d.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    d.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    d.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    d.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    d.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.c.host.test.dyndns.', qtype=ANY
-0      d.host.test.dyndns.     IN      A       3600    127.0.0.1
-0      d.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-0      d.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-0      d.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      d.host.test.dyndns.     3600    IN      A       127.0.0.1
+0      d.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+0      d.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+0      d.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      NSEC    3600    host-1.test.dyndns. A RRSIG NSEC
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-0      e.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      NSEC    host-1.test.dyndns. A RRSIG NSEC
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+0      e.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      e.host.test.dyndns.     IN      NSEC    3600    host-1.test.dyndns. A RRSIG NSEC
-1      e.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      e.host.test.dyndns.     3600    IN      NSEC    host-1.test.dyndns. A RRSIG NSEC
+1      e.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
 Answer:
@@ -136,67 +136,67 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='host.test.dyndns.', qtype=ANY
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-0      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+0      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.c.host.test.dyndns.', qtype=ANY
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      NSEC    3600    host-1.test.dyndns. A RRSIG NSEC
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-0      e.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      NSEC    host-1.test.dyndns. A RRSIG NSEC
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+0      e.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      e.host.test.dyndns.     IN      NSEC    3600    host-1.test.dyndns. A RRSIG NSEC
-1      e.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      e.host.test.dyndns.     3600    IN      NSEC    host-1.test.dyndns. A RRSIG NSEC
+1      e.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
index d7f6752b312b428be6bc9e73d95f0eaf783b721c..33cd7231f7fcd6bedf245d2866a8d276560b8db1 100644 (file)
@@ -1,73 +1,73 @@
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='host.test.dyndns.', qtype=ANY
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 9ESKCI2JT1GR4EB97DR5MG4A1NNJN5HL
-1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR7 A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd ECLK033HJV6D7GBOLF2UAMVD7A9HJULJ
-1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 9ESKCI2JT1GR4EB97DR5MG4A1NNJN5HL
+1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR7 A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd ECLK033HJV6D7GBOLF2UAMVD7A9HJULJ
+1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.c.host.test.dyndns.', qtype=ANY
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd TL50O2E6J8AJ79R56OLQQ5H6R0H7HVLT
-1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd TL50O2E6J8AJ79R56OLQQ5H6R0H7HVLT
+1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
 Answer:
@@ -76,70 +76,70 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='host.test.dyndns.', qtype=ANY
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 9ESKCI2JT1GR4EB97DR5MG4A1NNJN5HL
-1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR7 A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd ECLK033HJV6D7GBOLF2UAMVD7A9HJULJ
-1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 9ESKCI2JT1GR4EB97DR5MG4A1NNJN5HL
+1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR7 A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd ECLK033HJV6D7GBOLF2UAMVD7A9HJULJ
+1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.c.host.test.dyndns.', qtype=ANY
-0      d.host.test.dyndns.     IN      A       3600    127.0.0.1
-0      d.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      d.host.test.dyndns.     3600    IN      A       127.0.0.1
+0      d.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd TL50O2E6J8AJ79R56OLQQ5H6R0H7HVLT
-1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd TL50O2E6J8AJ79R56OLQQ5H6R0H7HVLT
+1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
 Answer:
@@ -148,75 +148,75 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='host.test.dyndns.', qtype=ANY
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 9ESKCI2JT1GR4EB97DR5MG4A1NNJN5HL
-1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR7 A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd ECLK033HJV6D7GBOLF2UAMVD7A9HJULJ
-1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 9ESKCI2JT1GR4EB97DR5MG4A1NNJN5HL
+1      9eskci2jt1gr4eb97dr5mg4a1nnjn5hj.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR7 A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd ECLK033HJV6D7GBOLF2UAMVD7A9HJULJ
+1      eclk033hjv6d7gbolf2uamvd7a9hjulh.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
-1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FGUN0RU4OE3G76TR551HG97MPU37B6MJ
+1      fgun0ru4oe3g76tr551hg97mpu37b6mh.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.c.host.test.dyndns.', qtype=ANY
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
-1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 4LU40MIOAPF67TRU8ES6U204HIFN8P3N
+1      4lu40mioapf67tru8es6u204hifn8p3l.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
-1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd TL50O2E6J8AJ79R56OLQQ5H6R0H7HVLT
-1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 6F4A5UATP57LKHGBUSC1KCFC51T5DI0D
+1      6f4a5uatp57lkhgbusc1kcfc51t5di0b.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LMRSADK2BB62QPRUAULES5I5AP06CP56
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd TL50O2E6J8AJ79R56OLQQ5H6R0H7HVLT
+1      tl50o2e6j8aj79r56olqq5h6r0h7hvlr.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
index 6aa9b93946eb1643010a046a2561cfd4ab85eb35..f5aa214d6e2046713039e9d980afc5f9d3424f90 100644 (file)
@@ -1,69 +1,69 @@
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='host.test.dyndns.', qtype=ANY
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
 Answer:
@@ -72,68 +72,68 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='host.test.dyndns.', qtype=ANY
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      4lu40mioapf67tru8es6u204hifn8p3m.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 A RRSIG
-1      4lu40mioapf67tru8es6u204hifn8p3m.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4lu40mioapf67tru8es6u204hifn8p3m.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 A RRSIG
+1      4lu40mioapf67tru8es6u204hifn8p3m.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      4lu40mioapf67tru8es6u204hifn8p3m.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 A RRSIG
-1      4lu40mioapf67tru8es6u204hifn8p3m.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4lu40mioapf67tru8es6u204hifn8p3m.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 A RRSIG
+1      4lu40mioapf67tru8es6u204hifn8p3m.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.c.host.test.dyndns.', qtype=ANY
-0      d.host.test.dyndns.     IN      A       3600    127.0.0.1
-0      d.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      d.host.test.dyndns.     3600    IN      A       127.0.0.1
+0      d.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      4lu40mioapf67tru8es6u204hifn8p3m.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 A RRSIG
-1      4lu40mioapf67tru8es6u204hifn8p3m.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4lu40mioapf67tru8es6u204hifn8p3m.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 A RRSIG
+1      4lu40mioapf67tru8es6u204hifn8p3m.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
 Answer:
@@ -142,71 +142,71 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='host.test.dyndns.', qtype=ANY
-0      a.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      a.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      a.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      a.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.host.test.dyndns.', qtype=ANY
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.a.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='a.c.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.host.test.dyndns.', qtype=ANY
-0      e.host.test.dyndns.     IN      A       3600    1.1.1.1
-0      e.host.test.dyndns.     IN      RRSIG   3600    A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      e.host.test.dyndns.     3600    IN      A       1.1.1.1
+0      e.host.test.dyndns.     3600    IN      RRSIG   A 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='e.host.test.dyndns.', qtype=ANY
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
-1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd TNTCBDFSIHHD24NT96BQ9973VD019U43 A RRSIG
+1      tdhv9cbk13jg8drivldhsl3mji8qqqe2.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='f.host.test.dyndns.', qtype=ANY
index a6936c26a1fbc433f870bcc4c06093945da3b08a..ee089b487058d088d612c14a2a29803d9a723322 100644 (file)
@@ -1,11 +1,11 @@
-1 test.dyndns. IN NSEC 3600 cname1.test.dyndns. NS SOA MX RRSIG NSEC DNSKEY 1 test.dyndns. IN RRSIG 3600 NSEC 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ... 1 test.dyndns. IN RRSIG 3600 SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ... 1 test.dyndns. IN SOA 3600 ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400 2 . IN OPT 32768 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1 test.dyndns. 3600 IN NSEC cname1.test.dyndns. NS SOA MX RRSIG NSEC DNSKEY 1 test.dyndns. 3600 IN RRSIG NSEC 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ... 1 test.dyndns. 3600 IN RRSIG SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ... 1 test.dyndns. 3600 IN SOA ns1.test.dyndns. ahu.example.dyndns. [serial] 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='test.dyndns.', qtype=NSEC3PARAM
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=A
 Answer:
@@ -14,18 +14,18 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      NSEC3PARAM      86400   1 0 10 dcbe
-0      test.dyndns.    IN      RRSIG   86400   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      test.dyndns.    86400   IN      NSEC3PARAM      1 0 10 dcbe
+0      test.dyndns.    86400   IN      RRSIG   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
-1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   IN      NSEC3   3600    1 [flags] 10 dcbe EE295AK1NDT9O0RLL1A4RPPB4NAOV4QM
-1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lavvds84bcal6n6qnavn3q1u4jcpjev9.test.dyndns.   IN      NSEC3   3600    1 [flags] 10 dcbe N6N81NDT5KU73E19K457TOUB8E6D2LPM A RRSIG
-1      lavvds84bcal6n6qnavn3q1u4jcpjev9.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   3600    IN      NSEC3   1 [flags] 10 dcbe EE295AK1NDT9O0RLL1A4RPPB4NAOV4QM
+1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lavvds84bcal6n6qnavn3q1u4jcpjev9.test.dyndns.   3600    IN      NSEC3   1 [flags] 10 dcbe N6N81NDT5KU73E19K457TOUB8E6D2LPM A RRSIG
+1      lavvds84bcal6n6qnavn3q1u4jcpjev9.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=A
 Answer:
@@ -34,19 +34,19 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-1      test.dyndns.    IN      NSEC    3600    cname1.test.dyndns. NS SOA MX RRSIG NSEC DNSKEY
-1      test.dyndns.    IN      RRSIG   3600    NSEC 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      NSEC    cname1.test.dyndns. NS SOA MX RRSIG NSEC DNSKEY
+1      test.dyndns.    3600    IN      RRSIG   NSEC 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='test.dyndns.', qtype=NSEC3PARAM
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=A
index 59a80bdea9c3ab66a5d2d861bf28b59600fba91d..5aa0c2548da319fbf6c352fde059008251fb9692 100644 (file)
@@ -1,13 +1,13 @@
-0 test.dyndns. IN NSEC3PARAM 86400 1 0 1 abcd 0 test.dyndns. IN RRSIG 86400 NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ... 2 . IN OPT 32768 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+0 test.dyndns. 86400 IN NSEC3PARAM 1 0 1 abcd 0 test.dyndns. 86400 IN RRSIG NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ... 2 . 32768 IN OPT Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=A
 Answer:
@@ -16,18 +16,18 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      NSEC3PARAM      86400   1 0 10 dcbe
-0      test.dyndns.    IN      RRSIG   86400   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      test.dyndns.    86400   IN      NSEC3PARAM      1 0 10 dcbe
+0      test.dyndns.    86400   IN      RRSIG   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
-1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   IN      NSEC3   3600    1 [flags] 10 dcbe EE295AK1NDT9O0RLL1A4RPPB4NAOV4QM
-1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lavvds84bcal6n6qnavn3q1u4jcpjev9.test.dyndns.   IN      NSEC3   3600    1 [flags] 10 dcbe N6N81NDT5KU73E19K457TOUB8E6D2LPM A RRSIG
-1      lavvds84bcal6n6qnavn3q1u4jcpjev9.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   3600    IN      NSEC3   1 [flags] 10 dcbe EE295AK1NDT9O0RLL1A4RPPB4NAOV4QM
+1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lavvds84bcal6n6qnavn3q1u4jcpjev9.test.dyndns.   3600    IN      NSEC3   1 [flags] 10 dcbe N6N81NDT5KU73E19K457TOUB8E6D2LPM A RRSIG
+1      lavvds84bcal6n6qnavn3q1u4jcpjev9.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=A
 Answer:
@@ -36,20 +36,20 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-1      test.dyndns.    IN      NSEC    3600    cname1.test.dyndns. NS SOA MX RRSIG NSEC DNSKEY
-1      test.dyndns.    IN      RRSIG   3600    NSEC 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      NSEC    cname1.test.dyndns. NS SOA MX RRSIG NSEC DNSKEY
+1      test.dyndns.    3600    IN      RRSIG   NSEC 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='test.dyndns.', qtype=NSEC3PARAM
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=A
 Answer:
@@ -58,19 +58,19 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      NSEC3PARAM      86400   1 0 1 abcd
-0      test.dyndns.    IN      RRSIG   86400   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      test.dyndns.    86400   IN      NSEC3PARAM      1 0 1 abcd
+0      test.dyndns.    86400   IN      RRSIG   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=A
index 7b0f6dac4fe6fec978da9e76b908f55cc30c71e5..b45b302d8e7a9bf0528af11d353894d147d295cf 100644 (file)
@@ -1,13 +1,13 @@
-0 test.dyndns. IN NSEC3PARAM 86400 1 0 1 abcd 0 test.dyndns. IN RRSIG 86400 NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ... 2 . IN OPT 32768 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+0 test.dyndns. 86400 IN NSEC3PARAM 1 0 1 abcd 0 test.dyndns. 86400 IN RRSIG NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ... 2 . 32768 IN OPT Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=A
 Answer:
@@ -16,18 +16,18 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      NSEC3PARAM      86400   1 0 10 dcbe
-0      test.dyndns.    IN      RRSIG   86400   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      test.dyndns.    86400   IN      NSEC3PARAM      1 0 10 dcbe
+0      test.dyndns.    86400   IN      RRSIG   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
-1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   IN      NSEC3   3600    1 [flags] 10 dcbe EE295AK1NDT9O0RLL1A4RPPB4NAOV4QM
-1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lavvds84bcal6n6qnavn3q1u4jcpjev9.test.dyndns.   IN      NSEC3   3600    1 [flags] 10 dcbe PE3H59F3RU6VID4OK0T4TSU6D0NDRVHS A RRSIG
-1      lavvds84bcal6n6qnavn3q1u4jcpjev9.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   3600    IN      NSEC3   1 [flags] 10 dcbe EE295AK1NDT9O0RLL1A4RPPB4NAOV4QM
+1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lavvds84bcal6n6qnavn3q1u4jcpjev9.test.dyndns.   3600    IN      NSEC3   1 [flags] 10 dcbe PE3H59F3RU6VID4OK0T4TSU6D0NDRVHS A RRSIG
+1      lavvds84bcal6n6qnavn3q1u4jcpjev9.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=A
 Answer:
@@ -36,20 +36,20 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-1      test.dyndns.    IN      NSEC    3600    cname1.test.dyndns. NS SOA MX RRSIG NSEC DNSKEY
-1      test.dyndns.    IN      RRSIG   3600    NSEC 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      NSEC    cname1.test.dyndns. NS SOA MX RRSIG NSEC DNSKEY
+1      test.dyndns.    3600    IN      RRSIG   NSEC 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='test.dyndns.', qtype=NSEC3PARAM
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=A
 Answer:
@@ -58,19 +58,19 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      NSEC3PARAM      86400   1 0 1 abcd
-0      test.dyndns.    IN      RRSIG   86400   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      test.dyndns.    86400   IN      NSEC3PARAM      1 0 1 abcd
+0      test.dyndns.    86400   IN      RRSIG   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
-1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd FQU365VN7BR5CSV8CG6NE9V8HA6D008P A RRSIG
+1      dsa3ti9nu3apdsvl3f63qlvakv555sr6.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='c.host.test.dyndns.', qtype=A
index 15516a4f6f0eb9ec07fa41392ecb8996762e2fa0..0d56a0d51846c46b79068c01060f51cd64d9eafa 100644 (file)
@@ -1,11 +1,11 @@
-1 test.dyndns. IN NSEC 3600 cname1.test.dyndns. NS SOA MX RRSIG NSEC DNSKEY 1 test.dyndns. IN RRSIG 3600 NSEC 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ... 1 test.dyndns. IN RRSIG 3600 SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ... 1 test.dyndns. IN SOA 3600 ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400 2 . IN OPT 32768 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1 test.dyndns. 3600 IN NSEC cname1.test.dyndns. NS SOA MX RRSIG NSEC DNSKEY 1 test.dyndns. 3600 IN RRSIG NSEC 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ... 1 test.dyndns. 3600 IN RRSIG SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ... 1 test.dyndns. 3600 IN SOA ns1.test.dyndns. ahu.example.dyndns. [serial] 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='test.dyndns.', qtype=NSEC3PARAM
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=A
 Answer:
@@ -14,18 +14,18 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      NSEC3PARAM      86400   1 0 10 dcbe
-0      test.dyndns.    IN      RRSIG   86400   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      test.dyndns.    86400   IN      NSEC3PARAM      1 0 10 dcbe
+0      test.dyndns.    86400   IN      RRSIG   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
-1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   IN      NSEC3   3600    1 [flags] 10 dcbe EE295AK1NDT9O0RLL1A4RPPB4NAOV4QM
-1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      uba3qp1vffon9pq2r07e7ldrnh5mg90v.test.dyndns.   IN      NSEC3   3600    1 [flags] 10 dcbe 4KLD1OCH52V50U3NG1HM8R7960VRSVOM A RRSIG
-1      uba3qp1vffon9pq2r07e7ldrnh5mg90v.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   3600    IN      NSEC3   1 [flags] 10 dcbe EE295AK1NDT9O0RLL1A4RPPB4NAOV4QM
+1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      uba3qp1vffon9pq2r07e7ldrnh5mg90v.test.dyndns.   3600    IN      NSEC3   1 [flags] 10 dcbe 4KLD1OCH52V50U3NG1HM8R7960VRSVOM A RRSIG
+1      uba3qp1vffon9pq2r07e7ldrnh5mg90v.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=A
 Check if NSEC3PARAM record exists and A-record added
@@ -90,20 +90,20 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-1      test.dyndns.    IN      NSEC    3600    cname1.test.dyndns. NS SOA MX RRSIG NSEC DNSKEY
-1      test.dyndns.    IN      RRSIG   3600    NSEC 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      NSEC    cname1.test.dyndns. NS SOA MX RRSIG NSEC DNSKEY
+1      test.dyndns.    3600    IN      RRSIG   NSEC 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='test.dyndns.', qtype=NSEC3PARAM
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=A
 Check if NSEC3PARAM is deleted
index 0d94a18e7943ee218739ca5aca44ee93e172f4d9..a98d5e063bfff5b76396c8bc97c455d0974e71ba 100644 (file)
@@ -1,13 +1,13 @@
-0 test.dyndns. IN NSEC3PARAM 86400 1 0 1 abcd 0 test.dyndns. IN RRSIG 86400 NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ... 2 . IN OPT 32768 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+0 test.dyndns. 86400 IN NSEC3PARAM 1 0 1 abcd 0 test.dyndns. 86400 IN RRSIG NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ... 2 . 32768 IN OPT Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=A
 Answer:
@@ -16,18 +16,18 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      NSEC3PARAM      86400   1 0 10 dcbe
-0      test.dyndns.    IN      RRSIG   86400   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      test.dyndns.    86400   IN      NSEC3PARAM      1 0 10 dcbe
+0      test.dyndns.    86400   IN      RRSIG   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
-1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   IN      NSEC3   3600    1 [flags] 10 dcbe EE295AK1NDT9O0RLL1A4RPPB4NAOV4QM
-1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      uba3qp1vffon9pq2r07e7ldrnh5mg90v.test.dyndns.   IN      NSEC3   3600    1 [flags] 10 dcbe 4KLD1OCH52V50U3NG1HM8R7960VRSVOM A RRSIG
-1      uba3qp1vffon9pq2r07e7ldrnh5mg90v.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   3600    IN      NSEC3   1 [flags] 10 dcbe EE295AK1NDT9O0RLL1A4RPPB4NAOV4QM
+1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      uba3qp1vffon9pq2r07e7ldrnh5mg90v.test.dyndns.   3600    IN      NSEC3   1 [flags] 10 dcbe 4KLD1OCH52V50U3NG1HM8R7960VRSVOM A RRSIG
+1      uba3qp1vffon9pq2r07e7ldrnh5mg90v.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=A
 Check if NSEC3PARAM record exists and A-record added
@@ -92,20 +92,20 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-1      test.dyndns.    IN      NSEC    3600    cname1.test.dyndns. NS SOA MX RRSIG NSEC DNSKEY
-1      test.dyndns.    IN      RRSIG   3600    NSEC 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      NSEC    cname1.test.dyndns. NS SOA MX RRSIG NSEC DNSKEY
+1      test.dyndns.    3600    IN      RRSIG   NSEC 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='test.dyndns.', qtype=NSEC3PARAM
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=A
 Check if NSEC3PARAM is deleted
@@ -170,20 +170,20 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      NSEC3PARAM      86400   1 0 1 abcd
-0      test.dyndns.    IN      RRSIG   86400   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      test.dyndns.    86400   IN      NSEC3PARAM      1 0 1 abcd
+0      test.dyndns.    86400   IN      RRSIG   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=A
 Check if NSEC3PARAM is added again
index 73b01596449411427c2aa96316f42a2a0e47bcfc..2dd0f2965f1e6ea9de429606f46a4c82f59bd278 100644 (file)
@@ -1,13 +1,13 @@
-0 test.dyndns. IN NSEC3PARAM 86400 1 0 1 abcd 0 test.dyndns. IN RRSIG 86400 NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ... 2 . IN OPT 32768 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+0 test.dyndns. 86400 IN NSEC3PARAM 1 0 1 abcd 0 test.dyndns. 86400 IN RRSIG NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ... 2 . 32768 IN OPT Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=A
 Answer:
@@ -16,18 +16,18 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      NSEC3PARAM      86400   1 0 10 dcbe
-0      test.dyndns.    IN      RRSIG   86400   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      test.dyndns.    86400   IN      NSEC3PARAM      1 0 10 dcbe
+0      test.dyndns.    86400   IN      RRSIG   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
-1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   IN      NSEC3   3600    1 [flags] 10 dcbe EE295AK1NDT9O0RLL1A4RPPB4NAOV4QM
-1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-1      uba3qp1vffon9pq2r07e7ldrnh5mg90v.test.dyndns.   IN      NSEC3   3600    1 [flags] 10 dcbe 4KLD1OCH52V50U3NG1HM8R7960VRSVOM A RRSIG
-1      uba3qp1vffon9pq2r07e7ldrnh5mg90v.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   3600    IN      NSEC3   1 [flags] 10 dcbe EE295AK1NDT9O0RLL1A4RPPB4NAOV4QM
+1      ac2jl1kik929tr9i5rfcmbucm547n51a.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      uba3qp1vffon9pq2r07e7ldrnh5mg90v.test.dyndns.   3600    IN      NSEC3   1 [flags] 10 dcbe 4KLD1OCH52V50U3NG1HM8R7960VRSVOM A RRSIG
+1      uba3qp1vffon9pq2r07e7ldrnh5mg90v.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=A
 Check if NSEC3PARAM record exists and A-record added
@@ -88,20 +88,20 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-1      test.dyndns.    IN      NSEC    3600    cname1.test.dyndns. NS SOA MX RRSIG NSEC DNSKEY
-1      test.dyndns.    IN      RRSIG   3600    NSEC 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.dyndns.    3600    IN      NSEC    cname1.test.dyndns. NS SOA MX RRSIG NSEC DNSKEY
+1      test.dyndns.    3600    IN      RRSIG   NSEC 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 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='test.dyndns.', qtype=NSEC3PARAM
-1      a.host.test.dyndns.     IN      NSEC    3600    e.host.test.dyndns. A RRSIG NSEC
-1      a.host.test.dyndns.     IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      delete-add.test.dyndns. IN      NSEC    3600    a.host.test.dyndns. A TXT RRSIG NSEC
-1      delete-add.test.dyndns. IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a.host.test.dyndns.     3600    IN      NSEC    e.host.test.dyndns. A RRSIG NSEC
+1      a.host.test.dyndns.     3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      delete-add.test.dyndns. 3600    IN      NSEC    a.host.test.dyndns. A TXT RRSIG NSEC
+1      delete-add.test.dyndns. 3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=A
 Check if NSEC3PARAM is deleted
@@ -166,20 +166,20 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      NSEC3PARAM      86400   1 0 1 abcd
-0      test.dyndns.    IN      RRSIG   86400   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
-2      .       IN      OPT     32768   
+0      test.dyndns.    86400   IN      NSEC3PARAM      1 0 1 abcd
+0      test.dyndns.    86400   IN      RRSIG   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] test.dyndns. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=NSEC3PARAM
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
-1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
-1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      NSEC3   3600    1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
-1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd 8PQJV4B3M0LCFMVAE0HP394LC154L1I7 CNAME RRSIG
+1      4i84rosksbmegcqfnkf6n6ci093h7rq4.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd DSA3TI9NU3APDSVL3F63QLVAKV555SR6 A RRSIG
+1      8ti9tgh8d81frr5vr4o1o5bvko42v9dq.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      NSEC3   1 [flags] 1 abcd LRESBBP3LV8BLGJ9FSGTDMM4Q7VJ3D6J
+1      lmrsadk2bb62qpruaules5i5ap06cp55.test.dyndns.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.dyndns. ...
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='b.host.test.dyndns.', qtype=A
 Check if NSEC3PARAM is added again
index 5c7481b4306164bce05546e1d2cc6cdc61121df4..9045ae3162af6d44f150f4a2ef3c9457160fd499 100644 (file)
@@ -1,4 +1,4 @@
-0      replace.test.dyndns.    IN      A       3600    127.0.0.1
+0      replace.test.dyndns.    3600    IN      A       127.0.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='replace.test.dyndns.', qtype=A
 Answer:
@@ -7,7 +7,7 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      replace.test.dyndns.    IN      A       3600    127.0.0.2
+0      replace.test.dyndns.    3600    IN      A       127.0.0.2
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='replace.test.dyndns.', qtype=A
 Answer:
@@ -16,6 +16,6 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      replace.test.dyndns.    IN      A       3600    127.0.0.1
+0      replace.test.dyndns.    3600    IN      A       127.0.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='replace.test.dyndns.', qtype=A
index 790cceac55148d1bee00999b99d8fcf03b038d4f..3fe39a56b3fd83fbc3d0db241482fce3beb15de5 100644 (file)
@@ -1,4 +1,4 @@
-0      cname1.test.dyndns.     IN      CNAME   3600    host-1.test.dyndns.
+0      cname1.test.dyndns.     3600    IN      CNAME   host-1.test.dyndns.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cname1.test.dyndns.', qtype=CNAME
 Answer:
@@ -7,7 +7,7 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      cname1.test.dyndns.     IN      CNAME   3600    host-2.test.dyndns.
+0      cname1.test.dyndns.     3600    IN      CNAME   host-2.test.dyndns.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cname1.test.dyndns.', qtype=CNAME
 Answer:
@@ -16,6 +16,6 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      cname1.test.dyndns.     IN      CNAME   3600    host-1.test.dyndns.
+0      cname1.test.dyndns.     3600    IN      CNAME   host-1.test.dyndns.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cname1.test.dyndns.', qtype=CNAME
index e14d411ef6d51f8ccd768ae24f07801003fec273..d2c3212b04ecf9cfa7361a9818fb19e177638ff5 100644 (file)
@@ -1,7 +1,7 @@
-0      test.dyndns.    IN      MX      3600    10 host-1.test.dyndns.
-0      test.dyndns.    IN      MX      3600    20 host-2.test.dyndns.
-2      host-1.test.dyndns.     IN      A       3600    127.0.0.101
-2      host-2.test.dyndns.     IN      A       3600    127.0.0.102
+0      test.dyndns.    3600    IN      MX      10 host-1.test.dyndns.
+0      test.dyndns.    3600    IN      MX      20 host-2.test.dyndns.
+2      host-1.test.dyndns.     3600    IN      A       127.0.0.101
+2      host-2.test.dyndns.     3600    IN      A       127.0.0.102
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=MX
 Answer:
@@ -10,10 +10,10 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      MX      3000    10 host-1.test.dyndns.
-0      test.dyndns.    IN      MX      3000    20 host-2.test.dyndns.
-2      host-1.test.dyndns.     IN      A       3600    127.0.0.101
-2      host-2.test.dyndns.     IN      A       3600    127.0.0.102
+0      test.dyndns.    3000    IN      MX      10 host-1.test.dyndns.
+0      test.dyndns.    3000    IN      MX      20 host-2.test.dyndns.
+2      host-1.test.dyndns.     3600    IN      A       127.0.0.101
+2      host-2.test.dyndns.     3600    IN      A       127.0.0.102
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=MX
 Answer:
@@ -22,9 +22,9 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      MX      3600    10 host-1.test.dyndns.
-0      test.dyndns.    IN      MX      3600    20 host-2.test.dyndns.
-2      host-1.test.dyndns.     IN      A       3600    127.0.0.101
-2      host-2.test.dyndns.     IN      A       3600    127.0.0.102
+0      test.dyndns.    3600    IN      MX      10 host-1.test.dyndns.
+0      test.dyndns.    3600    IN      MX      20 host-2.test.dyndns.
+2      host-1.test.dyndns.     3600    IN      A       127.0.0.101
+2      host-2.test.dyndns.     3600    IN      A       127.0.0.102
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=MX
index f2032dd7fb718fc4009dcfe313901820adbf695b..3c729d697ded52e4662a9d24c666090225ba03ac 100644 (file)
@@ -1,4 +1,4 @@
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='srv.test.dyndns.', qtype=SRV
 Answer:
@@ -7,7 +7,7 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      srv.test.dyndns.        IN      SRV     3600    0 100 389 server1.
+0      srv.test.dyndns.        3600    IN      SRV     0 100 389 server1.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='srv.test.dyndns.', qtype=SRV
 Answer:
@@ -16,10 +16,10 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      srv.test.dyndns.        IN      SRV     3601    0 100 389 server1.
-0      srv.test.dyndns.        IN      SRV     3601    1 100 389 server2.
+0      srv.test.dyndns.        3601    IN      SRV     0 100 389 server1.
+0      srv.test.dyndns.        3601    IN      SRV     1 100 389 server2.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='srv.test.dyndns.', qtype=SRV
-1      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+1      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='srv.test.dyndns.', qtype=SRV
index 076b75e053f3c6a8a85e4f994eea6526ee7324d8..54857efc259fa5c101aebe6e9f689027fb34bec1 100644 (file)
@@ -1,4 +1,4 @@
-0      ttl.test.dyndns.        IN      A       3600    127.0.0.1
+0      ttl.test.dyndns.        3600    IN      A       127.0.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='ttl.test.dyndns.', qtype=A
 Answer:
@@ -7,7 +7,7 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      ttl.test.dyndns.        IN      A       31337   127.0.0.1
+0      ttl.test.dyndns.        31337   IN      A       127.0.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='ttl.test.dyndns.', qtype=A
 Answer:
@@ -16,6 +16,6 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      ttl.test.dyndns.        IN      A       3600    127.0.0.1
+0      ttl.test.dyndns.        3600    IN      A       127.0.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='ttl.test.dyndns.', qtype=A
index 26c13b2975778e795a87bb3b54e3aeb4ba102af5..8bfcde8d777767c0d6b47ba6768966fd94698e06 100644 (file)
@@ -1,4 +1,4 @@
-0      test.dyndns.    IN      SOA     3600    ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
+0      test.dyndns.    3600    IN      SOA     ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=SOA
 Answer:
@@ -7,7 +7,7 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      SOA     3600    ns2.test.dyndns. ahu.example.dyndns. 2050101000 28800 7200 604800 86400
+0      test.dyndns.    3600    IN      SOA     ns2.test.dyndns. ahu.example.dyndns. 2050101000 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=SOA
 Answer:
@@ -16,7 +16,7 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      SOA     3600    ns2.test.dyndns. ahu.example.dyndns. 2050101000 28800 7200 604800 86400
+0      test.dyndns.    3600    IN      SOA     ns2.test.dyndns. ahu.example.dyndns. 2050101000 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=SOA
 Answer:
@@ -25,7 +25,7 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      SOA     3600    ns2.test.dyndns. ahu.example.dyndns. 2050101000 28800 7200 604800 86400
+0      test.dyndns.    3600    IN      SOA     ns2.test.dyndns. ahu.example.dyndns. 2050101000 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=SOA
 Answer:
@@ -34,6 +34,6 @@ Answer:
 ;; ZONE SECTION:
 ;test.dyndns.                  IN      SOA
 
-0      test.dyndns.    IN      SOA     3600    ns2.test.dyndns. ahu.example.dyndns. 2050101001 28800 7200 604800 86400
+0      test.dyndns.    3600    IN      SOA     ns2.test.dyndns. ahu.example.dyndns. 2050101001 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.dyndns.', qtype=SOA
index 4a1e67fe769c5a45db32a2c76711210cc2063c42..ee2a9e02168538d9fef23fc3ad314d71d461ebfb 100644 (file)
@@ -1,3 +1,3 @@
-0      hightxt.test.com.       IN      TXT     3600    "v=spf1 mx ip4:78.46.192.210 \226\128\147all"
+0      hightxt.test.com.       3600    IN      TXT     "v=spf1 mx ip4:78.46.192.210 \226\128\147all"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='hightxt.test.com.', qtype=TXT
index 01b4cebca5eb0ba0cd6d97f40ecb5f0324b70112..5e8197a80b40cfd3acf32fc0076f5e6523cbd843 100644 (file)
@@ -1,3 +1,3 @@
-0      aland.test.com. IN      TXT     3600    "\195\133LAND ISLANDS"
+0      aland.test.com. 3600    IN      TXT     "\195\133LAND ISLANDS"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='aland.test.com.', qtype=TXT
index a9601f9c7d15bed45c3234ffaf0b72dcb2b7181e..8badf963d83853e33fc6c2819432ce955be6c383 100644 (file)
@@ -1,16 +1,16 @@
-0      google-alias.example.com.       IN      A       [ttl]   8.8.8.8
+0      google-alias.example.com.       [ttl]   IN      A       8.8.8.8
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='google-alias.example.com.', qtype=A
-0      google-alias.example.com.       IN      AAAA    [ttl]   2001:4860:4860::8888
+0      google-alias.example.com.       [ttl]   IN      AAAA    2001:4860:4860::8888
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='google-alias.example.com.', qtype=AAAA
-0      google-alias.example.com.       IN      A       [ttl]   8.8.8.8
+0      google-alias.example.com.       [ttl]   IN      A       8.8.8.8
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='google-alias.example.com.', qtype=A
-0      google-alias.example.com.       IN      AAAA    [ttl]   2001:4860:4860::8888
+0      google-alias.example.com.       [ttl]   IN      AAAA    2001:4860:4860::8888
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='google-alias.example.com.', qtype=AAAA
-1      example.com.    IN      SOA     [ttl]   ns1.example.com. ahu.example.com. [serial] 28800 7200 604800 86400
-2      .       IN      OPT     [ttl]   
+1      example.com.    [ttl]   IN      SOA     ns1.example.com. ahu.example.com. [serial] 28800 7200 604800 86400
+2      .       [ttl]   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='google-alias1.example.com.', qtype=A
index b5ab5e53d624b8f575ce90c0ebee2b9bcc2d2df5..0877d59edb69a8bdb89affd0122046fb154c518c 100644 (file)
@@ -1,21 +1,21 @@
-0      google-alias.example.com.       IN      A       [ttl]   8.8.8.8
+0      google-alias.example.com.       [ttl]   IN      A       8.8.8.8
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='google-alias.example.com.', qtype=A
-0      google-alias.example.com.       IN      AAAA    [ttl]   2001:4860:4860::8888
+0      google-alias.example.com.       [ttl]   IN      AAAA    2001:4860:4860::8888
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='google-alias.example.com.', qtype=AAAA
-0      google-alias.example.com.       IN      A       [ttl]   8.8.8.8
+0      google-alias.example.com.       [ttl]   IN      A       8.8.8.8
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='google-alias.example.com.', qtype=A
-0      google-alias.example.com.       IN      AAAA    [ttl]   2001:4860:4860::8888
+0      google-alias.example.com.       [ttl]   IN      AAAA    2001:4860:4860::8888
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='google-alias.example.com.', qtype=AAAA
-1      example.com.    IN      NSEC    [ttl]   _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
-1      example.com.    IN      RRSIG   [ttl]   NSEC 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   [ttl]   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     [ttl]   ns1.example.com. ahu.example.com. [serial] 28800 7200 604800 86400
-1      google-alias.example.com.       IN      NSEC    [ttl]   hightype.example.com. A AAAA RRSIG NSEC
-1      google-alias.example.com.       IN      RRSIG   [ttl]   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     [ttl]   
+1      example.com.    [ttl]   IN      NSEC    _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
+1      example.com.    [ttl]   IN      RRSIG   NSEC 13 2 86400 [expiry] [inception] [keytag] example.com. ...
+1      example.com.    [ttl]   IN      RRSIG   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
+1      example.com.    [ttl]   IN      SOA     ns1.example.com. ahu.example.com. [serial] 28800 7200 604800 86400
+1      google-alias.example.com.       [ttl]   IN      NSEC    hightype.example.com. A AAAA RRSIG NSEC
+1      google-alias.example.com.       [ttl]   IN      RRSIG   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       [ttl]   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='google-alias1.example.com.', qtype=A
index e20f2979410944de6141389970d96c5216d85e91..7219d8c56a29d3413820548dc92be1c1136946cd 100644 (file)
@@ -1,3 +1,3 @@
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+1      example.com.    86400   IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='google-alias.example.com.', qtype=MX
index 5040539f70fb8d2961b39db28ea65475944250f1..2027ce860b9e5739c642c0da8c6f00789160b1d5 100644 (file)
@@ -1,4 +1,4 @@
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      example.com.    86400   IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nxdomain.example.com.', qtype=ANY
index 5775bc95ccbe099f48797ce9525f80e354f92a2d..77ae0649be34bc6d5d81d4fe990f9cd85043bc83 100644 (file)
@@ -1,9 +1,9 @@
-1      example.com.    IN      NSEC    86400   _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
-1      example.com.    IN      RRSIG   86400   NSEC 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      nxd.example.com.        IN      NSEC    86400   outpost.example.com. CNAME RRSIG NSEC
-1      nxd.example.com.        IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      example.com.    86400   IN      NSEC    _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
+1      example.com.    86400   IN      RRSIG   NSEC 13 2 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      nxd.example.com.        86400   IN      NSEC    outpost.example.com. CNAME RRSIG NSEC
+1      nxd.example.com.        86400   IN      RRSIG   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nxdomain.example.com.', qtype=ANY
index 6d2175946fb13e1969075fca3572ce93df9e47f4..67e82152bf991063c82964be415038e674b9ab7a 100644 (file)
@@ -1,11 +1,11 @@
-1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 9FAG9508OQU3M22QAC0U5EQGG45V8CF2
-1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      onnhv82alu3om3l4fkfes49n0j2c71ba.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd ONNHV82ALU3OM3L4FKFES49N0J2C71BC
-1      onnhv82alu3om3l4fkfes49n0j2c71ba.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 9FAG9508OQU3M22QAC0U5EQGG45V8CF2
+1      9fag9508oqu3m22qac0u5eqgg45v8cf0.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      onnhv82alu3om3l4fkfes49n0j2c71ba.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd ONNHV82ALU3OM3L4FKFES49N0J2C71BC
+1      onnhv82alu3om3l4fkfes49n0j2c71ba.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nxdomain.example.com.', qtype=ANY
index f737c03997cae64c8c7bc92a4de750d06b32139e..059427a40e6e4ac57324de11e1c6d243bb0dd595 100644 (file)
@@ -1,11 +1,11 @@
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      onn5kjcskcfqisao7tmqpjkp5kkh111o.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd ONNU1VP51T2LDROTDVQ10HVLRQQV2UAA A RRSIG
-1      onn5kjcskcfqisao7tmqpjkp5kkh111o.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.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      onn5kjcskcfqisao7tmqpjkp5kkh111o.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd ONNU1VP51T2LDROTDVQ10HVLRQQV2UAA A RRSIG
+1      onn5kjcskcfqisao7tmqpjkp5kkh111o.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nxdomain.example.com.', qtype=ANY
index 2571b0576f11c940267a977a1dec541037ac0fb0..cd6edffd0d4eb6ca778aeaad4e4005635f6bb2cb 100644 (file)
@@ -1,13 +1,13 @@
-0      example.com.    IN      MX      120     10 smtp-servers.example.com.
-0      example.com.    IN      MX      120     15 smtp-servers.test.com.
-0      example.com.    IN      NS      120     ns1.example.com.
-0      example.com.    IN      NS      120     ns2.example.com.
-0      example.com.    IN      SOA     100000  ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
-2      ns1.example.com.        IN      A       120     192.168.1.1
-2      ns2.example.com.        IN      A       120     192.168.1.2
-2      smtp-servers.example.com.       IN      A       120     192.168.0.2
-2      smtp-servers.example.com.       IN      A       120     192.168.0.3
-2      smtp-servers.example.com.       IN      A       120     192.168.0.4
+0      example.com.    100000  IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+0      example.com.    120     IN      MX      10 smtp-servers.example.com.
+0      example.com.    120     IN      MX      15 smtp-servers.test.com.
+0      example.com.    120     IN      NS      ns1.example.com.
+0      example.com.    120     IN      NS      ns2.example.com.
+2      .       32768   IN      OPT     
+2      ns1.example.com.        120     IN      A       192.168.1.1
+2      ns2.example.com.        120     IN      A       192.168.1.2
+2      smtp-servers.example.com.       120     IN      A       192.168.0.2
+2      smtp-servers.example.com.       120     IN      A       192.168.0.3
+2      smtp-servers.example.com.       120     IN      A       192.168.0.4
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='example.com.', qtype=ANY
index 6032816825aa0524f0f6dfddb71e6ff725bffbbb..51f847caa115f095fac09b719f54638adde186ec 100644 (file)
@@ -1,23 +1,23 @@
-0      example.com.    IN      DNSKEY  86400   257 3 13 ...
-0      example.com.    IN      MX      120     10 smtp-servers.example.com.
-0      example.com.    IN      MX      120     15 smtp-servers.test.com.
-0      example.com.    IN      NS      120     ns1.example.com.
-0      example.com.    IN      NS      120     ns2.example.com.
-0      example.com.    IN      NSEC    86400   _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
-0      example.com.    IN      RRSIG   100000  SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-0      example.com.    IN      RRSIG   120     MX 13 2 120 [expiry] [inception] [keytag] example.com. ...
-0      example.com.    IN      RRSIG   120     NS 13 2 120 [expiry] [inception] [keytag] example.com. ...
-0      example.com.    IN      RRSIG   86400   DNSKEY 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-0      example.com.    IN      RRSIG   86400   NSEC 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-0      example.com.    IN      SOA     100000  ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
-2      ns1.example.com.        IN      A       120     192.168.1.1
-2      ns1.example.com.        IN      RRSIG   120     A 13 3 120 [expiry] [inception] [keytag] example.com. ...
-2      ns2.example.com.        IN      A       120     192.168.1.2
-2      ns2.example.com.        IN      RRSIG   120     A 13 3 120 [expiry] [inception] [keytag] example.com. ...
-2      smtp-servers.example.com.       IN      A       120     192.168.0.2
-2      smtp-servers.example.com.       IN      A       120     192.168.0.3
-2      smtp-servers.example.com.       IN      A       120     192.168.0.4
-2      smtp-servers.example.com.       IN      RRSIG   120     A 13 3 120 [expiry] [inception] [keytag] example.com. ...
+0      example.com.    100000  IN      RRSIG   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
+0      example.com.    100000  IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+0      example.com.    120     IN      MX      10 smtp-servers.example.com.
+0      example.com.    120     IN      MX      15 smtp-servers.test.com.
+0      example.com.    120     IN      NS      ns1.example.com.
+0      example.com.    120     IN      NS      ns2.example.com.
+0      example.com.    120     IN      RRSIG   MX 13 2 120 [expiry] [inception] [keytag] example.com. ...
+0      example.com.    120     IN      RRSIG   NS 13 2 120 [expiry] [inception] [keytag] example.com. ...
+0      example.com.    86400   IN      DNSKEY  257 3 13 ...
+0      example.com.    86400   IN      NSEC    _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
+0      example.com.    86400   IN      RRSIG   DNSKEY 13 2 86400 [expiry] [inception] [keytag] example.com. ...
+0      example.com.    86400   IN      RRSIG   NSEC 13 2 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
+2      ns1.example.com.        120     IN      A       192.168.1.1
+2      ns1.example.com.        120     IN      RRSIG   A 13 3 120 [expiry] [inception] [keytag] example.com. ...
+2      ns2.example.com.        120     IN      A       192.168.1.2
+2      ns2.example.com.        120     IN      RRSIG   A 13 3 120 [expiry] [inception] [keytag] example.com. ...
+2      smtp-servers.example.com.       120     IN      A       192.168.0.2
+2      smtp-servers.example.com.       120     IN      A       192.168.0.3
+2      smtp-servers.example.com.       120     IN      A       192.168.0.4
+2      smtp-servers.example.com.       120     IN      RRSIG   A 13 3 120 [expiry] [inception] [keytag] example.com. ...
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='example.com.', qtype=ANY
index b36512962c9772cdb6cc0fea1f0ac7a546133642..15588c0f170efc78a1724d227a663cb2f99961a1 100644 (file)
@@ -1,23 +1,23 @@
-0      example.com.    IN      DNSKEY  86400   257 3 13 ...
-0      example.com.    IN      MX      120     10 smtp-servers.example.com.
-0      example.com.    IN      MX      120     15 smtp-servers.test.com.
-0      example.com.    IN      NS      120     ns1.example.com.
-0      example.com.    IN      NS      120     ns2.example.com.
-0      example.com.    IN      NSEC3PARAM      86400   1 0 1 abcd
-0      example.com.    IN      RRSIG   100000  SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-0      example.com.    IN      RRSIG   120     MX 13 2 120 [expiry] [inception] [keytag] example.com. ...
-0      example.com.    IN      RRSIG   120     NS 13 2 120 [expiry] [inception] [keytag] example.com. ...
-0      example.com.    IN      RRSIG   86400   DNSKEY 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-0      example.com.    IN      RRSIG   86400   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-0      example.com.    IN      SOA     100000  ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
-2      ns1.example.com.        IN      A       120     192.168.1.1
-2      ns1.example.com.        IN      RRSIG   120     A 13 3 120 [expiry] [inception] [keytag] example.com. ...
-2      ns2.example.com.        IN      A       120     192.168.1.2
-2      ns2.example.com.        IN      RRSIG   120     A 13 3 120 [expiry] [inception] [keytag] example.com. ...
-2      smtp-servers.example.com.       IN      A       120     192.168.0.2
-2      smtp-servers.example.com.       IN      A       120     192.168.0.3
-2      smtp-servers.example.com.       IN      A       120     192.168.0.4
-2      smtp-servers.example.com.       IN      RRSIG   120     A 13 3 120 [expiry] [inception] [keytag] example.com. ...
+0      example.com.    100000  IN      RRSIG   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
+0      example.com.    100000  IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+0      example.com.    120     IN      MX      10 smtp-servers.example.com.
+0      example.com.    120     IN      MX      15 smtp-servers.test.com.
+0      example.com.    120     IN      NS      ns1.example.com.
+0      example.com.    120     IN      NS      ns2.example.com.
+0      example.com.    120     IN      RRSIG   MX 13 2 120 [expiry] [inception] [keytag] example.com. ...
+0      example.com.    120     IN      RRSIG   NS 13 2 120 [expiry] [inception] [keytag] example.com. ...
+0      example.com.    86400   IN      DNSKEY  257 3 13 ...
+0      example.com.    86400   IN      NSEC3PARAM      1 0 1 abcd
+0      example.com.    86400   IN      RRSIG   DNSKEY 13 2 86400 [expiry] [inception] [keytag] example.com. ...
+0      example.com.    86400   IN      RRSIG   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
+2      ns1.example.com.        120     IN      A       192.168.1.1
+2      ns1.example.com.        120     IN      RRSIG   A 13 3 120 [expiry] [inception] [keytag] example.com. ...
+2      ns2.example.com.        120     IN      A       192.168.1.2
+2      ns2.example.com.        120     IN      RRSIG   A 13 3 120 [expiry] [inception] [keytag] example.com. ...
+2      smtp-servers.example.com.       120     IN      A       192.168.0.2
+2      smtp-servers.example.com.       120     IN      A       192.168.0.3
+2      smtp-servers.example.com.       120     IN      A       192.168.0.4
+2      smtp-servers.example.com.       120     IN      RRSIG   A 13 3 120 [expiry] [inception] [keytag] example.com. ...
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='example.com.', qtype=ANY
index b36512962c9772cdb6cc0fea1f0ac7a546133642..15588c0f170efc78a1724d227a663cb2f99961a1 100644 (file)
@@ -1,23 +1,23 @@
-0      example.com.    IN      DNSKEY  86400   257 3 13 ...
-0      example.com.    IN      MX      120     10 smtp-servers.example.com.
-0      example.com.    IN      MX      120     15 smtp-servers.test.com.
-0      example.com.    IN      NS      120     ns1.example.com.
-0      example.com.    IN      NS      120     ns2.example.com.
-0      example.com.    IN      NSEC3PARAM      86400   1 0 1 abcd
-0      example.com.    IN      RRSIG   100000  SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-0      example.com.    IN      RRSIG   120     MX 13 2 120 [expiry] [inception] [keytag] example.com. ...
-0      example.com.    IN      RRSIG   120     NS 13 2 120 [expiry] [inception] [keytag] example.com. ...
-0      example.com.    IN      RRSIG   86400   DNSKEY 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-0      example.com.    IN      RRSIG   86400   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-0      example.com.    IN      SOA     100000  ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
-2      ns1.example.com.        IN      A       120     192.168.1.1
-2      ns1.example.com.        IN      RRSIG   120     A 13 3 120 [expiry] [inception] [keytag] example.com. ...
-2      ns2.example.com.        IN      A       120     192.168.1.2
-2      ns2.example.com.        IN      RRSIG   120     A 13 3 120 [expiry] [inception] [keytag] example.com. ...
-2      smtp-servers.example.com.       IN      A       120     192.168.0.2
-2      smtp-servers.example.com.       IN      A       120     192.168.0.3
-2      smtp-servers.example.com.       IN      A       120     192.168.0.4
-2      smtp-servers.example.com.       IN      RRSIG   120     A 13 3 120 [expiry] [inception] [keytag] example.com. ...
+0      example.com.    100000  IN      RRSIG   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
+0      example.com.    100000  IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+0      example.com.    120     IN      MX      10 smtp-servers.example.com.
+0      example.com.    120     IN      MX      15 smtp-servers.test.com.
+0      example.com.    120     IN      NS      ns1.example.com.
+0      example.com.    120     IN      NS      ns2.example.com.
+0      example.com.    120     IN      RRSIG   MX 13 2 120 [expiry] [inception] [keytag] example.com. ...
+0      example.com.    120     IN      RRSIG   NS 13 2 120 [expiry] [inception] [keytag] example.com. ...
+0      example.com.    86400   IN      DNSKEY  257 3 13 ...
+0      example.com.    86400   IN      NSEC3PARAM      1 0 1 abcd
+0      example.com.    86400   IN      RRSIG   DNSKEY 13 2 86400 [expiry] [inception] [keytag] example.com. ...
+0      example.com.    86400   IN      RRSIG   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
+2      ns1.example.com.        120     IN      A       192.168.1.1
+2      ns1.example.com.        120     IN      RRSIG   A 13 3 120 [expiry] [inception] [keytag] example.com. ...
+2      ns2.example.com.        120     IN      A       192.168.1.2
+2      ns2.example.com.        120     IN      RRSIG   A 13 3 120 [expiry] [inception] [keytag] example.com. ...
+2      smtp-servers.example.com.       120     IN      A       192.168.0.2
+2      smtp-servers.example.com.       120     IN      A       192.168.0.3
+2      smtp-servers.example.com.       120     IN      A       192.168.0.4
+2      smtp-servers.example.com.       120     IN      RRSIG   A 13 3 120 [expiry] [inception] [keytag] example.com. ...
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='example.com.', qtype=ANY
index 238c9957f2cb924f09ad4954e6eaf5432cc10bd7..48fb94c40413872a6f0800a74a8e3df2587cc0be 100644 (file)
@@ -1,3 +1,3 @@
-2      .       IN      OPT     0       
+2      .       0       IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 1, AA: 1, opcode: 0
 Reply to question for qname='example.com.', qtype=ANY
index 299678deebef58d650b2f7508927818f75d47b23..d9ceb6067902d04dd2d76f4c40f691787e4705f2 100644 (file)
@@ -1,7 +1,7 @@
-0      www.something.wtest.com.        IN      A       3600    4.3.2.1
-0      www.something.wtest.com.        IN      RRSIG   3600    A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      a.something.wtest.com.  IN      NSEC    3600    wtest.com. A RRSIG NSEC
-1      a.something.wtest.com.  IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] wtest.com. ...
-2      .       IN      OPT     32768   
+0      www.something.wtest.com.        3600    IN      A       4.3.2.1
+0      www.something.wtest.com.        3600    IN      RRSIG   A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      a.something.wtest.com.  3600    IN      NSEC    wtest.com. A RRSIG NSEC
+1      a.something.wtest.com.  3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] wtest.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.something.wtest.com.', qtype=ANY
index 4c5eeab062e04678f481537ba28c1245c533c5c4..b79b3da0692c40f804549639b7f0ad499f36c05c 100644 (file)
@@ -1,7 +1,7 @@
-0      www.something.wtest.com.        IN      A       3600    4.3.2.1
-0      www.something.wtest.com.        IN      RRSIG   3600    A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      7q60llva2bt9ucubvn553q9s2pf8ho38.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd 7Q60LLVA2BT9UCUBVN553Q9S2PF8HO3A
-1      7q60llva2bt9ucubvn553q9s2pf8ho38.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-2      .       IN      OPT     32768   
+0      www.something.wtest.com.        3600    IN      A       4.3.2.1
+0      www.something.wtest.com.        3600    IN      RRSIG   A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      7q60llva2bt9ucubvn553q9s2pf8ho38.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd 7Q60LLVA2BT9UCUBVN553Q9S2PF8HO3A
+1      7q60llva2bt9ucubvn553q9s2pf8ho38.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.something.wtest.com.', qtype=ANY
index 8bfd25e918fc31d9f7faf8b0427f248e388b862e..ba4eee3effdefb1367f5fd99071363621496589f 100644 (file)
@@ -1,7 +1,7 @@
-0      www.something.wtest.com.        IN      A       3600    4.3.2.1
-0      www.something.wtest.com.        IN      RRSIG   3600    A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      7k2dfhl64f0ndftst8u5rr5euminddvb.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd 95QOQ246KN3VM7HL8KVG8O45JIHMNLNG A RRSIG
-1      7k2dfhl64f0ndftst8u5rr5euminddvb.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-2      .       IN      OPT     32768   
+0      www.something.wtest.com.        3600    IN      A       4.3.2.1
+0      www.something.wtest.com.        3600    IN      RRSIG   A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      7k2dfhl64f0ndftst8u5rr5euminddvb.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd 95QOQ246KN3VM7HL8KVG8O45JIHMNLNG A RRSIG
+1      7k2dfhl64f0ndftst8u5rr5euminddvb.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.something.wtest.com.', qtype=ANY
index fa040f17e7a01ec9701a183447cfb8b95d5a2fdc..70641ff4ec3c3fa8ad43b29e436be2e565b18cb9 100644 (file)
@@ -1,3 +1,3 @@
-0      www.something.wtest.com.        IN      A       3600    4.3.2.1
+0      www.something.wtest.com.        3600    IN      A       4.3.2.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.something.wtest.com.', qtype=ANY
index 13394a51df01fc37469947d70d586d4955496fd3..850967d827a6b292e653a01f9edd2f8e56e69558 100644 (file)
@@ -1,3 +1,3 @@
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.com.', qtype=A
index a0c935a004001de980808dbb8e77211b54766300..8a9124379fc1de8fb118ad7867adda1af70cdb23 100644 (file)
@@ -1,3 +1,3 @@
-0      wtest.com.      IN      A       3600    9.9.9.9
+0      wtest.com.      3600    IN      A       9.9.9.9
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='wtest.com.', qtype=A
index b5e926b696a361d6208d83f386b4a3e068ebcbff..66037f9ac6f015fc191466143088caac9853e121 100644 (file)
@@ -1,6 +1,6 @@
-0      test.com.       IN      NS      3600    ns1.test.com.
-0      test.com.       IN      NS      3600    ns2.test.com.
-2      ns1.test.com.   IN      A       3600    1.1.1.1
-2      ns2.test.com.   IN      A       3600    2.2.2.2
+0      test.com.       3600    IN      NS      ns1.test.com.
+0      test.com.       3600    IN      NS      ns2.test.com.
+2      ns1.test.com.   3600    IN      A       1.1.1.1
+2      ns2.test.com.   3600    IN      A       2.2.2.2
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.com.', qtype=NS
index 755fd0599296761a77c39650e10f5507a87ede3b..2d3c08c52480651029d594eca90acfb8008cff58 100644 (file)
@@ -1,3 +1,3 @@
-1      2.0.192.in-addr.arpa.   IN      SOA     120     ns1.example.com. ahu.example.com. 2000081501 28800 7200 604800 86400
+1      2.0.192.in-addr.arpa.   120     IN      SOA     ns1.example.com. ahu.example.com. 2000081501 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='1.2.0.192.in-addr.arpa.', qtype=PTR
index c4bbeba8c12864bb8295c6bb4d66757da754e6e7..e1f7463d770358317ff354d1b11a6cf8e5012a75 100644 (file)
@@ -1,5 +1,5 @@
-0      1.2.0.192.in-addr.arpa. IN      PTR     120     bar.svcb.example.com.
-0      1.2.0.192.in-addr.arpa. IN      PTR     120     foo.svcb.example.com.
-0      1.2.0.192.in-addr.arpa. IN      PTR     120     host-for-auto-ptr.example.com.
+0      1.2.0.192.in-addr.arpa. 120     IN      PTR     bar.svcb.example.com.
+0      1.2.0.192.in-addr.arpa. 120     IN      PTR     foo.svcb.example.com.
+0      1.2.0.192.in-addr.arpa. 120     IN      PTR     host-for-auto-ptr.example.com.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='1.2.0.192.in-addr.arpa.', qtype=PTR
index d831426e482d7021db15e3d741336b8db272ae2f..09e9dfaeb3b430c40f9d1c22de5130241bd5a6fb 100644 (file)
@@ -1,3 +1,4 @@
+*.dnssec-parent.com.   3600    IN      CNAME   secure-delegated.dnssec-parent.com.
 delegated.dnssec-parent.com.   3600    IN      NS      ns1.delegated.dnssec-parent.com.
 delegated.dnssec-parent.com.   3600    IN      NS      ns2.delegated.dnssec-parent.com.
 dnssec-parent.com.     3600    IN      A       9.9.9.9
index 4aac677664f62e07278bb8e7bd95b43672a79a2b..6930c4ce25921e2eee4e4702b022296dbf30397b 100644 (file)
@@ -1,3 +1,7 @@
+*.dnssec-parent.com.   3600    IN      CNAME   secure-delegated.dnssec-parent.com.
+*.dnssec-parent.com.   3600    IN      NSEC    insecure-delegated.ent.ent.auth-ent.dnssec-parent.com. CNAME RRSIG NSEC
+*.dnssec-parent.com.   3600    IN      RRSIG   CNAME 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+*.dnssec-parent.com.   3600    IN      RRSIG   NSEC 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
 delegated.dnssec-parent.com.   3600    IN      NS      ns1.delegated.dnssec-parent.com.
 delegated.dnssec-parent.com.   3600    IN      NS      ns2.delegated.dnssec-parent.com.
 delegated.dnssec-parent.com.   3600    IN      NSEC    insecure.dnssec-parent.com. NS RRSIG NSEC
@@ -5,7 +9,7 @@ delegated.dnssec-parent.com.    3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [
 dnssec-parent.com.     3600    IN      A       9.9.9.9
 dnssec-parent.com.     3600    IN      NS      ns1.dnssec-parent.com.
 dnssec-parent.com.     3600    IN      NS      ns2.dnssec-parent.com.
-dnssec-parent.com.     3600    IN      NSEC    insecure-delegated.ent.ent.auth-ent.dnssec-parent.com. A NS SOA RRSIG NSEC DNSKEY CDS CDNSKEY
+dnssec-parent.com.     3600    IN      NSEC    *.dnssec-parent.com. A NS SOA RRSIG NSEC DNSKEY CDS CDNSKEY
 dnssec-parent.com.     3600    IN      RRSIG   A 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
 dnssec-parent.com.     3600    IN      RRSIG   NS 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
 dnssec-parent.com.     3600    IN      RRSIG   NSEC 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
index 8b9d290b3ba66a46ad95d541e7a57c87d4a2fdcd..15b81621793007e1212b467a9ed4edfa08ad3d5c 100644 (file)
@@ -1,3 +1,7 @@
+*.dnssec-parent.com.   3600    IN      CNAME   secure-delegated.dnssec-parent.com.
+*.dnssec-parent.com.   3600    IN      NSEC3   1 0 1 abcd [next owner] CNAME RRSIG
+*.dnssec-parent.com.   3600    IN      RRSIG   CNAME 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+*.dnssec-parent.com.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
 auth-ent.dnssec-parent.com.    3600    IN      NSEC3   1 0 1 abcd [next owner]
 auth-ent.dnssec-parent.com.    3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
 delegated.dnssec-parent.com.   3600    IN      NS      ns1.delegated.dnssec-parent.com.
index c4da9a3352cc32d80b657a21fe8995e91aad2406..ad6ce2201f5e813bf44fde8e6492b6fe7b7019a2 100644 (file)
@@ -1,3 +1,7 @@
+*.dnssec-parent.com.   3600    IN      CNAME   secure-delegated.dnssec-parent.com.
+*.dnssec-parent.com.   3600    IN      NSEC3   1 1 1 abcd [next owner] CNAME RRSIG
+*.dnssec-parent.com.   3600    IN      RRSIG   CNAME 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+*.dnssec-parent.com.   3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
 auth-ent.dnssec-parent.com.    3600    IN      NSEC3   1 1 1 abcd [next owner]
 auth-ent.dnssec-parent.com.    3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
 delegated.dnssec-parent.com.   3600    IN      NS      ns1.delegated.dnssec-parent.com.
index b7318a1274eaaaa25fb05c426eed9934aafeeadb..abe4a12b3f86cd7c75a9fca131ba438cd86ec4e9 100644 (file)
@@ -1,3 +1,3 @@
-0      outpost.example.com.    IN      A       120     192.168.2.1
+0      outpost.example.com.    120     IN      A       192.168.2.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='outpost.example.com.', qtype=A
index 188088b70c06d0ef684e3557a3ae98474e400ba9..46c35e951db2d14626591f0e5bb6863ea46ff70d 100644 (file)
@@ -1,3 +1,3 @@
-0      ipv6.example.com.       IN      AAAA    120     2001:6a8:0:1:210:4bff:fe4b:4c61
+0      ipv6.example.com.       120     IN      AAAA    2001:6a8:0:1:210:4bff:fe4b:4c61
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='ipv6.example.com.', qtype=AAAA
index 43eb459812c7346c409e3d5d46b63493917ca5b5..524c9adb59b9d38cbc3576b922708ffc20f5db0d 100644 (file)
@@ -1,3 +1,3 @@
-0      host-0.example.com.     IN      EUI48   120     00-50-56-9b-00-e7
+0      host-0.example.com.     120     IN      EUI48   00-50-56-9b-00-e7
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='host-0.example.com.', qtype=EUI48
index 6d3ed0bbf4ffa978799ffaedeac499748d7bf396..8c410ee0c8204f263272d08ffd96e252baac425a 100644 (file)
@@ -1,3 +1,3 @@
-0      host-1.example.com.     IN      EUI64   120     00-50-56-9b-00-e7-7e-57
+0      host-1.example.com.     120     IN      EUI64   00-50-56-9b-00-e7-7e-57
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='host-1.example.com.', qtype=EUI64
index d34fbf760729260249c7deeecd20f3d923fd6a17..152c9bb47c319f1064e02362807a8316fdaf2ef1 100644 (file)
@@ -1,3 +1,3 @@
-0      hwinfo.example.com.     IN      HINFO   120     "abc" "def"
+0      hwinfo.example.com.     120     IN      HINFO   "abc" "def"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='hwinfo.example.com.', qtype=HINFO
index 8cbe941917f665b3e16071223176fb706cd15c12..db2cc0b8adda0f727551008f39d3f5dd90fcae35 100644 (file)
@@ -1,6 +1,6 @@
-0      location.example.com.   IN      LOC     120     51 56 0.123 N 5 54 0.000 E 4.00m 1.00m 10000.00m 10.00m
-0      location.example.com.   IN      LOC     120     51 56 1.456 S 5 54 0.000 E 4.00m 2.00m 10000.00m 10.00m
-0      location.example.com.   IN      LOC     120     51 56 2.789 N 5 54 0.000 W 4.00m 3.00m 10000.00m 10.00m
-0      location.example.com.   IN      LOC     120     51 56 3.012 S 5 54 0.000 W 4.00m 4.00m 10000.00m 10.00m
+0      location.example.com.   120     IN      LOC     51 56 0.123 N 5 54 0.000 E 4.00m 1.00m 10000.00m 10.00m
+0      location.example.com.   120     IN      LOC     51 56 1.456 S 5 54 0.000 E 4.00m 2.00m 10000.00m 10.00m
+0      location.example.com.   120     IN      LOC     51 56 2.789 N 5 54 0.000 W 4.00m 3.00m 10000.00m 10.00m
+0      location.example.com.   120     IN      LOC     51 56 3.012 S 5 54 0.000 W 4.00m 4.00m 10000.00m 10.00m
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='location.example.com.', qtype=LOC
index 35f67335938902e0743483ab6169eecf6273c518..06938ac8840f5c97a847ddc7fc835581bb27bfca 100644 (file)
@@ -1,3 +1,3 @@
-0      phil.mb.example.com.    IN      MB      120     pc.mb.example.com.
+0      phil.mb.example.com.    120     IN      MB      pc.mb.example.com.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='phil.mb.example.com.', qtype=MB
index 0fc0b590587e03320437c4322ec8ad988ff8062e..d67bef7df5782a9271b866a7affc0174b211ffba 100644 (file)
@@ -1,4 +1,4 @@
-0      hostmaster.mb.example.com.      IN      MG      120     phil.mb.example.com.
-0      hostmaster.mb.example.com.      IN      MG      120     sheila.mb.example.com.
+0      hostmaster.mb.example.com.      120     IN      MG      phil.mb.example.com.
+0      hostmaster.mb.example.com.      120     IN      MG      sheila.mb.example.com.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='hostmaster.mb.example.com.', qtype=MG
index b0b3b9c4774d82e039c66f4c3d3968dcdaca46ab..135dee7e25016f7f72857444b9edeb12a3bb0ab4 100644 (file)
@@ -1,3 +1,3 @@
-0      philip.mb.example.com.  IN      MR      120     phil.mb.example.com.
+0      philip.mb.example.com.  120     IN      MR      phil.mb.example.com.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='philip.mb.example.com.', qtype=MR
index 4c6f7d3fb3baa7973204b30d28dfca9d4d437081..88ec2192cae55df254a0750518e306911b7870c8 100644 (file)
@@ -1,4 +1,4 @@
-0      nztest.com.     IN      A       3600    127.0.0.1
+0      nztest.com.     3600    IN      A       127.0.0.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nztest.com.', qtype=A
 Rcode: 5 (Query Refused), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
index cf0ad3e6549f1e9af7375c0f5131635d900a0aae..9dd91d44bb67cd2ae8e6d830ad16697a70f573ac 100644 (file)
@@ -1,6 +1,6 @@
-0      example.com.    IN      NS      120     ns1.example.com.
-0      example.com.    IN      NS      120     ns2.example.com.
-2      ns1.example.com.        IN      A       120     192.168.1.1
-2      ns2.example.com.        IN      A       120     192.168.1.2
+0      example.com.    120     IN      NS      ns1.example.com.
+0      example.com.    120     IN      NS      ns2.example.com.
+2      ns1.example.com.        120     IN      A       192.168.1.1
+2      ns2.example.com.        120     IN      A       192.168.1.2
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='example.com.', qtype=NS
index c4062901828bdf7b1e2e50fc0eec456c0f73d8b0..61bdf9463f48e3787b3fd6b0afba2da52ee7da84 100644 (file)
@@ -1,3 +1,3 @@
-0      example.com.    IN      SOA     100000  ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+0      example.com.    100000  IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='example.com.', qtype=SOA
index c8aa5f580df9836051b83580d81cae4b8f55fd49..812a5b442f44b5a4b73aed7622179114c13e04b9 100644 (file)
@@ -1,3 +1,3 @@
-0      _ldap._tcp.dc.test.com. IN      SRV     3600    0 100 389 server2.example.net.
+0      _ldap._tcp.dc.test.com. 3600    IN      SRV     0 100 389 server2.example.net.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='_ldap._tcp.dc.test.com.', qtype=SRV
index d8f219ab4488ec5e2fd8e9461907345320f3c348..93c579bf12efc18c2cb94bb5de2e8df3b96bdb6d 100644 (file)
@@ -1,3 +1,3 @@
-0      text.example.com.       IN      TXT     120     "Hi, this is some text"
+0      text.example.com.       120     IN      TXT     "Hi, this is some text"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='text.example.com.', qtype=TXT
index 5650c31b2177c630ba95dbf48024fdb3f59aac64..be303587d282175b3b7bfc288cde2983016da365 100644 (file)
@@ -1,12 +1,12 @@
 Rcode: 5 (Query Refused), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='ns1.addzone.com.', qtype=A
-0      ns1.test.com.   IN      A       3600    1.1.1.1
+0      ns1.test.com.   3600    IN      A       1.1.1.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='ns1.test.com.', qtype=A
 Already loaded
-0      ns1.addzone.com.        IN      A       3600    1.1.1.5
+0      ns1.addzone.com.        3600    IN      A       1.1.1.5
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='ns1.addzone.com.', qtype=A
-0      ns1.test.com.   IN      A       3600    1.1.1.1
+0      ns1.test.com.   3600    IN      A       1.1.1.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='ns1.test.com.', qtype=A
index cf1dec052b9cf5a167e63c4cd11ea4188547b947..9e96cac89dba280a771f47ecd15a39233b8cd22e 100644 (file)
@@ -1,3 +1,3 @@
-1      wtest.com.      IN      SOA     3600    ns1.wtest.com. ahu.example.com. 2005092501 28800 7200 604800 86400
+1      wtest.com.      3600    IN      SOA     ns1.wtest.com. ahu.example.com. 2005092501 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='secure.wtest.com.', qtype=A
index bce73e2b3bae8533977a81e3e0df064e0b6ab5b7..435fe41e6acc431fcfc5af3c358627a7ff9d6106 100644 (file)
@@ -1,4 +1,4 @@
-0      yo.test.test.com.       IN      CNAME   3600    server1.test.com.
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
+0      yo.test.test.com.       3600    IN      CNAME   server1.test.com.
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='yo.test.test.com.', qtype=AAAA
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 7eb4eae8649b938d5956491fbe5f2df1cd31b5d2..cbfcac388b2772e7c4c8b8c12b248ba38499597d 100644 (file)
@@ -1,3 +1,3 @@
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.test.test.com.', qtype=MX
index 3385cb893b008566e021e734c4ee5fdaa99da6f8..0715599cb78c10e7eef4cb5a02a6ddfcb91cae9c 100644 (file)
@@ -1,4 +1,4 @@
-0      www.example.com.        IN      CNAME   120     outpost.example.com.
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+0      www.example.com.        120     IN      CNAME   outpost.example.com.
+1      example.com.    86400   IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.example.com.', qtype=AAAA
index 0186b5c091ebb7d1e4448df94cc2bf8203c998cf..48a5582405b6b9379c137d655d8f2e64eb794817 100644 (file)
@@ -1,3 +1,3 @@
-0      rhs-at-expansion.example.com.   IN      CNAME   120     example.com.
+0      rhs-at-expansion.example.com.   120     IN      CNAME   example.com.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='rhs-at-expansion.example.com.', qtype=CNAME
index af822fc263f318697c6de046241907665930e9a1..72543cd0f0a2effd9a7d2fe784a8ce3b2e42ef5d 100644 (file)
@@ -1,4 +1,4 @@
-0      nxd.example.com.        IN      CNAME   120     nxdomain.example.com.
-2      .       IN      OPT     32768   
+0      nxd.example.com.        120     IN      CNAME   nxdomain.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='nxd.example.com.', qtype=ANY
index d6bec8166ef43e5d0495267e030cbcbb552b427c..c28605d52d50390404388f736da6f229ecffac1f 100644 (file)
@@ -1,5 +1,5 @@
-0      nxd.example.com.        IN      CNAME   120     nxdomain.example.com.
-0      nxd.example.com.        IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+0      nxd.example.com.        120     IN      CNAME   nxdomain.example.com.
+0      nxd.example.com.        120     IN      RRSIG   CNAME 13 3 120 [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='nxd.example.com.', qtype=ANY
index d6bec8166ef43e5d0495267e030cbcbb552b427c..c28605d52d50390404388f736da6f229ecffac1f 100644 (file)
@@ -1,5 +1,5 @@
-0      nxd.example.com.        IN      CNAME   120     nxdomain.example.com.
-0      nxd.example.com.        IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+0      nxd.example.com.        120     IN      CNAME   nxdomain.example.com.
+0      nxd.example.com.        120     IN      RRSIG   CNAME 13 3 120 [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='nxd.example.com.', qtype=ANY
index d6bec8166ef43e5d0495267e030cbcbb552b427c..c28605d52d50390404388f736da6f229ecffac1f 100644 (file)
@@ -1,5 +1,5 @@
-0      nxd.example.com.        IN      CNAME   120     nxdomain.example.com.
-0      nxd.example.com.        IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+0      nxd.example.com.        120     IN      CNAME   nxdomain.example.com.
+0      nxd.example.com.        120     IN      RRSIG   CNAME 13 3 120 [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='nxd.example.com.', qtype=ANY
index d5c7aca8ea40828c0ed1236cfd687cedaf774cc0..d064cc7e0de8d1d36d2df95090b41e1b6f89c8b0 100644 (file)
@@ -1,5 +1,5 @@
-0      nxd.example.com.        IN      CNAME   120     nxdomain.example.com.
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+0      nxd.example.com.        120     IN      CNAME   nxdomain.example.com.
+1      example.com.    86400   IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nxd.example.com.', qtype=A
index 44341762a6b3b174b926e5183f0bb04359b4d10a..724ca01ba5b54c3f9708852fc626c6c062cef206 100644 (file)
@@ -1,11 +1,11 @@
-0      nxd.example.com.        IN      CNAME   120     nxdomain.example.com.
-0      nxd.example.com.        IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      NSEC    86400   _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
-1      example.com.    IN      RRSIG   86400   NSEC 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      nxd.example.com.        IN      NSEC    86400   outpost.example.com. CNAME RRSIG NSEC
-1      nxd.example.com.        IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+0      nxd.example.com.        120     IN      CNAME   nxdomain.example.com.
+0      nxd.example.com.        120     IN      RRSIG   CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
+1      example.com.    86400   IN      NSEC    _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
+1      example.com.    86400   IN      RRSIG   NSEC 13 2 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      nxd.example.com.        86400   IN      NSEC    outpost.example.com. CNAME RRSIG NSEC
+1      nxd.example.com.        86400   IN      RRSIG   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nxd.example.com.', qtype=A
index a8cf3e54650acb494a57ce6b50b480566849873b..9c7953134cce22e2fc41a77872f6fda9291dd989 100644 (file)
@@ -1,13 +1,13 @@
-0      nxd.example.com.        IN      CNAME   120     nxdomain.example.com.
-0      nxd.example.com.        IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 9FAG9508OQU3M22QAC0U5EQGG45V8CF2
-1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      onnhv82alu3om3l4fkfes49n0j2c71ba.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd ONNHV82ALU3OM3L4FKFES49N0J2C71BC
-1      onnhv82alu3om3l4fkfes49n0j2c71ba.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+0      nxd.example.com.        120     IN      CNAME   nxdomain.example.com.
+0      nxd.example.com.        120     IN      RRSIG   CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
+1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 9FAG9508OQU3M22QAC0U5EQGG45V8CF2
+1      9fag9508oqu3m22qac0u5eqgg45v8cf0.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      onnhv82alu3om3l4fkfes49n0j2c71ba.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd ONNHV82ALU3OM3L4FKFES49N0J2C71BC
+1      onnhv82alu3om3l4fkfes49n0j2c71ba.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nxd.example.com.', qtype=A
index 51c3b991ff978023739eb29cf9cd7e28327eefb9..339de51c3119491546582aa723322f0c46c93622 100644 (file)
@@ -1,13 +1,13 @@
-0      nxd.example.com.        IN      CNAME   120     nxdomain.example.com.
-0      nxd.example.com.        IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      onn5kjcskcfqisao7tmqpjkp5kkh111o.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd ONNU1VP51T2LDROTDVQ10HVLRQQV2UAA A RRSIG
-1      onn5kjcskcfqisao7tmqpjkp5kkh111o.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+0      nxd.example.com.        120     IN      CNAME   nxdomain.example.com.
+0      nxd.example.com.        120     IN      RRSIG   CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.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      onn5kjcskcfqisao7tmqpjkp5kkh111o.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd ONNU1VP51T2LDROTDVQ10HVLRQQV2UAA A RRSIG
+1      onn5kjcskcfqisao7tmqpjkp5kkh111o.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nxd.example.com.', qtype=A
index 0e9ee6d3f6afa0f77d98ef318d9ecd965d14e57c..284ccf963a7987546349151bdd00b751feda3924 100644 (file)
@@ -1,5 +1,5 @@
-0      server1.example.com.    IN      CNAME   120     server1.france.example.com.
-1      france.example.com.     IN      NS      120     ns1.otherprovider.net.
-1      france.example.com.     IN      NS      120     ns2.otherprovider.net.
+0      server1.example.com.    120     IN      CNAME   server1.france.example.com.
+1      france.example.com.     120     IN      NS      ns1.otherprovider.net.
+1      france.example.com.     120     IN      NS      ns2.otherprovider.net.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='server1.example.com.', qtype=A
index 1d85883948b94e1e14b731ed50bac12188b82183..f6da15018885a43adfff4f395eb542bc53a5dde0 100644 (file)
@@ -1,4 +1,4 @@
-0      unauth.example.com.     IN      CNAME   120     no-idea.example.org.
-2      .       IN      OPT     32768   
+0      unauth.example.com.     120     IN      CNAME   no-idea.example.org.
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='unauth.example.com.', qtype=ANY
index 45f3fd7ca5d094c2847c6d4a08166f417ba92c26..b56f898934852a7f00d05b164ed51d050b6891d9 100644 (file)
@@ -1,5 +1,5 @@
-0      unauth.example.com.     IN      CNAME   120     no-idea.example.org.
-0      unauth.example.com.     IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+0      unauth.example.com.     120     IN      CNAME   no-idea.example.org.
+0      unauth.example.com.     120     IN      RRSIG   CNAME 13 3 120 [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='unauth.example.com.', qtype=ANY
index 48bbfcaa2e8d92c44e8fca722b84633d4921d23d..d66a4734b6669bb6b1e92bc9c0316c9013763115 100644 (file)
@@ -1,4 +1,4 @@
-0      unauth.example.com.     IN      CNAME   120     no-idea.example.org.
-2      .       IN      OPT     32768   
+0      unauth.example.com.     120     IN      CNAME   no-idea.example.org.
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='unauth.example.com.', qtype=A
index e40da2257bc4312a9dedf8b3a55a2c0693e92602..015b01ee4b7787955ace243a9691d8c386ebaed4 100644 (file)
@@ -1,5 +1,5 @@
-0      unauth.example.com.     IN      CNAME   120     no-idea.example.org.
-0      unauth.example.com.     IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+0      unauth.example.com.     120     IN      CNAME   no-idea.example.org.
+0      unauth.example.com.     120     IN      RRSIG   CNAME 13 3 120 [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='unauth.example.com.', qtype=A
index 72e0634e612e89940a0709c3ff77ba831cd4aab3..97ebc5e1c3ddc09f339128f3ac11d70132b82318 100644 (file)
@@ -1,9 +1,9 @@
-0      start.example.com.      IN      CNAME   120     x.y.z.w1.example.com.
-0      x.y.z.w1.example.com.   IN      CNAME   120     x.y.z.w2.example.com.
-0      x.y.z.w2.example.com.   IN      CNAME   120     x.y.z.w3.example.com.
-0      x.y.z.w3.example.com.   IN      CNAME   120     x.y.z.w4.example.com.
-0      x.y.z.w4.example.com.   IN      CNAME   120     x.y.z.w5.example.com.
-0      x.y.z.w5.example.com.   IN      A       120     1.2.3.5
-2      .       IN      OPT     32768   
+0      start.example.com.      120     IN      CNAME   x.y.z.w1.example.com.
+0      x.y.z.w1.example.com.   120     IN      CNAME   x.y.z.w2.example.com.
+0      x.y.z.w2.example.com.   120     IN      CNAME   x.y.z.w3.example.com.
+0      x.y.z.w3.example.com.   120     IN      CNAME   x.y.z.w4.example.com.
+0      x.y.z.w4.example.com.   120     IN      CNAME   x.y.z.w5.example.com.
+0      x.y.z.w5.example.com.   120     IN      A       1.2.3.5
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='start.example.com.', qtype=A
index dc9b4d17197b0214afba107433d00d8840cfe936..fbdf334ebb5377007d6181e4b9e01edfad7ba3b5 100644 (file)
@@ -1,25 +1,25 @@
-0      start.example.com.      IN      CNAME   120     x.y.z.w1.example.com.
-0      start.example.com.      IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-0      x.y.z.w1.example.com.   IN      CNAME   120     x.y.z.w2.example.com.
-0      x.y.z.w1.example.com.   IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-0      x.y.z.w2.example.com.   IN      CNAME   120     x.y.z.w3.example.com.
-0      x.y.z.w2.example.com.   IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-0      x.y.z.w3.example.com.   IN      CNAME   120     x.y.z.w4.example.com.
-0      x.y.z.w3.example.com.   IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-0      x.y.z.w4.example.com.   IN      CNAME   120     x.y.z.w5.example.com.
-0      x.y.z.w4.example.com.   IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-0      x.y.z.w5.example.com.   IN      A       120     1.2.3.5
-0      x.y.z.w5.example.com.   IN      RRSIG   120     A 13 3 120 [expiry] [inception] [keytag] example.com. ...
-1      *.w1.example.com.       IN      NSEC    86400   *.w2.example.com. CNAME RRSIG NSEC
-1      *.w1.example.com.       IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      *.w2.example.com.       IN      NSEC    86400   *.w3.example.com. CNAME RRSIG NSEC
-1      *.w2.example.com.       IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      *.w3.example.com.       IN      NSEC    86400   *.w4.example.com. CNAME RRSIG NSEC
-1      *.w3.example.com.       IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      *.w4.example.com.       IN      NSEC    86400   *.w5.example.com. CNAME RRSIG NSEC
-1      *.w4.example.com.       IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      *.w5.example.com.       IN      NSEC    86400   www.example.com. A RRSIG NSEC
-1      *.w5.example.com.       IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+0      start.example.com.      120     IN      CNAME   x.y.z.w1.example.com.
+0      start.example.com.      120     IN      RRSIG   CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
+0      x.y.z.w1.example.com.   120     IN      CNAME   x.y.z.w2.example.com.
+0      x.y.z.w1.example.com.   120     IN      RRSIG   CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
+0      x.y.z.w2.example.com.   120     IN      CNAME   x.y.z.w3.example.com.
+0      x.y.z.w2.example.com.   120     IN      RRSIG   CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
+0      x.y.z.w3.example.com.   120     IN      CNAME   x.y.z.w4.example.com.
+0      x.y.z.w3.example.com.   120     IN      RRSIG   CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
+0      x.y.z.w4.example.com.   120     IN      CNAME   x.y.z.w5.example.com.
+0      x.y.z.w4.example.com.   120     IN      RRSIG   CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
+0      x.y.z.w5.example.com.   120     IN      A       1.2.3.5
+0      x.y.z.w5.example.com.   120     IN      RRSIG   A 13 3 120 [expiry] [inception] [keytag] example.com. ...
+1      *.w1.example.com.       86400   IN      NSEC    *.w2.example.com. CNAME RRSIG NSEC
+1      *.w1.example.com.       86400   IN      RRSIG   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      *.w2.example.com.       86400   IN      NSEC    *.w3.example.com. CNAME RRSIG NSEC
+1      *.w2.example.com.       86400   IN      RRSIG   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      *.w3.example.com.       86400   IN      NSEC    *.w4.example.com. CNAME RRSIG NSEC
+1      *.w3.example.com.       86400   IN      RRSIG   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      *.w4.example.com.       86400   IN      NSEC    *.w5.example.com. CNAME RRSIG NSEC
+1      *.w4.example.com.       86400   IN      RRSIG   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      *.w5.example.com.       86400   IN      NSEC    www.example.com. A RRSIG NSEC
+1      *.w5.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='start.example.com.', qtype=A
index 33ec283deb1c6777b0886a09a32bb1d186addc9c..811846504a56ff11f7206835782bddc51b4793fa 100644 (file)
@@ -1,25 +1,25 @@
-0      start.example.com.      IN      CNAME   120     x.y.z.w1.example.com.
-0      start.example.com.      IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-0      x.y.z.w1.example.com.   IN      CNAME   120     x.y.z.w2.example.com.
-0      x.y.z.w1.example.com.   IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-0      x.y.z.w2.example.com.   IN      CNAME   120     x.y.z.w3.example.com.
-0      x.y.z.w2.example.com.   IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-0      x.y.z.w3.example.com.   IN      CNAME   120     x.y.z.w4.example.com.
-0      x.y.z.w3.example.com.   IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-0      x.y.z.w4.example.com.   IN      CNAME   120     x.y.z.w5.example.com.
-0      x.y.z.w4.example.com.   IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-0      x.y.z.w5.example.com.   IN      A       120     1.2.3.5
-0      x.y.z.w5.example.com.   IN      RRSIG   120     A 13 3 120 [expiry] [inception] [keytag] example.com. ...
-1      6jmrie0v0hnp2flflt36lur7c08n9h45.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 6JMRIE0V0HNP2FLFLT36LUR7C08N9H47
-1      6jmrie0v0hnp2flflt36lur7c08n9h45.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      atcf56s7ucntm82nht67p3g2nqteplou.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd ATCF56S7UCNTM82NHT67P3G2NQTEPLP0
-1      atcf56s7ucntm82nht67p3g2nqteplou.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      b6drqdikagd74fa5eme4sdiek1s06343.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd B6DRQDIKAGD74FA5EME4SDIEK1S06345
-1      b6drqdikagd74fa5eme4sdiek1s06343.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      lr0g3vnj9r0nvtlsjnf8eqa68sqj06qg.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd LR0G3VNJ9R0NVTLSJNF8EQA68SQJ06QI
-1      lr0g3vnj9r0nvtlsjnf8eqa68sqj06qg.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      vsfa79vv78gd61567bkcai646ta0p276.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VSFA79VV78GD61567BKCAI646TA0P278
-1      vsfa79vv78gd61567bkcai646ta0p276.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+0      start.example.com.      120     IN      CNAME   x.y.z.w1.example.com.
+0      start.example.com.      120     IN      RRSIG   CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
+0      x.y.z.w1.example.com.   120     IN      CNAME   x.y.z.w2.example.com.
+0      x.y.z.w1.example.com.   120     IN      RRSIG   CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
+0      x.y.z.w2.example.com.   120     IN      CNAME   x.y.z.w3.example.com.
+0      x.y.z.w2.example.com.   120     IN      RRSIG   CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
+0      x.y.z.w3.example.com.   120     IN      CNAME   x.y.z.w4.example.com.
+0      x.y.z.w3.example.com.   120     IN      RRSIG   CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
+0      x.y.z.w4.example.com.   120     IN      CNAME   x.y.z.w5.example.com.
+0      x.y.z.w4.example.com.   120     IN      RRSIG   CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
+0      x.y.z.w5.example.com.   120     IN      A       1.2.3.5
+0      x.y.z.w5.example.com.   120     IN      RRSIG   A 13 3 120 [expiry] [inception] [keytag] example.com. ...
+1      6jmrie0v0hnp2flflt36lur7c08n9h45.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 6JMRIE0V0HNP2FLFLT36LUR7C08N9H47
+1      6jmrie0v0hnp2flflt36lur7c08n9h45.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      atcf56s7ucntm82nht67p3g2nqteplou.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd ATCF56S7UCNTM82NHT67P3G2NQTEPLP0
+1      atcf56s7ucntm82nht67p3g2nqteplou.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      b6drqdikagd74fa5eme4sdiek1s06343.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd B6DRQDIKAGD74FA5EME4SDIEK1S06345
+1      b6drqdikagd74fa5eme4sdiek1s06343.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      lr0g3vnj9r0nvtlsjnf8eqa68sqj06qg.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd LR0G3VNJ9R0NVTLSJNF8EQA68SQJ06QI
+1      lr0g3vnj9r0nvtlsjnf8eqa68sqj06qg.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      vsfa79vv78gd61567bkcai646ta0p276.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VSFA79VV78GD61567BKCAI646TA0P278
+1      vsfa79vv78gd61567bkcai646ta0p276.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='start.example.com.', qtype=A
index 714f98fb60a8518e78f6917f93e3e06aed42169a..3bf840747fd314ceaf398a667cfd44624cc6d4e2 100644 (file)
@@ -1,25 +1,25 @@
-0      start.example.com.      IN      CNAME   120     x.y.z.w1.example.com.
-0      start.example.com.      IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-0      x.y.z.w1.example.com.   IN      CNAME   120     x.y.z.w2.example.com.
-0      x.y.z.w1.example.com.   IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-0      x.y.z.w2.example.com.   IN      CNAME   120     x.y.z.w3.example.com.
-0      x.y.z.w2.example.com.   IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-0      x.y.z.w3.example.com.   IN      CNAME   120     x.y.z.w4.example.com.
-0      x.y.z.w3.example.com.   IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-0      x.y.z.w4.example.com.   IN      CNAME   120     x.y.z.w5.example.com.
-0      x.y.z.w4.example.com.   IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-0      x.y.z.w5.example.com.   IN      A       120     1.2.3.5
-0      x.y.z.w5.example.com.   IN      RRSIG   120     A 13 3 120 [expiry] [inception] [keytag] example.com. ...
-1      6jljjg5vg8ab1latv5khfq52jjpdlp9t.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 6JNMPRJN08RFG8QRUMBN91V2UURTV527 A RRSIG
-1      6jljjg5vg8ab1latv5khfq52jjpdlp9t.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      atbcoh7l1gr1cbifhkt3ikmv2o60g8sc.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd ATEJUO2QMEO1FORSEB6KH9B0DMVFRK08 A RRSIG
-1      atbcoh7l1gr1cbifhkt3ikmv2o60g8sc.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      b6cdleeregn514pnp2jgmtd67ig3q4qs.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd B6J68ESSIMG1HC5MGJ3B3OQUKL9PKEQB A RRSIG
-1      b6cdleeregn514pnp2jgmtd67ig3q4qs.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      lqu3s8oae1ipc1iobnslma8igo1335a4.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd LR1LEP75CII4P0CLER3MLLQBO1TGKHDO A RRSIG
-1      lqu3s8oae1ipc1iobnslma8igo1335a4.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      vscvfu442fdlbq07jpd7bdocd3ig7fo8.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VSGNH606MUV7BFQFN3TRH1D5FKP1IPIV A RRSIG
-1      vscvfu442fdlbq07jpd7bdocd3ig7fo8.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+0      start.example.com.      120     IN      CNAME   x.y.z.w1.example.com.
+0      start.example.com.      120     IN      RRSIG   CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
+0      x.y.z.w1.example.com.   120     IN      CNAME   x.y.z.w2.example.com.
+0      x.y.z.w1.example.com.   120     IN      RRSIG   CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
+0      x.y.z.w2.example.com.   120     IN      CNAME   x.y.z.w3.example.com.
+0      x.y.z.w2.example.com.   120     IN      RRSIG   CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
+0      x.y.z.w3.example.com.   120     IN      CNAME   x.y.z.w4.example.com.
+0      x.y.z.w3.example.com.   120     IN      RRSIG   CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
+0      x.y.z.w4.example.com.   120     IN      CNAME   x.y.z.w5.example.com.
+0      x.y.z.w4.example.com.   120     IN      RRSIG   CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
+0      x.y.z.w5.example.com.   120     IN      A       1.2.3.5
+0      x.y.z.w5.example.com.   120     IN      RRSIG   A 13 3 120 [expiry] [inception] [keytag] example.com. ...
+1      6jljjg5vg8ab1latv5khfq52jjpdlp9t.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 6JNMPRJN08RFG8QRUMBN91V2UURTV527 A RRSIG
+1      6jljjg5vg8ab1latv5khfq52jjpdlp9t.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      atbcoh7l1gr1cbifhkt3ikmv2o60g8sc.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd ATEJUO2QMEO1FORSEB6KH9B0DMVFRK08 A RRSIG
+1      atbcoh7l1gr1cbifhkt3ikmv2o60g8sc.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      b6cdleeregn514pnp2jgmtd67ig3q4qs.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd B6J68ESSIMG1HC5MGJ3B3OQUKL9PKEQB A RRSIG
+1      b6cdleeregn514pnp2jgmtd67ig3q4qs.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      lqu3s8oae1ipc1iobnslma8igo1335a4.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd LR1LEP75CII4P0CLER3MLLQBO1TGKHDO A RRSIG
+1      lqu3s8oae1ipc1iobnslma8igo1335a4.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      vscvfu442fdlbq07jpd7bdocd3ig7fo8.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VSGNH606MUV7BFQFN3TRH1D5FKP1IPIV A RRSIG
+1      vscvfu442fdlbq07jpd7bdocd3ig7fo8.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='start.example.com.', qtype=A
index 1ac99a9bd06e93810fcab0ba7537a6f65847cd76..421e9dd80e77539bb7bae68136ccad289d9a88f4 100644 (file)
@@ -1,4 +1,4 @@
-0      bla.something.wtest.com.        IN      A       3600    4.3.2.1
-0      semi-external.example.com.      IN      CNAME   120     bla.something.wtest.com.
+0      bla.something.wtest.com.        3600    IN      A       4.3.2.1
+0      semi-external.example.com.      120     IN      CNAME   bla.something.wtest.com.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='semi-external.example.com.', qtype=A
index a3e101c9f2a03cd4b7250fe904cf1da178fa08b5..dcd62ecfa37868234b565500f1cee90ab8a413f9 100644 (file)
@@ -1,14 +1,14 @@
-0      cryptokeys.org. IN      DNSKEY  3600    256 3 10 ...
-0      cryptokeys.org. IN      DNSKEY  3600    257 3 13 ...
-0      cryptokeys.org. IN      RRSIG   3600    DNSKEY 13 2 3600 [expiry] [inception] [keytag] cryptokeys.org. ...
-0      cryptokeys.org. IN      RRSIG   3600    DNSKEY 14 2 3600 [expiry] [inception] [keytag] cryptokeys.org. ...
-2      .       IN      OPT     32768   
+0      cryptokeys.org. 3600    IN      DNSKEY  256 3 10 ...
+0      cryptokeys.org. 3600    IN      DNSKEY  257 3 13 ...
+0      cryptokeys.org. 3600    IN      RRSIG   DNSKEY 13 2 3600 [expiry] [inception] [keytag] cryptokeys.org. ...
+0      cryptokeys.org. 3600    IN      RRSIG   DNSKEY 14 2 3600 [expiry] [inception] [keytag] cryptokeys.org. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cryptokeys.org.', qtype=DNSKEY
-1      hiddencryptokeys.org.   IN      NSEC    3600    hiddencryptokeys.org. A NS SOA RRSIG NSEC
-1      hiddencryptokeys.org.   IN      RRSIG   3600    NSEC 13 2 3600 [expiry] [inception] [keytag] hiddencryptokeys.org. ...
-1      hiddencryptokeys.org.   IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] hiddencryptokeys.org. ...
-1      hiddencryptokeys.org.   IN      SOA     3600    cryptokeys.ds9a.nl. ahu.ds9a.nl. 2009071301 14400 3600 604800 3600
-2      .       IN      OPT     32768   
+1      hiddencryptokeys.org.   3600    IN      NSEC    hiddencryptokeys.org. A NS SOA RRSIG NSEC
+1      hiddencryptokeys.org.   3600    IN      RRSIG   NSEC 13 2 3600 [expiry] [inception] [keytag] hiddencryptokeys.org. ...
+1      hiddencryptokeys.org.   3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] hiddencryptokeys.org. ...
+1      hiddencryptokeys.org.   3600    IN      SOA     cryptokeys.ds9a.nl. ahu.ds9a.nl. 2009071301 14400 3600 604800 3600
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='hiddencryptokeys.org.', qtype=DNSKEY
index 691ab9f9376a9b4f67b9bc2eef577e7e0a9621c4..ea37b4ff695cf0d0955edd584cb8df8472cfc3ac 100644 (file)
@@ -1,14 +1,14 @@
-0      cryptokeys.org. IN      DNSKEY  3600    256 3 10 ...
-0      cryptokeys.org. IN      DNSKEY  3600    257 3 13 ...
-0      cryptokeys.org. IN      RRSIG   3600    DNSKEY 13 2 3600 [expiry] [inception] [keytag] cryptokeys.org. ...
-0      cryptokeys.org. IN      RRSIG   3600    DNSKEY 14 2 3600 [expiry] [inception] [keytag] cryptokeys.org. ...
-2      .       IN      OPT     32768   
+0      cryptokeys.org. 3600    IN      DNSKEY  256 3 10 ...
+0      cryptokeys.org. 3600    IN      DNSKEY  257 3 13 ...
+0      cryptokeys.org. 3600    IN      RRSIG   DNSKEY 13 2 3600 [expiry] [inception] [keytag] cryptokeys.org. ...
+0      cryptokeys.org. 3600    IN      RRSIG   DNSKEY 14 2 3600 [expiry] [inception] [keytag] cryptokeys.org. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cryptokeys.org.', qtype=DNSKEY
-1      hiddencryptokeys.org.   IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] hiddencryptokeys.org. ...
-1      hiddencryptokeys.org.   IN      SOA     3600    cryptokeys.ds9a.nl. ahu.ds9a.nl. 2009071301 14400 3600 604800 3600
-1      vd844e5oi5854h79fnaa0f80nqo8brf0.hiddencryptokeys.org.  IN      NSEC3   3600    1 [flags] 1 abcd VD844E5OI5854H79FNAA0F80NQO8BRF1 A NS SOA RRSIG NSEC3PARAM
-1      vd844e5oi5854h79fnaa0f80nqo8brf0.hiddencryptokeys.org.  IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] hiddencryptokeys.org. ...
-2      .       IN      OPT     32768   
+1      hiddencryptokeys.org.   3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] hiddencryptokeys.org. ...
+1      hiddencryptokeys.org.   3600    IN      SOA     cryptokeys.ds9a.nl. ahu.ds9a.nl. 2009071301 14400 3600 604800 3600
+1      vd844e5oi5854h79fnaa0f80nqo8brf0.hiddencryptokeys.org.  3600    IN      NSEC3   1 [flags] 1 abcd VD844E5OI5854H79FNAA0F80NQO8BRF1 A NS SOA RRSIG NSEC3PARAM
+1      vd844e5oi5854h79fnaa0f80nqo8brf0.hiddencryptokeys.org.  3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] hiddencryptokeys.org. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='hiddencryptokeys.org.', qtype=DNSKEY
index af5aa673570b0426b605b4708622c62343a55456..7ce42390ddc5b34ce36ab0ec2a6a98a8774fb079 100644 (file)
@@ -1,14 +1,14 @@
-0      cryptokeys.org. IN      DNSKEY  3600    256 3 10 ...
-0      cryptokeys.org. IN      DNSKEY  3600    257 3 13 ...
-0      cryptokeys.org. IN      RRSIG   3600    DNSKEY 13 2 3600 [expiry] [inception] [keytag] cryptokeys.org. ...
-0      cryptokeys.org. IN      RRSIG   3600    DNSKEY 14 2 3600 [expiry] [inception] [keytag] cryptokeys.org. ...
-2      .       IN      OPT     32768   
+0      cryptokeys.org. 3600    IN      DNSKEY  256 3 10 ...
+0      cryptokeys.org. 3600    IN      DNSKEY  257 3 13 ...
+0      cryptokeys.org. 3600    IN      RRSIG   DNSKEY 13 2 3600 [expiry] [inception] [keytag] cryptokeys.org. ...
+0      cryptokeys.org. 3600    IN      RRSIG   DNSKEY 14 2 3600 [expiry] [inception] [keytag] cryptokeys.org. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cryptokeys.org.', qtype=DNSKEY
-1      hiddencryptokeys.org.   IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] hiddencryptokeys.org. ...
-1      hiddencryptokeys.org.   IN      SOA     3600    cryptokeys.ds9a.nl. ahu.ds9a.nl. 2009071301 14400 3600 604800 3600
-1      vd844e5oi5854h79fnaa0f80nqo8brf0.hiddencryptokeys.org.  IN      NSEC3   3600    1 [flags] 1 abcd VD844E5OI5854H79FNAA0F80NQO8BRF0 A NS SOA RRSIG NSEC3PARAM
-1      vd844e5oi5854h79fnaa0f80nqo8brf0.hiddencryptokeys.org.  IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] hiddencryptokeys.org. ...
-2      .       IN      OPT     32768   
+1      hiddencryptokeys.org.   3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] hiddencryptokeys.org. ...
+1      hiddencryptokeys.org.   3600    IN      SOA     cryptokeys.ds9a.nl. ahu.ds9a.nl. 2009071301 14400 3600 604800 3600
+1      vd844e5oi5854h79fnaa0f80nqo8brf0.hiddencryptokeys.org.  3600    IN      NSEC3   1 [flags] 1 abcd VD844E5OI5854H79FNAA0F80NQO8BRF0 A NS SOA RRSIG NSEC3PARAM
+1      vd844e5oi5854h79fnaa0f80nqo8brf0.hiddencryptokeys.org.  3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] hiddencryptokeys.org. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='hiddencryptokeys.org.', qtype=DNSKEY
index 7890cce0d00d10b6a910b0a9823bdced3619c365..0cfce021d09a847a97225f7110361a4cd03a458b 100644 (file)
@@ -1,4 +1,4 @@
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+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='example.com.', qtype=DNSKEY
index 6fa731dfed137ba14d4ae03e84cbed90f6bdad6d..976cae21847c27aa3fb8433a60c79d467437ef65 100644 (file)
@@ -1,5 +1,5 @@
-0      example.com.    IN      DNSKEY  86400   257 3 13 ...
-0      example.com.    IN      RRSIG   86400   DNSKEY 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+0      example.com.    86400   IN      DNSKEY  257 3 13 ...
+0      example.com.    86400   IN      RRSIG   DNSKEY 13 2 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='example.com.', qtype=DNSKEY
index eb7ac3e81806d0baff60a126263a303050dbb17f..b3f678c2a09a834b9c52dee470c015314085dd94 100644 (file)
@@ -1,4 +1,4 @@
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      example.com.    86400   IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='host-1234x.example.com.', qtype=NSEC
index fa82444fdf4a987b70040a9989279df7633be120..75a042c176694c8ba5f488c13cb07968a34be311 100644 (file)
@@ -1,9 +1,9 @@
-1      example.com.    IN      NSEC    86400   _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
-1      example.com.    IN      RRSIG   86400   NSEC 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      host-12349.example.com. IN      NSEC    86400   host-1235.example.com. A RRSIG NSEC
-1      host-12349.example.com. IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      example.com.    86400   IN      NSEC    _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
+1      example.com.    86400   IN      RRSIG   NSEC 13 2 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      host-12349.example.com. 86400   IN      NSEC    host-1235.example.com. A RRSIG NSEC
+1      host-12349.example.com. 86400   IN      RRSIG   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='host-1234x.example.com.', qtype=NSEC
index 7e49a1163212990f84baa91b9ccfe63b050cd754..af404cd9935f53407a404d28246ae414671db6aa 100644 (file)
@@ -1,11 +1,11 @@
-1      4jiv8rrf3verm9rp51f55587fbfms5g9.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 4JIV8RRF3VERM9RP51F55587FBFMS5GB
-1      4jiv8rrf3verm9rp51f55587fbfms5g9.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 9FAG9508OQU3M22QAC0U5EQGG45V8CF2
-1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      4jiv8rrf3verm9rp51f55587fbfms5g9.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 4JIV8RRF3VERM9RP51F55587FBFMS5GB
+1      4jiv8rrf3verm9rp51f55587fbfms5g9.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 9FAG9508OQU3M22QAC0U5EQGG45V8CF2
+1      9fag9508oqu3m22qac0u5eqgg45v8cf0.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      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='host-1234x.example.com.', qtype=NSEC
index 5f40ba9cbe8ca93ea0cc87bc290ccd8ff3dac9db..fe7d0d4c819ee2383f78b99222218842fc16630d 100644 (file)
@@ -1,11 +1,11 @@
-1      4j9ti2b4c7iibemvegh99nmoe5m72rb6.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 4JKT13JQPK715SGVL9KSRFVACKO95SV4 A RRSIG
-1      4j9ti2b4c7iibemvegh99nmoe5m72rb6.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      4j9ti2b4c7iibemvegh99nmoe5m72rb6.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 4JKT13JQPK715SGVL9KSRFVACKO95SV4 A RRSIG
+1      4j9ti2b4c7iibemvegh99nmoe5m72rb6.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.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      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='host-1234x.example.com.', qtype=NSEC
index 8a0c13611efab4817216d9c20021fc3ac55146ed..80dbd932d10dc2a8ecf965a9467d72e542fb79d5 100644 (file)
@@ -1,7 +1,7 @@
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+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='example.com.', qtype=NSEC3PARAM
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+1      example.com.    86400   IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='example.com.', qtype=NSEC3PARAM
index 9a45f8852608c975e2b6129a68ec91ead1ef6904..3fb57524c22cbb59f89fc83b35b9ccd520adba14 100644 (file)
@@ -1,10 +1,10 @@
-1      example.com.    IN      NSEC    86400   _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
-1      example.com.    IN      RRSIG   86400   NSEC 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      example.com.    86400   IN      NSEC    _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
+1      example.com.    86400   IN      RRSIG   NSEC 13 2 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
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='example.com.', qtype=NSEC3PARAM
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+1      example.com.    86400   IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='example.com.', qtype=NSEC3PARAM
index f86656c93ba09b68dca81e3a35f47716fab462af..ea048f6ee5619021164c2f8ae4a47c0b810b4748 100644 (file)
@@ -1,8 +1,8 @@
-0      example.com.    IN      NSEC3PARAM      86400   1 0 1 abcd
-0      example.com.    IN      RRSIG   86400   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+0      example.com.    86400   IN      NSEC3PARAM      1 0 1 abcd
+0      example.com.    86400   IN      RRSIG   NSEC3PARAM 13 2 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='example.com.', qtype=NSEC3PARAM
-0      example.com.    IN      NSEC3PARAM      86400   1 0 1 abcd
+0      example.com.    86400   IN      NSEC3PARAM      1 0 1 abcd
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='example.com.', qtype=NSEC3PARAM
index f86656c93ba09b68dca81e3a35f47716fab462af..ea048f6ee5619021164c2f8ae4a47c0b810b4748 100644 (file)
@@ -1,8 +1,8 @@
-0      example.com.    IN      NSEC3PARAM      86400   1 0 1 abcd
-0      example.com.    IN      RRSIG   86400   NSEC3PARAM 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+0      example.com.    86400   IN      NSEC3PARAM      1 0 1 abcd
+0      example.com.    86400   IN      RRSIG   NSEC3PARAM 13 2 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='example.com.', qtype=NSEC3PARAM
-0      example.com.    IN      NSEC3PARAM      86400   1 0 1 abcd
+0      example.com.    86400   IN      NSEC3PARAM      1 0 1 abcd
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='example.com.', qtype=NSEC3PARAM
index bd42abb7230979535df4d2368ef15bed60965b8f..ec4a1d33b79482246243e16f2701c158e56229c8 100644 (file)
@@ -1,3 +1,3 @@
-2      .       IN      OPT     32768   
+2      .       32768   IN      OPT     
 Rcode: 5 (Query Refused), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='example.com.', qtype=RRSIG
index 31fadb79dfc1dfc9801f5807be81e52e049abbaa..b5847968a8fb70232eff8e75b3309614e598a308 100644 (file)
@@ -1,3 +1,3 @@
-0      www.something.wtest.com.        IN      A       3600    4.3.2.1
+0      www.something.wtest.com.        3600    IN      A       4.3.2.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.something.wtest.com.', qtype=A
index 6c81e05d2fab28593802423bd706191a5ddb836f..16906d1cda2e77c1c77d33b97270f5ac569b6869 100644 (file)
@@ -1,3 +1,3 @@
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='d.test.com.', qtype=A
index dd2d6579ff96712c0908d41a4e5a5d6599a8c33a..2e77a14b96bcfaf0b3aeec92c072d3bdb73c6fc3 100644 (file)
@@ -1,4 +1,4 @@
-0      d.test.com.     IN      DNAME   3600    d2.test2.com.
-2      .       IN      OPT     32768   
+0      d.test.com.     3600    IN      DNAME   d2.test2.com.
+2      .       32768   IN      OPT     
 Rcode: 6 (Name Exists when it should not), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.123456.www.d.test.com.', qtype=A
index c3054a93f919125bfcdc216a6cbed49ebfaa07b9..6e16ae24a5d262fa30a51e88ed80b8dc3a3142c6 100644 (file)
@@ -1,5 +1,5 @@
-0      d.test.com.     IN      DNAME   3600    d2.test2.com.
-0      d.test.com.     IN      RRSIG   3600    DNAME 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-2      .       IN      OPT     32768   
+0      d.test.com.     3600    IN      DNAME   d2.test2.com.
+0      d.test.com.     3600    IN      RRSIG   DNAME 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+2      .       32768   IN      OPT     
 Rcode: 6 (Name Exists when it should not), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.123456.www.d.test.com.', qtype=A
index 37f1c7e7bd467374945c34cb18c5406f0c8be9d7..af4ea8d7aeef01e178f52b798001f335feee43bf 100644 (file)
@@ -1,5 +1,5 @@
-0      d.test.com.     IN      DNAME   3600    d2.test2.com.
-0      www.d.test.com. IN      CNAME   3600    www.d2.test2.com.
-2      .       IN      OPT     32768   
+0      d.test.com.     3600    IN      DNAME   d2.test2.com.
+0      www.d.test.com. 3600    IN      CNAME   www.d2.test2.com.
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.d.test.com.', qtype=A
index 24dbe8032f9f632a53e74dc5111deb29bc82430d..8c024383c5d22c191c98f243338bb6386eae1d17 100644 (file)
@@ -1,6 +1,6 @@
-0      d.test.com.     IN      DNAME   3600    d2.test2.com.
-0      d.test.com.     IN      RRSIG   3600    DNAME 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-0      www.d.test.com. IN      CNAME   3600    www.d2.test2.com.
-2      .       IN      OPT     32768   
+0      d.test.com.     3600    IN      DNAME   d2.test2.com.
+0      d.test.com.     3600    IN      RRSIG   DNAME 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+0      www.d.test.com. 3600    IN      CNAME   www.d2.test2.com.
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.d.test.com.', qtype=A
index 4977182dd50b80e4b76c7f6f86195962e8dee025..a768166c189bf229e33e879c26e6a717c5b3ff25 100644 (file)
@@ -1,5 +1,5 @@
-0      _double._tcp.dc.test.com.       IN      SRV     3600    0 100 389 server1.test.com.
-0      _double._tcp.dc.test.com.       IN      SRV     3600    1 100 389 server1.test.com.
-2      server1.test.com.       IN      A       3600    1.2.3.4
+0      _double._tcp.dc.test.com.       3600    IN      SRV     0 100 389 server1.test.com.
+0      _double._tcp.dc.test.com.       3600    IN      SRV     1 100 389 server1.test.com.
+2      server1.test.com.       3600    IN      A       1.2.3.4
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='_double._tcp.dc.test.com.', qtype=SRV
index e8c083124e00f0026b481e6d1e5411f30dacef92..d45d04abc6275db746b31d062b727fe9cebcb004 100644 (file)
@@ -1,4 +1,4 @@
-0      double.example.com.     IN      A       120     192.168.5.1
-2      .       IN      OPT     32768   
+0      double.example.com.     120     IN      A       192.168.5.1
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='double.example.com.', qtype=A
index 282bc1511f021436ac8cb648e55a003b0d1eef96..3dcb7258a11178122a1562117ef8470482141a7b 100644 (file)
@@ -1,5 +1,5 @@
-0      double.example.com.     IN      A       120     192.168.5.1
-0      double.example.com.     IN      RRSIG   120     A 13 3 120 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+0      double.example.com.     120     IN      A       192.168.5.1
+0      double.example.com.     120     IN      RRSIG   A 13 3 120 [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='double.example.com.', qtype=A
index 5cc9ca5f74ac03b105dc36bf90d1c4b31e6936db..b5228f6b9e05912bb4f714edc9ebdb6316d82d97 100644 (file)
@@ -1 +1 @@
-This test tries to resolve a non-existent DS at apex
+This test tries to resolve a nonexistent DS at apex
index d68238ff8c9d5a2610b735cb08edc78fe1ec64c1..c6eb39cd1a96db9deefaf7ad21e902910f967414 100644 (file)
@@ -1,4 +1,4 @@
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+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='example.com.', qtype=DS
index f668ae2542aeabfc1f8e77f30128f3a0bbd9191d..da420cca1c36f6fc7066291264fc19935844f53c 100644 (file)
@@ -1,7 +1,7 @@
-1      example.com.    IN      NSEC    86400   _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
-1      example.com.    IN      RRSIG   86400   NSEC 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      example.com.    86400   IN      NSEC    _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
+1      example.com.    86400   IN      RRSIG   NSEC 13 2 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
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='example.com.', qtype=DS
index c51cd216424634ead6da1a87be621c27cb5e0adb..f8ca842bd6d321097031122942194e5533742475 100644 (file)
@@ -1,7 +1,7 @@
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+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      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.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='example.com.', qtype=DS
index 8df160217b727206c8a0c38758798fbfe27c855e..7da5226b59a6ad7004d3c8ce97dbec7e3aad8edb 100644 (file)
@@ -1,7 +1,7 @@
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+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      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.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='example.com.', qtype=DS
index e39f397f9b9a67f84af84631a01deb498901f8ae..5b7cef949099f613e05648b490b2f27b25dc30b6 100644 (file)
@@ -1,5 +1,5 @@
-0      secure-delegated.dnssec-parent.com.     IN      DS      3600    54319 8 2 a0b9c38cd324182af0ef66830d0a0e85a1d58979c9834e18c871779e040857b7
-0      secure-delegated.dnssec-parent.com.     IN      RRSIG   3600    DS 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-2      .       IN      OPT     32768   
+0      secure-delegated.dnssec-parent.com.     3600    IN      DS      54319 8 2 a0b9c38cd324182af0ef66830d0a0e85a1d58979c9834e18c871779e040857b7
+0      secure-delegated.dnssec-parent.com.     3600    IN      RRSIG   DS 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='secure-delegated.dnssec-parent.com.', qtype=DS
index 97172cc7f8e198b48921ed24e9b689ec527f5aa5..e81e2053f773416473f5fe2cfb5193ad267a15ae 100644 (file)
@@ -1,3 +1,3 @@
-1      secure-delegated.dnssec-parent.com.     IN      SOA     3600    ns1.secure-delegated.dnssec-parent.com. ahu.example.com. 2005092501 28800 7200 604800 86400
+1      secure-delegated.dnssec-parent.com.     3600    IN      SOA     ns1.secure-delegated.dnssec-parent.com. ahu.example.com. 2005092501 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='something.secure-delegated.dnssec-parent.com.', qtype=DS
index 0f8143a561c2917d44c3386de93c2e248ff94bef..492c40d15ecb392480c26d847401811ce22bc47b 100644 (file)
@@ -1,4 +1,4 @@
-0      dsdelegation.example.com.       IN      DS      120     28129 8 1 caf1eaaecdabe7616670788f9022454bf5fd9fda
-2      .       IN      OPT     32768   
+0      dsdelegation.example.com.       120     IN      DS      28129 8 1 caf1eaaecdabe7616670788f9022454bf5fd9fda
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='dsdelegation.example.com.', qtype=DS
index c31ca2f5fa981c5eb56dc437e8d3ab49b5caaad2..33061cf0e1576288df43bea5662095eb038917f9 100644 (file)
@@ -1,5 +1,5 @@
-0      dsdelegation.example.com.       IN      DS      120     28129 8 1 caf1eaaecdabe7616670788f9022454bf5fd9fda
-0      dsdelegation.example.com.       IN      RRSIG   120     DS 13 3 120 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+0      dsdelegation.example.com.       120     IN      DS      28129 8 1 caf1eaaecdabe7616670788f9022454bf5fd9fda
+0      dsdelegation.example.com.       120     IN      RRSIG   DS 13 3 120 [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='dsdelegation.example.com.', qtype=DS
index a43425bed7a19c2c96184fce80428e14776e146f..d7c60022b4e2848f9bf4bd5063642333cf54eea9 100644 (file)
@@ -1,4 +1,4 @@
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+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='usa.example.com.', qtype=DS
index de91e66b7fadb0b0b8b7c3f117993b4f777a3c28..59e4ecec448b294b140addd7b29f70b77c91d4ec 100644 (file)
@@ -1,7 +1,7 @@
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      usa.example.com.        IN      NSEC    86400   *.w1.example.com. NS RRSIG NSEC
-1      usa.example.com.        IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+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      usa.example.com.        86400   IN      NSEC    *.w1.example.com. NS RRSIG NSEC
+1      usa.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='usa.example.com.', qtype=DS
index 56cc1fc02ed05f7ef916b3d1c94825f683ad83b0..530fc4f3fd121f7502ffd7138713a68db7986b07 100644 (file)
@@ -1,7 +1,7 @@
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      t67rqvqprigd7rtb5fah6c3o7g9th3j0.example.com.   IN      NSEC3   86400   1 1 1 abcd T67RQVQPRIGD7RTB5FAH6C3O7G9TH3J1 NS
-1      t67rqvqprigd7rtb5fah6c3o7g9th3j0.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+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      t67rqvqprigd7rtb5fah6c3o7g9th3j0.example.com.   86400   IN      NSEC3   1 1 1 abcd T67RQVQPRIGD7RTB5FAH6C3O7G9TH3J1 NS
+1      t67rqvqprigd7rtb5fah6c3o7g9th3j0.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='usa.example.com.', qtype=DS
index 5a0871bd47b3e5a7c0b1a51fa621421a88250511..1000b4ba9cf4f76ec93b22682690c78fe0561de7 100644 (file)
@@ -1,7 +1,7 @@
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      t67rqvqprigd7rtb5fah6c3o7g9th3j0.example.com.   IN      NSEC3   86400   1 0 1 abcd T6A44A7N1B90T5RIS4IBQKT51MMDL0LO NS
-1      t67rqvqprigd7rtb5fah6c3o7g9th3j0.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+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      t67rqvqprigd7rtb5fah6c3o7g9th3j0.example.com.   86400   IN      NSEC3   1 0 1 abcd T6A44A7N1B90T5RIS4IBQKT51MMDL0LO NS
+1      t67rqvqprigd7rtb5fah6c3o7g9th3j0.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='usa.example.com.', qtype=DS
index 6ba2bb3b608c35d6f86c9c2cd73f369dd5a9c66d..6e44723a8756bb3211640b2b587e294f02c5fe92 100644 (file)
@@ -1,9 +1,9 @@
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      t66sektb7egvs7s57m1qged4h6809g8s.example.com.   IN      NSEC3   86400   1 1 1 abcd T6A44A7N1B90T5RIS4IBQKT51MMDL0LO A RRSIG
-1      t66sektb7egvs7s57m1qged4h6809g8s.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 1 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+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      t66sektb7egvs7s57m1qged4h6809g8s.example.com.   86400   IN      NSEC3   1 1 1 abcd T6A44A7N1B90T5RIS4IBQKT51MMDL0LO A RRSIG
+1      t66sektb7egvs7s57m1qged4h6809g8s.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 1 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.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='usa.example.com.', qtype=DS
index 1f8a9c6c79ec2193c2c6de0fa3c5954097e2253a..2cbbabad31f49ebe683a7a642f918715b4acb5b2 100644 (file)
@@ -1,4 +1,4 @@
-1      dnssec-parent.com.      IN      SOA     3600    ns1.dnssec-parent.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      dnssec-parent.com.      3600    IN      SOA     ns1.dnssec-parent.com. ahu.example.com. 2005092501 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='delegated.dnssec-parent.com.', qtype=DS
index 03b9a2e53a0e5035bbbae261cea713a11818bbd2..ac21f93603129fe41d49433fcccd9fd04f8b7095 100644 (file)
@@ -1,7 +1,7 @@
-1      delegated.dnssec-parent.com.    IN      NSEC    3600    insecure.dnssec-parent.com. NS RRSIG NSEC
-1      delegated.dnssec-parent.com.    IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      dnssec-parent.com.      IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      dnssec-parent.com.      IN      SOA     3600    ns1.dnssec-parent.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      delegated.dnssec-parent.com.    3600    IN      NSEC    insecure.dnssec-parent.com. NS RRSIG NSEC
+1      delegated.dnssec-parent.com.    3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      dnssec-parent.com.      3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      dnssec-parent.com.      3600    IN      SOA     ns1.dnssec-parent.com. ahu.example.com. 2005092501 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='delegated.dnssec-parent.com.', qtype=DS
index fb35b4bce0cfa44f4d9c92120f97dd271d59a497..e5ebc7d4e752f0d0c64198a3fe0a1e1c232dffae 100644 (file)
@@ -1,7 +1,7 @@
-1      be6iqh4fjrtdhacqk7g3iq96qcvf2qoj.dnssec-parent.com.     IN      NSEC3   3600    1 1 1 abcd BE6IQH4FJRTDHACQK7G3IQ96QCVF2QOK NS
-1      be6iqh4fjrtdhacqk7g3iq96qcvf2qoj.dnssec-parent.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      dnssec-parent.com.      IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      dnssec-parent.com.      IN      SOA     3600    ns1.dnssec-parent.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      be6iqh4fjrtdhacqk7g3iq96qcvf2qoj.dnssec-parent.com.     3600    IN      NSEC3   1 1 1 abcd BE6IQH4FJRTDHACQK7G3IQ96QCVF2QOK NS
+1      be6iqh4fjrtdhacqk7g3iq96qcvf2qoj.dnssec-parent.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      dnssec-parent.com.      3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      dnssec-parent.com.      3600    IN      SOA     ns1.dnssec-parent.com. ahu.example.com. 2005092501 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='delegated.dnssec-parent.com.', qtype=DS
index 6d3c9917329effb2ab4600beadbc9c41d0a0b823..0be70184f23cb55a09a2dbbf80cf8f4a77856168 100644 (file)
@@ -1,7 +1,7 @@
-1      be6iqh4fjrtdhacqk7g3iq96qcvf2qoj.dnssec-parent.com.     IN      NSEC3   3600    1 0 1 abcd BT0PJS6CH1JQ6I3QEVR9U5HQBBB8B2M4 NS
-1      be6iqh4fjrtdhacqk7g3iq96qcvf2qoj.dnssec-parent.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      dnssec-parent.com.      IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      dnssec-parent.com.      IN      SOA     3600    ns1.dnssec-parent.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      be6iqh4fjrtdhacqk7g3iq96qcvf2qoj.dnssec-parent.com.     3600    IN      NSEC3   1 0 1 abcd BT0PJS6CH1JQ6I3QEVR9U5HQBBB8B2M4 NS
+1      be6iqh4fjrtdhacqk7g3iq96qcvf2qoj.dnssec-parent.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      dnssec-parent.com.      3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      dnssec-parent.com.      3600    IN      SOA     ns1.dnssec-parent.com. ahu.example.com. 2005092501 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='delegated.dnssec-parent.com.', qtype=DS
index aa2cafbf23bd3b977e6835f8409ddcfa787d8965..680fef8692f4be0dbd23a23f217e26c3cafa272b 100644 (file)
@@ -1,9 +1,9 @@
-1      7on3vems0f8k9999ikei0ig4lfijekdr.dnssec-parent.com.     IN      NSEC3   3600    1 1 1 abcd DVKUO8KJA65GCSQ600E6DI9U719LSJ8U NS DS RRSIG
-1      7on3vems0f8k9999ikei0ig4lfijekdr.dnssec-parent.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      dnssec-parent.com.      IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      dnssec-parent.com.      IN      SOA     3600    ns1.dnssec-parent.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-1      dvkuo8kja65gcsq600e6di9u719lsj8u.dnssec-parent.com.     IN      NSEC3   3600    1 1 1 abcd NIH4L3ODLUG7EN20PENJ8DGNU4OHC98F A NS SOA RRSIG DNSKEY NSEC3PARAM CDS CDNSKEY
-1      dvkuo8kja65gcsq600e6di9u719lsj8u.dnssec-parent.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-2      .       IN      OPT     32768   
+1      7on3vems0f8k9999ikei0ig4lfijekdr.dnssec-parent.com.     3600    IN      NSEC3   1 1 1 abcd DVKUO8KJA65GCSQ600E6DI9U719LSJ8U NS DS RRSIG
+1      7on3vems0f8k9999ikei0ig4lfijekdr.dnssec-parent.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      dnssec-parent.com.      3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      dnssec-parent.com.      3600    IN      SOA     ns1.dnssec-parent.com. ahu.example.com. 2005092501 28800 7200 604800 86400
+1      dvkuo8kja65gcsq600e6di9u719lsj8u.dnssec-parent.com.     3600    IN      NSEC3   1 1 1 abcd K25OPIULTRKGKRMR3UC09CSK20QHT1LJ A NS SOA RRSIG DNSKEY NSEC3PARAM CDS CDNSKEY
+1      dvkuo8kja65gcsq600e6di9u719lsj8u.dnssec-parent.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='delegated.dnssec-parent.com.', qtype=DS
index f45f85ff1d396d09da268e063473e0d2b170266b..aa12757d22c8fd5a7e6a19a217b68e66b861eb64 100644 (file)
@@ -1,7 +1,7 @@
-1      usa.example.com.        IN      NS      120     usa-ns1.usa.example.com.
-1      usa.example.com.        IN      NS      120     usa-ns2.usa.example.com.
-2      .       IN      OPT     32768   
-2      usa-ns1.usa.example.com.        IN      A       120     192.168.4.1
-2      usa-ns2.usa.example.com.        IN      A       120     192.168.4.2
+1      usa.example.com.        120     IN      NS      usa-ns1.usa.example.com.
+1      usa.example.com.        120     IN      NS      usa-ns2.usa.example.com.
+2      .       32768   IN      OPT     
+2      usa-ns1.usa.example.com.        120     IN      A       192.168.4.1
+2      usa-ns2.usa.example.com.        120     IN      A       192.168.4.2
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='sub.usa.example.com.', qtype=DS
index 8f3ebb950beec0473b6465fcee34a0facb1b52ef..43cf0ef1f64fa5c80a5874532c886c054d1d6236 100644 (file)
@@ -1,9 +1,9 @@
-1      usa.example.com.        IN      NS      120     usa-ns1.usa.example.com.
-1      usa.example.com.        IN      NS      120     usa-ns2.usa.example.com.
-1      usa.example.com.        IN      NSEC    86400   *.w1.example.com. NS RRSIG NSEC
-1      usa.example.com.        IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
-2      usa-ns1.usa.example.com.        IN      A       120     192.168.4.1
-2      usa-ns2.usa.example.com.        IN      A       120     192.168.4.2
+1      usa.example.com.        120     IN      NS      usa-ns1.usa.example.com.
+1      usa.example.com.        120     IN      NS      usa-ns2.usa.example.com.
+1      usa.example.com.        86400   IN      NSEC    *.w1.example.com. NS RRSIG NSEC
+1      usa.example.com.        86400   IN      RRSIG   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
+2      usa-ns1.usa.example.com.        120     IN      A       192.168.4.1
+2      usa-ns2.usa.example.com.        120     IN      A       192.168.4.2
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='sub.usa.example.com.', qtype=DS
index eb5b3691b9ff1f2f224bd169f2c84a3f12ae564c..0050dbcc89de7aeb4f6ce92932ea5c70294ce75f 100644 (file)
@@ -1,9 +1,9 @@
-1      t67rqvqprigd7rtb5fah6c3o7g9th3j0.example.com.   IN      NSEC3   86400   1 1 1 abcd T67RQVQPRIGD7RTB5FAH6C3O7G9TH3J1 NS
-1      t67rqvqprigd7rtb5fah6c3o7g9th3j0.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      usa.example.com.        IN      NS      120     usa-ns1.usa.example.com.
-1      usa.example.com.        IN      NS      120     usa-ns2.usa.example.com.
-2      .       IN      OPT     32768   
-2      usa-ns1.usa.example.com.        IN      A       120     192.168.4.1
-2      usa-ns2.usa.example.com.        IN      A       120     192.168.4.2
+1      t67rqvqprigd7rtb5fah6c3o7g9th3j0.example.com.   86400   IN      NSEC3   1 1 1 abcd T67RQVQPRIGD7RTB5FAH6C3O7G9TH3J1 NS
+1      t67rqvqprigd7rtb5fah6c3o7g9th3j0.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      usa.example.com.        120     IN      NS      usa-ns1.usa.example.com.
+1      usa.example.com.        120     IN      NS      usa-ns2.usa.example.com.
+2      .       32768   IN      OPT     
+2      usa-ns1.usa.example.com.        120     IN      A       192.168.4.1
+2      usa-ns2.usa.example.com.        120     IN      A       192.168.4.2
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='sub.usa.example.com.', qtype=DS
index df3fa6aa97a6710e9fdb7df11f8236e89de8edee..6cfe9e8399c2d243a8bc6302734e21e516ab6d51 100644 (file)
@@ -1,9 +1,9 @@
-1      t67rqvqprigd7rtb5fah6c3o7g9th3j0.example.com.   IN      NSEC3   86400   1 0 1 abcd T6A44A7N1B90T5RIS4IBQKT51MMDL0LO NS
-1      t67rqvqprigd7rtb5fah6c3o7g9th3j0.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      usa.example.com.        IN      NS      120     usa-ns1.usa.example.com.
-1      usa.example.com.        IN      NS      120     usa-ns2.usa.example.com.
-2      .       IN      OPT     32768   
-2      usa-ns1.usa.example.com.        IN      A       120     192.168.4.1
-2      usa-ns2.usa.example.com.        IN      A       120     192.168.4.2
+1      t67rqvqprigd7rtb5fah6c3o7g9th3j0.example.com.   86400   IN      NSEC3   1 0 1 abcd T6A44A7N1B90T5RIS4IBQKT51MMDL0LO NS
+1      t67rqvqprigd7rtb5fah6c3o7g9th3j0.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      usa.example.com.        120     IN      NS      usa-ns1.usa.example.com.
+1      usa.example.com.        120     IN      NS      usa-ns2.usa.example.com.
+2      .       32768   IN      OPT     
+2      usa-ns1.usa.example.com.        120     IN      A       192.168.4.1
+2      usa-ns2.usa.example.com.        120     IN      A       192.168.4.2
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='sub.usa.example.com.', qtype=DS
index 435dc693357c2774da52ef33ce21a4cc07cc3caa..c31c40ddeda843e422e2e6287d065b7b0946cc09 100644 (file)
@@ -1,11 +1,11 @@
-1      t66sektb7egvs7s57m1qged4h6809g8s.example.com.   IN      NSEC3   86400   1 1 1 abcd T6A44A7N1B90T5RIS4IBQKT51MMDL0LO A RRSIG
-1      t66sektb7egvs7s57m1qged4h6809g8s.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      usa.example.com.        IN      NS      120     usa-ns1.usa.example.com.
-1      usa.example.com.        IN      NS      120     usa-ns2.usa.example.com.
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 1 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
-2      usa-ns1.usa.example.com.        IN      A       120     192.168.4.1
-2      usa-ns2.usa.example.com.        IN      A       120     192.168.4.2
+1      t66sektb7egvs7s57m1qged4h6809g8s.example.com.   86400   IN      NSEC3   1 1 1 abcd T6A44A7N1B90T5RIS4IBQKT51MMDL0LO A RRSIG
+1      t66sektb7egvs7s57m1qged4h6809g8s.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      usa.example.com.        120     IN      NS      usa-ns1.usa.example.com.
+1      usa.example.com.        120     IN      NS      usa-ns2.usa.example.com.
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 1 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
+2      usa-ns1.usa.example.com.        120     IN      A       192.168.4.1
+2      usa-ns2.usa.example.com.        120     IN      A       192.168.4.2
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='sub.usa.example.com.', qtype=DS
index 4bc573a53baaa3803e686eebdf51dcf089df1609..33bd431b0b2883b67f0ee71050ee761fa5c8822c 100644 (file)
@@ -1,4 +1,4 @@
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 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='c.test.com.', qtype=ANY
index 48d512cd49044f0359fa2c2e2ba25949f5354e54..79621e831359dd948b7dc4ccf24e652b3af921a3 100644 (file)
@@ -1,7 +1,7 @@
-1      blah.test.com.  IN      NSEC    3600    b.c.test.com. NS RRSIG NSEC
-1      blah.test.com.  IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      blah.test.com.  3600    IN      NSEC    b.c.test.com. NS RRSIG NSEC
+1      blah.test.com.  3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 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='c.test.com.', qtype=ANY
index 07c211d3db575c762553919e79c8977e3b59d453..be91f130999696b9fee14c31193764ae9f930dbc 100644 (file)
@@ -1,7 +1,7 @@
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      NSEC3   3600    1 1 1 abcd S6G5SHC1JVOVL5FL9E943ADLONQLN7G5
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      NSEC3   1 1 1 abcd S6G5SHC1JVOVL5FL9E943ADLONQLN7G5
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 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='c.test.com.', qtype=ANY
index 119dfe5e4a7b0194488e40d641d9a40b56fccafd..6a16f770fa06834a64ec8bd5b868c79b29567942 100644 (file)
@@ -1,7 +1,7 @@
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      NSEC3   3600    1 0 1 abcd S96H2QICBT8D9I5AA43KP8SJJRESQ4KB
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      NSEC3   1 0 1 abcd S96H2QICBT8D9I5AA43KP8SJJRESQ4KB
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 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='c.test.com.', qtype=ANY
index fe0122037116d93b79328906bf56b6afc3e43de8..1a3aec7335745a240bad8910f2256d9ee3bbc67c 100644 (file)
@@ -1,7 +1,7 @@
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      NSEC3   3600    1 1 1 abcd SA5VVPQN1COEJGJ3HBKFEKDNII8KKSQA
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      NSEC3   1 1 1 abcd SA5VVPQN1COEJGJ3HBKFEKDNII8KKSQA
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 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='c.test.com.', qtype=ANY
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 16ac752a65ff285189c3382d47119b9efdf1fec6..51e64a592fd33b41ee1c0392469ee05443823753 100644 (file)
@@ -1,4 +1,8 @@
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+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=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 fcbcc4b84687a9be26e2b96e1c442b29f55499ad..3c5498c78eeeb58947b225570853f4469ce7f6f7 100644 (file)
@@ -1,9 +1,18 @@
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      host.*.sub.example.com. IN      NSEC    86400   bar.svcb.example.com. A RRSIG NSEC
-1      host.*.sub.example.com. IN      RRSIG   86400   NSEC 13 5 86400 [expiry] [inception] [keytag] example.com. ...
-1      start4.example.com.     IN      NSEC    86400   host.*.sub.example.com. A RRSIG NSEC
-1      start4.example.com.     IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+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=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 4f1faaa13f46bc3d317834c3912d137740d362cb..fa87e0b259214bdf34336d10a98e42259c659cd3 100644 (file)
@@ -1,11 +1,22 @@
-1      5ui8h56r4776maicvhpdegs6chr19i99.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 5UI8H56R4776MAICVHPDEGS6CHR19I9A
-1      5ui8h56r4776maicvhpdegs6chr19i99.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      hhrsadparthvtuou67trentjstdodla0.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd HHRSADPARTHVTUOU67TRENTJSTDODLA1
-1      hhrsadparthvtuou67trentjstdodla0.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      pbl3rtqv3mt7eb29gqp0a17o0h42nj76.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd PBL3RTQV3MT7EB29GQP0A17O0H42NJ78
-1      pbl3rtqv3mt7eb29gqp0a17o0h42nj76.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+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=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 3de50227353fcb8056833ef330876d67166248ce..127945409770e80d40dfdde8ceb30bdbe9758168 100644 (file)
@@ -1,11 +1,22 @@
-1      5ui8h56r4776maicvhpdegs6chr19i99.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 5UMB87SUFNRRMLILGL48A5GUUHG7RI58
-1      5ui8h56r4776maicvhpdegs6chr19i99.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      hhrsadparthvtuou67trentjstdodla0.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd HHTKKD5HB125SGANBTKMQK84LULH60LH
-1      hhrsadparthvtuou67trentjstdodla0.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      pbkjnd53pnsru5jmaqnk3k936pv2pq5j.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd PBL4SE96F8T4H4Q24UQMRQ4KS96AHPV3 A RRSIG
-1      pbkjnd53pnsru5jmaqnk3k936pv2pq5j.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+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=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
index 756c5d0fbbbd77200a49d6655b25b12ed7ea241f..6421d01dbea0ef7ba81438e5c05809fa898bdec4 100644 (file)
@@ -1,5 +1,8 @@
 *.a.b.c.test.com.      3600    IN      NSEC    counter.test.com. A RRSIG NSEC
 *.test.test.com.       3600    IN      NSEC    sub.test.test.com. CNAME RRSIG NSEC
+10.order.test.com.     3600    IN      NSEC    100.order.test.com. A RRSIG NSEC
+100.order.test.com.    3600    IN      NSEC    15.order.test.com. A RRSIG NSEC
+15.order.test.com.     3600    IN      NSEC    server1.test.com. A RRSIG NSEC
 _double._tcp.dc.test.com.      3600    IN      NSEC    _ldap._tcp.dc.test.com. SRV RRSIG NSEC
 _ldap._tcp.dc.test.com.        3600    IN      NSEC    _root._tcp.dc.test.com. SRV RRSIG NSEC
 _root._tcp.dc.test.com.        3600    IN      NSEC    enum.test.com. SRV RRSIG NSEC
@@ -13,7 +16,7 @@ enum.test.com.        3600    IN      NSEC    hightxt.test.com. NAPTR RRSIG NSEC
 hightxt.test.com.      3600    IN      NSEC    interrupted-rrset.test.com. TXT RRSIG NSEC SPF
 interrupted-rrset.test.com.    3600    IN      NSEC    ns1.test.com. A TXT RRSIG NSEC
 ns1.test.com.  3600    IN      NSEC    ns2.test.com. A RRSIG NSEC
-ns2.test.com.  3600    IN      NSEC    server1.test.com. A RRSIG NSEC
+ns2.test.com.  3600    IN      NSEC    10.order.test.com. A RRSIG NSEC
 server1.test.com.      3600    IN      NSEC    *.test.test.com. A RP RRSIG NSEC
 sub.test.test.com.     3600    IN      NSEC    www.test.test.com. NS RRSIG NSEC
 test.com.      3600    IN      NSEC    _underscore.test.com. NS SOA MX RRSIG NSEC DNSKEY
index 08586761b11a376d6c208e32a0dc840c3252a21e..1a516046258b6b68637e60672ed35a9be64e8a6d 100644 (file)
@@ -1,16 +1,20 @@
-0bh8di769i8vvtkdds8efjda19abigo5.test.com.     3600    IN      NSEC3   1 0 1 abcd 2EU2GULBU53H9UVHFALSHPBO2A83T6L2 TXT RRSIG SPF
+0bh8di769i8vvtkdds8efjda19abigo5.test.com.     3600    IN      NSEC3   1 0 1 abcd 23E9S542JEHG7KBKNCNJPJHPHUPCD1JE TXT RRSIG SPF
+23e9s542jehg7kbkncnjpjhphupcd1je.test.com.     3600    IN      NSEC3   1 0 1 abcd 2EU2GULBU53H9UVHFALSHPBO2A83T6L2 A RRSIG
 2eu2gulbu53h9uvhfalshpbo2a83t6l2.test.com.     3600    IN      NSEC3   1 0 1 abcd 2GKS2N3JPQF62QOHAVFQ1PHOLM3HR7RA NS SOA MX RRSIG DNSKEY NSEC3PARAM
 2gks2n3jpqf62qohavfq1pholm3hr7ra.test.com.     3600    IN      NSEC3   1 0 1 abcd 4UL8F3M96VCONEA85U93DH9SG570J4FU TXT RRSIG
 4ul8f3m96vconea85u93dh9sg570j4fu.test.com.     3600    IN      NSEC3   1 0 1 abcd 53L445R26RG1CSBI4TS1K3I3EL1F30VM DNAME RRSIG
 53l445r26rg1csbi4ts1k3i3el1f30vm.test.com.     3600    IN      NSEC3   1 0 1 abcd 53LJH1SKI76U8MVC0TPOA423TDUR0KL8 SRV RRSIG
-53ljh1ski76u8mvc0tpoa423tdur0kl8.test.com.     3600    IN      NSEC3   1 0 1 abcd 79RA8K3G5KAI1HG9JLHBR6P0TP933M7V RRSIG TYPE65226
+53ljh1ski76u8mvc0tpoa423tdur0kl8.test.com.     3600    IN      NSEC3   1 0 1 abcd 5VE0V7KDJ5BSD70O75FC4GTF8T2QJRB8 RRSIG TYPE65226
+5ve0v7kdj5bsd70o75fc4gtf8t2qjrb8.test.com.     3600    IN      NSEC3   1 0 1 abcd 79RA8K3G5KAI1HG9JLHBR6P0TP933M7V A RRSIG
 79ra8k3g5kai1hg9jlhbr6p0tp933m7v.test.com.     3600    IN      NSEC3   1 0 1 abcd 79U3DAS6UCCTNS1BR3TVD8QKANNI351L A RRSIG
 79u3das6ucctns1br3tvd8qkanni351l.test.com.     3600    IN      NSEC3   1 0 1 abcd 7MMURA8H40BE5N4KOAN7RNMKURSAMH99
 7mmura8h40be5n4koan7rnmkursamh99.test.com.     3600    IN      NSEC3   1 0 1 abcd 88F1BQRB2ISCVFEL2SQQCKSVFLNEKAP6
 88f1bqrb2iscvfel2sqqcksvflnekap6.test.com.     3600    IN      NSEC3   1 0 1 abcd A5LABAGJJEVR86GH0HF3JG7NUFHGA5AR CNAME RRSIG
 a5labagjjevr86gh0hf3jg7nufhga5ar.test.com.     3600    IN      NSEC3   1 0 1 abcd AOVP95MR44HQEFRQUS6NOMSD944BM3VB A RRSIG
 aovp95mr44hqefrqus6nomsd944bm3vb.test.com.     3600    IN      NSEC3   1 0 1 abcd B022O9DKSAJ737FH77E7KQQTJ3OM56KI A RRSIG
-b022o9dksaj737fh77e7kqqtj3om56ki.test.com.     3600    IN      NSEC3   1 0 1 abcd DAFC69CV5N2TFCF6OVBVTV94DRGMQJO5
+b022o9dksaj737fh77e7kqqtj3om56ki.test.com.     3600    IN      NSEC3   1 0 1 abcd BAE1G74DOIMCDKG03DAFHMGRLH2L19TU
+bae1g74doimcdkg03dafhmgrlh2l19tu.test.com.     3600    IN      NSEC3   1 0 1 abcd CGFU8T55L510A9K688GBO9UACMBBS2AB
+cgfu8t55l510a9k688gbo9uacmbbs2ab.test.com.     3600    IN      NSEC3   1 0 1 abcd DAFC69CV5N2TFCF6OVBVTV94DRGMQJO5 A RRSIG
 dafc69cv5n2tfcf6ovbvtv94drgmqjo5.test.com.     3600    IN      NSEC3   1 0 1 abcd DE592K86U3HEVDJ57JPBT7J5KV7DOO78 TXT RRSIG
 de592k86u3hevdj57jpbt7j5kv7doo78.test.com.     3600    IN      NSEC3   1 0 1 abcd EBAN51BJGUGORB20UNP5PEEC7S5D2EKA NS
 eban51bjgugorb20unp5peec7s5d2eka.test.com.     3600    IN      NSEC3   1 0 1 abcd ENG6HBK77VJMQFVG6S04HAJOA2201LII SRV RRSIG
index ea77f5093a85719d11439caeb44302e23d0b180b..f56193cb858dff010e0ce401f5431dc64787d6ea 100644 (file)
@@ -1,16 +1,20 @@
-0bh8di769i8vvtkdds8efjda19abigo5.test.com.     3600    IN      NSEC3   1 1 1 abcd 2EU2GULBU53H9UVHFALSHPBO2A83T6L2 TXT RRSIG SPF
+0bh8di769i8vvtkdds8efjda19abigo5.test.com.     3600    IN      NSEC3   1 1 1 abcd 23E9S542JEHG7KBKNCNJPJHPHUPCD1JE TXT RRSIG SPF
+23e9s542jehg7kbkncnjpjhphupcd1je.test.com.     3600    IN      NSEC3   1 1 1 abcd 2EU2GULBU53H9UVHFALSHPBO2A83T6L2 A RRSIG
 2eu2gulbu53h9uvhfalshpbo2a83t6l2.test.com.     3600    IN      NSEC3   1 1 1 abcd 2GKS2N3JPQF62QOHAVFQ1PHOLM3HR7RA NS SOA MX RRSIG DNSKEY NSEC3PARAM
 2gks2n3jpqf62qohavfq1pholm3hr7ra.test.com.     3600    IN      NSEC3   1 1 1 abcd 4UL8F3M96VCONEA85U93DH9SG570J4FU TXT RRSIG
 4ul8f3m96vconea85u93dh9sg570j4fu.test.com.     3600    IN      NSEC3   1 1 1 abcd 53L445R26RG1CSBI4TS1K3I3EL1F30VM DNAME RRSIG
 53l445r26rg1csbi4ts1k3i3el1f30vm.test.com.     3600    IN      NSEC3   1 1 1 abcd 53LJH1SKI76U8MVC0TPOA423TDUR0KL8 SRV RRSIG
-53ljh1ski76u8mvc0tpoa423tdur0kl8.test.com.     3600    IN      NSEC3   1 1 1 abcd 79RA8K3G5KAI1HG9JLHBR6P0TP933M7V RRSIG TYPE65226
+53ljh1ski76u8mvc0tpoa423tdur0kl8.test.com.     3600    IN      NSEC3   1 1 1 abcd 5VE0V7KDJ5BSD70O75FC4GTF8T2QJRB8 RRSIG TYPE65226
+5ve0v7kdj5bsd70o75fc4gtf8t2qjrb8.test.com.     3600    IN      NSEC3   1 1 1 abcd 79RA8K3G5KAI1HG9JLHBR6P0TP933M7V A RRSIG
 79ra8k3g5kai1hg9jlhbr6p0tp933m7v.test.com.     3600    IN      NSEC3   1 1 1 abcd 79U3DAS6UCCTNS1BR3TVD8QKANNI351L A RRSIG
 79u3das6ucctns1br3tvd8qkanni351l.test.com.     3600    IN      NSEC3   1 1 1 abcd 7MMURA8H40BE5N4KOAN7RNMKURSAMH99
 7mmura8h40be5n4koan7rnmkursamh99.test.com.     3600    IN      NSEC3   1 1 1 abcd 88F1BQRB2ISCVFEL2SQQCKSVFLNEKAP6
 88f1bqrb2iscvfel2sqqcksvflnekap6.test.com.     3600    IN      NSEC3   1 1 1 abcd A5LABAGJJEVR86GH0HF3JG7NUFHGA5AR CNAME RRSIG
 a5labagjjevr86gh0hf3jg7nufhga5ar.test.com.     3600    IN      NSEC3   1 1 1 abcd AOVP95MR44HQEFRQUS6NOMSD944BM3VB A RRSIG
 aovp95mr44hqefrqus6nomsd944bm3vb.test.com.     3600    IN      NSEC3   1 1 1 abcd B022O9DKSAJ737FH77E7KQQTJ3OM56KI A RRSIG
-b022o9dksaj737fh77e7kqqtj3om56ki.test.com.     3600    IN      NSEC3   1 1 1 abcd DAFC69CV5N2TFCF6OVBVTV94DRGMQJO5
+b022o9dksaj737fh77e7kqqtj3om56ki.test.com.     3600    IN      NSEC3   1 1 1 abcd BAE1G74DOIMCDKG03DAFHMGRLH2L19TU
+bae1g74doimcdkg03dafhmgrlh2l19tu.test.com.     3600    IN      NSEC3   1 1 1 abcd CGFU8T55L510A9K688GBO9UACMBBS2AB
+cgfu8t55l510a9k688gbo9uacmbbs2ab.test.com.     3600    IN      NSEC3   1 1 1 abcd DAFC69CV5N2TFCF6OVBVTV94DRGMQJO5 A RRSIG
 dafc69cv5n2tfcf6ovbvtv94drgmqjo5.test.com.     3600    IN      NSEC3   1 1 1 abcd EBAN51BJGUGORB20UNP5PEEC7S5D2EKA TXT RRSIG
 eban51bjgugorb20unp5peec7s5d2eka.test.com.     3600    IN      NSEC3   1 1 1 abcd ENG6HBK77VJMQFVG6S04HAJOA2201LII SRV RRSIG
 eng6hbk77vjmqfvg6s04hajoa2201lii.test.com.     3600    IN      NSEC3   1 1 1 abcd H5855RVON2AASM8QV1NK49I1B2MKBEJP A TXT RRSIG
index 603e87d85da746219d396b1da29e20b69e455d31..52487ef6136d9665f83bdd081ce656ca4b4e97fb 100644 (file)
@@ -1,7 +1,7 @@
-1      b.c.test.com.   IN      NSEC    3600    *.a.b.c.test.com. A RRSIG NSEC
-1      b.c.test.com.   IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      b.c.test.com.   3600    IN      NSEC    *.a.b.c.test.com. A RRSIG NSEC
+1      b.c.test.com.   3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 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='b.c.test.com.', qtype=TXT
index 314cc70ca2c955391363acd74aa6f7f7850f9ea2..f9443757965011a523b9613c748f40ff27908d48 100644 (file)
@@ -1,7 +1,7 @@
-1      a5labagjjevr86gh0hf3jg7nufhga5ar.test.com.      IN      NSEC3   3600    1 [flags] 1 abcd A5LABAGJJEVR86GH0HF3JG7NUFHGA5AS A RRSIG
-1      a5labagjjevr86gh0hf3jg7nufhga5ar.test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a5labagjjevr86gh0hf3jg7nufhga5ar.test.com.      3600    IN      NSEC3   1 [flags] 1 abcd A5LABAGJJEVR86GH0HF3JG7NUFHGA5AS A RRSIG
+1      a5labagjjevr86gh0hf3jg7nufhga5ar.test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 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='b.c.test.com.', qtype=TXT
index f31d9f153bbe3aa407a0dfc8956486a4ad879865..88e1f3afbd61d18532c168fce357c3ece9f4f558 100644 (file)
@@ -1,7 +1,7 @@
-1      a5labagjjevr86gh0hf3jg7nufhga5ar.test.com.      IN      NSEC3   3600    1 [flags] 1 abcd AOVP95MR44HQEFRQUS6NOMSD944BM3VB A RRSIG
-1      a5labagjjevr86gh0hf3jg7nufhga5ar.test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      a5labagjjevr86gh0hf3jg7nufhga5ar.test.com.      3600    IN      NSEC3   1 [flags] 1 abcd AOVP95MR44HQEFRQUS6NOMSD944BM3VB A RRSIG
+1      a5labagjjevr86gh0hf3jg7nufhga5ar.test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 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='b.c.test.com.', qtype=TXT
index bd9a668a993ba5f83dcf457cd56e0c6b4ce2aef5..89a760f74b67e4af0fc23c1308da9bf26b52f9b6 100644 (file)
@@ -1,4 +1,4 @@
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 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='c.test.com.', qtype=SOA
index d8346bcd0bcdbea96ee72bb07520bdbcef35844e..e79cd55ff5945fd00afec9a61471a10acec017ed 100644 (file)
@@ -1,7 +1,7 @@
-1      blah.test.com.  IN      NSEC    3600    b.c.test.com. NS RRSIG NSEC
-1      blah.test.com.  IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      blah.test.com.  3600    IN      NSEC    b.c.test.com. NS RRSIG NSEC
+1      blah.test.com.  3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 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='c.test.com.', qtype=SOA
index dc6c27b7538edd84895702bde9b5937f667dcb28..4e7f9645d52113a6bb89a16d49ac66130e59bcfa 100644 (file)
@@ -1,7 +1,7 @@
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      NSEC3   3600    1 1 1 abcd S6G5SHC1JVOVL5FL9E943ADLONQLN7G5
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      NSEC3   1 1 1 abcd S6G5SHC1JVOVL5FL9E943ADLONQLN7G5
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 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='c.test.com.', qtype=SOA
index 1d99f83a2aaa422fe1aa38b444410996813b5c82..893c9262c808595434c6b3fee0c91864493a5323 100644 (file)
@@ -1,7 +1,7 @@
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      NSEC3   3600    1 0 1 abcd S96H2QICBT8D9I5AA43KP8SJJRESQ4KB
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      NSEC3   1 0 1 abcd S96H2QICBT8D9I5AA43KP8SJJRESQ4KB
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 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='c.test.com.', qtype=SOA
index 4a70c16df9cafa5aa9f11ee3df7d103f47fff9fb..dfa3fb3af9732369402c210872847fc0fb32fb2c 100644 (file)
@@ -1,7 +1,7 @@
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      NSEC3   3600    1 1 1 abcd SA5VVPQN1COEJGJ3HBKFEKDNII8KKSQA
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      NSEC3   1 1 1 abcd SA5VVPQN1COEJGJ3HBKFEKDNII8KKSQA
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 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='c.test.com.', qtype=SOA
index 81e37590bc6e310f37af402cc49de558ee40d7e3..41fecb90babe4069f8b5676fbfa26c138a8fdaf8 100644 (file)
@@ -1,4 +1,4 @@
-1      dnssec-parent.com.      IN      SOA     3600    ns1.dnssec-parent.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      dnssec-parent.com.      3600    IN      SOA     ns1.dnssec-parent.com. ahu.example.com. 2005092501 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='ent.ent.auth-ent.dnssec-parent.com.', qtype=A
index f7edf1b5e5cd4ae8806866b82dabeab6f7856b74..c9444d0f6c7daa0bae122ae8b3df6162a6ede963 100644 (file)
@@ -1,7 +1,7 @@
-1      dnssec-parent.com.      IN      NSEC    3600    insecure-delegated.ent.ent.auth-ent.dnssec-parent.com. A NS SOA RRSIG NSEC DNSKEY CDS CDNSKEY
-1      dnssec-parent.com.      IN      RRSIG   3600    NSEC 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      dnssec-parent.com.      IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      dnssec-parent.com.      IN      SOA     3600    ns1.dnssec-parent.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      *.dnssec-parent.com.    3600    IN      NSEC    insecure-delegated.ent.ent.auth-ent.dnssec-parent.com. CNAME RRSIG NSEC
+1      *.dnssec-parent.com.    3600    IN      RRSIG   NSEC 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      dnssec-parent.com.      3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      dnssec-parent.com.      3600    IN      SOA     ns1.dnssec-parent.com. ahu.example.com. 2005092501 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='ent.ent.auth-ent.dnssec-parent.com.', qtype=A
index bb5e390030cf6c5728af6fea24d836b773f9a1e9..f171ee260703b186eb9480f30af70002c17edbea 100644 (file)
@@ -1,7 +1,7 @@
-1      7r6pbiscipot7md4qjkea2lgrd2srr19.dnssec-parent.com.     IN      NSEC3   3600    1 1 1 abcd 7R6PBISCIPOT7MD4QJKEA2LGRD2SRR1A
-1      7r6pbiscipot7md4qjkea2lgrd2srr19.dnssec-parent.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      dnssec-parent.com.      IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      dnssec-parent.com.      IN      SOA     3600    ns1.dnssec-parent.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      7r6pbiscipot7md4qjkea2lgrd2srr19.dnssec-parent.com.     3600    IN      NSEC3   1 1 1 abcd 7R6PBISCIPOT7MD4QJKEA2LGRD2SRR1A
+1      7r6pbiscipot7md4qjkea2lgrd2srr19.dnssec-parent.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      dnssec-parent.com.      3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      dnssec-parent.com.      3600    IN      SOA     ns1.dnssec-parent.com. ahu.example.com. 2005092501 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='ent.ent.auth-ent.dnssec-parent.com.', qtype=A
index 816d7ebff18399edd018371ea9cdbddb30fd8e32..d064071caa4884d12782bcc15369bbe11be5648a 100644 (file)
@@ -1,7 +1,7 @@
-1      7r6pbiscipot7md4qjkea2lgrd2srr19.dnssec-parent.com.     IN      NSEC3   3600    1 0 1 abcd BE6IQH4FJRTDHACQK7G3IQ96QCVF2QOJ
-1      7r6pbiscipot7md4qjkea2lgrd2srr19.dnssec-parent.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      dnssec-parent.com.      IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      dnssec-parent.com.      IN      SOA     3600    ns1.dnssec-parent.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      7r6pbiscipot7md4qjkea2lgrd2srr19.dnssec-parent.com.     3600    IN      NSEC3   1 0 1 abcd BE6IQH4FJRTDHACQK7G3IQ96QCVF2QOJ
+1      7r6pbiscipot7md4qjkea2lgrd2srr19.dnssec-parent.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      dnssec-parent.com.      3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      dnssec-parent.com.      3600    IN      SOA     ns1.dnssec-parent.com. ahu.example.com. 2005092501 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='ent.ent.auth-ent.dnssec-parent.com.', qtype=A
index 8880e3211b1081698f1eda78e16d4d6ecc55897c..cba2aa1ea6ea9d82684f43010618f04013693e58 100644 (file)
@@ -1,9 +1,9 @@
-1      dnssec-parent.com.      IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      dnssec-parent.com.      IN      SOA     3600    ns1.dnssec-parent.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-1      nih4l3odlug7en20penj8dgnu4ohc98f.dnssec-parent.com.     IN      NSEC3   3600    1 1 1 abcd QOQSRIQRVI1G1QL3TPPH2248Q9LDPEPF
-1      nih4l3odlug7en20penj8dgnu4ohc98f.dnssec-parent.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      qoqsriqrvi1g1ql3tpph2248q9ldpepf.dnssec-parent.com.     IN      NSEC3   3600    1 1 1 abcd 1SCAQA30LQ0DO5EIRNE4KPJFBEBFGR54 A RRSIG
-1      qoqsriqrvi1g1ql3tpph2248q9ldpepf.dnssec-parent.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-2      .       IN      OPT     32768   
+1      dnssec-parent.com.      3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      dnssec-parent.com.      3600    IN      SOA     ns1.dnssec-parent.com. ahu.example.com. 2005092501 28800 7200 604800 86400
+1      nih4l3odlug7en20penj8dgnu4ohc98f.dnssec-parent.com.     3600    IN      NSEC3   1 1 1 abcd QOQSRIQRVI1G1QL3TPPH2248Q9LDPEPF
+1      nih4l3odlug7en20penj8dgnu4ohc98f.dnssec-parent.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      qoqsriqrvi1g1ql3tpph2248q9ldpepf.dnssec-parent.com.     3600    IN      NSEC3   1 1 1 abcd 1SCAQA30LQ0DO5EIRNE4KPJFBEBFGR54 A RRSIG
+1      qoqsriqrvi1g1ql3tpph2248q9ldpepf.dnssec-parent.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='ent.ent.auth-ent.dnssec-parent.com.', qtype=A
index 7b3d4629310966940912381a0d962c47c9cdc2ad..aae69d278d099d6720a3ac8b5af92c0e77ccf6f7 100644 (file)
@@ -1,4 +1,4 @@
-0      something.a.b.c.test.com.       IN      A       3600    8.7.6.5
-2      .       IN      OPT     32768   
+0      something.a.b.c.test.com.       3600    IN      A       8.7.6.5
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='something.a.b.c.test.com.', qtype=A
index 4a6ac0ef25cbc02c50b735f3609e60dbdc137c65..a9dd716784c2d77f636a5c6e241d3c476df5e818 100644 (file)
@@ -1,7 +1,7 @@
-0      something.a.b.c.test.com.       IN      A       3600    8.7.6.5
-0      something.a.b.c.test.com.       IN      RRSIG   3600    A 13 5 3600 [expiry] [inception] [keytag] test.com. ...
-1      *.a.b.c.test.com.       IN      NSEC    3600    counter.test.com. A RRSIG NSEC
-1      *.a.b.c.test.com.       IN      RRSIG   3600    NSEC 13 5 3600 [expiry] [inception] [keytag] test.com. ...
-2      .       IN      OPT     32768   
+0      something.a.b.c.test.com.       3600    IN      A       8.7.6.5
+0      something.a.b.c.test.com.       3600    IN      RRSIG   A 13 5 3600 [expiry] [inception] [keytag] test.com. ...
+1      *.a.b.c.test.com.       3600    IN      NSEC    counter.test.com. A RRSIG NSEC
+1      *.a.b.c.test.com.       3600    IN      RRSIG   NSEC 13 5 3600 [expiry] [inception] [keytag] test.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='something.a.b.c.test.com.', qtype=A
index 18724720e8846b10ed794a97481c0a53a746e5bd..36734a97a1e27639f82f8c00308e87ad0c5199d7 100644 (file)
@@ -1,7 +1,7 @@
-0      something.a.b.c.test.com.       IN      A       3600    8.7.6.5
-0      something.a.b.c.test.com.       IN      RRSIG   3600    A 13 5 3600 [expiry] [inception] [keytag] test.com. ...
-1      qjeirdhb04ir4vbs5pbbhbue69dlq9nr.test.com.      IN      NSEC3   3600    1 [flags] 1 abcd QJEIRDHB04IR4VBS5PBBHBUE69DLQ9NT
-1      qjeirdhb04ir4vbs5pbbhbue69dlq9nr.test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-2      .       IN      OPT     32768   
+0      something.a.b.c.test.com.       3600    IN      A       8.7.6.5
+0      something.a.b.c.test.com.       3600    IN      RRSIG   A 13 5 3600 [expiry] [inception] [keytag] test.com. ...
+1      qjeirdhb04ir4vbs5pbbhbue69dlq9nr.test.com.      3600    IN      NSEC3   1 [flags] 1 abcd QJEIRDHB04IR4VBS5PBBHBUE69DLQ9NT
+1      qjeirdhb04ir4vbs5pbbhbue69dlq9nr.test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='something.a.b.c.test.com.', qtype=A
index e4556b3835db3b98a6a7d1dd71758e886411ffd6..f55b269427a872de2b1dc941533bdc5f3b141385 100644 (file)
@@ -1,7 +1,7 @@
-0      something.a.b.c.test.com.       IN      A       3600    8.7.6.5
-0      something.a.b.c.test.com.       IN      RRSIG   3600    A 13 5 3600 [expiry] [inception] [keytag] test.com. ...
-1      qd81ag9inqts1ocs7api0pji94k27btr.test.com.      IN      NSEC3   3600    1 [flags] 1 abcd S6G5SHC1JVOVL5FL9E943ADLONQLN7G4 CNAME RRSIG
-1      qd81ag9inqts1ocs7api0pji94k27btr.test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-2      .       IN      OPT     32768   
+0      something.a.b.c.test.com.       3600    IN      A       8.7.6.5
+0      something.a.b.c.test.com.       3600    IN      RRSIG   A 13 5 3600 [expiry] [inception] [keytag] test.com. ...
+1      qd81ag9inqts1ocs7api0pji94k27btr.test.com.      3600    IN      NSEC3   1 [flags] 1 abcd S6G5SHC1JVOVL5FL9E943ADLONQLN7G4 CNAME RRSIG
+1      qd81ag9inqts1ocs7api0pji94k27btr.test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='something.a.b.c.test.com.', qtype=A
index b0d28ac6353bd9b24b67cc256291c2fa62a2a59d..ee339350ab9c9336063f6ccd094308dd9de2fa7b 100644 (file)
@@ -1,4 +1,4 @@
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 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='c.test.com.', qtype=A
index b44bcb568f9feb6c304fd6bf4bff2a502bbd9bcc..7dd84cf29122fa633db835879cbb7f2c8939d52a 100644 (file)
@@ -1,7 +1,7 @@
-1      blah.test.com.  IN      NSEC    3600    b.c.test.com. NS RRSIG NSEC
-1      blah.test.com.  IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      blah.test.com.  3600    IN      NSEC    b.c.test.com. NS RRSIG NSEC
+1      blah.test.com.  3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 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='c.test.com.', qtype=A
index 959c581a44706d6bb87839b78fdfc6b3272cdc60..673e2b3340ec74ba68ec592601ff0eadd3d8570b 100644 (file)
@@ -1,7 +1,7 @@
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      NSEC3   3600    1 1 1 abcd S6G5SHC1JVOVL5FL9E943ADLONQLN7G5
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      NSEC3   1 1 1 abcd S6G5SHC1JVOVL5FL9E943ADLONQLN7G5
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 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='c.test.com.', qtype=A
index b7c8ce0030d024e896273da3e8af3f353cb049c1..6b06fdb73fc3d4fbe82b6880a88600490700038f 100644 (file)
@@ -1,7 +1,7 @@
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      NSEC3   3600    1 0 1 abcd S96H2QICBT8D9I5AA43KP8SJJRESQ4KB
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      NSEC3   1 0 1 abcd S96H2QICBT8D9I5AA43KP8SJJRESQ4KB
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 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='c.test.com.', qtype=A
index f4058c4f2fd0962c7b272aea1ce0fbc2b9677ba5..6f2e78dc0109cb4e8d548330499d57ab5629dd12 100644 (file)
@@ -1,7 +1,7 @@
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      NSEC3   3600    1 1 1 abcd SA5VVPQN1COEJGJ3HBKFEKDNII8KKSQA
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      NSEC3   1 1 1 abcd SA5VVPQN1COEJGJ3HBKFEKDNII8KKSQA
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 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='c.test.com.', qtype=A
index 5cc5be1b2b6c5705cdead2bed41e35714ae93a6c..dc7354fa888c719eaea31aff148d1be49e0c3e04 100644 (file)
@@ -1,12 +1,12 @@
-0      text0.example.com.      IN      TXT     120     "k=rsa; p=one"
+0      text0.example.com.      120     IN      TXT     "k=rsa; p=one"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='text0.example.com.', qtype=TXT
-0      text1.example.com.      IN      TXT     120     "k=rsa; p=one"
+0      text1.example.com.      120     IN      TXT     "k=rsa; p=one"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='text1.example.com.', qtype=TXT
-0      text2.example.com.      IN      TXT     120     "k=rsa\\; p=one"
+0      text2.example.com.      120     IN      TXT     "k=rsa\\; p=one"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='text2.example.com.', qtype=TXT
-0      text3.example.com.      IN      TXT     120     "k=rsa\\; p=one"
+0      text3.example.com.      120     IN      TXT     "k=rsa\\; p=one"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='text3.example.com.', qtype=TXT
index a83476a2d674cd241781eeccc7ca716fd8b15e7b..24290fcda767b14dfbf20030132f3092e252d795 100644 (file)
@@ -1,3 +1,3 @@
-0      external.example.com.   IN      CNAME   120     somewhere.else.net.
+0      external.example.com.   120     IN      CNAME   somewhere.else.net.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='external.example.com.', qtype=A
index faf854789cb6ebb7f4a701df805a18f5d4f6a78a..395a22e7dbfdd1b7c98b96a8e933521a2630a12d 100644 (file)
@@ -1,7 +1,7 @@
-0      www.a.b.c.d.e.something.wtest.com.      IN      A       3600    4.3.2.1
-0      www.a.b.c.d.e.something.wtest.com.      IN      RRSIG   3600    A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      a.something.wtest.com.  IN      NSEC    3600    wtest.com. A RRSIG NSEC
-1      a.something.wtest.com.  IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] wtest.com. ...
-2      .       IN      OPT     32768   
+0      www.a.b.c.d.e.something.wtest.com.      3600    IN      A       4.3.2.1
+0      www.a.b.c.d.e.something.wtest.com.      3600    IN      RRSIG   A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      a.something.wtest.com.  3600    IN      NSEC    wtest.com. A RRSIG NSEC
+1      a.something.wtest.com.  3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] wtest.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.a.b.c.d.e.something.wtest.com.', qtype=A
index 53655e7016e346c868faccfd1e2aa7627a7465a8..81bc06e04b25c3528209aeb1748c8a6c2ca84067 100644 (file)
@@ -1,7 +1,7 @@
-0      www.a.b.c.d.e.something.wtest.com.      IN      A       3600    4.3.2.1
-0      www.a.b.c.d.e.something.wtest.com.      IN      RRSIG   3600    A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      pqgjjrj5si55uc1208gt1hp1k217fhqu.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd PQGJJRJ5SI55UC1208GT1HP1K217FHR0
-1      pqgjjrj5si55uc1208gt1hp1k217fhqu.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-2      .       IN      OPT     32768   
+0      www.a.b.c.d.e.something.wtest.com.      3600    IN      A       4.3.2.1
+0      www.a.b.c.d.e.something.wtest.com.      3600    IN      RRSIG   A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      pqgjjrj5si55uc1208gt1hp1k217fhqu.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd PQGJJRJ5SI55UC1208GT1HP1K217FHR0
+1      pqgjjrj5si55uc1208gt1hp1k217fhqu.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.a.b.c.d.e.something.wtest.com.', qtype=A
index d22bb8df7b42d3a3f06124d7f279c66061fed120..f0ea4556251428049a7813a0bdd52208d74ee76f 100644 (file)
@@ -1,7 +1,7 @@
-0      www.a.b.c.d.e.something.wtest.com.      IN      A       3600    4.3.2.1
-0      www.a.b.c.d.e.something.wtest.com.      IN      RRSIG   3600    A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd SHEGK154N8362AG22AR9VDDRF3127M6I A RRSIG
-1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-2      .       IN      OPT     32768   
+0      www.a.b.c.d.e.something.wtest.com.      3600    IN      A       4.3.2.1
+0      www.a.b.c.d.e.something.wtest.com.      3600    IN      RRSIG   A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd SHEGK154N8362AG22AR9VDDRF3127M6I A RRSIG
+1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.a.b.c.d.e.something.wtest.com.', qtype=A
index 3d0495b7de95aa9bd9a6ec75bebb8258f46c90d7..d577e54aa2a9e52be9435cab3a56c2a1a57a84b4 100644 (file)
@@ -1,7 +1,7 @@
-0      www.a.b.c.d.e.wtest.com.        IN      A       3600    6.7.8.9
-0      www.a.b.c.d.e.wtest.com.        IN      RRSIG   3600    A 13 7 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      *.a.b.c.d.e.wtest.com.  IN      NSEC    3600    ns1.wtest.com. A RRSIG NSEC
-1      *.a.b.c.d.e.wtest.com.  IN      RRSIG   3600    NSEC 13 7 3600 [expiry] [inception] [keytag] wtest.com. ...
-2      .       IN      OPT     32768   
+0      www.a.b.c.d.e.wtest.com.        3600    IN      A       6.7.8.9
+0      www.a.b.c.d.e.wtest.com.        3600    IN      RRSIG   A 13 7 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      *.a.b.c.d.e.wtest.com.  3600    IN      NSEC    ns1.wtest.com. A RRSIG NSEC
+1      *.a.b.c.d.e.wtest.com.  3600    IN      RRSIG   NSEC 13 7 3600 [expiry] [inception] [keytag] wtest.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.a.b.c.d.e.wtest.com.', qtype=A
index 4825cf61786ce40cf245d904cd1b48889435722c..f7480b1fb1dc02811cbfc4f43eb39ff9faca59d9 100644 (file)
@@ -1,7 +1,7 @@
-0      www.a.b.c.d.e.wtest.com.        IN      A       3600    6.7.8.9
-0      www.a.b.c.d.e.wtest.com.        IN      RRSIG   3600    A 13 7 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      pet5iqbgccga60p2n38nmuanrk50papg.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd PET5IQBGCCGA60P2N38NMUANRK50PAPI
-1      pet5iqbgccga60p2n38nmuanrk50papg.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-2      .       IN      OPT     32768   
+0      www.a.b.c.d.e.wtest.com.        3600    IN      A       6.7.8.9
+0      www.a.b.c.d.e.wtest.com.        3600    IN      RRSIG   A 13 7 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      pet5iqbgccga60p2n38nmuanrk50papg.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd PET5IQBGCCGA60P2N38NMUANRK50PAPI
+1      pet5iqbgccga60p2n38nmuanrk50papg.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.a.b.c.d.e.wtest.com.', qtype=A
index 784cdf8503548b10d3f200a86eb10a11da67213f..2b3467e281623f11bd281d0d57c717d958855452 100644 (file)
@@ -1,7 +1,7 @@
-0      www.a.b.c.d.e.wtest.com.        IN      A       3600    6.7.8.9
-0      www.a.b.c.d.e.wtest.com.        IN      RRSIG   3600    A 13 7 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd SHEGK154N8362AG22AR9VDDRF3127M6I A RRSIG
-1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-2      .       IN      OPT     32768   
+0      www.a.b.c.d.e.wtest.com.        3600    IN      A       6.7.8.9
+0      www.a.b.c.d.e.wtest.com.        3600    IN      RRSIG   A 13 7 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd SHEGK154N8362AG22AR9VDDRF3127M6I A RRSIG
+1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.a.b.c.d.e.wtest.com.', qtype=A
index 923da347e2fa0824bd9c482611877ecf51778513..4da9e6738a1cfc1b7b8bed99d235875ac86bf693 100644 (file)
@@ -1,6 +1,6 @@
-1      usa.example.com.        IN      NS      120     usa-ns1.usa.example.com.
-1      usa.example.com.        IN      NS      120     usa-ns2.usa.example.com.
-2      usa-ns1.usa.example.com.        IN      A       120     192.168.4.1
-2      usa-ns2.usa.example.com.        IN      A       120     192.168.4.2
+1      usa.example.com.        120     IN      NS      usa-ns1.usa.example.com.
+1      usa.example.com.        120     IN      NS      usa-ns2.usa.example.com.
+2      usa-ns1.usa.example.com.        120     IN      A       192.168.4.1
+2      usa-ns2.usa.example.com.        120     IN      A       192.168.4.2
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='usa-ns2.usa.example.com.', qtype=A
index 1f0a1362c7ed6df5350b090751953336130b75cf..35243bb11f7331b11bf4d3dfee8e02fa2b24f5e4 100644 (file)
@@ -1,6 +1,6 @@
-1      usa.example.com.        IN      NS      120     usa-ns1.usa.example.com.
-1      usa.example.com.        IN      NS      120     usa-ns2.usa.example.com.
-2      usa-ns1.usa.example.com.        IN      A       120     192.168.4.1
-2      usa-ns2.usa.example.com.        IN      A       120     192.168.4.2
+1      usa.example.com.        120     IN      NS      usa-ns1.usa.example.com.
+1      usa.example.com.        120     IN      NS      usa-ns2.usa.example.com.
+2      usa-ns1.usa.example.com.        120     IN      A       192.168.4.1
+2      usa-ns2.usa.example.com.        120     IN      A       192.168.4.2
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='www.usa.example.com.', qtype=A
index 930c733dfa85d219043a3fd25def66979c37e125..c226aa61730b76d0a2cc63483c1ae0ceab3e992a 100644 (file)
@@ -1,6 +1,6 @@
-1      italy.example.com.      IN      NS      120     italy-ns1.example.com.
-1      italy.example.com.      IN      NS      120     italy-ns2.example.com.
-2      italy-ns1.example.com.  IN      A       120     192.168.5.1
-2      italy-ns2.example.com.  IN      A       120     192.168.5.2
+1      italy.example.com.      120     IN      NS      italy-ns1.example.com.
+1      italy.example.com.      120     IN      NS      italy-ns2.example.com.
+2      italy-ns1.example.com.  120     IN      A       192.168.5.1
+2      italy-ns2.example.com.  120     IN      A       192.168.5.2
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='www.italy.example.com.', qtype=A
index 60a9f863ce00bfa51d5d60a0325b14e1b394e067..6afbf5ad3458930f5859d04a1dfb2099d3c50888 100644 (file)
@@ -1,3 +1,3 @@
-0      largettl.example.com.   IN      TXT     1073741724      "this record has a huge TTL"
+0      largettl.example.com.   1073741724      IN      TXT     "this record has a huge TTL"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='largettl.example.com.', qtype=TXT
index 620e73dc9587939ef9093cdf9ae373acab6930b3..4328210d8b103e8a95f7a1d0f3f6d586b37b3fa6 100644 (file)
@@ -1,3 +1,3 @@
-1      test.com.       IN      SOA     3600    ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
+1      test.com.       3600    IN      SOA     ns1.test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.test.com.', qtype=TXT
index b6b65ba3edfc393ffe8271c72b90885ecadd30db..09c5a016a8f12868c2f2f7e8e87b5946ac25f978 100644 (file)
@@ -1 +1 @@
-1      minimal.com.    IN      NSEC    120     minimal.com. NS SOA RRSIG NSEC DNSKEY
+1      minimal.com.    120     IN      NSEC    minimal.com. NS SOA RRSIG NSEC DNSKEY
index 9ed56088064ddc8a57b8897c286f56df1cc50f33..3cf93cc705cb885fcec06b8d050db19530bcef22 100644 (file)
@@ -1 +1 @@
-1      09lo11rs63u9b3d538a86ijvqcqt9312.minimal.com.   IN      NSEC3   120     1 [flags] 1 abcd 09LO11RS63U9B3D538A86IJVQCQT9313 NS SOA RRSIG DNSKEY NSEC3PARAM
+1      09lo11rs63u9b3d538a86ijvqcqt9312.minimal.com.   120     IN      NSEC3   1 [flags] 1 abcd 09LO11RS63U9B3D538A86IJVQCQT9313 NS SOA RRSIG DNSKEY NSEC3PARAM
index cf388a1c4af9789d2cfda5f8d596c53f5de01d24..f20b684716978775540f88f9d53c2c40d46a4c0e 100644 (file)
@@ -1 +1 @@
-1      09lo11rs63u9b3d538a86ijvqcqt9312.minimal.com.   IN      NSEC3   120     1 [flags] 1 abcd 09LO11RS63U9B3D538A86IJVQCQT9312 NS SOA RRSIG DNSKEY NSEC3PARAM
+1      09lo11rs63u9b3d538a86ijvqcqt9312.minimal.com.   120     IN      NSEC3   1 [flags] 1 abcd 09LO11RS63U9B3D538A86IJVQCQT9312 NS SOA RRSIG DNSKEY NSEC3PARAM
index 212c5e8bd6e4f429d3e1b7f8fc08a309098869c9..6d3f54a82271bb7b48a7972827b0972374df423b 100644 (file)
@@ -1,2 +1,2 @@
-Minimal zone (only NS records) Make sure non-existent hosts generates a correct
+Minimal zone (only NS records) Make sure nonexistent hosts generates a correct
 NSEC(3) denial.
index b6b65ba3edfc393ffe8271c72b90885ecadd30db..09c5a016a8f12868c2f2f7e8e87b5946ac25f978 100644 (file)
@@ -1 +1 @@
-1      minimal.com.    IN      NSEC    120     minimal.com. NS SOA RRSIG NSEC DNSKEY
+1      minimal.com.    120     IN      NSEC    minimal.com. NS SOA RRSIG NSEC DNSKEY
index 520a244c2c9882d1155b8557002f1d41199de48e..759304daa2fa543cc267a7b2583c01556d86e08e 100644 (file)
@@ -1,3 +1,3 @@
-1      09lo11rs63u9b3d538a86ijvqcqt9312.minimal.com.   IN      NSEC3   120     1 [flags] 1 abcd 09LO11RS63U9B3D538A86IJVQCQT9313 NS SOA RRSIG DNSKEY NSEC3PARAM
-1      8hki26qt36v6qs8cll4e4nvjit38uhap.minimal.com.   IN      NSEC3   120     1 [flags] 1 abcd 8HKI26QT36V6QS8CLL4E4NVJIT38UHAR
-1      9oadfe8c55evko75kb06spdl23p4fmrh.minimal.com.   IN      NSEC3   120     1 [flags] 1 abcd 9OADFE8C55EVKO75KB06SPDL23P4FMRJ
+1      09lo11rs63u9b3d538a86ijvqcqt9312.minimal.com.   120     IN      NSEC3   1 [flags] 1 abcd 09LO11RS63U9B3D538A86IJVQCQT9313 NS SOA RRSIG DNSKEY NSEC3PARAM
+1      8hki26qt36v6qs8cll4e4nvjit38uhap.minimal.com.   120     IN      NSEC3   1 [flags] 1 abcd 8HKI26QT36V6QS8CLL4E4NVJIT38UHAR
+1      9oadfe8c55evko75kb06spdl23p4fmrh.minimal.com.   120     IN      NSEC3   1 [flags] 1 abcd 9OADFE8C55EVKO75KB06SPDL23P4FMRJ
index cf388a1c4af9789d2cfda5f8d596c53f5de01d24..f20b684716978775540f88f9d53c2c40d46a4c0e 100644 (file)
@@ -1 +1 @@
-1      09lo11rs63u9b3d538a86ijvqcqt9312.minimal.com.   IN      NSEC3   120     1 [flags] 1 abcd 09LO11RS63U9B3D538A86IJVQCQT9312 NS SOA RRSIG DNSKEY NSEC3PARAM
+1      09lo11rs63u9b3d538a86ijvqcqt9312.minimal.com.   120     IN      NSEC3   1 [flags] 1 abcd 09LO11RS63U9B3D538A86IJVQCQT9312 NS SOA RRSIG DNSKEY NSEC3PARAM
index 03733d6044aa51853b84ebbd61f97e5c387b978c..4ffd50f17261d41ee4a7c22128fd9a7d6c1057da 100644 (file)
@@ -1,6 +1,6 @@
-0      start1.example.com.     IN      CNAME   120     start2.example.com.
-0      start2.example.com.     IN      CNAME   120     start3.example.com.
-0      start3.example.com.     IN      CNAME   120     start4.example.com.
-0      start4.example.com.     IN      A       120     192.168.2.2
+0      start1.example.com.     120     IN      CNAME   start2.example.com.
+0      start2.example.com.     120     IN      CNAME   start3.example.com.
+0      start3.example.com.     120     IN      CNAME   start4.example.com.
+0      start4.example.com.     120     IN      A       192.168.2.2
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='start1.example.com.', qtype=A
index 5f0925ea7a2038b6f470c8a3618fd98d5d1f513f..864fb23b5bd2021d1182eae8c2c63692e179ff3c 100644 (file)
@@ -1,3 +1,3 @@
-0      escapedtext.example.com.        IN      TXT     120     "begin" "the \"middle\" p\\art" "the end"
+0      escapedtext.example.com.        120     IN      TXT     "begin" "the \"middle\" p\\art" "the end"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='escapedtext.example.com.', qtype=TXT
index 5134e4d859eb6643fef7fd0b884396c7af1ac145..5de53b45e1385b3fa8d91be2dcb30ea80afcc366 100644 (file)
@@ -1,3 +1,3 @@
-0      multitext.example.com.  IN      TXT     120     "text part one" "text part two" "text part three"
+0      multitext.example.com.  120     IN      TXT     "text part one" "text part two" "text part three"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='multitext.example.com.', qtype=TXT
index b242aeae488314019bb711f24a83398c5836eae8..6472552a87f2a7fa95840dee53b11b49fe97a727 100644 (file)
@@ -1,7 +1,7 @@
-0      exAmplE.com.    IN      MX      120     10 smtp-servers.exAmplE.com.
-0      exAmplE.com.    IN      MX      120     15 smtp-servers.test.com.
-2      smtp-servers.exAmplE.com.       IN      A       120     192.168.0.2
-2      smtp-servers.exAmplE.com.       IN      A       120     192.168.0.3
-2      smtp-servers.exAmplE.com.       IN      A       120     192.168.0.4
+0      exAmplE.com.    120     IN      MX      10 smtp-servers.exAmplE.com.
+0      exAmplE.com.    120     IN      MX      15 smtp-servers.test.com.
+2      smtp-servers.exAmplE.com.       120     IN      A       192.168.0.2
+2      smtp-servers.exAmplE.com.       120     IN      A       192.168.0.3
+2      smtp-servers.exAmplE.com.       120     IN      A       192.168.0.4
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='exAmplE.com.', qtype=MX
index d2a60ab8596f5150caea26c505cacd7efda790eb..d4e39cf5c843cc61910227f5e76d8a08672bb48c 100644 (file)
@@ -1,3 +1,3 @@
-0      mail.example.com.       IN      MX      120     25 smtp1.example.com.
+0      mail.example.com.       120     IN      MX      25 smtp1.example.com.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='mail.example.com.', qtype=MX
index 19981306308f27ee03f2dabe7395a69d019dd3e2..fc1ecc4adf696b3117d8268d2209b71455f73fc6 100644 (file)
@@ -1,7 +1,7 @@
-0      example.com.    IN      MX      120     10 smtp-servers.example.com.
-0      example.com.    IN      MX      120     15 smtp-servers.test.com.
-2      smtp-servers.example.com.       IN      A       120     192.168.0.2
-2      smtp-servers.example.com.       IN      A       120     192.168.0.3
-2      smtp-servers.example.com.       IN      A       120     192.168.0.4
+0      example.com.    120     IN      MX      10 smtp-servers.example.com.
+0      example.com.    120     IN      MX      15 smtp-servers.test.com.
+2      smtp-servers.example.com.       120     IN      A       192.168.0.2
+2      smtp-servers.example.com.       120     IN      A       192.168.0.3
+2      smtp-servers.example.com.       120     IN      A       192.168.0.4
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='example.com.', qtype=MX
index d154728f4d728aef8da2efe08852eb8eb1aaaa4e..4a4aa625613d64c0a640ec48dafb3b4bbe5d6e14 100644 (file)
@@ -1,3 +1,3 @@
-0      enum.test.com.  IN      NAPTR   3600    100 50 "u" "e2u+sip" "" testuser.domain.com.
+0      enum.test.com.  3600    IN      NAPTR   100 50 "u" "e2u+sip" "" testuser.domain.com.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='enum.test.com.', qtype=NAPTR
index 22664113817935fcca137928d3197246b5114d96..52239fc20bb5b1925895352049a4ea0ae088a7d6 100644 (file)
@@ -1,3 +1,3 @@
-0      _imap._tcp.example.com. IN      SRV     120     0 1 143 blah.test.com.
+0      _imap._tcp.example.com. 120     IN      SRV     0 1 143 blah.test.com.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='_imap._tcp.example.com.', qtype=SRV
index 205bb4ef922e26606cf4c1ab977484ff8ca8a535..1608fa73cbfa835b1a83ce7f6489fecd5275fc13 100644 (file)
@@ -1,3 +1,3 @@
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+1      example.com.    86400   IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='ns1.example.com.', qtype=AAAA
index e2b5772d8c884be59be33d299b1f395a61de9a7a..b04f09e0bc145326ebdbf977bc654552bbaaea3a 100644 (file)
@@ -1,3 +1,3 @@
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+1      example.com.    86400   IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='outpost.example.com.', qtype=AAAA
index 70b292c60e2275ade924a739e323a7211b1cce6f..a0df396741805a74d9eaf414b07e01cee05b011f 100644 (file)
@@ -1,6 +1,6 @@
-1      usa.example.com.        IN      NS      120     usa-ns1.usa.example.com.
-1      usa.example.com.        IN      NS      120     usa-ns2.usa.example.com.
-2      usa-ns1.usa.example.com.        IN      A       120     192.168.4.1
-2      usa-ns2.usa.example.com.        IN      A       120     192.168.4.2
+1      usa.example.com.        120     IN      NS      usa-ns1.usa.example.com.
+1      usa.example.com.        120     IN      NS      usa-ns2.usa.example.com.
+2      usa-ns1.usa.example.com.        120     IN      A       192.168.4.1
+2      usa-ns2.usa.example.com.        120     IN      A       192.168.4.2
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='usa.example.com.', qtype=NS
index c2ab01351a43e39206996210040c606ce82cf161..46cf5fdc5a3a4f4eb983b9e1d3f78a87065703c7 100644 (file)
@@ -1,4 +1,4 @@
-1      blah.test.com.  IN      NS      3600    blah.test.com.
-2      blah.test.com.  IN      A       3600    192.168.6.1
+1      blah.test.com.  3600    IN      NS      blah.test.com.
+2      blah.test.com.  3600    IN      A       192.168.6.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='blah.test.com.', qtype=MX
diff --git a/regression-tests/tests/nsec-at-delegation/command b/regression-tests/tests/nsec-at-delegation/command
new file mode 100755 (executable)
index 0000000..58ee8e8
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+cleandig secure-delegated1.dnssec-parent.com A dnssec
diff --git a/regression-tests/tests/nsec-at-delegation/description b/regression-tests/tests/nsec-at-delegation/description
new file mode 100644 (file)
index 0000000..d59bcc5
--- /dev/null
@@ -0,0 +1 @@
+Check that we generate the right NSECs when the NSEC name is a delegation point.
diff --git a/regression-tests/tests/nsec-at-delegation/expected_result b/regression-tests/tests/nsec-at-delegation/expected_result
new file mode 100644 (file)
index 0000000..19f980e
--- /dev/null
@@ -0,0 +1,9 @@
+0      secure-delegated.dnssec-parent.com.     3600    IN      A       9.9.9.9
+0      secure-delegated.dnssec-parent.com.     3600    IN      RRSIG   A 8 3 3600 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
+0      secure-delegated1.dnssec-parent.com.    3600    IN      CNAME   secure-delegated.dnssec-parent.com.
+0      secure-delegated1.dnssec-parent.com.    3600    IN      RRSIG   CNAME 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      secure-delegated.dnssec-parent.com.     3600    IN      NSEC    www.dnssec-parent.com. NS DS RRSIG NSEC
+1      secure-delegated.dnssec-parent.com.     3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+2      .       32768   IN      OPT     
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='secure-delegated1.dnssec-parent.com.', qtype=A
diff --git a/regression-tests/tests/nsec-at-delegation/expected_result.narrow b/regression-tests/tests/nsec-at-delegation/expected_result.narrow
new file mode 100644 (file)
index 0000000..dafbed6
--- /dev/null
@@ -0,0 +1,9 @@
+0      secure-delegated.dnssec-parent.com.     3600    IN      A       9.9.9.9
+0      secure-delegated.dnssec-parent.com.     3600    IN      RRSIG   A 8 3 3600 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
+0      secure-delegated1.dnssec-parent.com.    3600    IN      CNAME   secure-delegated.dnssec-parent.com.
+0      secure-delegated1.dnssec-parent.com.    3600    IN      RRSIG   CNAME 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      1an9kidorpirlabrh3be2n8k5taoe1v0.dnssec-parent.com.     3600    IN      NSEC3   1 [flags] 1 abcd 1AN9KIDORPIRLABRH3BE2N8K5TAOE1V2
+1      1an9kidorpirlabrh3be2n8k5taoe1v0.dnssec-parent.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+2      .       32768   IN      OPT     
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='secure-delegated1.dnssec-parent.com.', qtype=A
diff --git a/regression-tests/tests/nsec-at-delegation/expected_result.nsec3 b/regression-tests/tests/nsec-at-delegation/expected_result.nsec3
new file mode 100644 (file)
index 0000000..e65ad7a
--- /dev/null
@@ -0,0 +1,9 @@
+0      secure-delegated.dnssec-parent.com.     3600    IN      A       9.9.9.9
+0      secure-delegated.dnssec-parent.com.     3600    IN      RRSIG   A 8 3 3600 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
+0      secure-delegated1.dnssec-parent.com.    3600    IN      CNAME   secure-delegated.dnssec-parent.com.
+0      secure-delegated1.dnssec-parent.com.    3600    IN      RRSIG   CNAME 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      u97st412oa8b4bgjc1dgtb4qi5di8dmv.dnssec-parent.com.     3600    IN      NSEC3   1 [flags] 1 abcd 1SCAQA30LQ0DO5EIRNE4KPJFBEBFGR54
+1      u97st412oa8b4bgjc1dgtb4qi5di8dmv.dnssec-parent.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+2      .       32768   IN      OPT     
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='secure-delegated1.dnssec-parent.com.', qtype=A
diff --git a/regression-tests/tests/nsec-at-delegation/expected_result.nsec3-optout b/regression-tests/tests/nsec-at-delegation/expected_result.nsec3-optout
new file mode 100644 (file)
index 0000000..aee66de
--- /dev/null
@@ -0,0 +1,9 @@
+0      secure-delegated.dnssec-parent.com.     3600    IN      A       9.9.9.9
+0      secure-delegated.dnssec-parent.com.     3600    IN      RRSIG   A 8 3 3600 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
+0      secure-delegated1.dnssec-parent.com.    3600    IN      CNAME   secure-delegated.dnssec-parent.com.
+0      secure-delegated1.dnssec-parent.com.    3600    IN      RRSIG   CNAME 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      qoqsriqrvi1g1ql3tpph2248q9ldpepf.dnssec-parent.com.     3600    IN      NSEC3   1 [flags] 1 abcd 1SCAQA30LQ0DO5EIRNE4KPJFBEBFGR54 A RRSIG
+1      qoqsriqrvi1g1ql3tpph2248q9ldpepf.dnssec-parent.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+2      .       32768   IN      OPT     
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='secure-delegated1.dnssec-parent.com.', qtype=A
diff --git a/regression-tests/tests/nsec-at-delegation/skip.nodnssec b/regression-tests/tests/nsec-at-delegation/skip.nodnssec
new file mode 100644 (file)
index 0000000..e69de29
index cdd640e012d96865faa939196efa95e59ba519e0..277ef573496447365aa29d26a83e97acf8d0bc3e 100644 (file)
@@ -1,5 +1,5 @@
-0      hightype.example.com.   IN      NSEC    86400   host-0.example.com. A RRSIG NSEC TYPE65534
-0      hightype.example.com.   IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+0      hightype.example.com.   86400   IN      NSEC    host-0.example.com. A RRSIG NSEC TYPE65534
+0      hightype.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='hightype.example.com.', qtype=NSEC
index eebaa97ce8d982c171cc5e9885bae7b29bc10d9b..7375b5bf621deb00968cb6e36ce72396e137c1e5 100644 (file)
@@ -1,7 +1,7 @@
-1      3v4it454kfh142bi7afagnuvigrpfptt.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 3V4IT454KFH142BI7AFAGNUVIGRPFPTU A RRSIG TYPE65534
-1      3v4it454kfh142bi7afagnuvigrpfptt.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      3v4it454kfh142bi7afagnuvigrpfptt.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 3V4IT454KFH142BI7AFAGNUVIGRPFPTU A RRSIG TYPE65534
+1      3v4it454kfh142bi7afagnuvigrpfptt.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
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='hightype.example.com.', qtype=NSEC
index f4521b53e48ca5d722a55ffc969fd5a6362e69cc..3db625f3dddd5520456572ad83b471cf5855d126 100644 (file)
@@ -1,7 +1,7 @@
-1      3v4it454kfh142bi7afagnuvigrpfptt.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 3V4S43RV1GT28N0F2PPJ8I8482ESMUOB A RRSIG TYPE65534
-1      3v4it454kfh142bi7afagnuvigrpfptt.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      3v4it454kfh142bi7afagnuvigrpfptt.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 3V4S43RV1GT28N0F2PPJ8I8482ESMUOB A RRSIG TYPE65534
+1      3v4it454kfh142bi7afagnuvigrpfptt.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
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='hightype.example.com.', qtype=NSEC
index b60d97b1ba1a6f0e52f1a988cb5057051b480552..b33b48694b9ed7a044f0031e3ed6eebe51304d2c 100644 (file)
@@ -1,7 +1,7 @@
-1      blah.test.com.  IN      NS      3600    blah.test.com.
-1      blah.test.com.  IN      NSEC    3600    b.c.test.com. NS RRSIG NSEC
-1      blah.test.com.  IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-2      .       IN      OPT     32768   
-2      blah.test.com.  IN      A       3600    192.168.6.1
+1      blah.test.com.  3600    IN      NS      blah.test.com.
+1      blah.test.com.  3600    IN      NSEC    b.c.test.com. NS RRSIG NSEC
+1      blah.test.com.  3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+2      .       32768   IN      OPT     
+2      blah.test.com.  3600    IN      A       192.168.6.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='blah.test.com.', qtype=MX
index 3d22be6863e642bc0937dbd963198146265a5e93..6e93adfdd3a3c7fc78810af48aa65899a82ff451 100644 (file)
@@ -1,7 +1,7 @@
-1      blah.test.com.  IN      NS      3600    blah.test.com.
-1      s96h2qicbt8d9i5aa43kp8sjjresq4kb.test.com.      IN      NSEC3   3600    1 1 1 abcd S96H2QICBT8D9I5AA43KP8SJJRESQ4KC NS
-1      s96h2qicbt8d9i5aa43kp8sjjresq4kb.test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-2      .       IN      OPT     32768   
-2      blah.test.com.  IN      A       3600    192.168.6.1
+1      blah.test.com.  3600    IN      NS      blah.test.com.
+1      s96h2qicbt8d9i5aa43kp8sjjresq4kb.test.com.      3600    IN      NSEC3   1 1 1 abcd S96H2QICBT8D9I5AA43KP8SJJRESQ4KC NS
+1      s96h2qicbt8d9i5aa43kp8sjjresq4kb.test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+2      .       32768   IN      OPT     
+2      blah.test.com.  3600    IN      A       192.168.6.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='blah.test.com.', qtype=MX
index 372079be226e22db5e9d4e13ff0d960f2675b703..7f364a3465f144d9250b05b65f4cc196894fc335 100644 (file)
@@ -1,7 +1,7 @@
-1      blah.test.com.  IN      NS      3600    blah.test.com.
-1      s96h2qicbt8d9i5aa43kp8sjjresq4kb.test.com.      IN      NSEC3   3600    1 0 1 abcd SA5VVPQN1COEJGJ3HBKFEKDNII8KKSQA NS
-1      s96h2qicbt8d9i5aa43kp8sjjresq4kb.test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-2      .       IN      OPT     32768   
-2      blah.test.com.  IN      A       3600    192.168.6.1
+1      blah.test.com.  3600    IN      NS      blah.test.com.
+1      s96h2qicbt8d9i5aa43kp8sjjresq4kb.test.com.      3600    IN      NSEC3   1 0 1 abcd SA5VVPQN1COEJGJ3HBKFEKDNII8KKSQA NS
+1      s96h2qicbt8d9i5aa43kp8sjjresq4kb.test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+2      .       32768   IN      OPT     
+2      blah.test.com.  3600    IN      A       192.168.6.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='blah.test.com.', qtype=MX
index 97495f46ac03a460c06b059d07774015ca8d3e4c..24f2973b9b316a2da304144e9d447324e9a32a3b 100644 (file)
@@ -1,9 +1,9 @@
-1      2eu2gulbu53h9uvhfalshpbo2a83t6l2.test.com.      IN      NSEC3   3600    1 1 1 abcd 2GKS2N3JPQF62QOHAVFQ1PHOLM3HR7RA NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      2eu2gulbu53h9uvhfalshpbo2a83t6l2.test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-1      blah.test.com.  IN      NS      3600    blah.test.com.
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      NSEC3   3600    1 1 1 abcd SA5VVPQN1COEJGJ3HBKFEKDNII8KKSQA
-1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-2      .       IN      OPT     32768   
-2      blah.test.com.  IN      A       3600    192.168.6.1
+1      2eu2gulbu53h9uvhfalshpbo2a83t6l2.test.com.      3600    IN      NSEC3   1 1 1 abcd 2GKS2N3JPQF62QOHAVFQ1PHOLM3HR7RA NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      2eu2gulbu53h9uvhfalshpbo2a83t6l2.test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+1      blah.test.com.  3600    IN      NS      blah.test.com.
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      NSEC3   1 1 1 abcd SA5VVPQN1COEJGJ3HBKFEKDNII8KKSQA
+1      s6g5shc1jvovl5fl9e943adlonqln7g4.test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+2      .       32768   IN      OPT     
+2      blah.test.com.  3600    IN      A       192.168.6.1
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='blah.test.com.', qtype=MX
index f3879c9a188b9678d67bf080032772a252f1c08d..1392289daa2ff0447ead04124c4a7df466bc0787 100644 (file)
@@ -1,9 +1,9 @@
-1      example.com.    IN      NSEC    86400   _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
-1      example.com.    IN      RRSIG   86400   NSEC 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      usa.example.com.        IN      NSEC    86400   *.w1.example.com. NS RRSIG NSEC
-1      usa.example.com.        IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      example.com.    86400   IN      NSEC    _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
+1      example.com.    86400   IN      RRSIG   NSEC 13 2 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      usa.example.com.        86400   IN      NSEC    *.w1.example.com. NS RRSIG NSEC
+1      usa.example.com.        86400   IN      RRSIG   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='usazzz.example.com.', qtype=A
index f9f08ef6d4628b593a3a746804561e6ceb2495cf..25b768b0bec3d0398059d6e25392b8d8f631ded1 100644 (file)
@@ -1,11 +1,11 @@
-1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 9FAG9508OQU3M22QAC0U5EQGG45V8CF2
-1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      kt3ll2fgp7p2s71mk7frk5igi8pc8gl1.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd KT3LL2FGP7P2S71MK7FRK5IGI8PC8GL3
-1      kt3ll2fgp7p2s71mk7frk5igi8pc8gl1.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 9FAG9508OQU3M22QAC0U5EQGG45V8CF2
+1      9fag9508oqu3m22qac0u5eqgg45v8cf0.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      kt3ll2fgp7p2s71mk7frk5igi8pc8gl1.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd KT3LL2FGP7P2S71MK7FRK5IGI8PC8GL3
+1      kt3ll2fgp7p2s71mk7frk5igi8pc8gl1.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='usazzz.example.com.', qtype=A
index 99d3e7db962968b652e724ec6af48fa3d9b7db94..de64c157eb56903ddfff55ed7e79e85aed6ac509 100644 (file)
@@ -1,11 +1,11 @@
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      kt0pu1qu9of4ek09a6amheu1l4c4dq6b.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd KT832M4L92B5MCUCJI8QJF16MM2DU3MK A RRSIG
-1      kt0pu1qu9of4ek09a6amheu1l4c4dq6b.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.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      kt0pu1qu9of4ek09a6amheu1l4c4dq6b.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd KT832M4L92B5MCUCJI8QJF16MM2DU3MK A RRSIG
+1      kt0pu1qu9of4ek09a6amheu1l4c4dq6b.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='usazzz.example.com.', qtype=A
index fb2388fde1c3357fc7e7773491ffdcf04d3b3216..bdc74f9668264269100c0a81b285078549d42875 100644 (file)
@@ -1,9 +1,9 @@
-1      example.com.    IN      NSEC    86400   _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
-1      example.com.    IN      RRSIG   86400   NSEC 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      nxd.example.com.        IN      NSEC    86400   outpost.example.com. CNAME RRSIG NSEC
-1      nxd.example.com.        IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      example.com.    86400   IN      NSEC    _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
+1      example.com.    86400   IN      RRSIG   NSEC 13 2 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      nxd.example.com.        86400   IN      NSEC    outpost.example.com. CNAME RRSIG NSEC
+1      nxd.example.com.        86400   IN      RRSIG   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='outerpost.example.com.', qtype=A
index a03847d83854fefebe6d344fcca4d19026293a33..56fa9fbc06d496d6f668fa5f2815917b80d89bcf 100644 (file)
@@ -1,11 +1,11 @@
-1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 9FAG9508OQU3M22QAC0U5EQGG45V8CF2
-1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      sthvu2kihc96kc1tu8v3curr8og5dghn.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd STHVU2KIHC96KC1TU8V3CURR8OG5DGHP
-1      sthvu2kihc96kc1tu8v3curr8og5dghn.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 9FAG9508OQU3M22QAC0U5EQGG45V8CF2
+1      9fag9508oqu3m22qac0u5eqgg45v8cf0.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      sthvu2kihc96kc1tu8v3curr8og5dghn.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd STHVU2KIHC96KC1TU8V3CURR8OG5DGHP
+1      sthvu2kihc96kc1tu8v3curr8og5dghn.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='outerpost.example.com.', qtype=A
index f3d2c62b608a3438c89ff8662622b60f5c860839..aec10682d18abab92b7e396d44ba2e41abe0c3d9 100644 (file)
@@ -1,11 +1,11 @@
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      sthkgrndv06hbdrfe7a329lup4mctmqr.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd STKPKJBN0URUBBIM832MF33V5OGJR396 A RRSIG
-1      sthkgrndv06hbdrfe7a329lup4mctmqr.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.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      sthkgrndv06hbdrfe7a329lup4mctmqr.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd STKPKJBN0URUBBIM832MF33V5OGJR396 A RRSIG
+1      sthkgrndv06hbdrfe7a329lup4mctmqr.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='outerpost.example.com.', qtype=A
index fc26daf2aa5d0b81f0fad1dd6c77ec6d99e573b0..53614a540f0db2108db3fb3c817ca0130482bbd8 100644 (file)
@@ -1,5 +1,5 @@
-0      ns1.dnssec-parent.com.  IN      NSEC    3600    ns2.dnssec-parent.com. A RRSIG NSEC
-0      ns1.dnssec-parent.com.  IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-2      .       IN      OPT     32768   
+0      ns1.dnssec-parent.com.  3600    IN      NSEC    ns2.dnssec-parent.com. A RRSIG NSEC
+0      ns1.dnssec-parent.com.  3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='ns1.dnssec-parent.com.', qtype=NSEC
index 79d4f60a18a949aae84e8293306413b5aab7e83e..f147abe5a98c926a7dca34056fee77c6d0d5e57c 100644 (file)
@@ -1,7 +1,7 @@
-1      1scaqa30lq0do5eirne4kpjfbebfgr54.dnssec-parent.com.     IN      NSEC3   3600    1 [flags] 1 abcd 1SCAQA30LQ0DO5EIRNE4KPJFBEBFGR55 A RRSIG
-1      1scaqa30lq0do5eirne4kpjfbebfgr54.dnssec-parent.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      dnssec-parent.com.      IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      dnssec-parent.com.      IN      SOA     3600    ns1.dnssec-parent.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      1scaqa30lq0do5eirne4kpjfbebfgr54.dnssec-parent.com.     3600    IN      NSEC3   1 [flags] 1 abcd 1SCAQA30LQ0DO5EIRNE4KPJFBEBFGR55 A RRSIG
+1      1scaqa30lq0do5eirne4kpjfbebfgr54.dnssec-parent.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      dnssec-parent.com.      3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      dnssec-parent.com.      3600    IN      SOA     ns1.dnssec-parent.com. ahu.example.com. 2005092501 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='ns1.dnssec-parent.com.', qtype=NSEC
index 17a8b3a612538d3cc8f065d1d8f335e0da9109dd..c650c18c4ed61e1ad64fcc1e80a8d24161385fea 100644 (file)
@@ -1,7 +1,7 @@
-1      1scaqa30lq0do5eirne4kpjfbebfgr54.dnssec-parent.com.     IN      NSEC3   3600    1 [flags] 1 abcd 29CEQCF4EKGL2GR9I0VJJTK62H5LQS40 A RRSIG
-1      1scaqa30lq0do5eirne4kpjfbebfgr54.dnssec-parent.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      dnssec-parent.com.      IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-1      dnssec-parent.com.      IN      SOA     3600    ns1.dnssec-parent.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      1scaqa30lq0do5eirne4kpjfbebfgr54.dnssec-parent.com.     3600    IN      NSEC3   1 [flags] 1 abcd 29CEQCF4EKGL2GR9I0VJJTK62H5LQS40 A RRSIG
+1      1scaqa30lq0do5eirne4kpjfbebfgr54.dnssec-parent.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      dnssec-parent.com.      3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+1      dnssec-parent.com.      3600    IN      SOA     ns1.dnssec-parent.com. ahu.example.com. 2005092501 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='ns1.dnssec-parent.com.', qtype=NSEC
index cb01e40379ef28ac0eefc6a48bbae201829b4da1..88f21b089d6c97504bd302d63a487099f1937338 100644 (file)
@@ -1,9 +1,9 @@
-1      *.something.wtest.com.  IN      NSEC    3600    a.something.wtest.com. A RRSIG NSEC
-1      *.something.wtest.com.  IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      a.something.wtest.com.  IN      NSEC    3600    wtest.com. A RRSIG NSEC
-1      a.something.wtest.com.  IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      wtest.com.      IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      wtest.com.      IN      SOA     3600    ns1.wtest.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      *.something.wtest.com.  3600    IN      NSEC    a.something.wtest.com. A RRSIG NSEC
+1      *.something.wtest.com.  3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      a.something.wtest.com.  3600    IN      NSEC    wtest.com. A RRSIG NSEC
+1      a.something.wtest.com.  3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      wtest.com.      3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      wtest.com.      3600    IN      SOA     ns1.wtest.com. ahu.example.com. 2005092501 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='t.something.wtest.com.', qtype=TXT
index fa50ff29ab0fe865e7eea03e999a3a0b3ec77e06..28d8dcb3ffdf532c1d9cb494cabdabc83978133d 100644 (file)
@@ -1,11 +1,11 @@
-1      368r0s1q794jmkdrcpf6f85v316hd9ak.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd 368R0S1Q794JMKDRCPF6F85V316HD9AM
-1      368r0s1q794jmkdrcpf6f85v316hd9ak.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      54njs65s8u96tkffrft6l7j1t1556vik.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd 54NJS65S8U96TKFFRFT6L7J1T1556VIL
-1      54njs65s8u96tkffrft6l7j1t1556vik.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd PD15QDSJJBFOSU5FG2OQRNLB8R8OIFL7 A RRSIG
-1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      wtest.com.      IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      wtest.com.      IN      SOA     3600    ns1.wtest.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      368r0s1q794jmkdrcpf6f85v316hd9ak.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd 368R0S1Q794JMKDRCPF6F85V316HD9AM
+1      368r0s1q794jmkdrcpf6f85v316hd9ak.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      54njs65s8u96tkffrft6l7j1t1556vik.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd 54NJS65S8U96TKFFRFT6L7J1T1556VIL
+1      54njs65s8u96tkffrft6l7j1t1556vik.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd PD15QDSJJBFOSU5FG2OQRNLB8R8OIFL7 A RRSIG
+1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      wtest.com.      3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      wtest.com.      3600    IN      SOA     ns1.wtest.com. ahu.example.com. 2005092501 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='t.something.wtest.com.', qtype=TXT
index 281dea6d91f68943cc992469eab8924283729717..6228883503256a30203335be3c7c60bf213688db 100644 (file)
@@ -1,11 +1,11 @@
-1      2uspqp0ldid6481h33c7lakfkk2g2rdq.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd 53I5J7TGM8QG2GBV716RVQVARQCIJUE2 A RRSIG
-1      2uspqp0ldid6481h33c7lakfkk2g2rdq.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      54njs65s8u96tkffrft6l7j1t1556vik.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd 67I2ESLUBOJ7DPG4263L3T8DV19G6D0G
-1      54njs65s8u96tkffrft6l7j1t1556vik.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd SHEGK154N8362AG22AR9VDDRF3127M6I A RRSIG
-1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      wtest.com.      IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      wtest.com.      IN      SOA     3600    ns1.wtest.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      2uspqp0ldid6481h33c7lakfkk2g2rdq.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd 53I5J7TGM8QG2GBV716RVQVARQCIJUE2 A RRSIG
+1      2uspqp0ldid6481h33c7lakfkk2g2rdq.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      54njs65s8u96tkffrft6l7j1t1556vik.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd 67I2ESLUBOJ7DPG4263L3T8DV19G6D0G
+1      54njs65s8u96tkffrft6l7j1t1556vik.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd SHEGK154N8362AG22AR9VDDRF3127M6I A RRSIG
+1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      wtest.com.      3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      wtest.com.      3600    IN      SOA     ns1.wtest.com. ahu.example.com. 2005092501 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='t.something.wtest.com.', qtype=TXT
index 4d7890d937249399b0d2fbc1f7544cf8df9bda93..f3c4d305682c02422014cd8cf7bc8a56973e37ca 100644 (file)
@@ -1,9 +1,9 @@
-1      example.com.    IN      NSEC    86400   _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
-1      example.com.    IN      RRSIG   86400   NSEC 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      www.example.com.        IN      NSEC    86400   example.com. CNAME RRSIG NSEC
-1      www.example.com.        IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      example.com.    86400   IN      NSEC    _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
+1      example.com.    86400   IN      RRSIG   NSEC 13 2 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      www.example.com.        86400   IN      NSEC    example.com. CNAME RRSIG NSEC
+1      www.example.com.        86400   IN      RRSIG   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='zzz.example.com.', qtype=A
index 2515dc88e40c16b979bcd722cadf5e9b6aaf432c..53fb1fec7ca04c22bb18a13bd3aa1d1692ab5da3 100644 (file)
@@ -1,11 +1,11 @@
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      gnk5kv3h2h1h8ge405j6093608ukp3i5.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd GNO4LESKG6U7HKEJ9UL71SF1HD7F1P96 A RRSIG
-1      gnk5kv3h2h1h8ge405j6093608ukp3i5.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.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      gnk5kv3h2h1h8ge405j6093608ukp3i5.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd GNO4LESKG6U7HKEJ9UL71SF1HD7F1P96 A RRSIG
+1      gnk5kv3h2h1h8ge405j6093608ukp3i5.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='zzz.example.com.', qtype=A
index 740a02be5bb47f2b47b4bbb82a080aa36f8e3ada..51978a814269ffdbace234d0421ea32ea2e40e3a 100644 (file)
@@ -1,7 +1,7 @@
-1      example.com.    IN      NSEC    86400   _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
-1      example.com.    IN      RRSIG   86400   NSEC 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      example.com.    86400   IN      NSEC    _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
+1      example.com.    86400   IN      RRSIG   NSEC 13 2 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
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='example.com.', qtype=TXT
index 9be8a7d7de90ef797726b851b9cf70acac5fa5df..9076124cd2000d237ad55790864103eda328c8df 100644 (file)
@@ -1,7 +1,7 @@
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+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      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.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='example.com.', qtype=TXT
index e011352568dcbe9f628dc4195de3d0bd858163cd..aeed05f5e48185824d220406aa9d363671a67744 100644 (file)
@@ -1,7 +1,7 @@
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+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      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.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='example.com.', qtype=TXT
index 24b47e2eccb0d616de3dbb50e617a98bba0239a4..0a99e7d4022b76e63e9826e8b8ef42f248d52a08 100644 (file)
@@ -1,7 +1,7 @@
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      outpost.example.com.    IN      NSEC    86400   rhs-at-expansion.example.com. A RRSIG NSEC
-1      outpost.example.com.    IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+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      outpost.example.com.    86400   IN      NSEC    rhs-at-expansion.example.com. A RRSIG NSEC
+1      outpost.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='outpost.example.com.', qtype=TXT
index dc42f98bfafe167ea451cff3ec4e01c1a67d1447..5695eba5e71de219f31607d43638bbe858bcfd8f 100644 (file)
@@ -1,7 +1,7 @@
-1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 5UVGFM2VJCJE09SVS7LFB22I1UUQJF99 A RRSIG
-1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 5UVGFM2VJCJE09SVS7LFB22I1UUQJF99 A RRSIG
+1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.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
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='outpost.example.com.', qtype=TXT
index d2b1f6a52a739b0d579b039b96c4fc025b9d38e9..5628d70267902dc6e87b094a7e340ed10b850004 100644 (file)
@@ -1,7 +1,7 @@
-1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 5V0S7HPRC5IAFH3C3RO0HHNH543D3UIU A RRSIG
-1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 5V0S7HPRC5IAFH3C3RO0HHNH543D3UIU A RRSIG
+1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.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
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='outpost.example.com.', qtype=TXT
index 322427636bc1b96fba83e4b7977d794941e38a5f..a6ffacde201c67717601d39047248173423f7a24 100644 (file)
@@ -1,4 +1,4 @@
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      example.com.    86400   IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.', qtype=A
index 8e50e4953392dfc26394d59a593e0aaf0799f362..3f56c3edb30c47c8367ce9b3e85df29bafb308ba 100644 (file)
@@ -1,9 +1,9 @@
-1      example.com.    IN      NSEC    86400   _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
-1      example.com.    IN      RRSIG   86400   NSEC 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      usa.example.com.        IN      NSEC    86400   *.w1.example.com. NS RRSIG NSEC
-1      usa.example.com.        IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      example.com.    86400   IN      NSEC    _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
+1      example.com.    86400   IN      RRSIG   NSEC 13 2 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      usa.example.com.        86400   IN      NSEC    *.w1.example.com. NS RRSIG NSEC
+1      usa.example.com.        86400   IN      RRSIG   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.', qtype=A
index b3214e29edda4c3215b79ac5313fe53a966f4f9e..2e06b0f89ca8e1b19041f8668ab7fb6a11785340 100644 (file)
@@ -1,11 +1,11 @@
-1      1a140bgusgdcs1r19730hkq46r7bffq8.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 1A140BGUSGDCS1R19730HKQ46R7BFFQA
-1      1a140bgusgdcs1r19730hkq46r7bffq8.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 9FAG9508OQU3M22QAC0U5EQGG45V8CF2
-1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      1a140bgusgdcs1r19730hkq46r7bffq8.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 1A140BGUSGDCS1R19730HKQ46R7BFFQA
+1      1a140bgusgdcs1r19730hkq46r7bffq8.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 9FAG9508OQU3M22QAC0U5EQGG45V8CF2
+1      9fag9508oqu3m22qac0u5eqgg45v8cf0.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      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.', qtype=A
index e19231a32a4e228e55fda253a975e94be102030d..41e616324c614810d3b32fe8c379957575c16e87 100644 (file)
@@ -1,11 +1,11 @@
-1      1a0gisil40k7t5sedn514s889mmi96kn.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 1A3V0V070LETVUTCAHLH0EQ5K5PQ8M4M A RRSIG
-1      1a0gisil40k7t5sedn514s889mmi96kn.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      1a0gisil40k7t5sedn514s889mmi96kn.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 1A3V0V070LETVUTCAHLH0EQ5K5PQ8M4M A RRSIG
+1      1a0gisil40k7t5sedn514s889mmi96kn.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.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      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.', qtype=A
index 207500a983675ab2f703085ce15a6912c07c6de1..2f29f5f01828b2f8f4b518cfef9e1f3d0001e9d8 100644 (file)
@@ -1,9 +1,9 @@
-1      *.something.wtest.com.  IN      NSEC    3600    a.something.wtest.com. A RRSIG NSEC
-1      *.something.wtest.com.  IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      a.something.wtest.com.  IN      NSEC    3600    wtest.com. A RRSIG NSEC
-1      a.something.wtest.com.  IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      wtest.com.      IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      wtest.com.      IN      SOA     3600    ns1.wtest.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      *.something.wtest.com.  3600    IN      NSEC    a.something.wtest.com. A RRSIG NSEC
+1      *.something.wtest.com.  3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      a.something.wtest.com.  3600    IN      NSEC    wtest.com. A RRSIG NSEC
+1      a.something.wtest.com.  3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      wtest.com.      3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      wtest.com.      3600    IN      SOA     ns1.wtest.com. ahu.example.com. 2005092501 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='second.first.something.wtest.com.', qtype=TXT
index c5fd82aa5f9c565a4aef757411995126884bc21f..449b61cc7f44eb993bd5f849c58a503ddd7c469b 100644 (file)
@@ -1,11 +1,11 @@
-1      54njs65s8u96tkffrft6l7j1t1556vik.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd 54NJS65S8U96TKFFRFT6L7J1T1556VIL
-1      54njs65s8u96tkffrft6l7j1t1556vik.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      d0rjlf3tful8jfjk86vi5ce50nuea9a6.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd D0RJLF3TFUL8JFJK86VI5CE50NUEA9A8
-1      d0rjlf3tful8jfjk86vi5ce50nuea9a6.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd PD15QDSJJBFOSU5FG2OQRNLB8R8OIFL7 A RRSIG
-1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      wtest.com.      IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      wtest.com.      IN      SOA     3600    ns1.wtest.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      54njs65s8u96tkffrft6l7j1t1556vik.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd 54NJS65S8U96TKFFRFT6L7J1T1556VIL
+1      54njs65s8u96tkffrft6l7j1t1556vik.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      d0rjlf3tful8jfjk86vi5ce50nuea9a6.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd D0RJLF3TFUL8JFJK86VI5CE50NUEA9A8
+1      d0rjlf3tful8jfjk86vi5ce50nuea9a6.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd PD15QDSJJBFOSU5FG2OQRNLB8R8OIFL7 A RRSIG
+1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      wtest.com.      3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      wtest.com.      3600    IN      SOA     ns1.wtest.com. ahu.example.com. 2005092501 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='second.first.something.wtest.com.', qtype=TXT
index cc2161664ed1f3b7747bcad53d4a301b691e1d25..f9fa5deff6ce165d3f5d793090d8be419760650c 100644 (file)
@@ -1,11 +1,11 @@
-1      54njs65s8u96tkffrft6l7j1t1556vik.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd 67I2ESLUBOJ7DPG4263L3T8DV19G6D0G
-1      54njs65s8u96tkffrft6l7j1t1556vik.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      cv382m4jqhle9u45mdqfh64vp0jbfpn5.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd J02K7MH36PLGFKRS6UTOCESCCQ5P7EOB A RRSIG
-1      cv382m4jqhle9u45mdqfh64vp0jbfpn5.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd SHEGK154N8362AG22AR9VDDRF3127M6I A RRSIG
-1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      wtest.com.      IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      wtest.com.      IN      SOA     3600    ns1.wtest.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-2      .       IN      OPT     32768   
+1      54njs65s8u96tkffrft6l7j1t1556vik.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd 67I2ESLUBOJ7DPG4263L3T8DV19G6D0G
+1      54njs65s8u96tkffrft6l7j1t1556vik.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      cv382m4jqhle9u45mdqfh64vp0jbfpn5.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd J02K7MH36PLGFKRS6UTOCESCCQ5P7EOB A RRSIG
+1      cv382m4jqhle9u45mdqfh64vp0jbfpn5.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd SHEGK154N8362AG22AR9VDDRF3127M6I A RRSIG
+1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      wtest.com.      3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      wtest.com.      3600    IN      SOA     ns1.wtest.com. ahu.example.com. 2005092501 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='second.first.something.wtest.com.', qtype=TXT
index 4ea55835d00b42549fceab90a781546b792dcc89..c66f7396581c0d8b4db42088e8f99bf738d1b00e 100644 (file)
@@ -1,7 +1,7 @@
-0      second.first.something.wtest.com.       IN      A       3600    4.3.2.1
-0      second.first.something.wtest.com.       IN      RRSIG   3600    A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      a.something.wtest.com.  IN      NSEC    3600    wtest.com. A RRSIG NSEC
-1      a.something.wtest.com.  IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] wtest.com. ...
-2      .       IN      OPT     32768   
+0      second.first.something.wtest.com.       3600    IN      A       4.3.2.1
+0      second.first.something.wtest.com.       3600    IN      RRSIG   A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      a.something.wtest.com.  3600    IN      NSEC    wtest.com. A RRSIG NSEC
+1      a.something.wtest.com.  3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] wtest.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='second.first.something.wtest.com.', qtype=A
index fa3be13c5c3c4a431db2f03d533937886c41369b..fbbb0a80ee5162ecfdbd3cc2dbcf5804573e77b1 100644 (file)
@@ -1,7 +1,7 @@
-0      second.first.something.wtest.com.       IN      A       3600    4.3.2.1
-0      second.first.something.wtest.com.       IN      RRSIG   3600    A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      d0rjlf3tful8jfjk86vi5ce50nuea9a6.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd D0RJLF3TFUL8JFJK86VI5CE50NUEA9A8
-1      d0rjlf3tful8jfjk86vi5ce50nuea9a6.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-2      .       IN      OPT     32768   
+0      second.first.something.wtest.com.       3600    IN      A       4.3.2.1
+0      second.first.something.wtest.com.       3600    IN      RRSIG   A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      d0rjlf3tful8jfjk86vi5ce50nuea9a6.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd D0RJLF3TFUL8JFJK86VI5CE50NUEA9A8
+1      d0rjlf3tful8jfjk86vi5ce50nuea9a6.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='second.first.something.wtest.com.', qtype=A
index 4e0f5adeed0eb298707db2b22c8578d4a1b99b98..fd7e0353f1dd7f9c7345d36d54fe38c84e668c9b 100644 (file)
@@ -1,7 +1,7 @@
-0      second.first.something.wtest.com.       IN      A       3600    4.3.2.1
-0      second.first.something.wtest.com.       IN      RRSIG   3600    A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      cv382m4jqhle9u45mdqfh64vp0jbfpn5.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd J02K7MH36PLGFKRS6UTOCESCCQ5P7EOB A RRSIG
-1      cv382m4jqhle9u45mdqfh64vp0jbfpn5.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-2      .       IN      OPT     32768   
+0      second.first.something.wtest.com.       3600    IN      A       4.3.2.1
+0      second.first.something.wtest.com.       3600    IN      RRSIG   A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      cv382m4jqhle9u45mdqfh64vp0jbfpn5.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd J02K7MH36PLGFKRS6UTOCESCCQ5P7EOB A RRSIG
+1      cv382m4jqhle9u45mdqfh64vp0jbfpn5.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='second.first.something.wtest.com.', qtype=A
index 0372662222cddc62c4804ea1697a1baa691732de..5629c94ff25add8fef1d110d6afea97be006bb9a 100644 (file)
@@ -1,9 +1,9 @@
-0      Z1234567890.wtest.com.  IN      CNAME   3600    server1.wtest.com.
-0      Z1234567890.wtest.com.  IN      RRSIG   3600    CNAME 13 2 3600 [expiry] [inception] [keytag] wtest.com. ...
-0      server1.wtest.com.      IN      A       3600    1.2.3.4
-0      server1.wtest.com.      IN      RRSIG   3600    A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      a.something.wtest.com.  IN      NSEC    3600    wtest.com. A RRSIG NSEC
-1      a.something.wtest.com.  IN      RRSIG   3600    NSEC 13 4 3600 [expiry] [inception] [keytag] wtest.com. ...
-2      .       IN      OPT     32768   
+0      Z1234567890.wtest.com.  3600    IN      CNAME   server1.wtest.com.
+0      Z1234567890.wtest.com.  3600    IN      RRSIG   CNAME 13 2 3600 [expiry] [inception] [keytag] wtest.com. ...
+0      server1.wtest.com.      3600    IN      A       1.2.3.4
+0      server1.wtest.com.      3600    IN      RRSIG   A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      a.something.wtest.com.  3600    IN      NSEC    wtest.com. A RRSIG NSEC
+1      a.something.wtest.com.  3600    IN      RRSIG   NSEC 13 4 3600 [expiry] [inception] [keytag] wtest.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='Z1234567890.wtest.com.', qtype=A
index da2ae323b2c3c5ac9d582e26a8c8945d27215f29..da6f827d4ab496ab5e693ab35d87143d617560a8 100644 (file)
@@ -1,9 +1,9 @@
-0      Z1234567890.wtest.com.  IN      CNAME   3600    server1.wtest.com.
-0      Z1234567890.wtest.com.  IN      RRSIG   3600    CNAME 13 2 3600 [expiry] [inception] [keytag] wtest.com. ...
-0      server1.wtest.com.      IN      A       3600    1.2.3.4
-0      server1.wtest.com.      IN      RRSIG   3600    A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      pv38thf7bl9rb2o5cf77jkuehfupln6u.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd PV38THF7BL9RB2O5CF77JKUEHFUPLN70
-1      pv38thf7bl9rb2o5cf77jkuehfupln6u.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-2      .       IN      OPT     32768   
+0      Z1234567890.wtest.com.  3600    IN      CNAME   server1.wtest.com.
+0      Z1234567890.wtest.com.  3600    IN      RRSIG   CNAME 13 2 3600 [expiry] [inception] [keytag] wtest.com. ...
+0      server1.wtest.com.      3600    IN      A       1.2.3.4
+0      server1.wtest.com.      3600    IN      RRSIG   A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      pv38thf7bl9rb2o5cf77jkuehfupln6u.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd PV38THF7BL9RB2O5CF77JKUEHFUPLN70
+1      pv38thf7bl9rb2o5cf77jkuehfupln6u.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='Z1234567890.wtest.com.', qtype=A
index e5bc2afda140c0219b09964b14b17fe13b6ad5c3..1708f6d711e145ef753f13fc7de41b25bacb27b7 100644 (file)
@@ -1,9 +1,9 @@
-0      Z1234567890.wtest.com.  IN      CNAME   3600    server1.wtest.com.
-0      Z1234567890.wtest.com.  IN      RRSIG   3600    CNAME 13 2 3600 [expiry] [inception] [keytag] wtest.com. ...
-0      server1.wtest.com.      IN      A       3600    1.2.3.4
-0      server1.wtest.com.      IN      RRSIG   3600    A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     IN      NSEC3   3600    1 [flags] 1 abcd SHEGK154N8362AG22AR9VDDRF3127M6I A RRSIG
-1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
-2      .       IN      OPT     32768   
+0      Z1234567890.wtest.com.  3600    IN      CNAME   server1.wtest.com.
+0      Z1234567890.wtest.com.  3600    IN      RRSIG   CNAME 13 2 3600 [expiry] [inception] [keytag] wtest.com. ...
+0      server1.wtest.com.      3600    IN      A       1.2.3.4
+0      server1.wtest.com.      3600    IN      RRSIG   A 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     3600    IN      NSEC3   1 [flags] 1 abcd SHEGK154N8362AG22AR9VDDRF3127M6I A RRSIG
+1      pd15qdsjjbfosu5fg2oqrnlb8r8oifl6.wtest.com.     3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] wtest.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='Z1234567890.wtest.com.', qtype=A
index 377eef2e2bfc89da9bc810e91742e0448dbf2061..7cb1b15403411e0f3a9a4578bf7af5e85456aebd 100644 (file)
@@ -1,7 +1,7 @@
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      outpost.example.com.    IN      NSEC    86400   rhs-at-expansion.example.com. A RRSIG NSEC
-1      outpost.example.com.    IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+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      outpost.example.com.    86400   IN      NSEC    rhs-at-expansion.example.com. A RRSIG NSEC
+1      outpost.example.com.    86400   IN      RRSIG   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nx1.nx2.outpost.example.com.', qtype=A
index b6f29e82e1454ba03b65bb528a7f1e67c483af76..612fab6c9ffc70b60e217193eac864b9e80d7efa 100644 (file)
@@ -1,11 +1,11 @@
-1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 5UVGFM2VJCJE09SVS7LFB22I1UUQJF99 A RRSIG
-1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      sdgbafmjek5v4t8c89q9u0n03qmcslor.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd SDGBAFMJEK5V4T8C89Q9U0N03QMCSLOT
-1      sdgbafmjek5v4t8c89q9u0n03qmcslor.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      tsdp8hajlfgr90cv4ib634g1m25nc5up.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd TSDP8HAJLFGR90CV4IB634G1M25NC5UR
-1      tsdp8hajlfgr90cv4ib634g1m25nc5up.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 5UVGFM2VJCJE09SVS7LFB22I1UUQJF99 A RRSIG
+1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.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      sdgbafmjek5v4t8c89q9u0n03qmcslor.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd SDGBAFMJEK5V4T8C89Q9U0N03QMCSLOT
+1      sdgbafmjek5v4t8c89q9u0n03qmcslor.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      tsdp8hajlfgr90cv4ib634g1m25nc5up.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd TSDP8HAJLFGR90CV4IB634G1M25NC5UR
+1      tsdp8hajlfgr90cv4ib634g1m25nc5up.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nx1.nx2.outpost.example.com.', qtype=A
index 91d316da846978cd4b556e0235cf24049313d83e..8ef66775bb549db9118ccc6e563a75885953541c 100644 (file)
@@ -1,11 +1,11 @@
-1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 5V0S7HPRC5IAFH3C3RO0HHNH543D3UIU A RRSIG
-1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      sdeu4ba3b451gf8ijikm2tphu3bugl4g.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd SDH8FVJ6LQLSVCQCO8QP82I6JTR574H2 A RRSIG
-1      sdeu4ba3b451gf8ijikm2tphu3bugl4g.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      tsbl3ev9tces1kjgto3qtn36ltlu0te1.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd TSIKPRKTT53V9ILUK08SMR9KADQ44TR1 A RRSIG
-1      tsbl3ev9tces1kjgto3qtn36ltlu0te1.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 5V0S7HPRC5IAFH3C3RO0HHNH543D3UIU A RRSIG
+1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.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      sdeu4ba3b451gf8ijikm2tphu3bugl4g.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd SDH8FVJ6LQLSVCQCO8QP82I6JTR574H2 A RRSIG
+1      sdeu4ba3b451gf8ijikm2tphu3bugl4g.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      tsbl3ev9tces1kjgto3qtn36ltlu0te1.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd TSIKPRKTT53V9ILUK08SMR9KADQ44TR1 A RRSIG
+1      tsbl3ev9tces1kjgto3qtn36ltlu0te1.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nx1.nx2.outpost.example.com.', qtype=A
index 4fc9cad0daeb4732b4a0bb8ca0745f0d60209f10..f455c020d8d1aff9fa5e58a5e1e35312fb654c97 100644 (file)
@@ -1,3 +1,3 @@
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+1      example.com.    86400   IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='no-such-host.example.com.', qtype=A
index 8ed83f1e01bdec6744ca05286a214c449e62dd17..dcde87cd9cbdaa02eafb746df52aa3f256f2e572 100644 (file)
@@ -1,3 +1,3 @@
-1      wtest.com.      IN      SOA     3600    ns1.wtest.com. ahu.example.com. 2005092501 28800 7200 604800 86400
+1      wtest.com.      3600    IN      SOA     ns1.wtest.com. ahu.example.com. 2005092501 28800 7200 604800 86400
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.a.something.wtest.com.', qtype=A
index e519f7412d68c231daea26abe3ce060f7ad15be1..2a15399fa33303de119b04bbb7728da1bd59d4b5 100644 (file)
@@ -1,4 +1,4 @@
-0      outpost.example.com.    IN      A       120     192.168.2.1
-0      www.example.com.        IN      CNAME   120     outpost.example.com.
+0      outpost.example.com.    120     IN      A       192.168.2.1
+0      www.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='www.example.com.', qtype=A
index e74303b54856318768ff1a6c1801897cd09752be..717c59078b7d9f5c4cad8d01d88c09187c51e593 100644 (file)
@@ -1,4 +1,4 @@
-1      france.example.com.     IN      NS      120     ns1.otherprovider.net.
-1      france.example.com.     IN      NS      120     ns2.otherprovider.net.
+1      france.example.com.     120     IN      NS      ns1.otherprovider.net.
+1      france.example.com.     120     IN      NS      ns2.otherprovider.net.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='www.france.example.com.', qtype=A
index c564d86288617a044e8f21e99a9fb9097bd0262c..48b51570629cb9820c1b446fd57165225774a2a4 100644 (file)
@@ -1,27 +1,27 @@
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.1
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.10
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.11
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.12
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.13
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.14
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.15
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.16
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.17
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.18
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.19
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.2
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.20
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.21
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.22
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.23
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.24
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.25
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.3
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.4
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.5
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.6
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.7
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.8
-0      toomuchinfo-a.example.com.      IN      A       120     192.168.99.9
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.1
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.10
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.11
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.12
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.13
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.14
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.15
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.16
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.17
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.18
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.19
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.2
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.20
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.21
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.22
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.23
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.24
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.25
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.3
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.4
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.5
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.6
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.7
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.8
+0      toomuchinfo-a.example.com.      120     IN      A       192.168.99.9
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='toomuchinfo-a.example.com.', qtype=A
index 42d41acf2512f7547221b8e9d609a27afe44a734..505da2cc4ddba505216675ff500145729dddbb66 100644 (file)
@@ -1,46 +1,46 @@
-0      secure-delegated.dnssec-parent.com.     IN      CDS     86400   54319 8 2 a0b9c38cd324182af0ef66830d0a0e85a1d58979c9834e18c871779e040857b7
-0      secure-delegated.dnssec-parent.com.     IN      RRSIG   86400   CDS 8 3 86400 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
-0      secure-delegated.dnssec-parent.com.     IN      RRSIG   86400   CDS 8 3 86400 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
-2      .       IN      OPT     32768   
+0      secure-delegated.dnssec-parent.com.     86400   IN      CDS     54319 8 2 a0b9c38cd324182af0ef66830d0a0e85a1d58979c9834e18c871779e040857b7
+0      secure-delegated.dnssec-parent.com.     86400   IN      RRSIG   CDS 8 3 86400 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
+0      secure-delegated.dnssec-parent.com.     86400   IN      RRSIG   CDS 8 3 86400 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='secure-delegated.dnssec-parent.com.', qtype=CDS
-0      secure-delegated.dnssec-parent.com.     IN      CDNSKEY 86400   257 3 8 AwEAAZd9R7SWWGqA12oG7Ls+h3b0/IAyMj/Pqn/ZuKWM/OdpxT/cn2xwLDhkdmqP/pUqAzvyFPyd4kTqrmLfbohBwA7+07pBVa4qf/jxlHivdMNUD72H+dUYqBlmhCC6l3eG+8FZi2tkdwn8kUoa9kyLMtrEaFnOd/oUQbmNvIDp+8VWv1cSnRJ8UXKdXLl0smpvC7h1K2AUiC5oGIYQTCYWwYRM1wCbb+q1fbFCdkbI7OQW/h7Pj30eLpIuz0bJj4vdKXXZHK8clSdTMAFm6rQsNDI0w7QdCgaDmTn3b6TF2UJi4eDnh7uDbSpUd1mI5XWNw4C6WrUmebFLfiry6vqdiIc=
-0      secure-delegated.dnssec-parent.com.     IN      RRSIG   86400   CDNSKEY 8 3 86400 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
-0      secure-delegated.dnssec-parent.com.     IN      RRSIG   86400   CDNSKEY 8 3 86400 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
-2      .       IN      OPT     32768   
+0      secure-delegated.dnssec-parent.com.     86400   IN      CDNSKEY 257 3 8 AwEAAZd9R7SWWGqA12oG7Ls+h3b0/IAyMj/Pqn/ZuKWM/OdpxT/cn2xwLDhkdmqP/pUqAzvyFPyd4kTqrmLfbohBwA7+07pBVa4qf/jxlHivdMNUD72H+dUYqBlmhCC6l3eG+8FZi2tkdwn8kUoa9kyLMtrEaFnOd/oUQbmNvIDp+8VWv1cSnRJ8UXKdXLl0smpvC7h1K2AUiC5oGIYQTCYWwYRM1wCbb+q1fbFCdkbI7OQW/h7Pj30eLpIuz0bJj4vdKXXZHK8clSdTMAFm6rQsNDI0w7QdCgaDmTn3b6TF2UJi4eDnh7uDbSpUd1mI5XWNw4C6WrUmebFLfiry6vqdiIc=
+0      secure-delegated.dnssec-parent.com.     86400   IN      RRSIG   CDNSKEY 8 3 86400 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
+0      secure-delegated.dnssec-parent.com.     86400   IN      RRSIG   CDNSKEY 8 3 86400 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='secure-delegated.dnssec-parent.com.', qtype=CDNSKEY
-0      secure-delegated.dnssec-parent.com.     IN      CDNSKEY 86400   257 3 8 AwEAAZd9R7SWWGqA12oG7Ls+h3b0/IAyMj/Pqn/ZuKWM/OdpxT/cn2xwLDhkdmqP/pUqAzvyFPyd4kTqrmLfbohBwA7+07pBVa4qf/jxlHivdMNUD72H+dUYqBlmhCC6l3eG+8FZi2tkdwn8kUoa9kyLMtrEaFnOd/oUQbmNvIDp+8VWv1cSnRJ8UXKdXLl0smpvC7h1K2AUiC5oGIYQTCYWwYRM1wCbb+q1fbFCdkbI7OQW/h7Pj30eLpIuz0bJj4vdKXXZHK8clSdTMAFm6rQsNDI0w7QdCgaDmTn3b6TF2UJi4eDnh7uDbSpUd1mI5XWNw4C6WrUmebFLfiry6vqdiIc=
-0      secure-delegated.dnssec-parent.com.     IN      CDS     86400   54319 8 2 a0b9c38cd324182af0ef66830d0a0e85a1d58979c9834e18c871779e040857b7
-0      secure-delegated.dnssec-parent.com.     IN      RRSIG   86400   CDNSKEY 8 3 86400 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
-0      secure-delegated.dnssec-parent.com.     IN      RRSIG   86400   CDNSKEY 8 3 86400 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
-0      secure-delegated.dnssec-parent.com.     IN      RRSIG   86400   CDS 8 3 86400 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
-0      secure-delegated.dnssec-parent.com.     IN      RRSIG   86400   CDS 8 3 86400 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
-0      dnssec-parent.com.      IN      CDS     86400   0 0 0 00
-0      dnssec-parent.com.      IN      RRSIG   86400   CDS 13 2 86400 [expiry] [inception] [keytag] dnssec-parent.com. ...
-2      .       IN      OPT     32768   
+0      secure-delegated.dnssec-parent.com.     86400   IN      CDNSKEY 257 3 8 AwEAAZd9R7SWWGqA12oG7Ls+h3b0/IAyMj/Pqn/ZuKWM/OdpxT/cn2xwLDhkdmqP/pUqAzvyFPyd4kTqrmLfbohBwA7+07pBVa4qf/jxlHivdMNUD72H+dUYqBlmhCC6l3eG+8FZi2tkdwn8kUoa9kyLMtrEaFnOd/oUQbmNvIDp+8VWv1cSnRJ8UXKdXLl0smpvC7h1K2AUiC5oGIYQTCYWwYRM1wCbb+q1fbFCdkbI7OQW/h7Pj30eLpIuz0bJj4vdKXXZHK8clSdTMAFm6rQsNDI0w7QdCgaDmTn3b6TF2UJi4eDnh7uDbSpUd1mI5XWNw4C6WrUmebFLfiry6vqdiIc=
+0      secure-delegated.dnssec-parent.com.     86400   IN      CDS     54319 8 2 a0b9c38cd324182af0ef66830d0a0e85a1d58979c9834e18c871779e040857b7
+0      secure-delegated.dnssec-parent.com.     86400   IN      RRSIG   CDNSKEY 8 3 86400 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
+0      secure-delegated.dnssec-parent.com.     86400   IN      RRSIG   CDNSKEY 8 3 86400 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
+0      secure-delegated.dnssec-parent.com.     86400   IN      RRSIG   CDS 8 3 86400 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
+0      secure-delegated.dnssec-parent.com.     86400   IN      RRSIG   CDS 8 3 86400 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
+0      dnssec-parent.com.      86400   IN      CDS     0 0 0 00
+0      dnssec-parent.com.      86400   IN      RRSIG   CDS 13 2 86400 [expiry] [inception] [keytag] dnssec-parent.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='dnssec-parent.com.', qtype=CDS
-0      dnssec-parent.com.      IN      CDNSKEY 86400   0 3 0 AA==
-0      dnssec-parent.com.      IN      RRSIG   86400   CDNSKEY 13 2 86400 [expiry] [inception] [keytag] dnssec-parent.com. ...
-2      .       IN      OPT     32768   
+0      dnssec-parent.com.      86400   IN      CDNSKEY 0 3 0 AA==
+0      dnssec-parent.com.      86400   IN      RRSIG   CDNSKEY 13 2 86400 [expiry] [inception] [keytag] dnssec-parent.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='dnssec-parent.com.', qtype=CDNSKEY
-0      dnssec-parent.com.      IN      CDNSKEY 86400   0 3 0 AA==
-0      dnssec-parent.com.      IN      CDS     86400   0 0 0 00
-0      dnssec-parent.com.      IN      RRSIG   86400   CDNSKEY 13 2 86400 [expiry] [inception] [keytag] dnssec-parent.com. ...
-0      dnssec-parent.com.      IN      RRSIG   86400   CDS 13 2 86400 [expiry] [inception] [keytag] dnssec-parent.com. ...
-0      cdnskey-cds-test.com.   IN      CDS     86400
-0      cdnskey-cds-test.com.   IN      RRSIG   86400
-2      .       IN      OPT     32768   
+0      dnssec-parent.com.      86400   IN      CDNSKEY 0 3 0 AA==
+0      dnssec-parent.com.      86400   IN      CDS     0 0 0 00
+0      dnssec-parent.com.      86400   IN      RRSIG   CDNSKEY 13 2 86400 [expiry] [inception] [keytag] dnssec-parent.com. ...
+0      dnssec-parent.com.      86400   IN      RRSIG   CDS 13 2 86400 [expiry] [inception] [keytag] dnssec-parent.com. ...
+0      cdnskey-cds-test.com.   86400
+0      cdnskey-cds-test.com.   86400
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cdnskey-cds-test.com.', qtype=CDS
-0      cdnskey-cds-test.com.   IN      CDNSKEY 86400
-0      cdnskey-cds-test.com.   IN      RRSIG   86400
-2      .       IN      OPT     32768   
+0      cdnskey-cds-test.com.   86400
+0      cdnskey-cds-test.com.   86400
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cdnskey-cds-test.com.', qtype=CDNSKEY
-0      cdnskey-cds-test.com.   IN      CDNSKEY 86400
-0      cdnskey-cds-test.com.   IN      CDS     86400
-0      cdnskey-cds-test.com.   IN      RRSIG   86400
-0      cdnskey-cds-test.com.   IN      RRSIG   86400
+0      cdnskey-cds-test.com.   86400
+0      cdnskey-cds-test.com.   86400
+0      cdnskey-cds-test.com.   86400
+0      cdnskey-cds-test.com.   86400
index bb40a96c4a7ab6a21b9849c0801e3bf8fe4f815f..a919844e548383c724dcd77dafcc861e60a5e0b8 100644 (file)
@@ -1,3 +1,3 @@
-0      toroot.test.com.        IN      CNAME   3600    .
+0      toroot.test.com.        3600    IN      CNAME   .
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='toroot.test.com.', qtype=CNAME
index f4ba5e02096dd3d8dfba46cd83d7051a930909d2..a2ef229a559cb24d97de8a1eca84f3242e14773c 100644 (file)
@@ -1,4 +1,4 @@
-0      test.com.       IN      MX      3600    10 .
-0      test.com.       IN      MX      3600    15 smtp-servers.test.com.
+0      test.com.       3600    IN      MX      10 .
+0      test.com.       3600    IN      MX      15 smtp-servers.test.com.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='test.com.', qtype=MX
index 33ce7aa1d8e6e18d97b4ab6835583dff73910b0d..757f05409cc4520bbbbb053b3b68506937bf7467 100644 (file)
@@ -1,5 +1,5 @@
-0      wtest.com.      IN      NS      3600    .
-0      wtest.com.      IN      NS      3600    ns1.wtest.com.
-2      ns1.wtest.com.  IN      A       3600    2.3.4.5
+0      wtest.com.      3600    IN      NS      .
+0      wtest.com.      3600    IN      NS      ns1.wtest.com.
+2      ns1.wtest.com.  3600    IN      A       2.3.4.5
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='wtest.com.', qtype=NS
index a987c0db66a46d0fc03c03ed6ea93fe4d5aec728..3cd7ee63c891f01791c56ea6a26570bd39b91e73 100644 (file)
@@ -1,3 +1,3 @@
-0      _root._tcp.dc.test.com. IN      SRV     3600    0 0 0 .
+0      _root._tcp.dc.test.com. 3600    IN      SRV     0 0 0 .
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='_root._tcp.dc.test.com.', qtype=SRV
index 32acf173bfcee864a03aa8965e80ec129f3c7084..d8633b912975dde8edf358636f6d852c7f609aa6 100644 (file)
@@ -1,3 +1,3 @@
-0      server1.test.com.       IN      RP      3600    ahu.ds9a.nl. counter.test.com.
+0      server1.test.com.       3600    IN      RP      ahu.ds9a.nl. counter.test.com.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='server1.test.com.', qtype=RP
index 7fcadae9695f453fa7e1573cfab1e2941f42ad4c..6ca3b1e1b6a99d6c894292a912d7332fc7722186 100644 (file)
@@ -1,4 +1,4 @@
-1      france.example.com.     IN      NS      120     ns1.otherprovider.net.
-1      france.example.com.     IN      NS      120     ns2.otherprovider.net.
+1      france.example.com.     120     IN      NS      ns1.otherprovider.net.
+1      france.example.com.     120     IN      NS      ns2.otherprovider.net.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='france.example.com.', qtype=SOA
index 7780e348f583e55d4033c8df5d81a0e872de137e..6dcfddd7d67fded1f1f268240e487eaad9c36a02 100644 (file)
@@ -1,4 +1,4 @@
-1      france.example.com.     IN      NS      120     ns1.otherprovider.net.
-1      france.example.com.     IN      NS      120     ns2.otherprovider.net.
+1      france.example.com.     120     IN      NS      ns1.otherprovider.net.
+1      france.example.com.     120     IN      NS      ns2.otherprovider.net.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='france.example.com.', qtype=A
index a6c944ad67d0a54595bb9ae04f8e1d9bfcef228f..55c72bdbe56359ac8ee7e6e38ef6dcd8a6462896 100644 (file)
@@ -1,7 +1,7 @@
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      outpost.example.com.    IN      NSEC    86400   rhs-at-expansion.example.com. A RRSIG NSEC
-1      outpost.example.com.    IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+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      outpost.example.com.    86400   IN      NSEC    rhs-at-expansion.example.com. A RRSIG NSEC
+1      outpost.example.com.    86400   IN      RRSIG   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nx.outpost.example.com.', qtype=A
index 9f53f6b4d56f41d99b81add5fec93306df566506..618224b572b7782c2948edd4a5a2dcb758f20325 100644 (file)
@@ -1,11 +1,11 @@
-1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 5UVGFM2VJCJE09SVS7LFB22I1UUQJF99 A RRSIG
-1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      k6ta8mhi455hk3jskn0b2st81j6fa1l0.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd K6TA8MHI455HK3JSKN0B2ST81J6FA1L2
-1      k6ta8mhi455hk3jskn0b2st81j6fa1l0.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      tsdp8hajlfgr90cv4ib634g1m25nc5up.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd TSDP8HAJLFGR90CV4IB634G1M25NC5UR
-1      tsdp8hajlfgr90cv4ib634g1m25nc5up.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 5UVGFM2VJCJE09SVS7LFB22I1UUQJF99 A RRSIG
+1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.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      k6ta8mhi455hk3jskn0b2st81j6fa1l0.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd K6TA8MHI455HK3JSKN0B2ST81J6FA1L2
+1      k6ta8mhi455hk3jskn0b2st81j6fa1l0.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      tsdp8hajlfgr90cv4ib634g1m25nc5up.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd TSDP8HAJLFGR90CV4IB634G1M25NC5UR
+1      tsdp8hajlfgr90cv4ib634g1m25nc5up.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nx.outpost.example.com.', qtype=A
index 27b3c08a5ebfe1dbaa264c5d543a0a8dc2743947..02755b08c5cb8cdabc617d0450ad79ba2b8e1880 100644 (file)
@@ -1,11 +1,11 @@
-1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 5V0S7HPRC5IAFH3C3RO0HHNH543D3UIU A RRSIG
-1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      k6r6482mfo4upme9n407c2grb6opp1ip.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd K6TDMVV7BP54FEFUIVR0BVABIBUN0AV9 A RRSIG
-1      k6r6482mfo4upme9n407c2grb6opp1ip.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      tsbl3ev9tces1kjgto3qtn36ltlu0te1.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd TSIKPRKTT53V9ILUK08SMR9KADQ44TR1 A RRSIG
-1      tsbl3ev9tces1kjgto3qtn36ltlu0te1.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 5V0S7HPRC5IAFH3C3RO0HHNH543D3UIU A RRSIG
+1      5uvgfm2vjcje09svs7lfb22i1uuqjf98.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      k6r6482mfo4upme9n407c2grb6opp1ip.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd K6TDMVV7BP54FEFUIVR0BVABIBUN0AV9 A RRSIG
+1      k6r6482mfo4upme9n407c2grb6opp1ip.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      tsbl3ev9tces1kjgto3qtn36ltlu0te1.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd TSIKPRKTT53V9ILUK08SMR9KADQ44TR1 A RRSIG
+1      tsbl3ev9tces1kjgto3qtn36ltlu0te1.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nx.outpost.example.com.', qtype=A
index 288e33ba1802827c87dfa9df379d618fad9c7ca8..d98397052f9935ea04e8c3075ed22c4876014e63 100644 (file)
@@ -1,5 +1,5 @@
-0      www.dnssec-parent.com.  IN      CNAME   3600    www.insecure.dnssec-parent.com.
-0      www.insecure.dnssec-parent.com. IN      A       120     192.0.2.88
-2      .       IN      OPT     32768   
+0      www.dnssec-parent.com.  3600    IN      CNAME   www.insecure.dnssec-parent.com.
+0      www.insecure.dnssec-parent.com. 120     IN      A       192.0.2.88
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.dnssec-parent.com.', qtype=A
index 937f3a3c006b9b5a6df2d5c2c24260e3dcf9c987..49117e9a81b49cd5896ff629181ae56da4bb3331 100644 (file)
@@ -1,6 +1,6 @@
-0      www.dnssec-parent.com.  IN      CNAME   3600    www.insecure.dnssec-parent.com.
-0      www.dnssec-parent.com.  IN      RRSIG   3600    CNAME 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
-0      www.insecure.dnssec-parent.com. IN      A       120     192.0.2.88
-2      .       IN      OPT     32768   
+0      www.dnssec-parent.com.  3600    IN      CNAME   www.insecure.dnssec-parent.com.
+0      www.dnssec-parent.com.  3600    IN      RRSIG   CNAME 13 3 3600 [expiry] [inception] [keytag] dnssec-parent.com. ...
+0      www.insecure.dnssec-parent.com. 120     IN      A       192.0.2.88
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.dnssec-parent.com.', qtype=A
index 7bcd930365e4412c027d27767359e47b72fc66a3..59a86214bb9d993d3814cf88c1ad7f7af2c537db 100644 (file)
@@ -1,5 +1,5 @@
-0      cname-to-insecure.example.com.  IN      CNAME   120     www.insecure.dnssec-parent.com.
-0      www.insecure.dnssec-parent.com. IN      A       120     192.0.2.88
-2      .       IN      OPT     32768   
+0      cname-to-insecure.example.com.  120     IN      CNAME   www.insecure.dnssec-parent.com.
+0      www.insecure.dnssec-parent.com. 120     IN      A       192.0.2.88
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cname-to-insecure.example.com.', qtype=A
index 76458ceacbfc81e0ce13960243fdd94ec3d16389..c4835f71b590cbb279247afc671c6dd459a79285 100644 (file)
@@ -1,6 +1,6 @@
-0      cname-to-insecure.example.com.  IN      CNAME   120     www.insecure.dnssec-parent.com.
-0      cname-to-insecure.example.com.  IN      RRSIG   120     CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
-0      www.insecure.dnssec-parent.com. IN      A       120     192.0.2.88
-2      .       IN      OPT     32768   
+0      cname-to-insecure.example.com.  120     IN      CNAME   www.insecure.dnssec-parent.com.
+0      cname-to-insecure.example.com.  120     IN      RRSIG   CNAME 13 3 120 [expiry] [inception] [keytag] example.com. ...
+0      www.insecure.dnssec-parent.com. 120     IN      A       192.0.2.88
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cname-to-insecure.example.com.', qtype=A
index ddfa55f2fdc92590dcd4b1f3de1fe53da8e39d54..86431972a29cba3775ed998dcb9e200fe5005fc1 100644 (file)
@@ -1,4 +1,4 @@
-1      dsdelegation.example.com.       IN      NS      120     ns.example.com.
-2      .       IN      OPT     32768   
+1      dsdelegation.example.com.       120     IN      NS      ns.example.com.
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='www.dsdelegation.example.com.', qtype=A
index 47f190b99e1d80b3160ad45f83c37c0ce64d5f3a..f692fa3e693f361b25300056b389e533d40dc9c2 100644 (file)
@@ -1,6 +1,6 @@
-1      dsdelegation.example.com.       IN      DS      120     28129 8 1 caf1eaaecdabe7616670788f9022454bf5fd9fda
-1      dsdelegation.example.com.       IN      NS      120     ns.example.com.
-1      dsdelegation.example.com.       IN      RRSIG   120     DS 13 3 120 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      dsdelegation.example.com.       120     IN      DS      28129 8 1 caf1eaaecdabe7616670788f9022454bf5fd9fda
+1      dsdelegation.example.com.       120     IN      NS      ns.example.com.
+1      dsdelegation.example.com.       120     IN      RRSIG   DS 13 3 120 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='www.dsdelegation.example.com.', qtype=A
index d35c8c6ce3889556931076be4399b5fdd295bbeb..65cfd70cd546dbc0d97a245eda2444b7abc8a751 100644 (file)
@@ -1,4 +1,4 @@
-0      secure-delegated.dnssec-parent.com.     IN      A       3600    9.9.9.9
-2      .       IN      OPT     32768   
+0      secure-delegated.dnssec-parent.com.     3600    IN      A       9.9.9.9
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='secure-delegated.dnssec-parent.com.', qtype=A
index 93840376b7f6712042bfeea9487389301336d2e9..fc53931e46bc01d318cfdc63260a23b674aca82a 100644 (file)
@@ -1,5 +1,5 @@
-0      secure-delegated.dnssec-parent.com.     IN      A       3600    9.9.9.9
-0      secure-delegated.dnssec-parent.com.     IN      RRSIG   3600    A 8 3 3600 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
-2      .       IN      OPT     32768   
+0      secure-delegated.dnssec-parent.com.     3600    IN      A       9.9.9.9
+0      secure-delegated.dnssec-parent.com.     3600    IN      RRSIG   A 8 3 3600 [expiry] [inception] [keytag] secure-delegated.dnssec-parent.com. ...
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='secure-delegated.dnssec-parent.com.', qtype=A
index da87b5ffd155c1156c0a1d932648ad8539016a72..c28a9670843cc0850616da2cde0164fbb837c10b 100644 (file)
@@ -1,9 +1,9 @@
-1      example.com.    IN      NSEC    86400   _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
-1      example.com.    IN      RRSIG   86400   NSEC 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      smtp1.example.com.      IN      NSEC    86400   start.example.com. CNAME RRSIG NSEC
-1      smtp1.example.com.      IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      example.com.    86400   IN      NSEC    _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
+1      example.com.    86400   IN      RRSIG   NSEC 13 2 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      smtp1.example.com.      86400   IN      NSEC    start.example.com. CNAME RRSIG NSEC
+1      smtp1.example.com.      86400   IN      RRSIG   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='space\032name.example.com.', qtype=A
index de870e2f3363e8f6aadb299705070db397f7df84..9c8f65c15d5aa1d01ef2c224569232a8a948275e 100644 (file)
@@ -1,11 +1,11 @@
-1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 9FAG9508OQU3M22QAC0U5EQGG45V8CF2
-1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      gl4qf9db2fkivonidgs9954bhkhpvviq.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd GL4QF9DB2FKIVONIDGS9954BHKHPVVIS
-1      gl4qf9db2fkivonidgs9954bhkhpvviq.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 9FAG9508OQU3M22QAC0U5EQGG45V8CF2
+1      9fag9508oqu3m22qac0u5eqgg45v8cf0.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      gl4qf9db2fkivonidgs9954bhkhpvviq.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd GL4QF9DB2FKIVONIDGS9954BHKHPVVIS
+1      gl4qf9db2fkivonidgs9954bhkhpvviq.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='space\032name.example.com.', qtype=A
index e899b33cfabf544c8f4603d20e11e362ec8906cb..ee653d3fa5a6a1dbd90df6be38af369a0b985c6d 100644 (file)
@@ -1,11 +1,11 @@
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      gl3vilecelbsri6t44urj9lp6m5853mq.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd GL5I9VH027O95O1M3UTE1A8KR1TJ253D A RRSIG
-1      gl3vilecelbsri6t44urj9lp6m5853mq.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.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      gl3vilecelbsri6t44urj9lp6m5853mq.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd GL5I9VH027O95O1M3UTE1A8KR1TJ253D A RRSIG
+1      gl3vilecelbsri6t44urj9lp6m5853mq.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='space\032name.example.com.', qtype=A
index 145d33364c1a178a615ebfcc25a5e0d7dcc2e49a..2ec88ae8fd1507cde9cdcbf55347391ce05248dc 100644 (file)
@@ -1,10 +1,10 @@
-0      foo.svcb.example.com.   IN      SVCB    120     0 foo1.svcb.example.com.
-2      .       IN      OPT     32768   
-2      foo1.svcb.example.com.  IN      A       120     192.0.2.2
-2      foo1.svcb.example.com.  IN      SVCB    120     1 . alpn=h2,h3
+0      foo.svcb.example.com.   120     IN      SVCB    0 foo1.svcb.example.com.
+2      .       32768   IN      OPT     
+2      foo1.svcb.example.com.  120     IN      A       192.0.2.2
+2      foo1.svcb.example.com.  120     IN      SVCB    1 . alpn=h2,h3
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='foo.svcb.example.com.', qtype=SVCB
-0      baz.svcb.example.com.   IN      SVCB    120     0 foo1.svcb.example.net.
-2      .       IN      OPT     32768   
+0      baz.svcb.example.com.   120     IN      SVCB    0 foo1.svcb.example.net.
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='baz.svcb.example.com.', qtype=SVCB
index 64f1189e0ce4bd698ae4ee9a18491ef015dcc3de..2c7f67a6288d33fa220e8819325a0c2b195dfa47 100644 (file)
@@ -1,14 +1,14 @@
-0      foo.svcb.example.com.   IN      RRSIG   120     SVCB 13 4 120 [expiry] [inception] [keytag] example.com. ...
-0      foo.svcb.example.com.   IN      SVCB    120     0 foo1.svcb.example.com.
-2      .       IN      OPT     32768   
-2      foo1.svcb.example.com.  IN      A       120     192.0.2.2
-2      foo1.svcb.example.com.  IN      RRSIG   120     A 13 4 120 [expiry] [inception] [keytag] example.com. ...
-2      foo1.svcb.example.com.  IN      RRSIG   120     SVCB 13 4 120 [expiry] [inception] [keytag] example.com. ...
-2      foo1.svcb.example.com.  IN      SVCB    120     1 . alpn=h2,h3
+0      foo.svcb.example.com.   120     IN      RRSIG   SVCB 13 4 120 [expiry] [inception] [keytag] example.com. ...
+0      foo.svcb.example.com.   120     IN      SVCB    0 foo1.svcb.example.com.
+2      .       32768   IN      OPT     
+2      foo1.svcb.example.com.  120     IN      A       192.0.2.2
+2      foo1.svcb.example.com.  120     IN      RRSIG   A 13 4 120 [expiry] [inception] [keytag] example.com. ...
+2      foo1.svcb.example.com.  120     IN      RRSIG   SVCB 13 4 120 [expiry] [inception] [keytag] example.com. ...
+2      foo1.svcb.example.com.  120     IN      SVCB    1 . alpn=h2,h3
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='foo.svcb.example.com.', qtype=SVCB
-0      baz.svcb.example.com.   IN      RRSIG   120     SVCB 13 4 120 [expiry] [inception] [keytag] example.com. ...
-0      baz.svcb.example.com.   IN      SVCB    120     0 foo1.svcb.example.net.
-2      .       IN      OPT     32768   
+0      baz.svcb.example.com.   120     IN      RRSIG   SVCB 13 4 120 [expiry] [inception] [keytag] example.com. ...
+0      baz.svcb.example.com.   120     IN      SVCB    0 foo1.svcb.example.net.
+2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='baz.svcb.example.com.', qtype=SVCB
index b8f811b39e04aa7b207cece199d13ed2552c67c3..c4a8c4303a4115a2a92161f7d067578400c9a8e3 100644 (file)
@@ -1,8 +1,8 @@
-0      bar.svcb.example.com.   IN      SVCB    120     1 . alpn=h2
-0      bar.svcb.example.com.   IN      SVCB    120     3 . alpn=h3 port=1500
-2      .       IN      OPT     32768   
-2      bar.svcb.example.com.   IN      A       120     192.0.2.1
-2      bar.svcb.example.com.   IN      AAAA    120     2001:db8::3:1
-2      bar.svcb.example.com.   IN      AAAA    120     2001:db8::3:4
+0      bar.svcb.example.com.   120     IN      SVCB    1 . alpn=h2
+0      bar.svcb.example.com.   120     IN      SVCB    3 . alpn=h3 port=1500
+2      .       32768   IN      OPT     
+2      bar.svcb.example.com.   120     IN      A       192.0.2.1
+2      bar.svcb.example.com.   120     IN      AAAA    2001:db8::3:1
+2      bar.svcb.example.com.   120     IN      AAAA    2001:db8::3:4
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='bar.svcb.example.com.', qtype=SVCB
index 3faec47c51828eb7b181109df408f5af1687fbb2..7920485fa5ec3929d68357eff95104f8e07315b9 100644 (file)
@@ -1,11 +1,11 @@
-0      bar.svcb.example.com.   IN      RRSIG   120     SVCB 13 4 120 [expiry] [inception] [keytag] example.com. ...
-0      bar.svcb.example.com.   IN      SVCB    120     1 . alpn=h2
-0      bar.svcb.example.com.   IN      SVCB    120     3 . alpn=h3 port=1500
-2      .       IN      OPT     32768   
-2      bar.svcb.example.com.   IN      A       120     192.0.2.1
-2      bar.svcb.example.com.   IN      AAAA    120     2001:db8::3:1
-2      bar.svcb.example.com.   IN      AAAA    120     2001:db8::3:4
-2      bar.svcb.example.com.   IN      RRSIG   120     A 13 4 120 [expiry] [inception] [keytag] example.com. ...
-2      bar.svcb.example.com.   IN      RRSIG   120     AAAA 13 4 120 [expiry] [inception] [keytag] example.com. ...
+0      bar.svcb.example.com.   120     IN      RRSIG   SVCB 13 4 120 [expiry] [inception] [keytag] example.com. ...
+0      bar.svcb.example.com.   120     IN      SVCB    1 . alpn=h2
+0      bar.svcb.example.com.   120     IN      SVCB    3 . alpn=h3 port=1500
+2      .       32768   IN      OPT     
+2      bar.svcb.example.com.   120     IN      A       192.0.2.1
+2      bar.svcb.example.com.   120     IN      AAAA    2001:db8::3:1
+2      bar.svcb.example.com.   120     IN      AAAA    2001:db8::3:4
+2      bar.svcb.example.com.   120     IN      RRSIG   A 13 4 120 [expiry] [inception] [keytag] example.com. ...
+2      bar.svcb.example.com.   120     IN      RRSIG   AAAA 13 4 120 [expiry] [inception] [keytag] example.com. ...
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='bar.svcb.example.com.', qtype=SVCB
index 6787668eeba20c2114c71359386582594c3d5355..728deafef450700a0137bec76ff57f049eb9ef10 100644 (file)
@@ -1,3 +1,3 @@
-0      urc65226.test.com.      IN      TYPE65226       3600    \# 3 414243
+0      urc65226.test.com.      3600    IN      TYPE65226       \# 3 414243
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='urc65226.test.com.', qtype=TYPE65226
index d8d21b05572e4cc79e6222144b1c434a1440a95b..f5bf604f7713e9c5288e969647322b5b321660a3 100644 (file)
@@ -1,9 +1,9 @@
-1      example.com.    IN      NSEC    86400   _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
-1      example.com.    IN      RRSIG   86400   NSEC 13 2 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      ns2.example.com.        IN      NSEC    86400   nxd.example.com. A RRSIG NSEC
-1      ns2.example.com.        IN      RRSIG   86400   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      example.com.    86400   IN      NSEC    _imap._tcp.example.com. NS SOA MX RRSIG NSEC DNSKEY
+1      example.com.    86400   IN      RRSIG   NSEC 13 2 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      ns2.example.com.        86400   IN      NSEC    nxd.example.com. A RRSIG NSEC
+1      ns2.example.com.        86400   IN      RRSIG   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nx1.nx2.example.com.', qtype=A
index 315280afa5b1004597324542f0255078495c98c4..3d59f3c1abe8190e17a2f84a776a2ff7374566df 100644 (file)
@@ -1,11 +1,11 @@
-1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 9FAG9508OQU3M22QAC0U5EQGG45V8CF2
-1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      ectnliqstqsjnnrpuhjj5h0j3c3odkk3.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd ECTNLIQSTQSJNNRPUHJJ5H0J3C3ODKK5
-1      ectnliqstqsjnnrpuhjj5h0j3c3odkk3.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 9FAG9508OQU3M22QAC0U5EQGG45V8CF2
+1      9fag9508oqu3m22qac0u5eqgg45v8cf0.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      ectnliqstqsjnnrpuhjj5h0j3c3odkk3.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd ECTNLIQSTQSJNNRPUHJJ5H0J3C3ODKK5
+1      ectnliqstqsjnnrpuhjj5h0j3c3odkk3.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      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTNQ6OCN2VKUIV3NJU14OQTAEN2MT5SL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nx1.nx2.example.com.', qtype=A
index ad2307d11287c2a972600c1b6355eb047c27f968..1f38c6f2bb646ed0d5e1992a24f7fc3759246d53 100644 (file)
@@ -1,11 +1,11 @@
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
-1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      ecskkg9s6f7lap5qjrnns1bf8pjunshj.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd ECTPI4N8UNDE9GNVKHG28NJR512JBD4O A RRSIG
-1      ecskkg9s6f7lap5qjrnns1bf8pjunshj.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      RRSIG   86400   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      NSEC3   86400   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   IN      RRSIG   86400   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
-2      .       IN      OPT     32768   
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 9FDAOFPLLN0FQFU9DP274GOU59QFHSLD A RRSIG
+1      9f8hti7cc7oqnqjv84klnp89glqrss3r.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      ecskkg9s6f7lap5qjrnns1bf8pjunshj.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd ECTPI4N8UNDE9GNVKHG28NJR512JBD4O A RRSIG
+1      ecskkg9s6f7lap5qjrnns1bf8pjunshj.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      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd VTP9NUQBEH436S7J0K8TI2A32MMKCUUL NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      vtnq6ocn2vkuiv3nju14oqtaen2mt5sk.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='nx1.nx2.example.com.', qtype=A
index 39db1b923ffceedb893aa3f0fa7b870541906853..1c4f937acb75c2d2402da71520f0e84b881bb0cd 100644 (file)
@@ -1,3 +1,3 @@
-1      example.com.    IN      SOA     86400   ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+1      example.com.    86400   IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='outpost.example.com.', qtype=TYPE65535
index 4464fafc739df8ee543c38c5204f3040b1e40516..b5d1f08bb803d37902ff1950604b898db1d3a84b 100644 (file)
@@ -1,2 +1,2 @@
-1      test.com.       IN      NSEC    3600    _underscore.test.com. NS SOA MX RRSIG NSEC DNSKEY
-1      www.test.com.   IN      NSEC    3600    test.com. CNAME RRSIG NSEC
+1      test.com.       3600    IN      NSEC    _underscore.test.com. NS SOA MX RRSIG NSEC DNSKEY
+1      www.test.com.   3600    IN      NSEC    test.com. CNAME RRSIG NSEC
index 49323995e15a58935e6fbfc4474f3a9f15ab2b76..c679e01e4615c05ad9e285c9c5bf4cdb7c99100a 100644 (file)
@@ -1,3 +1,3 @@
-1      2eu2gulbu53h9uvhfalshpbo2a83t6l2.test.com.      IN      NSEC3   3600    1 [flags] 1 abcd 2EU2GULBU53H9UVHFALSHPBO2A83T6L3 NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      npce7etkesd3umcst08psfape1cnno5o.test.com.      IN      NSEC3   3600    1 [flags] 1 abcd NPCE7ETKESD3UMCST08PSFAPE1CNNO5Q
-1      nqf0papl2qmp38upr87f930kmebc0o0n.test.com.      IN      NSEC3   3600    1 [flags] 1 abcd NQF0PAPL2QMP38UPR87F930KMEBC0O0P
+1      2eu2gulbu53h9uvhfalshpbo2a83t6l2.test.com.      3600    IN      NSEC3   1 [flags] 1 abcd 2EU2GULBU53H9UVHFALSHPBO2A83T6L3 NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      npce7etkesd3umcst08psfape1cnno5o.test.com.      3600    IN      NSEC3   1 [flags] 1 abcd NPCE7ETKESD3UMCST08PSFAPE1CNNO5Q
+1      nqf0papl2qmp38upr87f930kmebc0o0n.test.com.      3600    IN      NSEC3   1 [flags] 1 abcd NQF0PAPL2QMP38UPR87F930KMEBC0O0P
index 09be2c04a23903917fc474f46df3ae524a573bfd..6a69976b301668eb01fd4ab49ac4ba9ffd2175aa 100644 (file)
@@ -1,2 +1,2 @@
-1      2eu2gulbu53h9uvhfalshpbo2a83t6l2.test.com.      IN      NSEC3   3600    1 [flags] 1 abcd 2GKS2N3JPQF62QOHAVFQ1PHOLM3HR7RA NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      n5rskfsbg0uk5sspj595r6546hkk5vk1.test.com.      IN      NSEC3   3600    1 [flags] 1 abcd O1L0FB73HI3QP4A3FNQJSLEANLC883I3 CNAME RRSIG
+1      2eu2gulbu53h9uvhfalshpbo2a83t6l2.test.com.      3600    IN      NSEC3   1 [flags] 1 abcd 2GKS2N3JPQF62QOHAVFQ1PHOLM3HR7RA NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      n5rskfsbg0uk5sspj595r6546hkk5vk1.test.com.      3600    IN      NSEC3   1 [flags] 1 abcd O1L0FB73HI3QP4A3FNQJSLEANLC883I3 CNAME RRSIG
index 72553dc70daa7ed267654f62bd0b14f7af3459cf..566c3491b671e3617ba4f2f50aecc57590ded6e8 100644 (file)
@@ -1,9 +1,9 @@
-1      Test.com.       IN      NSEC    3600    _underscore.test.com. NS SOA MX RRSIG NSEC DNSKEY
-1      Test.com.       IN      RRSIG   3600    NSEC 13 2 3600 [expiry] [inception] [keytag] test.com. ...
-1      Test.com.       IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
-1      Test.com.       IN      SOA     3600    ns1.Test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-1      www.Test.com.   IN      NSEC    3600    test.com. CNAME RRSIG NSEC
-1      www.Test.com.   IN      RRSIG   3600    NSEC 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-2      .       IN      OPT     32768   
+1      Test.com.       3600    IN      NSEC    _underscore.test.com. NS SOA MX RRSIG NSEC DNSKEY
+1      Test.com.       3600    IN      RRSIG   NSEC 13 2 3600 [expiry] [inception] [keytag] test.com. ...
+1      Test.com.       3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
+1      Test.com.       3600    IN      SOA     ns1.Test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
+1      www.Test.com.   3600    IN      NSEC    test.com. CNAME RRSIG NSEC
+1      www.Test.com.   3600    IN      RRSIG   NSEC 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='z.Test.com.', qtype=A
index 02b01302fa014bdbae004c8d0a1935f2d263a010..f840aa8c081e6517476d6d8c0ef7d4c7dc592e9a 100644 (file)
@@ -1,11 +1,11 @@
-1      2eu2gulbu53h9uvhfalshpbo2a83t6l2.Test.com.      IN      NSEC3   3600    1 [flags] 1 abcd 2EU2GULBU53H9UVHFALSHPBO2A83T6L3 NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      2eu2gulbu53h9uvhfalshpbo2a83t6l2.Test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-1      Test.com.       IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
-1      Test.com.       IN      SOA     3600    ns1.Test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-1      npce7etkesd3umcst08psfape1cnno5o.Test.com.      IN      NSEC3   3600    1 [flags] 1 abcd NPCE7ETKESD3UMCST08PSFAPE1CNNO5Q
-1      npce7etkesd3umcst08psfape1cnno5o.Test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-1      nqf0papl2qmp38upr87f930kmebc0o0n.Test.com.      IN      NSEC3   3600    1 [flags] 1 abcd NQF0PAPL2QMP38UPR87F930KMEBC0O0P
-1      nqf0papl2qmp38upr87f930kmebc0o0n.Test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-2      .       IN      OPT     32768   
+1      2eu2gulbu53h9uvhfalshpbo2a83t6l2.Test.com.      3600    IN      NSEC3   1 [flags] 1 abcd 2EU2GULBU53H9UVHFALSHPBO2A83T6L3 NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      2eu2gulbu53h9uvhfalshpbo2a83t6l2.Test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+1      Test.com.       3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
+1      Test.com.       3600    IN      SOA     ns1.Test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
+1      npce7etkesd3umcst08psfape1cnno5o.Test.com.      3600    IN      NSEC3   1 [flags] 1 abcd NPCE7ETKESD3UMCST08PSFAPE1CNNO5Q
+1      npce7etkesd3umcst08psfape1cnno5o.Test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+1      nqf0papl2qmp38upr87f930kmebc0o0n.Test.com.      3600    IN      NSEC3   1 [flags] 1 abcd NQF0PAPL2QMP38UPR87F930KMEBC0O0P
+1      nqf0papl2qmp38upr87f930kmebc0o0n.Test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='z.Test.com.', qtype=A
index 3f5797159dde807d35a04126cc382efc284c5bdb..6aa189ddd7175734bb811bef3e9cfe7ceaa6b594 100644 (file)
@@ -1,9 +1,9 @@
-1      2eu2gulbu53h9uvhfalshpbo2a83t6l2.Test.com.      IN      NSEC3   3600    1 [flags] 1 abcd 2GKS2N3JPQF62QOHAVFQ1PHOLM3HR7RA NS SOA MX RRSIG DNSKEY NSEC3PARAM
-1      2eu2gulbu53h9uvhfalshpbo2a83t6l2.Test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-1      Test.com.       IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
-1      Test.com.       IN      SOA     3600    ns1.Test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
-1      n5rskfsbg0uk5sspj595r6546hkk5vk1.Test.com.      IN      NSEC3   3600    1 [flags] 1 abcd O1L0FB73HI3QP4A3FNQJSLEANLC883I3 CNAME RRSIG
-1      n5rskfsbg0uk5sspj595r6546hkk5vk1.Test.com.      IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
-2      .       IN      OPT     32768   
+1      2eu2gulbu53h9uvhfalshpbo2a83t6l2.Test.com.      3600    IN      NSEC3   1 [flags] 1 abcd 2GKS2N3JPQF62QOHAVFQ1PHOLM3HR7RA NS SOA MX RRSIG DNSKEY NSEC3PARAM
+1      2eu2gulbu53h9uvhfalshpbo2a83t6l2.Test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+1      Test.com.       3600    IN      RRSIG   SOA 13 2 3600 [expiry] [inception] [keytag] test.com. ...
+1      Test.com.       3600    IN      SOA     ns1.Test.com. ahu.example.com. 2005092501 28800 7200 604800 86400
+1      n5rskfsbg0uk5sspj595r6546hkk5vk1.Test.com.      3600    IN      NSEC3   1 [flags] 1 abcd O1L0FB73HI3QP4A3FNQJSLEANLC883I3 CNAME RRSIG
+1      n5rskfsbg0uk5sspj595r6546hkk5vk1.Test.com.      3600    IN      RRSIG   NSEC3 13 3 3600 [expiry] [inception] [keytag] test.com. ...
+2      .       32768   IN      OPT     
 Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='z.Test.com.', qtype=A
index 383bfbbf9ae83cb085ec26276fd3c8a07d920841..33a4c7b587ef3fe4a8da6f587284b78148e6cb09 100644 (file)
@@ -1,3 +1,3 @@
-0      very-long-txt.test.com. IN      TXT     3600    "A very long TXT record! boy you won't believe how long. A very long TXT record! boy you won't believe how long. A very long TXT record! boy you won't believe how long. A very long TXT record! boy you won't believe how long. A very long TXT record! boy you" " won't believe how long!"
+0      very-long-txt.test.com. 3600    IN      TXT     "A very long TXT record! boy you won't believe how long. A very long TXT record! boy you won't believe how long. A very long TXT record! boy you won't believe how long. A very long TXT record! boy you won't believe how long. A very long TXT record! boy you" " won't believe how long!"
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='very-long-txt.test.com.', qtype=TXT
index e8041cc48587188445d6ca4df7c7be7fafb97142..4ce4a88fc6b66f7af4aad34d9aad09b4fb50a162 100644 (file)
@@ -1,3 +1,3 @@
-1      sub.test.test.com.      IN      NS      3600    ns-test.example.net.test.com.
+1      sub.test.test.com.      3600    IN      NS      ns-test.example.net.test.com.
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='www.sub.test.test.com.', qtype=A
index 6622ad913ac3f200c7c15a840feb613a730aaec3..221da07c90d8998edaef97438c30a5db52650aae 100644 (file)
@@ -1,3 +1,3 @@
-1      wtest.com.      IN      SOA     3600    ns1.wtest.com. ahu.example.com. 2005092501 28800 7200 604800 86400
+1      wtest.com.      3600    IN      SOA     ns1.wtest.com. ahu.example.com. 2005092501 28800 7200 604800 86400
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='www.something.wtest.com.', qtype=TXT
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 8bbd6f9c94b34a74738d926f85d619ee43395af7..1a466284e9ed3d1e699e2c9e52fcaff64051ad45 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import socket
 import select
diff --git a/regression-tests/zones/catalog.invalid b/regression-tests/zones/catalog.invalid
new file mode 100644 (file)
index 0000000..ae12614
--- /dev/null
@@ -0,0 +1,10 @@
+$TTL 3600
+$ORIGIN catalog.invalid.
+@              IN      SOA     ns1.zone.invalid. hostmaster.zone.invalid. (  1
+                       1M ; refresh
+                       30S ; retry
+                       1W ; expire
+                       1D ; default_ttl
+                       )
+
+@              IN      NS      ns1.zone.invalid.
index 0800ccf1eba52b999fa95af63ecd47020c0ea81a..f32469bbf744be7b290ce567a55d22fbbd8197c5 100644 (file)
@@ -25,3 +25,4 @@ insecure-delegated.ent.ent.auth-ent   IN      NS      ns.example.com.
 something1.auth-ent    IN      A       1.1.2.3
 insecure               IN      NS      ns.example.com.
 www                    IN      CNAME   www.insecure
+*                      IN      CNAME   secure-delegated
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 d040d06edfa85125b3083bd22afc25b4dad07126..f3adcb1fe7edf2745da35fd468df143ada947b2a 100644 (file)
@@ -41,3 +41,8 @@ urc65226      IN      TYPE65226 \# 3 414243
 interrupted-rrset      IN      A       1.1.1.1
 interrupted-rrset      IN      TXT     "check AXFR signpipe"
 interrupted-rrset      IN      A       2.2.2.2
+
+; ordername sorting
+10.order IN A 192.168.0.1
+15.order IN A 192.168.0.1
+100.order IN A 192.168.0.1
index 3f843c591601c5b68edd2113bb035e03531efa8a..823bb035008ba74f8cb8cf5f72e81dad43df2b8f 100644 (file)
@@ -32,3 +32,6 @@ e.host                        IN      A       1.1.1.1
 
 sub                            IN NS ns1.test.dyndns.
 sub                            IN NS ns2.test.dyndns.
+
+; unlike other records in this zone, the record below will actually disappear during the 1dyndns-update-add-delete-txt test
+xautosplit       IN TXT "They fixed up the corner store like it was a nightclub - It's permanently disco - Everyone is dressed so oddly I can't recognize them - I can't tell the staff from the customers - Baby check this out, I've got something to say - Man, it's so loud in here - When they stop the drum machine and I can think again - I'll remember what it was"
index 49dccd727f99aa96139f684f145a300ac6c8e395..9c5fe5b305bdebc8ada9d59bb0530512cd040de1 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.0'
+quiche_hash = '7125bc82ddcf38fbfbc69882ccb2723bfb4d5bfeb42718b8291d26ec06042e38'
+
 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',
@@ -46,6 +52,7 @@ auth_build_deps = [    # FIXME: perhaps we should be stealing these from the deb
     'ruby-dev',
     'sqlite3',
     'unixodbc-dev',
+    'cmake',
 ]
 rec_build_deps = [
     'libcap-dev',
@@ -58,13 +65,12 @@ rec_bulk_deps = [
     'libcap2',
     'libfstrm0',
     'libluajit-5.1-2',
-    'libsnmp35',
+    '"libsnmp[1-9]+"',
     'libsodium23',
-    'libssl1.1',
     'libsystemd0',
     'moreutils',
     'pdns-tools',
-    'unzip'
+    'unzip',
 ]
 dnsdist_build_deps = [
     'libcap-dev',
@@ -78,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',
@@ -87,13 +97,14 @@ auth_test_deps = [   # FIXME: we should be generating some of these from shlibde
     'dnsutils',
     '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',
@@ -102,39 +113,123 @@ 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',
     'ruby-bundler',
     'ruby-dev',
     'socat',
     'softhsm2',
     'unbound-host',
     'unixodbc',
-    'wget'
+    'wget',
+]
+doc_deps = [
+    'autoconf',
+    'automake',
+    'bison',
+    'curl',
+    'flex',
+    'g++',
+    'git',
+    'latexmk',
+    'libboost-all-dev',
+    'libedit-dev',
+    'libluajit-5.1-dev',
+    'libssl-dev',
+    'make',
+    'pkg-config',
+    'python3-venv',
+    'ragel',
+    'rsync',
+]
+doc_deps_pdf = [
+    'texlive-binaries',
+    'texlive-formats-extra',
+    'texlive-latex-extra',
 ]
 
 @task
 def apt_fresh(c):
     c.sudo('apt-get update')
-    c.sudo('apt-get 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(f'CC={get_c_compiler()} CXX={get_cxx_compiler()} '
+              'cmake -B build '
+              '-DCMAKE_INSTALL_PREFIX=/usr/local '
+              '-DCMAKE_INSTALL_LIBDIR=lib '
+              '-DENABLE_STATIC=OFF '
+              '-DENABLE_TESTS=OFF '
+              '-DCMAKE_C_FLAGS="-Wno-sizeof-array-div -Wno-array-parameter" .')
+        c.run('make -C build')
+        c.run('sudo make -C build install')
+    c.sudo(f'mkdir -p /opt/{product}/libdecaf')
+    c.sudo(f'cp /usr/local/lib/libdecaf.so* /opt/{product}/libdecaf/.')
+
+@task
+def install_doc_deps(c):
+    c.sudo('apt-get install -y ' + ' '.join(doc_deps))
+
+@task
+def install_doc_deps_pdf(c):
+    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))
+    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')
@@ -150,7 +245,11 @@ auth_backend_test_deps = dict(
     geoip=[],
     lua2=[],
     tinydns=[],
-    authpy=[]
+    authpy=[],
+    godbc_sqlite3=['libsqliteodbc'],
+    godbc_mssql=['freetds-bin','tdsodbc'],
+    ldap=[],
+    geoip_mmdb=[]
 )
 
 @task(help={'backend': 'Backend to install test deps for, e.g. gsqlite3; can be repeated'}, iterable=['backend'], optional=['backend'])
@@ -158,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
@@ -171,14 +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)
 
+    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 \
@@ -189,95 +293,223 @@ 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):
+    c.run('make -f Makefile.sphinx -C docs html')
+
+@task
+def ci_docs_build_pdf(c):
+    c.run('make -f Makefile.sphinx -C docs latexpdf')
+
+@task
+def ci_docs_upload_master(c, docs_host, pdf, username, product, directory=""):
+    rsync_cmd = " ".join([
+        "rsync",
+        "--checksum",
+        "--recursive",
+        "--verbose",
+        "--no-p",
+        "--chmod=g=rwX",
+        "--exclude '*~'",
+    ])
+    c.run(f"{rsync_cmd} --delete ./docs/_build/{product}-html-docs/ {username}@{docs_host}:{directory}")
+    c.run(f"{rsync_cmd} ./docs/_build/{product}-html-docs.tar.bz2 {username}@{docs_host}:{directory}/html-docs.tar.bz2")
+    c.run(f"{rsync_cmd} ./docs/_build/latex/{pdf} {username}@{docs_host}:{directory}")
+
+@task
+def ci_docs_add_ssh(c, ssh_key, host_key):
+    c.run('mkdir -m 700 -p ~/.ssh')
+    c.run(f'echo "{ssh_key}" > ~/.ssh/id_ed25519')
+    c.run('chmod 600 ~/.ssh/id_ed25519')
+    c.run(f'echo "{host_key}" > ~/.ssh/known_hosts')
+
+
+def get_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([
+        get_optimizations(),
+        "-Werror=vla",
+        "-Werror=shadow",
+        "-Wformat=2",
+        "-Werror=format-security",
+        "-fstack-clash-protection",
+        "-fstack-protector-strong",
+        "-fcf-protection=full",
+        "-Werror=string-plus-int" if is_compiler_clang() else '',
+    ])
+
+
+def get_cxxflags():
+    return " ".join([
+        get_cflags(),
+        "-Wp,-D_GLIBCXX_ASSERTIONS",
+    ])
+
+
+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="{cflags}"',
+        f'CXXFLAGS="{cxxflags}"',
+        './configure',
+        f"CC='{get_c_compiler()}'",
+        f"CXX='{get_cxx_compiler()}'",
+        "--enable-option-checking=fatal",
+        "--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):
-    res = c.run('''CFLAGS="-O1 -Werror=vla -Werror=shadow -Wformat=2 -Werror=format-security -Werror=string-plus-int" \
-                   CXXFLAGS="-O1 -Werror=vla -Werror=shadow -Wformat=2 -Werror=format-security -Werror=string-plus-int -Wp,-D_GLIBCXX_ASSERTIONS" \
-                   ./configure \
-                      CC='clang-12' \
-                      CXX='clang++-12' \
-                      --enable-option-checking=fatal \
-                      --with-modules='bind geoip gmysql godbc gpgsql gsqlite3 ldap lmdb lua2 pipe remote tinydns' \
-                      --enable-systemd \
-                      --enable-tools \
-                      --enable-unit-tests \
-                      --enable-backend-unit-tests \
-                      --enable-fuzz-targets \
-                      --enable-experimental-pkcs11 \
-                      --enable-remotebackend-zeromq \
-                      --with-lmdb=/usr \
-                      --with-libsodium \
-                      --prefix=/opt/pdns-auth \
-                      --enable-ixfrdist \
-                      --enable-asan \
-                      --enable-ubsan''', warn=True)
+    unittests = get_unit_tests(True)
+    fuzz_targets = get_fuzzing_targets()
+    modules = " ".join([
+        "bind",
+        "geoip",
+        "gmysql",
+        "godbc",
+        "gpgsql",
+        "gsqlite3",
+        "ldap",
+        "lmdb",
+        "lua2",
+        "pipe",
+        "remote",
+        "tinydns",
+    ])
+    configure_cmd = " ".join([
+        get_base_configure_cmd(),
+        "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" if os.getenv('DECAF_SUPPORT', 'no') == 'yes' else '',
+        "--prefix=/opt/pdns-auth",
+        "--enable-ixfrdist",
+        unittests,
+        fuzz_targets
+    ])
+    res = c.run(configure_cmd, warn=True)
     if res.exited != 0:
         c.run('cat config.log')
         raise UnexpectedExit(res)
+
+
 @task
 def ci_rec_configure(c):
-    sanitizers = ' '.join('--enable-'+x for x in os.getenv('SANITIZERS').split('+'))
-    res = c.run('''            CFLAGS="-O1 -Werror=vla -Werror=shadow -Wformat=2 -Werror=format-security -Werror=string-plus-int" \
-            CXXFLAGS="-O1 -Werror=vla -Werror=shadow -Wformat=2 -Werror=format-security -Werror=string-plus-int -Wp,-D_GLIBCXX_ASSERTIONS" \
-            ./configure \
-              CC='clang-12' \
-              CXX='clang++-12' \
-              --enable-option-checking=fatal \
-              --enable-unit-tests \
-              --enable-nod \
-              --enable-systemd \
-              --prefix=/opt/pdns-recursor \
-              --with-libsodium \
-              --with-lua=luajit \
-              --with-libcap \
-              --with-net-snmp \
-              --enable-dns-over-tls ''' + sanitizers, warn=True)
+    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",
+        "--enable-verbose-logging",
+        unittests,
+    ])
+    res = c.run(configure_cmd, warn=True)
     if res.exited != 0:
         c.run('cat config.log')
         raise UnexpectedExit(res)
 
+
 @task
 def ci_dnsdist_configure(c, features):
     additional_flags = ''
@@ -286,14 +518,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 \
@@ -302,13 +538,16 @@ 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 \
                           -DDISABLE_PROMETHEUS \
                           -DDISABLE_PROTOBUF \
                           -DDISABLE_BUILTIN_HTML \
@@ -328,43 +567,65 @@ def ci_dnsdist_configure(c, features):
                           -DDISABLE_DNSNAME_BINDINGS \
                           -DDISABLE_DNSHEADER_BINDINGS \
                           -DDISABLE_RECVMMSG \
+                          -DDISABLE_WEB_CACHE_MANAGEMENT \
                           -DDISABLE_WEB_CONFIG \
                           -DDISABLE_RULES_ALTERING_QUERIES \
                           -DDISABLE_ECS_ACTIONS \
-                          -DDISABLE_TOP_N_BINDINGS'
-    sanitizers = ' '.join('--enable-'+x for x in os.getenv('SANITIZERS').split('+'))
-    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" \
-                   ./configure \
-                     CC='clang-12' \
-                     CXX='clang++-12' \
-                     --enable-option-checking=fatal \
-                     --enable-unit-tests \
-                     --prefix=/opt/dnsdist %s %s''' % (cflags, cxxflags, features_set, sanitizers), warn=True)
+                          -DDISABLE_TOP_N_BINDINGS \
+                          -DDISABLE_OCSP_STAPLING \
+                          -DDISABLE_HASHED_CREDENTIALS \
+                          -DDISABLE_FALSE_SHARING_PADDING \
+                          -DDISABLE_NPN'
+    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):
@@ -388,22 +649,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")
@@ -416,73 +677,186 @@ 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')
 
 backend_regress_tests = dict(
     bind = [
-      'bind-both',
-      'bind-dnssec-both',
-      'bind-dnssec-nsec3-both',
-      'bind-dnssec-nsec3-optout-both',
-      'bind-dnssec-nsec3-narrow',
-    # FIXME  'bind-dnssec-pkcs11'
+        'bind-both',
+        'bind-dnssec-both',
+        'bind-dnssec-nsec3-both',
+        'bind-dnssec-nsec3-optout-both',
+        'bind-dnssec-nsec3-narrow',
+        'bind-dnssec-pkcs11'
     ],
     geoip = [
-      'geoip',
-      'geoip-nsec3-narrow'
-      # FIXME: also run this with the mmdb we ship
-    ],
-    lua2 = [
-      'lua2',
-      'lua2-dnssec'
-    ],
-    tinydns = [
-      'tinydns'
+        'geoip',
+        'geoip-nsec3-narrow'
     ],
+    lua2 = ['lua2', 'lua2-dnssec'],
+    tinydns = ['tinydns'],
     remote = [
-      'remotebackend-pipe',
-      'remotebackend-unix',
-      'remotebackend-http',
-      'remotebackend-zeromq',
-      'remotebackend-pipe-dnssec',
-      'remotebackend-unix-dnssec',
-      'remotebackend-http-dnssec',
-      'remotebackend-zeromq-dnssec'
+        'remotebackend-pipe',
+        'remotebackend-unix',
+        'remotebackend-http',
+        'remotebackend-zeromq',
+        'remotebackend-pipe-dnssec',
+        'remotebackend-unix-dnssec',
+        'remotebackend-http-dnssec',
+        'remotebackend-zeromq-dnssec'
     ],
     lmdb = [
-      'lmdb-nodnssec-both',
-      'lmdb-both',
-      'lmdb-nsec3-both',
-      'lmdb-nsec3-optout-both',
-      'lmdb-nsec3-narrow'
+        'lmdb-nodnssec-both',
+        'lmdb-both',
+        'lmdb-nsec3-both',
+        'lmdb-nsec3-optout-both',
+        'lmdb-nsec3-narrow'
+    ],
+    gmysql = [
+        'gmysql',
+        'gmysql-nodnssec-both',
+        'gmysql-nsec3-both',
+        'gmysql-nsec3-optout-both',
+        'gmysql-nsec3-narrow',
+        'gmysql_sp-both'
     ],
-    gmysql   = ['gmysql',     'gmysql-nodnssec-both',   'gmysql-nsec3-both',   'gmysql-nsec3-optout-both',   'gmysql-nsec3-narrow',   'gmysql_sp-both'],
-    gpgsql   = ['gpgsql',     'gpgsql-nodnssec-both',   'gpgsql-nsec3-both',   'gpgsql-nsec3-optout-both',   'gpgsql-nsec3-narrow',   'gpgsql_sp-both'],
-    gsqlite3 = ['gsqlite3', 'gsqlite3-nodnssec-both', 'gsqlite3-nsec3-both', 'gsqlite3-nsec3-optout-both', 'gsqlite3-nsec3-narrow'],
+    gpgsql = [
+        'gpgsql',
+        'gpgsql-nodnssec-both',
+        'gpgsql-nsec3-both',
+        'gpgsql-nsec3-optout-both',
+        'gpgsql-nsec3-narrow',
+        'gpgsql_sp-both'
+    ],
+    gsqlite3 = [
+        'gsqlite3',
+        'gsqlite3-nodnssec-both',
+        'gsqlite3-nsec3-both',
+        'gsqlite3-nsec3-optout-both',
+        'gsqlite3-nsec3-narrow'
+    ],
+    godbc_sqlite3 = ['godbc_sqlite3-nodnssec'],
+    godbc_mssql = [
+        'godbc_mssql',
+        'godbc_mssql-nodnssec',
+        'godbc_mssql-nsec3',
+        'godbc_mssql-nsec3-optout',
+        'godbc_mssql-nsec3-narrow'
+    ],
+    ldap = [
+        'ldap-tree',
+        'ldap-simple',
+        'ldap-strict'
+    ],
+    geoip_mmdb = ['geoip'],
 )
 
+godbc_mssql_credentials = {"username": "sa", "password": "SAsa12%%-not-a-secret-password"}
+
+godbc_config = f'''
+[pdns-mssql-docker]
+Driver=FreeTDS
+Trace=No
+Server={auth_backend_ip_addr}
+Port=1433
+Database=pdns
+TDS_Version=7.1
+
+[pdns-mssql-docker-nodb]
+Driver=FreeTDS
+Trace=No
+Server={auth_backend_ip_addr}
+Port=1433
+TDS_Version=7.1
+
+[pdns-sqlite3-1]
+Driver = SQLite3
+Database = pdns.sqlite3
+
+[pdns-sqlite3-2]
+Driver = SQLite3
+Database = pdns.sqlite32
+'''
+
+def setup_godbc_mssql(c):
+    with open(os.path.expanduser("~/.odbc.ini"), "a") as f:
+        f.write(godbc_config)
+    c.sudo('sh -c \'echo "Threading=1" | cat /usr/share/tdsodbc/odbcinst.ini - | tee -a /etc/odbcinst.ini\'')
+    c.sudo('sed -i "s/libtdsodbc.so/\/usr\/lib\/x86_64-linux-gnu\/odbc\/libtdsodbc.so/g" /etc/odbcinst.ini')
+    c.run(f'echo "create database pdns" | isql -v pdns-mssql-docker-nodb {godbc_mssql_credentials["username"]} {godbc_mssql_credentials["password"]}')
+    # FIXME: Skip 8bit-txt-unescaped test
+    c.run('touch ${PWD}/regression-tests/tests/8bit-txt-unescaped/skip')
+
+def setup_godbc_sqlite3(c):
+    with open(os.path.expanduser("~/.odbc.ini"), "a") as f:
+        f.write(godbc_config)
+    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 -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 = 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=/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" ./runtests')
+            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'):
+            for variant in backend_regress_tests[backend]:
+                c.run(f'{pdns_auth_env_vars} GODBC_SQLITE3_DSN=pdns-sqlite3-1 ./start-test-stop 5300 {variant}')
+        return
+
+    if backend == 'godbc_mssql':
+        setup_godbc_mssql(c)
+        with c.cd('regression-tests'):
+            for variant in backend_regress_tests[backend]:
+                c.run(f'{pdns_auth_env_vars} GODBC_MSSQL_PASSWORD={godbc_mssql_credentials["password"]} GODBC_MSSQL_USERNAME={godbc_mssql_credentials["username"]} GODBC_MSSQL_DSN=pdns-mssql-docker GODBC_MSSQL2_PASSWORD={godbc_mssql_credentials["password"]} GODBC_MSSQL2_USERNAME={godbc_mssql_credentials["username"]} GODBC_MSSQL2_DSN=pdns-mssql-docker ./start-test-stop 5300 {variant}')
+        return
+
+    if backend == 'ldap':
+        setup_ldap_client(c)
+
+    if backend == 'geoip_mmdb':
+        with c.cd('regression-tests'):
+            for variant in backend_regress_tests[backend]:
+                c.run(f'{pdns_auth_env_vars} geoipdatabase=../modules/geoipbackend/regression-tests/GeoLiteCity.mmdb ./start-test-stop 5300 {variant}')
         return
 
     with c.cd('regression-tests'):
         if backend == 'lua2':
             c.run('touch trustedkeys')  # avoid silly error during cleanup
         for variant in backend_regress_tests[backend]:
-            # FIXME this long line is terrible
-            c.run(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=127.0.0.1 GMYSQL2HOST=127.0.0.1 MYSQL_HOST="127.0.0.1" PGHOST="127.0.0.1" PGPORT="5432" ./start-test-stop 5300 {variant}')
+            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=/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" ./runtests')
+            c.run(f'{pdns_auth_env_vars} ./runtests')
         c.run('/opt/pdns-auth/bin/pdnsutil test-algorithms')
         return
 
@@ -497,12 +871,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):
@@ -511,7 +885,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):
@@ -521,6 +895,55 @@ def install_swagger_tools(c):
 def swagger_syntax_check(c):
     c.run('api-spec-converter docs/http-api/swagger/authoritative-api-swagger.yaml -f swagger_2 -t openapi_3 -s json -c')
 
+@task
+def install_coverity_tools(c, project):
+    token = os.getenv('COVERITY_TOKEN')
+    c.run(f'curl -s https://scan.coverity.com/download/linux64 --data "token={token}&project={project}" | gunzip | sudo tar xvf /dev/stdin --strip-components=1 --no-same-owner -C /usr/local', hide=True)
+
+@task
+def coverity_clang_configure(c):
+    c.sudo(f'/usr/local/bin/cov-configure --template --comptype clangcc --compiler clang++-{clang_version}')
+
+@task
+def coverity_make(c):
+    c.run('/usr/local/bin/cov-build --dir cov-int make -j8 -k')
+
+@task
+def coverity_tarball(c, tarball):
+    c.run(f'tar caf {tarball} cov-int')
+
+@task
+def coverity_upload(c, email, project, tarball):
+    token = os.getenv('COVERITY_TOKEN')
+    c.run(f'curl --form token={token} \
+            --form email="{email}" \
+            --form file=@{tarball} \
+            --form version="$(./builder-support/gen-version)" \
+            --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']: